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.
goal
THE MINT HOOK: stamp every deliverable into the artifact registry AT COMMIT TIME, so the universal
resolver can find it. Small, one seam, no new stores, no new tables, no new routes.
WHY — A REGISTRY POPULATED BY SCANNING IS STALE THE MOMENT IT FINISHES. Everything here was designed
as a scan target rather than a mint hook, and so every registry is EMPTY:
ticket_artifacts 0 rows guarantees 0 rows mandates 0 rows deployed apps 0 rows
MEASURED TODAY: five deliverables were minted, adversarially reviewed, and MERGED to main — #438
(ollama_agent.py), #440 (regression_capture.py), #445 (forge_family_tree.py), #448 (TreeView.php),
#439 (pair_loop.py). ALL FIVE WENT UNSTAMPED. ticket_artifacts is still 0. Nothing was forgotten by
a person; nothing was ever written down.
CONSEQUENCE, VERIFIED: the universal resolver (#193, DONE, tools/resolver.py) already probes
'forge-artifacts' for a sha256 — the lookup path EXISTS:
resolver.py <sha256> -> kind: sha256 | probes: content-sha256, forge-artifacts, git-object-db
-> resolved: 0 blob(s) (the collection is empty)
It finds nothing because nothing registers anything. Fix the tether, not the resolver.
THE SEAM: pair_loop.py:457 `commit_deliverable(self, tid, deliverable, message, package="auto")` —
"the harness holds the git pen". At that moment the harness ALREADY knows the ticket id, the
deliverable path, and the commit. The blob sha is one command away and git already content-addresses:
git rev-parse <commit>:<path> -> 61da1cda16808223259e425dcaa5f157b5339e09
That value IS ticket_artifacts.forge_sha. No new blob store: FOR CODE, GIT IS THE STORE.
(large-blobs / artifacts/blobs exist only because VM images do not fit in git.)
DELIVERABLE: checkouts/current/congruency/tools/pair_loop.py
1. In commit_deliverable, AFTER the commit succeeds, call the EXISTING governed route for EVERY file
that landed in the commit's scope (a forge package commits several — stamp each, not just one):
artifact.record { ticket_id, name, version, forge_sha, kind }
* forge_sha = `git rev-parse <commit>:<path>` — the git BLOB sha of that exact file.
* name = the deliverable's repo-relative PATH (stable, resolvable, what a human greps).
* version = the short commit sha the file landed in.
* kind = a STABLE artifact class, documented in the docstring. Use 'forge-source' for pair
deliverables. Do NOT reuse the ticket's kind (build/test/...) — that is the WORK's
kind, not the ARTIFACT's. (VMs register as 'forge-vm'; same column, different class.)
2. NEVER FATAL. A failed artifact.record MUST NOT fail the commit or the round. Log it and continue —
the stamp is instrumentation, and instrumentation that can break the pipeline is worse than none.
(Same discipline TreeView's telemetry already follows.)
3. IDEMPOTENT. Re-committing the same bytes must not mint a duplicate row: same (ticket_id, forge_sha)
-> record once. A round-2 commit of an unchanged file is not a new artifact.
4. DO NOT TOUCH THE #279 GUARD. pair_loop.py:978 refuses `process_status -> ACTIVE` before any
ticket.assign — "This is ticket #279's regression, guarded at its source." That guard and the LAZY
registration around it are load-bearing: #279 broke EVERY dispatch for hours. Leave the
assign/ACTIVE ordering exactly as it is. This ticket adds a stamp; it changes no governance.
5. DO NOT hand-write the db. artifact.record only.
ACCEPTANCE — three gates, all re-runnable:
* REGRESSION: `pair_loop.py --selftest` >= 140/140 (it is 140/140 on main after #439; do not lose a
check), AND a live `--ticket N` dispatch still reaches ACTIVE (dispatching a BRAKED ticket must
still print "== HALT ==" and exit 3 — that proves the governance path runs).
* THE STAMP: after a pair commits, ticket_artifacts gains one row per file in the commit scope, with
forge_sha equal to `git rev-parse <commit>:<path>` for that file. Verify by committing in the
selftest's THROWAWAY repo — no live db writes from the selftest.
* THE DIRECTORY-SERVICE GATE (the point): `resolver.py <that forge_sha>` MUST resolve to a match
naming that path via the forge-artifacts probe. A row the resolver cannot see is a FAILED ticket.
RUN ON CLAUDE. This file is the orchestrator every pair runs — patch it, do not reimagine it.