Timeline Audit & Repair
Keep a contributor's Hero's Journey honest — detect rank drift between the registry and the user tree, trace every missing event, and backfill it with the right tooling.
Overview
Every contributor's profile page renders a Hero's Journey — a rank chart
and event list that shows how each Named Skill progressed over time.
The source of truth for that chart is the contributor's
user tree (skill-trees/<owner>/skill-tree.json),
specifically its timeline[] and unlockedSkills[] arrays.
The registry node (registry/named/<contributor>/<skill>.md)
is a separate file that records the canonical level of a skill and any level-change
events that maintainers have applied (promotions, demotions, reclassifications).
These two files are supposed to stay in sync — but the pipeline that applies registry
changes doesn't automatically mirror them onto the user tree.
The result: a skill can silently chart at its old rank. A 3★ → 1★ demotion that landed on the registry node may never appear as a demote event on the contributor profile, leaving the Hero's Journey misleading or outright wrong.
This guide covers three tools that fix the problem:
| Tool | Role | Gate |
|---|---|---|
scripts/validate_timelines.py |
CI gate — lists every drifting skill, exits 1 if any found | CI |
scripts/trace_timeline.py |
Bulk auditor/fixer — dry-run trace and --apply backfill |
verifier / OPERATOR_OVERRIDE |
gaia dev timeline |
Manual one-off event append; limited (see CLI gaps) | verifier / OPERATOR_OVERRIDE |
The drift problem
Rank changes flow from the registry node to the user tree along a path that requires manual intervention. When that step is skipped, the two files disagree:
unlockedSkills[].levelmay be staletimeline[]may be missing demote / rank_up events- Hero's Journey chart reads from here
There is no runtime error when the two files disagree. The contributor profile
simply renders the wrong rank and omits the rank-change event from the chart.
The only way to catch drift is to run validate_timelines.py.
1 — Detect drift
Run the validation gate to see every drifting skill across all user trees. This script is also run automatically in release CI as the Transparency Gate.
python scripts/validate_timelines.py
For each drifting skill it prints one line per violation. Exit code is 0
when all timelines are clean, 1 when any drift is found.
✗ marcotngsn/semantic-cache unlockedSkills.level = 3★ registry = 1★ → stale level ✗ marcotngsn/code-generation latest timeline newValue = 2★ registry = 3★ → missing rank_up event Checked 1 user tree · 2 drifting skills · exit 1
✓ Checked 1 user tree · 0 drifting skills
The gate checks two invariants for each skill a contributor owns in their own tree:
| Invariant | What fails it |
|---|---|
unlockedSkills[].level == registry level |
User tree stores a stale level (e.g. still shows 3★ after a 3★→1★ demotion) |
Latest level-bearing timeline event newValue == registry level |
Timeline exists but is missing the most recent rank change — the demotion or promotion was never appended |
2 — Trace a skill
Before writing anything, inspect what trace_timeline.py would append.
The dry-run mode prints each missing event and its source:
python scripts/trace_timeline.py <handle>/<slug> # Examples python scripts/trace_timeline.py marcotngsn/semantic-cache python scripts/trace_timeline.py --all # every drifting skill
Each event in the output is labelled with its provenance:
marcotngsn/semantic-cache registry level : 1★ tree level : 3★ (stale) would append: demote 2026-06-02T23:49:18Z 3★ → 1★ (from registry node) marcotngsn/context-window-mgmt registry level : 2★ tree level : 2★ (ok — only timeline missing) would append: rank_up 2026-05-28T11:00:01Z 1★ → 2★ (reconciled — no source event in .md)
Two event labels appear in the output:
| Label | Meaning |
|---|---|
(from registry node) |
The .md file's timeline: frontmatter records this exact level change — the event is mirrored verbatim |
(reconciled) |
No source event exists in the .md — the change was applied without documentation. trace_timeline.py synthesizes a clearly labelled event dated one second after the skill's latest existing event |
For a more accurate note on a reconciled event, cross-reference git history. Demotions usually land in sweep commits:
git log --oneline -- registry/named/<handle>/<slug>.md git log --oneline --all | grep -iE "demot|star-bar|calibrat|reclassif"
3 — Apply the fix
Once you've reviewed the dry-run output, apply with --apply.
Mutating the user tree requires Verifier authorization or the
GAIA_OPERATOR_OVERRIDE=1 env var (for CI and bots).
# Fix one skill GAIA_OPERATOR_OVERRIDE=1 python scripts/trace_timeline.py marcotngsn/semantic-cache --apply # Fix every drifting skill at once GAIA_OPERATOR_OVERRIDE=1 python scripts/trace_timeline.py --all --apply
The script does three things per drifting skill:
-
Appends the missing events to
timeline[]in the user tree, includingpreviousValueandnewValueso the rank chart plots them. -
Sets
unlockedSkills[].levelto the current registry level, resolving the stale-level invariant. -
Rebuilds
levelHistoryfrom the full run of events — register event + all level changes in chronological order.
Manual CLI path
For a single hand-authored event, gaia dev timeline can append it directly.
Use this when you want to write a custom note rather than mirror the registry node verbatim.
gaia dev timeline <handle>/<slug> \
--user <owner> \
--action demote \
--timestamp 2026-06-02T23:48:18Z \
--notes "Star-Bar hard reset (META §2.4): installable link was tree/ not blob/"
gaia dev timeline does not write previousValue or
newValue fields — those are what the rank chart reads to plot transitions.
Without them the event appears in the timeline list but the chart line stays flat.
Use trace_timeline.py --apply for any demote or rank_up event.
Reserve gaia dev timeline for milestone events (register, fuse, notes)
that don't represent rank changes.
Known CLI gaps
Several gaps exist in gaia dev timeline as of v5.8.2.
Do not silently work around them with hand-edits; flag any gap-forced edit in
the PR description and include a "(direct edit — CLI gap)" marker in
the event's details string.
| Gap | Impact | Workaround |
|---|---|---|
Without --user, writes to the registry node, not skill-tree.json |
User-tree timeline cannot be appended via CLI without the flag | Always pass --user <handle> when targeting a user tree |
No previousValue / newValue output |
Level changes don't plot on the rank chart | Use trace_timeline.py --apply for demote / rank_up |
No gaia demote command |
Demotion events can't be written via promote flow | Use trace_timeline.py --apply or gaia dev timeline --action demote + manual previousValue |
No gaia remove-skill command |
Stale generic entries can't be removed via CLI | Direct JSON edit to remove from unlockedSkills; then log with gaia dev timeline --action demote |
When a gap forces a direct JSON edit: add "(direct edit — CLI gap)"
in the details string, document the gap in the PR description, and
open or link a tracking issue. An imperfect timeline entry is better than none.
After the backfill
After applying fixes, regenerate the public profile artifacts and re-run the gate to confirm clean state before committing.
# 1. Regenerate profiles and rank charts GAIA_OPERATOR_OVERRIDE=1 gaia dev docs # 2. Re-run the Transparency Gate — must print ✓ python scripts/validate_timelines.py # 3. Discard generated-artifact churn before staging git checkout registry/gaia.json docs/graph/gaia.json registry/gaia.gexf docs/css/tokens.css # 4. Stage and commit the user-tree change only git add skill-trees/<owner>/skill-tree.json git commit -m "fix(timeline): backfill missing demote event for <handle>/<slug>"
gaia dev docs regenerates registry/gaia.json,
docs/graph/gaia.json, registry/gaia.gexf, and
docs/css/tokens.css with fresh timestamps. These are build artifacts —
always git checkout them before staging. Only commit the
skill-trees/ file(s) you intentionally changed.
Common drift causes
Most timeline drift traces to one of three registry-side operations that touch skill levels without triggering a user-tree update:
META §2.4
blob/ link.
The Star-Bar policy hard-resets the skill to 1★. The registry node level
drops immediately; the user tree is not updated.
Look for sweep commits with messages matching
star-bar or
installable: false.
registry-only change
installable: false, which caps the rank. Applied via
gaia dev reclassify on the registry node only.
Search commit history for
reclassif or calibrat.
dead or broken link
gaia dev rm-evidence or an audit sweep).
The user tree keeps the old rank.
Check the skill's
.md evidence: block for links
that 404 or changed path.
CI enforcement
The Transparency Gate runs as part of the release validation suite triggered by
gaia dev validate and in release CI. PRs that mutate
skill-trees/ files are checked against the gate automatically via
.github/workflows/validate.yml.
gaia dev validate # → canonical graph validator # → redaction gate # → Transparency Gate (validate_timelines.py) # Or run the Transparency Gate alone python scripts/validate_timelines.py
A clean Transparency Gate is required for release. If the gate fails in CI after
a curation PR (registry level changed but user tree not updated), use
trace_timeline.py --apply to fix the drift in a follow-up commit.
In CI pipelines and bots, set GAIA_OPERATOR_OVERRIDE=1 before
calling trace_timeline.py --apply. This bypasses the Verifier
check without needing a 4★ named skill credential.
The meta-guard.yml workflow allowlists known bot actors
(claude-bot, jules, codex, gemini-bot)
so those PRs pass the meta guard automatically.