Contract — P0.7.3 ζ Chain Verification Tool
1. Purpose
Specify the behavioral contract for src/domains/trail/verifier.ts and the
audit_verify_chain MCP tool. This is the final ζ axis task; it closes the
Decision Trail surface.
2. Exports
2.1 VerifyChainResult (type)
export interface VerifyChainResult {
readonly valid: boolean;
readonly broken_count: number;
readonly first_broken_at?: string; // id of the first broken record; absent when valid
}
2.2 verifyChain(records: ThoughtRecord[]): VerifyChainResult
Pure function. No DB access, no side effects.
Inputs:
records— an ordered array ofThoughtRecordvalues, in insertion order (caller must supply them sorted byrowid ASCas returned bylistThoughtRecords).
Algorithm (O(n)):
For each record at position i:
-
Content-hash check: recompute
computeHash(record)and compare torecord.hash. If mismatch → record is broken. -
Link check: compare
record.prev_hashto the expected prev_hash:- If
i === 0: expected =ZERO_HASH. - If
i > 0: expected =records[i-1].hash. If mismatch → record is broken.
- If
A record is broken if either check fails. The first broken record sets
first_broken_at. The walk continues to count all broken records
(broken_count).
Output invariants:
| Condition | valid |
first_broken_at |
broken_count |
|---|---|---|---|
| Empty input | true |
absent | 0 |
| All records pass both checks | true |
absent | 0 |
| One or more records fail | false |
id of first broken | ≥ 1 |
Note on multi-task chains: if records spans multiple task_id values,
the prev_hash check uses position-in-array (not task boundary) as context.
Callers that want per-task isolation must filter before calling verifyChain.
The MCP tool always filters by task_id when a filter is supplied.
2.3 AuditVerifyChainToolInputSchema (Zod schema)
export const AuditVerifyChainToolInputSchema = z.object({
task_id: z.string().min(1).optional(),
});
Both fields optional; omitting task_id verifies the full chain across all tasks.
2.4 registerVerifyChainTool(ctx: ColibriServerContext): void
Registers the audit_verify_chain MCP tool against ctx. Idempotent — a
second registration on the same ctx throws via registerColibriTool’s
duplicate-registration guard.
Tool name: audit_verify_chain
MCP tool description: "Verify the integrity of the ζ decision-trail hash chain. Returns {valid, broken_count, first_broken_at?, record_count}."
Handler behaviour:
- Lazy-resolve DB via
getDb()at call-time (Phase-2 safe). - Call
listThoughtRecords(db, filters)with the optionaltask_idfilter. - Call
verifyChain(records). - Return
{ ...result, record_count: records.length }.
Return shape (success, wrapped in {ok: true, data: ...} by α middleware):
{
valid: boolean;
broken_count: number;
first_broken_at?: string;
record_count: number;
}
3. Purity contract
verifyChainis a pure function — no DB access, no I/O, no side effects. Tests drive it directly with fabricatedThoughtRecord[]arrays.registerVerifyChainToolhas no module-level state.getDb()is resolved inside the handler closure (call-time), not at registration time.- No new dependencies. Reuses
computeHashfromschema.tsandlistThoughtRecordsfromrepository.ts.
4. Error handling
| Condition | Behaviour |
|---|---|
verifyChain with empty array |
Returns { valid: true, broken_count: 0 } |
| DB error in MCP handler | Propagates; α middleware wraps in {ok: false, error} |
| Zod validation failure | Propagates ZodError; α middleware wraps |
5. No migration
This task adds no new SQL tables, columns, or indexes. user_version is not
bumped. verifier.ts is read-only against the existing thought_records table.
6. Server.ts wiring
server.ts imports registerVerifyChainTool from ./domains/trail/verifier.js
and calls it alongside registerThoughtTools. One import line, one call site.
7. Acceptance criteria mapping
| Spec criterion | Contract clause |
|---|---|
verifyChain(records[]) iterates chain, recomputes each hash |
§2.2 algorithm |
Returns { valid, first_broken_at?, broken_count } |
§2.1 type |
audit_verify_chain MCP tool, optional task_id filter |
§2.3, §2.4 |
Tamper test: valid: false at correct position |
§2.2 + test plan |
100-record chain: valid: true in < 500ms |
§2.2 + test plan |