Ticket graphs

Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.

← all ticket-graphs

DONE#452 congruency

Forge evolution PART 2 (v2, supersedes #449): EVOLUTION TIMELINE from git dates + containment retirement gate + 118-blob orphan worklist. OMNI IS BLACK-BOXED — it is not a git (every git cmd fails) and covers 11% of forge; config/forge/remotes is the only authority.

goal

PART 2 OF 2. Part 1 (#445) LANDED on main: tools/forge_family_tree.py clusters 264 bare repos into 109 lineages, classifies each, and emits --json. It deliberately never retires or merges anything. THIS ticket adds the two things Part 1 does not do: the EVOLUTION TIMELINE and the RETIREMENT GATE. DELIVERABLE: checkouts/current/congruency/tools/forge_evolution.py — standalone, stdlib + git only. CONSUME Part 1, do not re-derive it: run `forge_family_tree.py --json -` (or accept --family-json PATH) and build on its lineages/members/tips/classification. Part 1 already solved tip resolution (183 of 264 repos have an UNRESOLVABLE HEAD — a symref to a refs/heads/master that does not exist; `rev-parse HEAD` prints the literal string "HEAD" and EXITS 0). Never re-resolve tips yourself; never trust HEAD. 1. EVOLUTION IN STAGES — the temporal dimension. Part 1 carries NO time data at all (verified: zero ts/date keys in its JSON). Git already has every date for free. For each lineage emit a TIMELINE: * root commit date (`log -1 --format=%ai <root>`) * FORK POINT date per member (`log -1 --format=%ai $(merge-base <canonical-tip> <member-tip>)`) * tip date per member * the fork WIDTH — tip date minus fork-point date — i.e. how long the fork ran alone. Order members by fork-point date so the tree reads as stages, oldest first. WORKED EXAMPLE to reproduce (measured, agent_tools_suite lineage, root 379ac05319): 2026-07-08 20:38 root 379ac05 "agent_tools_suite: crankimg ..." 2026-07-11 02:23 FORK POINT = agent_tools_suite tip 888d3447c6 ("add deploy_test loop") 2026-07-12 14:07 jail tip 38e95ad16d -> jail ran 4 days alone, adding 6 files 2. OMNI IS BLACK-BOXED — DO NOT OPEN IT. omni.git is NOT a git; it is a TOOL MADE OF GITS (an archive of many repos' refs). Every ordinary git command against it FAILS — measured: git -C omni.git rev-parse --abbrev-ref HEAD -> "HEAD" (the literal string) git -C omni.git ls-tree HEAD -> fatal: Not a valid object name HEAD git -C omni.git log -1 -> fatal: your current branch 'master' has no commit It also does not cover the forge: measured 264 forge bare repos vs 145 repos in omni, of which only 29 overlap — omni COVERS 11% AND MISSES 235 OF THE FORGE REPOS. (The "~86%" figure the ticketer cites is BLOB coverage in `occ`, not repo coverage. Do not conflate them.) THEREFORE: do not walk refs/archives/*, do not read omni.git, do not treat it as a source of dates or provenance, and do not confuse it with forge. config/forge/remotes/*.git is the ONLY authority. If a future omni interface is wanted it is a separate tool with its own contract — NOT this ticket, and NOT by an agent reaching into omni's internals. 3. RETIREMENT GATE — CONTAINMENT PROOF (mechanical, not a judgement). A fork is SAFE TO OMIT IF AND ONLY IF every file at its tip resolves, by CONTENT IDENTITY, to a blob present in the home repo(s). Use the SAME module-sha the ticketer computes: sha256("raw:" + bytes)[:16] — non-Python, or Python that does not parse sha256("ast:" + codebean._ast_norm(src))[:16] — Python that parses AST-normalized identity means a file that came home with only whitespace/comment drift still counts as CONTAINED (same module), while a semantically changed file correctly FAILS and surfaces as work. * every blob resolves -> report "SAFE TO OMIT (proven)" — proven, never assumed. * any blob unresolved -> emit the exact file list. The fork MUST NOT be dropped while that list is non-empty. THE UNRESOLVED LIST *IS* THE MERGE-HOME WORKLIST; it shrinks to empty exactly when the fork is safe to drop. 4. THE ORPHAN SET. Measured across the forge: 118 blobs (2.1% of 5,741 unique code blobs) exist ONLY in non-canonical tips — content that would be SILENTLY LOST by naively unifying on canonical tips. Emit them as a first-class report: blob sha, path, which member holds it, which lineage. This is small enough to review by hand and is the highest-value output of this tool. 5. DIRECTION IS NOT YOURS TO DECIDE. Part 1 already accepts --override / --override-file for the operator's per-PACKAGE home mapping (a grab-bag fork like jail has packages belonging to SEVERAL different homes). Honour that mapping; PROPOSE only; never infer direction from topology. LINEAR and FORK-CAME-HOME are the same git shape and differ only in intent, which is not in the DAG. NOTE: for LINEAR the direction cannot lose code (canonical supersets the rest) — it is a naming decision. Only DIVERGED/SPLIT-STORE put content at risk. Say so in the report. 6. READ-ONLY, ALWAYS. Never write to config/forge/remotes/*.git — the bare repos are the archive. Use `git show`/`cat-file`/`ls-tree`/`log`/`merge-base` plumbing only. Never write to the governance db. This tool PLANS retirement; it never performs it. 7. --selftest on THROWAWAY git repos in a temp dir: a fork fully contained in home (-> SAFE TO OMIT), a fork with one un-merged file (-> worklist of exactly 1, NOT safe), whitespace/comment-only drift (-> contained via ast identity), a semantic change (-> correctly NOT contained), and a fork-width timeline assertion. No network, no live db, no writes outside the temp dir. ACCEPTANCE: --selftest green; a live read-only run reproduces the measured 118-blob orphan set and prints the agent_tools_suite timeline above (root 2026-07-08 20:38 -> fork point 2026-07-11 02:23 -> jail tip 2026-07-12 14:07, width 4 days, 6 files added); jail reports NOT-safe-to-omit with its 6 files as the worklist (they have not come home yet); no writes anywhere. RUN ON CLAUDE.

patch

none

approval

unapproved

runs

no runs recorded

mandate (clauses)

not yet recorded — lands with the ticket-class build

manifestations

not yet recorded — lands with the ticket-class build

guarantees

not yet recorded — lands with the ticket-class build

journey (blunders & successes)

not yet recorded — lands with the ticket-class build

artifacts (forge)

not yet recorded — lands with the ticket-class build

source (forge tree)

browse congruency source (forge-parked tree)

children

#462 forge_evolution.py docs: my fabricated 'omni covers 11%' propagated into a merged tool (3 places). Measured by OBJECT it is 74% (193/261 tips) — my by-name join was broken. Conclusion unchanged (omni stays black-boxed); docs only. (spawned · DONE)