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

<<<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).

goal

Make the artifact registry VIEWABLE AND INTERACTIVE in congruency, as a tag — not a path, not a sqlite3 invocation. The registry is worthless if reading it requires a shell. DELIVERABLE: checkouts/current/congruency/invocators/tags/dev/ArtifactRegistry.php Invoked as <<<ArtifactRegistry>>> on ?page=artifacts. MATCH THE EXISTING TAG CONTRACT — 31 dev tags already do this; copy their shape, invent nothing: * `class ArtifactRegistry implements Tag_Interface`, `__construct($arguments)`, `private static function esc($s) { return htmlspecialchars((string) $s, ENT_QUOTES); }` * Read ONLY. Facets come from $_GET and are BOUND (:param), NEVER interpolated. * ESCAPE-FIRST: EVERY value out of the db (name, kind, version, sha, path) is esc()'d BEFORE it flows back through execute_all_tags — a stored <<<Tag>>> or <script> in an artifact name must stay inert. This is the posture ForgeView/OmniExplorer/TreeView already hold; do not weaken it. * Degrade gracefully, never crash the page. ONE DIFFERENCE FROM ITS SIBLINGS: ForgeView/OmniExplorer/ShadowForge open a SECOND PDO to a separate db (forge.db / omni_index.db). `ticket_artifacts` lives in the PRIMARY congruency db, so use the existing handle. Still never write: SELECT only, no INSERT/UPDATE/DELETE anywhere in this file. THE DATA: ticket_artifacts (id, ts, ticket_id, name, version, forge_sha, kind). TODAY IT HAS 0 ROWS — #455 (VM registry) and #457 (the mint hook) are what populate it. This tag MUST render correctly against an EMPTY table today and light up automatically as rows arrive. KIND IS THE TAG; ITS ITEMS ARE INTERACTIVE. Three views, each a link away from the others: 1. ?page=artifacts — the index. GROUP BY kind: one row per kind with its count (e.g. forge-vm 12, forge-source 5). EVERY KIND IS A LINK to its filtered view. Plus the N most recent artifacts overall (ts DESC), each linking to its detail. 2. ?page=artifacts&kind=<k> — the items of that tag. One row per artifact: name, version, short forge_sha, ts, minting ticket. EVERY ROW LINKS to the detail view; the ticket id links to that ticket. Bound :kind. Unknown kind -> "no artifacts of kind X" + a link back, never an error. 3. ?page=artifacts&sha=<forge_sha> — ONE artifact, the interactive leaf: * its identity: name, kind, version, full forge_sha (shown in full — it is the lookup key) * EVERY LOCATION the same forge_sha was registered at (the same bytes in two places is ONE artifact with several locations — that is the entire point of content addressing) * the minting ticket(s), linked * ts / first seen Bound :sha. Unknown sha -> "not registered" + a link back. WHY THE SHA IS SHOWN IN FULL: it is what the universal resolver (#193, DONE, tools/resolver.py) takes as input — `resolver.py <forge_sha>` probes 'forge-artifacts' and returns every place that blob lives. The page and the resolver are two doors on the same registry. State that in the docstring so the next reader knows the sha is a live handle, not decoration. DO NOT: add a nav/menu entry, touch any other tag, create tables, write rows, or read the filesystem. This tag DISPLAYS the registry. Registering is #455/#457's job; repairing is nobody's job yet. ACCEPTANCE: * ?page=artifacts renders TODAY against the EMPTY table: a clear "no artifacts registered yet" (naming #455/#457 as what fills it), HTTP 200, page not crashed. This is the state you can actually test right now. * Seed a THROWAWAY copy of the db with a few rows across two kinds and verify: the index groups by kind with correct counts; each kind links to its filtered list; each row links to the sha detail; the detail shows all locations sharing that sha. Never seed the live db. * SECURITY: an artifact whose name contains `<<<TreeView>>>` and `<script>alert(1)</script>` renders INERT (escaped) — prove it in the throwaway. * grep the file: zero INSERT/UPDATE/DELETE; every $_GET facet bound, none interpolated. 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

#459 migrations/0034_artifacts_page.sql — wire ?page=artifacts to the <<<ArtifactRegistry>>> tag (#458 merged it INERT: no Documents/Document_Templates pair, no nav entry). Copy 0025_shadowforge_page.sql / 0023_bug_catalog.sql. (spawned · DONE)