// Pure derivation tests for the unified Tasks-inbox read-model (issue #236). `buildUserTaskRow` turns // one resolved open escalation user task into its desired `user_tasks` row (or null for a // non-escalation / blank key), and `reconcileUserTasks` diffs the desired open set against the // persisted rows into the minimal insert/update/delete plan the `pollUserTasks` reconcile applies. // These are the pure source of truth the poller projects. import { test } from "node:test"; import { assert, assertEquals } from "#test-assert"; import { CONFORMANCE_ESCALATION_ELEMENT } from "./conformance.ts"; import { DELIVERY_HUMAN_ELEMENT } from "./deliveryHuman.ts"; import type { PlanReview } from "./plan.ts"; import type { TrialMergeAuditRow } from "./trialMerge.ts"; import { buildUserTaskRow, PLAN_REVIEW_ELEMENT, PR_WAIT_ANSWER_ELEMENT, latestFeatureEscalationQuestion, latestOpenEscalationQuestion, latestPlanReviewFindings, latestTrialMergeQuestion, type PrEscalationRow, reconcileUserTasks, toEscalationView, TRIAL_MERGE_ELEMENT, userTaskKindLabel, type UserTaskRow, } from "./userTasks.ts"; const AT = "2026-01-01T00:00:00.000Z"; test("buildUserTaskRow: a plan-review task becomes a labelled row with its findings as the question", () => { const row = buildUserTaskRow( { userTaskKey: "ut-1", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#1", subjectUrl: "https://github.com/o/r/issues/1", question: " cap reached: revise scope ", processKey: "pk-1", }, AT, ); assertEquals(row, { user_task_key: "ut-1", element_id: PLAN_REVIEW_ELEMENT, kind_label: "Plan review", subject_type: "plan", subject_key: "o/r#1", subject_title: "o/r#1", subject_url: "https://github.com/o/r/issues/1", question: "cap reached: revise scope", process_key: "pk-1", form_key: null, created_at: AT, updated_at: AT, }); }); test("buildUserTaskRow: denormalises the engine form_key, trimming blanks to null (issue #461)", () => { const withForm = buildUserTaskRow( { userTaskKey: "ut-f", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#1", formKey: " form-9 " }, AT, ); assertEquals(withForm?.form_key, "form-9"); const blank = buildUserTaskRow( { userTaskKey: "ut-b", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#1", formKey: " " }, AT, ); assertEquals(blank?.form_key, null); const absent = buildUserTaskRow( { userTaskKey: "ut-n", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#1" }, AT, ); assertEquals(absent?.form_key, null); }); test("buildUserTaskRow: a blank question / missing url normalises to null", () => { const row = buildUserTaskRow( { userTaskKey: "ut-2", elementId: TRIAL_MERGE_ELEMENT, subjectType: "plan", subjectKey: "o/r#2", question: " " }, AT, ); assert(row !== null); assertEquals(row?.question, null); assertEquals(row?.subject_url, null); assertEquals(row?.process_key, null); assertEquals(row?.kind_label, "Trial merge"); }); test("buildUserTaskRow: a conformance-escalation projects the 'Conformance review' label (issue #216)", () => { const row = buildUserTaskRow( { userTaskKey: "ut-c", elementId: CONFORMANCE_ESCALATION_ELEMENT, subjectType: "plan", subjectKey: "o/r#7", subjectTitle: "Ship the cache", question: "slice 2 reduced", }, AT, ); assert(row !== null); assertEquals(row?.element_id, "conformance-escalation"); assertEquals(row?.kind_label, "Conformance review"); assertEquals(row?.subject_type, "plan"); assertEquals(row?.question, "slice 2 reduced"); }); test("buildUserTaskRow: an unknown (non-escalation) element yields null — no arbitrary user task leaks", () => { const row = buildUserTaskRow( { userTaskKey: "ut-3", elementId: "some-internal-task", subjectType: "plan", subjectKey: "o/r#3" }, AT, ); assertEquals(row, null); }); test("userTaskKindLabel/buildUserTaskRow: an INLINED delivery-human element surfaces under one label; a non-matching internal task is dropped (app/userTasks.ts:122)", () => { // The S4 compiler inlines each `human` node (and its bounded-timeout escalation twin) with a // per-node id `delivery-human-task__[__esc]`, which a bare `USER_TASK_KIND_LABELS` lookup would // miss — so the delivery-human convention MUST be recognised through `isDeliveryHumanElement`. const LABEL = "Delivery: human step"; // The bare static body id and both inlined conventions resolve to the one delivery-human label… assertEquals(userTaskKindLabel(DELIVERY_HUMAN_ELEMENT), LABEL); assertEquals(userTaskKindLabel(`${DELIVERY_HUMAN_ELEMENT}__ship-it`), LABEL); assertEquals(userTaskKindLabel(`${DELIVERY_HUMAN_ELEMENT}__ship-it__esc`), LABEL); // …while a non-matching internal task (a near-miss that is NOT the delivery-human prefix) is not a // surfaced kind, so it never leaks into the inbox. assertEquals(userTaskKindLabel("delivery-human-taskish"), undefined); assertEquals(userTaskKindLabel("some-internal-task"), undefined); // And end-to-end through the row builder: an inlined delivery-human task projects a labelled row… const inlined = buildUserTaskRow( { userTaskKey: "ut-dh", elementId: `${DELIVERY_HUMAN_ELEMENT}__ship-it`, subjectType: "delivery", subjectKey: "graph-1", question: "approve the release?" }, AT, ); assert(inlined !== null); assertEquals(inlined?.element_id, `${DELIVERY_HUMAN_ELEMENT}__ship-it`); assertEquals(inlined?.kind_label, LABEL); assertEquals(inlined?.subject_type, "delivery"); assertEquals(inlined?.question, "approve the release?"); // …whereas a non-matching internal task still yields null (the leak guard holds for the near-miss). assertEquals( buildUserTaskRow( { userTaskKey: "ut-dh2", elementId: "delivery-human-taskish", subjectType: "delivery", subjectKey: "graph-1" }, AT, ), null, ); }); test("buildUserTaskRow: a blank userTaskKey yields null; a known kind with a blank subject key still builds (issue #358)", () => { // The completable key is load-bearing (the page completes THROUGH it), so a blank one is still null. assertEquals( buildUserTaskRow({ userTaskKey: " ", elementId: PR_WAIT_ANSWER_ELEMENT, subjectType: "pr", subjectKey: "o/r#4" }, AT), null, ); // But a KNOWN-kind escalation with no resolved subject (orphaned/untracked instance) is NO LONGER // dropped — it falls back to a stable, non-blank subject: the instance (`processKey`) when known… const orphaned = buildUserTaskRow( { userTaskKey: "ut-4", elementId: PR_WAIT_ANSWER_ELEMENT, subjectType: "pr", subjectKey: " ", processKey: "pik-4" }, AT, ); assert(orphaned !== null); assertEquals(orphaned?.subject_key, "pik-4"); assertEquals(orphaned?.subject_title, "pik-4"); assertEquals(orphaned?.kind_label, "PR review"); // …else the completable key itself, so a row always renders. const noInstance = buildUserTaskRow( { userTaskKey: "ut-5", elementId: PR_WAIT_ANSWER_ELEMENT, subjectType: "pr", subjectKey: "" }, AT, ); assertEquals(noInstance?.subject_key, "ut-5"); // The leak guard still holds: an unknown element is null regardless of subject fallback. assertEquals( buildUserTaskRow({ userTaskKey: "ut-6", elementId: "some-internal-task", subjectType: "plan", subjectKey: "", processKey: "pik-6" }, AT), null, ); }); test("buildUserTaskRow: subject_title carries the subject title, trimmed, and coalesces to subject_key when absent/blank (issue #308)", () => { const titled = buildUserTaskRow( { userTaskKey: "ut-t1", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#1", subjectTitle: " Add the widget ", }, AT, ); assertEquals(titled?.subject_title, "Add the widget"); for (const subjectTitle of [undefined, null, " "]) { const row = buildUserTaskRow( { userTaskKey: "ut-t2", elementId: PLAN_REVIEW_ELEMENT, subjectType: "plan", subjectKey: "o/r#2", subjectTitle }, AT, ); assertEquals(row?.subject_title, "o/r#2"); } }); function row(key: string, extra: Partial = {}): UserTaskRow { return { user_task_key: key, element_id: PLAN_REVIEW_ELEMENT, kind_label: "Plan review", subject_type: "plan", subject_key: "o/r#1", subject_title: "o/r#1", subject_url: null, question: null, process_key: null, created_at: AT, updated_at: AT, ...extra, }; } test("reconcileUserTasks: a new open task is an insert; a vanished task is a delete", () => { const persisted = [row("keep"), row("gone")]; const desired = [row("keep"), row("fresh")]; const { inserts, updates, deletes } = reconcileUserTasks(persisted, desired); assertEquals(inserts.map((r) => r.user_task_key), ["fresh"]); assertEquals(updates, []); assertEquals(deletes, ["gone"]); }); test("reconcileUserTasks: an unchanged steady state yields no writes (idempotent)", () => { const persisted = [row("a", { question: "q" }), row("b")]; const desired = [row("a", { question: "q" }), row("b")]; assertEquals(reconcileUserTasks(persisted, desired), { inserts: [], updates: [], deletes: [] }); }); test("reconcileUserTasks: a drifted question is an update that preserves the original created_at", () => { const persisted = [row("a", { question: "old", created_at: "2025-06-01T00:00:00.000Z", updated_at: "2025-06-01T00:00:00.000Z" })]; const desired = [row("a", { question: "new" })]; const { inserts, updates, deletes } = reconcileUserTasks(persisted, desired); assertEquals(inserts, []); assertEquals(deletes, []); assertEquals(updates.length, 1); assertEquals(updates[0].question, "new"); assertEquals(updates[0].created_at, "2025-06-01T00:00:00.000Z"); }); // ── Audit-log derivations for the plan escalations (migration 027 retired the bespoke mirror tables, // so the question text is derived from the surviving `plan_reviews` / `plan_trial_merges` logs). ── const review = (over: Partial): PlanReview => ({ plan_key: "o/r#20", epoch: 0, round: 0, approved: 0, findings: null, created_at: AT, job_key: null, ...over, }); const audit = (over: Partial): TrialMergeAuditRow => ({ id: 1, plan_key: "o/r#20", wave: 0, result: "suite-failed", heads: null, conflicts: null, failing: null, summary: null, job_key: null, resolved: 0, created_at: AT, updated_at: AT, ...over, }); test("latestPlanReviewFindings: picks the latest round's findings across epochs", () => { const reviews = [ review({ epoch: 0, round: 0, findings: "epoch0 round0" }), review({ epoch: 1, round: 1, findings: "latest" }), review({ epoch: 1, round: 0, findings: "epoch1 round0" }), ]; assertEquals(latestPlanReviewFindings(reviews), "latest"); }); test("latestPlanReviewFindings: null when there are no review rows", () => { assertEquals(latestPlanReviewFindings([]), null); }); test("latestTrialMergeQuestion: picks the newest UNRESOLVED red row's summary", () => { const audits = [ audit({ id: 1, wave: 0, result: "suite-failed", summary: "old red", resolved: 1 }), audit({ id: 2, wave: 0, result: "suite-failed", summary: "latest red", resolved: 0 }), audit({ id: 3, wave: 1, result: "clean", summary: "green", resolved: 0 }), ]; assertEquals(latestTrialMergeQuestion(audits), "latest red"); }); test("latestTrialMergeQuestion: null when every red row is resolved (escalation answered)", () => { const audits = [audit({ id: 1, result: "suite-failed", summary: "red", resolved: 1 })]; assertEquals(latestTrialMergeQuestion(audits), null); }); function esc(o: Partial & { id: number }): PrEscalationRow { return { pr_key: "o/r#1", round_no: 1, kind: "question", question: "q", answer: null, status: "open", asked_at: "t", answered_at: null, ...o, }; } test("latestOpenEscalationQuestion: picks the newest OPEN row (highest id), not a positional [0]", () => { const rows = [ esc({ id: 3, question: "stale open" }), esc({ id: 7, question: "newest open" }), esc({ id: 9, status: "answered", question: "answered" }), ]; assertEquals(latestOpenEscalationQuestion(rows), "newest open"); }); test("latestOpenEscalationQuestion: null when there is no open escalation", () => { assertEquals(latestOpenEscalationQuestion([esc({ id: 1, status: "answered" })]), null); assertEquals(latestOpenEscalationQuestion([]), null); }); test("latestFeatureEscalationQuestion: picks the newest audit row (highest id), not a positional [0]", () => { const rows = [ { id: 3, feature_key: "o/r#1", question: "stale", created_at: "t0", job_key: "j0" }, { id: 8, feature_key: "o/r#1", question: "newest", created_at: "t1", job_key: "j1" }, { id: 5, feature_key: "o/r#1", question: "middle", created_at: "t2", job_key: "j2" }, ]; assertEquals(latestFeatureEscalationQuestion(rows), "newest"); }); test("latestFeatureEscalationQuestion: null when the feature has no recorded escalation", () => { assertEquals(latestFeatureEscalationQuestion([]), null); }); // toEscalationView.prKey must be a genuine PR key (owner/repo#N), never a subject key that fell back // to a non-PR value. `buildUserTaskRow` coalesces a blank subjectKey to `processKey`/`userTaskKey` // for an orphaned/untracked instance (#358); for a PR-subject task that yields `subject_key` = a // numeric engine key, which must NOT be emitted as `prKey` (OpenAPI: prKey is the PR key only when // known). Copilot review suppressed advisory app/userTasks.ts:136. const escRow = (over: Partial): UserTaskRow => ({ user_task_key: "ut-1", element_id: PR_WAIT_ANSWER_ELEMENT, kind_label: "PR review", subject_type: "pr", subject_key: "o/r#7", subject_title: "o/r#7", subject_url: null, question: null, process_key: null, form_key: null, created_at: AT, updated_at: AT, ...over, }); test("toEscalationView: PR-subject row with a PR-shaped subject key emits it as prKey", () => { assertEquals(toEscalationView(escRow({ subject_key: "o/r#7" })).prKey, "o/r#7"); }); test("toEscalationView: PR-subject row whose subject key fell back to a non-PR value yields prKey null (still correlatable via subjectKey)", () => { const view = toEscalationView(escRow({ subject_key: "19153" })); assertEquals(view.prKey, null); assertEquals(view.subjectKey, "19153"); }); test("toEscalationView: non-PR subject always yields prKey null", () => { assertEquals(toEscalationView(escRow({ subject_type: "plan", subject_key: "o/r#7" })).prKey, null); });