Packet — P0.7.3 ζ Chain Verification Tool

1. Objective

Implement src/domains/trail/verifier.ts (pure verifier + MCP tool registration) and src/__tests__/domains/trail/verifier.test.ts (test suite), then wire the new tool into src/server.ts. No migration. Closes the ζ Decision Trail axis.

2. Files to create or modify

Action Path
CREATE src/domains/trail/verifier.ts
CREATE src/__tests__/domains/trail/verifier.test.ts
MODIFY src/server.ts (add import + registerVerifyChainTool call)

3. Implementation plan

3.1 src/domains/trail/verifier.ts

Imports:

import type Database from 'better-sqlite3';
import { z } from 'zod';
import { getDb } from '../../db/index.js';
import type { ColibriServerContext } from '../../server.js';
import { registerColibriTool } from '../../server.js';
import { computeHash, ZERO_HASH, type ThoughtRecord } from './schema.js';
import { listThoughtRecords, type ListThoughtRecordsFilters } from './repository.js';

VerifyChainResult interface:

export interface VerifyChainResult {
  readonly valid: boolean;
  readonly broken_count: number;
  readonly first_broken_at?: string;
}

verifyChain(records: ThoughtRecord[]): VerifyChainResult:

  • Loop i = 0..n-1.
  • expectedPrevHash = (i === 0) ? ZERO_HASH : records[i-1].hash.
  • recomputedHash = computeHash(records[i]).
  • Broken if recomputedHash !== records[i].hash OR records[i].prev_hash !== expectedPrevHash.
  • Track brokenCount, set firstBrokenAt on first broken record.
  • Return { valid: brokenCount === 0, broken_count: brokenCount, first_broken_at: firstBrokenAt }.

AuditVerifyChainToolInputSchema:

export const AuditVerifyChainToolInputSchema = z.object({
  task_id: z.string().min(1).optional(),
});

registerVerifyChainTool(ctx: ColibriServerContext): void:

  • Registers audit_verify_chain via registerColibriTool.
  • Handler: lazy getDb(), listThoughtRecords(db, filters), verifyChain(records), return { ...result, record_count: records.length }.

3.2 src/server.ts modifications

Add one import:

import { registerVerifyChainTool } from './domains/trail/verifier.js';

Add one call in the tool-registration section alongside registerThoughtTools(ctx):

registerVerifyChainTool(ctx);

3.3 src/__tests__/domains/trail/verifier.test.ts

Test groups:

Group A — verifyChain pure function:

  1. Empty array → { valid: true, broken_count: 0 }, no first_broken_at.
  2. Single-record intact chain → valid.
  3. Two-record intact chain → valid.
  4. Content tampered on record[0] → valid: false, broken_count: 1, first_broken_at = record[0].id.
  5. Content tampered on record[1] (not record[0]) → valid: false, broken_count: 1, first_broken_at = record[1].id.
  6. prev_hash broken on record[1] (prev_hash does not match record[0].hash) → valid: false.
  7. Multiple broken records → broken_count equals number of broken records, first_broken_at is first.
  8. 100-record intact chain → valid: true, completes in < 500ms.

Group B — registerVerifyChainTool + DB integration:

  1. audit_verify_chain via MCP transport: empty DB → { valid: true, broken_count: 0, record_count: 0 }.
  2. Insert 3 records for same task_id, call tool with task_id filter → valid.
  3. Insert 3 records, tamper content via raw SQL update, call tool → valid: false with correct first_broken_at.
  4. Insert records for two different task_ids, call without filter → valid.
  5. Call with task_id filter that has no matching records → { valid: true, broken_count: 0, record_count: 0 }.

Helper pattern: reuse 003_thought_records.sql migration + in-memory DB, same pattern as repository.test.ts.

4. Key invariants to test

  • computeHash is called with the same 6-field subset as during creation — the verifier must NOT include agent_id or hash in its recomputed input.
  • prev_hash for record at index 0 must be ZERO_HASH.
  • Tamper of prev_hash field itself (not content) also triggers broken detection.

5. No migration

No new SQL file. No user_version bump. Purely additive TypeScript.

6. Commit template (Step 4)

feat(p0-7-3): ζ audit_verify_chain tool — verifier.ts + MCP wiring + tests

7. Test gate

npm run build && npm test

Known pre-existing flake: startup-subprocess smoke test may fail intermittently on CI (predates this task). Note in PR body if it hits; do not chase it.


Back to top

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

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