Contract — R93 B2 audit_verify_chain Cross-Task Partition

Round: R93 debug-sweep Branch: feature/r93-b2-verifier-partition Audit: docs/audits/r93-b2-verifier-partition-audit.md β task: 635d0ed2-5ee5-4414-93d4-489a07fee1bc

§1. Behavioural invariants (post-fix)

ID Invariant Verifier
I-1 audit_verify_chain({task_id: "X"}) returns {valid, broken_count, first_broken_at?, record_count} unchanged from current behaviour. No by_task field on this path. Existing tests at verifier.test.ts Group B; backward-compat assertion.
I-2 audit_verify_chain({}) (no filter) returns the same 4-field shape PLUS a by_task: Array<TaskRollup> field listing each task’s partition. New test: 2 intact chains → valid:true, broken_count:0, by_task.length === 2, each row valid:true.
I-3 valid in the unfiltered case is true iff every partition is valid (logical AND across by_task). New test: 1 intact + 1 tampered chain → valid:false.
I-4 broken_count in the unfiltered case equals the sum of by_task[i].broken_count. New test asserts arithmetic equivalence.
I-5 record_count in the unfiltered case equals the total records across partitions (matches the current behaviour). New test.
I-6 first_broken_at in the unfiltered case (when present) is the first_broken_at of the first partition (in insertion / rowid order) that has broken_count > 0. New test: tamper task-B record-1; build task-A first with 3 intact records; assert first_broken_at is task-B’s record-1, not the lowest task_id alphabetically.
I-7 Partition iteration order is the order each task_id first appears in listThoughtRecords(db, {}) (i.e. rowid-ASC of the first record per task). New test: assert by_task order matches first-appearance order.
I-8 The pure verifyChain function is unchanged. Its 24 existing tests pass byte-identically. Diff inspection + test run.
I-9 Empty DB → {valid:true, broken_count:0, record_count:0, by_task:[]}. New test.
I-10 Single-task DB with audit_verify_chain({}){valid:true, broken_count:0, record_count:N, by_task:[{task_id, valid:true, broken_count:0, record_count:N}]}. New test.

§2. Output type

The handler’s typed return signature widens to:

{
  valid: boolean;
  broken_count: number;
  first_broken_at?: string;
  record_count: number;
  by_task?: ReadonlyArray<{
    task_id: string;
    valid: boolean;
    broken_count: number;
    record_count: number;
    first_broken_at?: string;
  }>;
}

by_task is undefined (absent) when input.task_id is supplied; present when omitted.

§3. Non-invariants

  • N-1. The pure verifyChain is NOT extended with multi-task awareness. The handler does the partitioning; verifyChain continues to walk a homogeneous chain.
  • N-2. The VerifyChainResult exported type in verifier.ts:64-68 is not modified — it represents the pure function’s return, not the handler’s.
  • N-3. No DB schema change. No migration. No new table.
  • N-4. No output runtime validation via the SDK (B1 already removed outputSchema passthrough, and this handler never declared one).

§4. Acceptance criteria

AC Statement
AC-1 git diff origin/main..HEAD -- src/domains/trail/verifier.ts shows only the handler closure changed (lines 174-202 region). The pure verifyChain function (lines 119-153) is untouched.
AC-2 git diff origin/main..HEAD -- src/__tests__/domains/trail/verifier.test.ts adds a new describe block (Group D or extension to Group B) covering the unfiltered handler path. ≥ 5 new tests covering I-2 through I-10.
AC-3 npm run build exits 0.
AC-4 npm run lint exits 0.
AC-5 npm test exits 0 (modulo documented flakes that don’t relate to this slice). The new tests appear in the verifier.test.ts run.
AC-6 PR body documents the false-positive reproduction + the fix + the new by_task field semantics.
AC-7 Writeback per CLAUDE.md §7.

§5. Risks

  • R-1. Existing handler tests assert the old 4-field return shape. The unfiltered handler is invoked very little in tests; any consumer that destructures result will continue to work because by_task is an additive field. Mitigation: grep test code for record_count to confirm no test asserts the shape contains only 4 fields.
  • R-2. A single-task DB calling audit_verify_chain({}) now returns by_task: [{...}] instead of bare result. Documented as part of the new contract; no client should fail on an additional field. Mitigation: I-10 invariant is the test that documents this.

Proceeding to packet.


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.