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

Route equivalence for the 24 WRITE (POST) routes — currently ALL skipped, so the governance spine (process_status, ticket.create, ticket.assign, edge.record) has ZERO Go/PHP proof. Verify response AND resulting rows against a throwaway scratch db. This is the #279 class.

goal

Extend the Go/PHP route equivalence harness to cover the 24 WRITE (POST) routes. Today they are ALL skipped, so the governance spine has ZERO equivalence proof between the two engines. THE GAP (measured, re-runnable): cd congruency-go && go test . -run TestRouteEquivalence -v -> "skipped write (POST) routes (24): agent.register, artifact.record, authorize_process, binding.record, bug.record, doc.record, edge.record, guarantee.record, job.put, journey.record, mandate.record, policy.create, process_status, run.record, search.record, source.record, telemetry.record, ticket.approve, ticket.assign, ticket.create, ticket.kind, ticket.lesson, ticket.progress, work.record" The GET routes all PASS byte-identical. The writes are simply not checked - verifying them would mutate the live db, so the harness declines. That is an honest skip, not a bug, but it leaves the MOST DANGEROUS routes unverified: process_status, ticket.create, ticket.assign, edge.record. WHY IT MATTERS - THIS CLASS ALREADY BIT US. #279 shipped a pair_loop change where process_status set ACTIVE *before* ticket.assign. Nothing verified that write path. It merged, and EVERY new dispatch died with HTTP 500 ("a ticket authorizes an agent: assign one before it can go ACTIVE") until it was reverted. An unverified write path is exactly how that happened, and the Go port could diverge from PHP on any of these 24 with nothing to notice. THE PATTERN ALREADY EXISTS - DO NOT INVENT ONE. per_unit_test_harness.py already "copies it to a throwaway scratch db under" a temp dir and drives the target against that. Reuse that discipline. DELIVERABLE: congruency-go/equivalence_test.go (extend it; do not fork a second harness). 1. THROWAWAY DB, NEVER THE LIVE ONE. Per POST route: copy the state db to a temp scratch file, point BOTH engines at the copy, run the route once against each (two independent copies - PHP gets one, Go gets one, from the SAME pristine snapshot so neither sees the other's writes). 2. EQUIVALENCE FOR A WRITE IS TWO THINGS, NOT ONE: (a) the RESPONSE bodies must match byte-identically (as the GET tests already assert), AND (b) the RESULTING DB STATE must match. Compare the post-state of the touched table(s) - the affected rows, not the whole db (ts/rowid/autoincrement will legitimately differ). Normalize or exclude volatile columns (strftime('%s','now') timestamps, AUTOINCREMENT ids) and SAY IN THE TEST which columns were excluded and why. A write route that returns identical JSON but writes different rows is exactly the #279 failure and MUST fail this test. 3. ROUTES ARE DATA - both engines read the SAME `api_routes` row (62 rows; boot/rest.php and congruency-go/db.go are two engines over one table). So the test is: same row + same bound params -> same response + same resulting rows. Drive every route from its row, not from hand-written per-route code. 4. INPUTS. Each POST route needs valid bound params. Derive them from the route's declared `params` column where possible; where a route needs a precondition (process_status ACTIVE requires a prior ticket.assign - the #279 lesson), set that precondition up IN THE SCRATCH DB and say so. 5. NO SILENT SKIPS. If a route genuinely cannot be driven (needs a live agent, external side effect), it may be skipped ONLY with a NAMED reason printed in the test output. The current blanket "skipped write (POST) routes (24)" is what hid this for so long. Zero unnamed skips. 6. READ-ONLY TOWARD THE WORLD: no writes to the live state db, no network, no agents spawned. Assert it: capture the live db's mtime+size before and after and fail if either changed. ACCEPTANCE: `go test . -run TestRouteEquivalence` still green; all 24 POST routes are either PROVEN (response + resulting rows identical Go vs PHP) or skipped with a NAMED reason; process_status, ticket.create, ticket.assign and edge.record are among the PROVEN set (they are the spine); the live state db is byte-unchanged by the run; the volatile-column exclusions are enumerated in the test. NOTE FOR THE FILER (not the worker): once these proofs exist they are the natural first rows for the `guarantees` table (ticket_id, clause_seq, digest) + guarantee.held, which is BUILT and has 0 rows. That wiring is deliberately OUT OF SCOPE here - this ticket delivers the proofs, not the ledger. 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