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#457 congruency

THE MINT HOOK: pair_loop.commit_deliverable must artifact.record every file it commits (forge_sha = the git blob sha, free at commit) — 5 deliverables merged today, ALL unstamped, ticket_artifacts still 0 rows. Gate: resolver.py <sha> must find it.

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.

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

#458 <<<ArtifactRegistry>>> on ?page=artifacts — make the registry VIEWABLE and INTERACTIVE in congruency: kind is the tag, its items drill in, sha detail shows every location. Reads ticket_artifacts (0 rows today; #455/#457 fill it). (spawned · DONE)