// Read-model coverage for the delivery-graph progress projection — the member-PR rollup, the coarse-key // stage/stage_state derivation, and the promoted `pipeline` render binding — authored via Urban's // ADR-0065 declare-once primitives (app/deliveryGraphReadModel.ts). ADR 0006 §4b, issue #541 / S7; the // exemplars are app/featureReadModel.test.ts and app/planReadModel.test.ts. // // Guards: // 1. DRIFT GUARD — migration 087 embeds the rollup VIEW DDL VERBATIM from `rollup.viewDdl()` and each // derived column VERBATIM from `deliveryGraphReadModel.sqlSelectFor(...)`, so the checked-in VIEWs // cannot drift from the declarations. // 2. FRAMEWORK PARITY GUARD — `assertRollupParity` / `assertReadModelParity` prove the SQL and TS // lowerings each declaration compiles to agree. // 3. END-TO-END BEHAVIOUR on the REAL migration VIEW (087 applied to an in-memory DB): the coarse-key // matrix, the member-PR temper, the terminal-fold bypass, and the companion `park_label`. // 4. PARITY vs TODAY'S PHASE — for representative `deriveDeliveryPhase` outputs (what the plain Phase // text cell showed), the derived stepper matches, and the actionable park label is retained. // 5. ONE PROJECTION — the VIEW's (stage, state) equals `reduceFrontier` of the single derived branch, // tying the render half to the canonical axis (app/stepAxis.ts). // 6. PAGE BINDINGS — the delivery-graph pages bind the derived VIEW + the `pipeline` kind, not a plain // `phase` text cell on the raw table. import { readFileSync } from "node:fs"; import { DatabaseSync } from "node:sqlite"; import { test } from "node:test"; import { fileURLToPath } from "node:url"; import { assertReadModelParity, assertRollupParity, type ParityDb, type ParitySample, type ProcessInstanceState, type RollupInputs } from "@nanobpm/urban"; import { assert, assertEquals } from "#test-assert"; import { deriveDeliveryPhase } from "./deliveryGraphRun.ts"; import { DELIVERY_GRAPH_READ_MODEL_BASE_ALIAS, DELIVERY_GRAPH_READ_MODEL_DERIVED, DELIVERY_GRAPH_ROLLUPS, deliveryGraphPrCounts, deliveryGraphReadModel, PR_COUNTS_LOOKUP, } from "./deliveryGraphReadModel.ts"; import { reduceFrontier, type StepKey } from "./stepAxis.ts"; import { applyMigrationSet, readMigrationSetFromDisk } from "../test/migrations.ts"; const MIG = (name: string) => readFileSync(fileURLToPath(new URL(`../db/migrations/${name}`, import.meta.url)), "utf8"); const PAGE = (name: string) => JSON.parse(readFileSync(fileURLToPath(new URL(`../pages/${name}`, import.meta.url)), "utf8")); const READ_MODEL_MIGRATION = "096_delivery_graph_read_model_list_bucket.sql"; // A minimal in-memory DB carrying the base `delivery_graph_runs` / `pull_requests` shapes the VIEW // reads, plus stand-ins for the managed `__tracking` derived VIEWs urban provisions at mount // (each re-exports `base.*` plus the terminal-folded `derived_status`). `derived_status_override` models // the reconciler's derive edge (a terminated instance ⇒ `failed`/`abandoned` while base `status` stays // frozen). Then migration 087 (the rollup VIEW + the read model VIEW) is applied. function viewDb(): DatabaseSync { const db = new DatabaseSync(":memory:"); db.exec( `CREATE TABLE delivery_graph_runs ( run_key TEXT PRIMARY KEY, process_key TEXT, process_definition_id TEXT, digest TEXT, status TEXT, side_effecting INTEGER, node_count INTEGER, human_node_count INTEGER, side_effect_count INTEGER, title TEXT, phase TEXT, phase_node_id TEXT, human_labels TEXT, created_at TEXT, updated_at TEXT, acknowledged_at TEXT, derived_status_override TEXT); CREATE TABLE pull_requests (pr_key TEXT PRIMARY KEY, root_request_key TEXT, status TEXT, derived_status_override TEXT);`, ); db.exec( `CREATE VIEW delivery_graph_runs__tracking AS SELECT d.*, COALESCE(d.derived_status_override, d.status) AS derived_status FROM delivery_graph_runs d; CREATE VIEW pull_requests__tracking AS SELECT p.*, COALESCE(p.derived_status_override, p.status) AS derived_status FROM pull_requests p;`, ); db.exec(MIG(READ_MODEL_MIGRATION)); return db; } interface SampleRun { status: string; phase?: string | null; phase_node_id?: string | null; derived_status_override?: string | null; acknowledged_at?: string | null; } function addRun(db: DatabaseSync, run_key: string, run: SampleRun): void { db.prepare( `INSERT INTO delivery_graph_runs (run_key, process_key, process_definition_id, digest, status, side_effecting, node_count, human_node_count, side_effect_count, title, phase, phase_node_id, human_labels, created_at, updated_at, acknowledged_at, derived_status_override) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( run_key, `pk-${run_key}`, "delivery-graph", `digest-${run_key}`, run.status, 0, 3, 1, 0, `Graph ${run_key}`, run.phase ?? null, run.phase_node_id ?? null, null, "2026-01-01T00:00:00Z", "2026-01-01T00:00:00Z", run.acknowledged_at ?? null, run.derived_status_override ?? null, ); } function addPr(db: DatabaseSync, pr_key: string, root_request_key: string | null, status: string, derived_status_override: string | null = null): void { db.prepare("INSERT INTO pull_requests (pr_key, root_request_key, status, derived_status_override) VALUES (?, ?, ?, ?)").run( pr_key, root_request_key, status, derived_status_override, ); } function projection(db: DatabaseSync, run_key: string): { stage: string; stage_state: string | null; park_label: string | null; status: string } { const r = db .prepare("SELECT stage, stage_state, park_label, status FROM delivery_graph_read_model WHERE run_key = ?") .get(run_key) as { stage: string; stage_state: string | null; park_label: string | null; status: string }; return { stage: r.stage, stage_state: r.stage_state, park_label: r.park_label, status: r.status }; } // A `ParityDb` over node:sqlite's `DatabaseSync` for the framework parity guards. function parityDb(db: DatabaseSync): ParityDb { return { exec: (sql) => db.exec(sql), all: >(sql: string, params: unknown[] = []) => db.prepare(sql).all(...(params as never[])) as T[], run: (sql, params: unknown[] = []) => { const r = db.prepare(sql).run(...(params as never[])); return { changes: Number(r.changes), lastInsertRowid: r.lastInsertRowid }; }, }; } // ── 1. DRIFT GUARD ──────────────────────────────────────────────────────────────────────────────── test("DRIFT GUARD: migration 087 embeds the rollup VIEW DDL VERBATIM from rollup.viewDdl() (the VIEW cannot drift from defineRollup)", () => { const sql = MIG(READ_MODEL_MIGRATION); for (const rollup of DELIVERY_GRAPH_ROLLUPS) { assert( sql.includes(rollup.viewDdl()), `migration ${READ_MODEL_MIGRATION} no longer embeds the declaration's VIEW DDL for rollup "${rollup.decl.name}" — ` + `regenerate it from app/deliveryGraphReadModel.ts. Expected to contain:\n${rollup.viewDdl()}`, ); assert(new RegExp(`DROP VIEW IF EXISTS ${rollup.decl.name};`).test(sql), `087 must DROP "${rollup.decl.name}" first`); } }); test("DRIFT GUARD: migration 087 embeds each derived column VERBATIM from deliveryGraphReadModel.sqlSelectFor (the VIEW cannot drift from the declaration)", () => { const sql = MIG(READ_MODEL_MIGRATION); const alias = DELIVERY_GRAPH_READ_MODEL_BASE_ALIAS; for (const c of DELIVERY_GRAPH_READ_MODEL_DERIVED) { const emitted = deliveryGraphReadModel.sqlSelectFor(c, { baseAlias: alias }); assert( sql.includes(`${emitted} AS ${c}`), `migration ${READ_MODEL_MIGRATION} no longer embeds the declaration's SQL for "${c}" — regenerate it from ` + `app/deliveryGraphReadModel.ts. Expected to contain:\n ${emitted} AS ${c}`, ); } assert(/DROP VIEW IF EXISTS delivery_graph_read_model;/.test(sql), "087 must DROP the VIEW first"); assert(/CREATE VIEW delivery_graph_read_model AS/.test(sql), "087 must (re)create delivery_graph_read_model"); // Base identity pass-throughs — DERIVED from the REAL `delivery_graph_runs` schema (the migration // chain applied to a throwaway DB), NOT a hand-kept list that could silently omit a column: the VIEW // must re-export EVERY base column so the static pages↔schema contract guard sees them (and a future // regeneration can't drop one without failing here). `status` is the one exception — it is exposed as // the effective COALESCE below rather than a bare pass-through — so it is asserted separately. const schemaDb = new DatabaseSync(":memory:"); applyMigrationSet(schemaDb, readMigrationSetFromDisk()); const baseColumns = (schemaDb.prepare("PRAGMA table_info(delivery_graph_runs)").all() as { name: string }[]).map((r) => r.name); schemaDb.close(); assert(baseColumns.length > 0, "the migration chain must create the delivery_graph_runs base table"); for (const base of baseColumns) { if (base === "status") continue; assert(sql.includes(`dg.${base} AS ${base}`), `087 must pass base column "${base}" through the VIEW (derived from the real delivery_graph_runs schema)`); } assert(sql.includes("COALESCE(dg.derived_status, dg.status) AS status"), "087 must expose the effective status so the pages' Active/History filter tracks a terminated run"); assert(sql.includes("AS park_label"), "087 must carry the hand-authored park_label companion column"); // FROM/JOIN relations are DERIVED from the declaration (baseTable + lookup rollup name + join keys). const alias2 = DELIVERY_GRAPH_READ_MODEL_BASE_ALIAS; assert(sql.includes(`FROM ${deliveryGraphReadModel.decl.baseTable} ${alias2}`), `087's FROM must be the declaration's baseTable "${deliveryGraphReadModel.decl.baseTable}"`); for (const lk of deliveryGraphReadModel.decl.lookups) { const on = lk.on.map((k) => `${alias2}.${k.base} = ${lk.as}.${k.rollup}`).join(" AND "); const join = `LEFT JOIN ${lk.rollup.decl.name} ${lk.as} ON ${on}`; assert(sql.includes(join), `087 must LEFT JOIN the declaration's "${lk.rollup.decl.name}" lookup exactly as "${join}"`); } }); // ── 2. FRAMEWORK PARITY GUARD ────────────────────────────────────────────────────────────────────── test("FRAMEWORK PARITY GUARD: delivery_graph_pr_counts VIEW and TS reduce agree (assertRollupParity)", () => { const sampleSets: RollupInputs[] = [ { pull_requests__tracking: [ { pr_key: "p0", root_request_key: "run-a", derived_status: "converging" }, { pr_key: "p1", root_request_key: "run-a", derived_status: "merged" }, { pr_key: "p2", root_request_key: "run-a", derived_status: "waiting_review" }, { pr_key: "p3", root_request_key: "run-b", derived_status: "abandoned" }, { pr_key: "p4", root_request_key: "run-b", derived_status: "converged" }, { pr_key: "p5", root_request_key: null, derived_status: "converging" }, ], }, { pull_requests__tracking: [] }, ]; for (const rollup of DELIVERY_GRAPH_ROLLUPS) { const db = new DatabaseSync(":memory:"); assertRollupParity(rollup, parityDb(db), sampleSets); db.close(); } }); test("FRAMEWORK PARITY GUARD: deliveryGraphReadModel's SQL and TS lowerings agree over the status × PR-in-flight matrix (assertReadModelParity)", () => { const samples: ParitySample[] = []; for (const status of ["awaiting-approval", "running", "done", "failed", "abandoned"]) { for (const derived_status of [status, "failed"]) { for (const prs_in_flight of [0, 1, 3]) { for (const acknowledged_at of [null, "2026-02-02T00:00:00Z"]) { samples.push({ baseRow: { run_key: "self", status, derived_status, acknowledged_at }, lookups: { [PR_COUNTS_LOOKUP]: [{ root_request_key: "self", prs_in_flight }] }, }); } } } } const db = new DatabaseSync(":memory:"); assertReadModelParity(deliveryGraphReadModel, parityDb(db), samples, { sql: { baseAlias: DELIVERY_GRAPH_READ_MODEL_BASE_ALIAS } }); db.close(); }); // ── 3. END-TO-END BEHAVIOUR on the real migration VIEW ──────────────────────────────────────────── test("the migration 087 VIEW maps the run lifecycle onto the coarse STAGE_KEYS bracket + render state", () => { const db = viewDb(); // awaiting-approval (reserved legacy pre-dispatch rows) → the initial Requested bracket. addRun(db, "await", { status: "awaiting-approval", phase: "Awaiting approval" }); // running, dispatch begun, no PR frontier → the deterministic initial Implementing. addRun(db, "run-plain", { status: "running", phase: "Running" }); // terminal done → Done / ok (settles outright). addRun(db, "done", { status: "done", phase: "Completed" }); // terminal failed / abandoned → the Done tail bracket / failed. addRun(db, "failed", { status: "failed", phase: "Failed" }); addRun(db, "aband", { status: "abandoned", phase: "Failed" }); assertEquals(projection(db, "await"), { stage: "Requested", stage_state: null, park_label: null, status: "awaiting-approval" }); assertEquals(projection(db, "run-plain"), { stage: "Implementing", stage_state: null, park_label: null, status: "running" }); assertEquals(projection(db, "done"), { stage: "Done", stage_state: "ok", park_label: null, status: "done" }); assertEquals(projection(db, "failed"), { stage: "Done", stage_state: "failed", park_label: null, status: "failed" }); assertEquals(projection(db, "aband"), { stage: "Done", stage_state: "failed", park_label: null, status: "abandoned" }); db.close(); }); test("a running run with a member PR still in flight (root_request_key = run_key) tempers to Converging; all-terminal members stay Implementing", () => { const db = viewDb(); addRun(db, "run-c", { status: "running", phase: "Running" }); addPr(db, "pr-open", "run-c", "converging"); addPr(db, "pr-merged", "run-c", "merged"); addRun(db, "run-i", { status: "running", phase: "Running" }); addPr(db, "pr-done", "run-i", "merged"); addPr(db, "pr-gone", "run-i", "abandoned"); assertEquals(projection(db, "run-c").stage, "Converging"); assertEquals(projection(db, "run-i").stage, "Implementing"); db.close(); }); test("an out-of-band-terminated member PR (derived_status='abandoned', base frozen) is NOT held in the live frontier", () => { const db = viewDb(); addRun(db, "run-x", { status: "running", phase: "Running" }); // Base status frozen at 'converging' but the reconciler's derive edge reports 'abandoned' (resolved). addPr(db, "pr-stale", "run-x", "converging", "abandoned"); assertEquals(projection(db, "run-x").stage, "Implementing"); db.close(); }); test("a DERIVE-ONLY terminated run (base status frozen at 'running', derived_status='failed') renders Done/failed, not wedged Implementing", () => { const db = viewDb(); addRun(db, "run-t", { status: "running", phase: "Running", derived_status_override: "failed" }); const p = projection(db, "run-t"); assertEquals(p.stage, "Done"); assertEquals(p.stage_state, "failed"); // The effective status the pages filter on tracks the derive edge (so it drops to History). assertEquals(p.status, "failed"); db.close(); }); test("park_label carries the actionable 'Parked on human node: