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

TreeView: build TESTED ref-resolution tooling + record ref_resolve telemetry to `events` — 180/264 forge repos unbrowsable because 183 registry rows store the literal string 'HEAD' (dangling symref; rev-parse exits 0). Do NOT repair the data.

goal

Build TESTED tooling for forge ref-resolution in TreeView, and RECORD TELEMETRY so the underlying registry corruption stops being invisible. Do NOT repair data. Do NOT hand-edit the db. THE SYMPTOM (operator-reported, reproduced): ?page=tree&repo=code__rebuild__jazz_wallet renders "Could not read the tree at ." — an unhelpful message that names neither the cause nor the repo state. THE CAUSE (measured, not inferred): * TreeView.php:49 does `$commit = (string) $repos[$repo]['head_commit'];` and hands that value straight to `git ls-tree` (line 94) / `git show` (line 133). It trusts the registry blindly. * 183 of the 263 rows in `repos` store head_commit = the LITERAL 4-CHAR STRING 'HEAD' — not a sha, not null, not empty. Verified: SELECT DISTINCT head_commit ... -> 'HEAD' x183. * WHY the string got stored: in those bare remotes HEAD is a DANGLING SYMREF — it points at refs/heads/master, which does NOT exist (the real branch is `main`). On such a repo, `git rev-parse HEAD` prints the literal text "HEAD" AND EXITS 0. It looks like success. Whatever wrote the registry believed it. * So `git ls-tree HEAD` -> "fatal: Not a valid object name HEAD" -> the useless page message. BLAST RADIUS (measured over all 264 bare remotes in config/forge/remotes): 81 repos — `ls-tree HEAD` works 180 repos — fails, BUT the tree is fully readable via refs/heads/* -> RECOVERABLE (68% of forge) 3 repos — genuinely carry no refs at all -> must fail, but must fail CLEANLY DO NOT "FIX" THE DATA. The 183 rows stay exactly as they are. Every tool that matters ALREADY treats head_commit as untrustworthy input and routes around it — ingest_repo_docs.py documents the identical 183/263 finding and carries the fallback chain, and forge_refactor_ticketer.py resolves tips itself ("TIP BEATS OMNI"). TreeView is the ONLY consumer that trusts the value. Repairing 183 rows by hand would be surgery on the governance db to fix data that three tools deliberately ignore. Not wanted. DELIVERABLE: checkouts/current/congruency/invocators/tags/dev/TreeView.php 1. RESOLUTION AS TOOLING, NOT AN INLINE PATCH. Extract ref resolution into ONE small, separately testable method. Resolve in this order — COPY the chain that already exists, do not reinvent it (ingest_repo_docs.py:110, FALLBACK_REFS): a. repos.head_commit — ONLY if it resolves: `rev-parse --verify --quiet <val>^{commit}`. This single guard is what rejects the literal 'HEAD'. Never trust the column's contents. b. HEAD c. refs/heads/main, refs/heads/master, refs/heads/source, refs/heads/trunk d. the first ref under refs/heads/ Every candidate MUST be validated with `rev-parse --verify --quiet <ref>^{commit}` and the resolved 40-hex sha used thereafter — never a symbolic name. Keep TreeView's existing security posture verbatim: escapeshellarg() every git argument, read-only subcommands only (ls-tree/show/ rev-parse/for-each-ref), htmlspecialchars() all output before it re-enters execute_all_tags. 2. FAIL PREDICTABLY. The 3 no-ref repos MUST produce a clear, typed, named outcome — e.g. "repo X has no refs under refs/heads (bare remote is empty)" — not "Could not read the tree at .". Any failure must name the repo AND which resolution tiers were attempted. A predictable failure is the point of building this as tooling. 3. RECORD TELEMETRY — this is a REQUIREMENT, not a nice-to-have. Emit one row per resolution to the EXISTING `events` table (id, ts, component, event, status, ms, meta) — do NOT invent a new sink, do NOT use front_telemetry (that is the front-end page beacon, wrong shape). component = 'TreeView' event = 'ref_resolve' status = 'ok' | 'fallback' | 'no_refs' ms = resolution wall time meta = json: {repo, tier: 'head_commit'|'HEAD'|'refs/heads/main'|..., resolved_sha, head_commit_rejected: true|false} The POINT: today the 183-row corruption is INVISIBLE because ingest_repo_docs.py falls back silently and nobody learns. With this telemetry the fallback tier distribution is queryable, so the corruption becomes observable. Telemetry must never throw — a telemetry failure must not break the page (TreeView already degrades gracefully; preserve that). 4. TEST THE TOOLING. Two layers: * synthetic: throwaway bare repos in a temp dir covering every case — dangling symref to a missing master with only `main` present (the live 180-repo shape), HEAD resolving normally, main-only, master-only, NO refs at all, and head_commit holding the literal 'HEAD'. Assert the resolved sha (or the typed no-refs outcome) for each. No network, no live db. * live: run the resolver across all 264 real remotes under config/forge/remotes and assert the measured distribution — 261 resolve to a 40-hex sha, 3 report no_refs. Read-only. ACCEPTANCE: ?page=tree renders the real tree for the previously-broken repos (verify with code__rebuild__jazz_wallet, whose tree is README.md/deferred/install.json/install.py/src/tests via refs/heads/main); 261/264 resolve, the 3 no-ref repos fail with a named reason; `events` gains one ref_resolve row per resolution with the tier recorded; the registry is UNCHANGED (assert: SELECT COUNT(*) FROM repos WHERE head_commit='HEAD' is STILL 183 afterwards — this ticket must not write to the db); TreeView's escapeshellarg/htmlspecialchars posture is unchanged. 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

not yet recorded — lands with the ticket-class build