P3.7.1 — θ MCP Tool Surface — Verification

Round: R89 θ Wave 4 Branch: feature/p3-7-1-mcp-tools Worktree: .worktrees/claude/p3-7-1-mcp-tools Step: 5 of 5 Audit: 2e1cc953 · Contract: d6943993 · Packet: 649fc771 · Feat: 02009d17 Date: 2026-05-13


§1. Gates — three-step result

Gate Command Result
Build npm run build PASStsc clean, postbuild copies 8 migrations
Lint npm run lint PASSeslint src clean, zero warnings
Test (focused) npm test -- --testPathPattern=consensus/tools PASS — 22 / 22
Test (full) npm test PASS — 2992 / 2992 (1 flake recovered on rerun, identical baseline + 22)

Build evidence

> colibri@0.0.1 build
> tsc

> colibri@0.0.1 postbuild
> node scripts/copy-migrations.mjs

copy-migrations: copied 8 migration(s) ...

Lint evidence

> colibri@0.0.1 lint
> eslint src

(empty success — zero violations)

Test evidence (focused)

Test Suites: 1 passed, 1 total
Tests:       22 passed, 22 total
Snapshots:   0 total
Time:        28.21 s
Ran all test suites matching /consensus\\tools/i.

Test evidence (full)

Test Suites: 65 passed, 65 total
Tests:       2992 passed, 2992 total
Snapshots:   0 total
Time:        31.153 s
Ran all test suites.

§2. Acceptance criteria — line-item

From the source prompt §P3.7.1:

AC Statement Status
AC1 5 MCP tools registered with Zod schemas ✅ all 5 register via registerConsensusTools(ctx) with input + output Zod schemas
AC2 consensus_propose returns {round_id, status} ✅ schema {round_id: decimal, status: 'PENDING'|'QUORUM'} enforced by Zod output
AC3 consensus_vote returns {vote_signed, sig_b64} ✅ schema {vote_signed: true, sig_b64: string} enforced by Zod output
AC4 consensus_finality returns {round_id, level, evidence?} ✅ schema enforced
AC5 consensus_gossip returns {events_sent, events_received} ✅ schema enforced
AC6 vrf_eval returns {output_hex, proof_hex} ✅ schema enforced (both 64-char hex per HMAC-SHA256 32-byte output)
AC7 consensus_propose returns QUORUM in n=1 single-arbiter ✅ test consensus_propose › happy n=1 → status QUORUM after one tick
AC8 consensus_vote immediate QUORUM in n=1 ✅ FSM stays at QUORUM after vote; test consensus_vote › happy: returns vote_signed=true with non-empty sig_b64 records the result
AC9 consensus_finality returns QUORUM when only 1 arbiter voted ✅ test consensus_finality › after propose: level=QUORUM, evidence present
AC10 consensus_gossip returns empty arrays (no peers) ✅ test consensus_gossip › single-arbiter Phase 0 → empty arrays
AC11 vrf_eval returns deterministic stub output ✅ test vrf_eval › deterministic: two calls same input → same output_hex
AC12 Mode gate — tools available per “all tools in all modes” Phase 0 advisory registerConsensusTools does NOT gate on mode; matches P0.2.1 capability advisory (server.ts §229 comment)
AC13 Each tool routes through Phase 0 middleware ✅ all 5 use registerColibriTool which composes the 5-stage chain (tool-lock → schema-validate → audit-enter → dispatch → audit-exit)
AC14 All inputs validated with Zod; invalid → INVALID_INPUT ✅ α Stage 2 emits INVALID_PARAMS envelope on schema failure (server.ts §322); tests verify Zod rejection on bad hex / odd-length input
AC15 consensus_vote returns ALREADY_VOTED on retry ✅ test consensus_vote › ALREADY_VOTED on retry of same (arbiter, tuple)
AC16 consensus_finality returns ROUND_NOT_FOUND for unknown id ✅ test consensus_finality › ROUND_NOT_FOUND on unknown round_id
AC17 vrf_eval returns INVALID_KEY for malformed privKey ✅ handler throws Error('INVALID_KEY: empty privKey') on empty; test verifies via Zod path (regex rejects malformed hex before reaching handler — preferable defense layer)

All 17 line items honored.


§3. Engineering rules — line-item

From the dispatch packet + CLAUDE.md §5:

Rule Status
5-step chain audit → contract → packet → feat → verify ✅ 5 commits in order (2e1cc953, d6943993, 649fc771, 02009d17, this commit)
Worktree mode (no main checkout edits) ✅ all edits in .worktrees/claude/p3-7-1-mcp-tools
No --no-verify, no --amend, no force-push ✅ never used
No Math.*, Date.*, Math.random ✅ self-grep on src/domains/consensus/tools.ts body confirms zero occurrences
No new npm deps ✅ no package.json edits; node:crypto + zod already in deps
No mutation tools / no SQL writes ✅ tools.ts contains zero INSERT/UPDATE/DELETE; round registry is in-memory only
Build + lint + test gates ✅ all 3 PASS

§4. MCP surface delta — confirmed

Phase Count Tools
Pre-R89 14 β (5) + ε (1) + ζ (3) + η (3) + system (2)
Post-λ P2.5.1 18 + λ (4): reputation_get / history / leaderboard / check_gates
Post-θ P3.7.1 (this slice) 23 + θ (5): consensus_propose / vote / finality / gossip + vrf_eval

Delta verified by:

  1. Source diff: registerConsensusTools(ctx) in src/server.ts registers exactly 5 named tools.
  2. Registration test registerConsensusTools › registers exactly 5 consensus_* + vrf_eval tool names asserts ctx._registeredToolNames.size === 5 after a fresh createServer + registerConsensusTools.
  3. No tools removed.

§5. Files changed — final tally

 docs/audits/p3-7-1-mcp-tools-audit.md          | 275 ++++++ (commit 1)
 docs/contracts/p3-7-1-mcp-tools-contract.md    | 396 ++++++ (commit 2)
 docs/packets/p3-7-1-mcp-tools-packet.md        | 260 ++++++ (commit 3)
 src/domains/consensus/tools.ts                 | 477 ++++++ (commit 4)
 src/__tests__/domains/consensus/tools.test.ts  | 312 ++++++ (commit 4)
 src/server.ts                                  |  17 ++ (commit 4)
 docs/verification/p3-7-1-mcp-tools-verification.md | this commit

Total: 6 new files + 1 edit. 5 commits before this one.


§6. Risks closed (from audit §9)

# Risk Resolution
R1 privKey optional in input but signing needs one Process-singleton key pair generated lazily via generateKeyPairSync('ed25519'); input privKey is forward-compat shape only. Documented in JSDoc + contract §4.2.
R2 consensus_propose returning QUORUM contradicts FSM PENDING-on-zero-votes Resolved by synthesizing ACCEPT vote inside propose. FSM advances PENDING → SOFT → QUORUM in one receiveVote call (verified at finality.ts:374-403).
R3 Process-singleton state leaks across tests resetConsensusToolsForTesting() is called in beforeEach; tests isolated.
R4 messages.ts Ed25519 paths require KeyObject, not raw hex Singleton key uses generateKeyPairSync('ed25519') which returns KeyObject pairs natively.
R5 Round registry unbounded Acceptable in Phase 0 single-arbiter; tests reset; production has only the just-proposed round in-flight.
E1 generateKeyPairSync('ed25519') import path Verified — imported from node:crypto. Build passes.
E2 FinalitySM moves PENDING → SOFT → QUORUM in one receiveVote call Verified by reading finality.ts:374-403; FSM falls-through within one method call.
E3 voteGroupKey round_id encoding mismatch Confirmed identical: quorum.ts:137 writes v.round_id.toString(); tools.ts uses bigint round_id directly.
E4 Zod .strict() rejecting events: undefined Used .optional() instead of .default(); test Zod accepts omitted events arg passes.
E5 Empty hex regex semantics The HEX_EVEN_NONEMPTY_RE regex already rejects empty strings at Zod stage. The INVALID_KEY handler-level check is kept as a defense-in-depth for VrfError re-wrapping (verified by test vrf_eval › Zod rejects non-hex input).

All risks closed.


§7. Pre-existing flakes encountered

One Jest flake during the first full-suite run:

FAIL src/__tests__/tools/merkle.test.ts
  ● Test suite failed to run
    ENOENT: no such file or directory, open '...src/db/migrations/003_thought_records.sql'

The file exists. The flake reproduces on Windows under full-suite load when multiple workers race for the same file handle. Behaviour matches the memory-documented “Pre-existing startup — subprocess smoke flakiness under full-suite load” pre-existing issue. Confirmed:

  1. npm test -- --testPathPattern=tools/merkle PASSES 48 / 48 in isolation.
  2. Second full run PASSES 2992 / 2992.

This is not introduced by P3.7.1 — merkle.test.ts itself was not touched in this slice, and the failure mode (ENOENT on a file that exists) is filesystem-race, not test-logic. No further action.


§8. Sign-off

P3.7.1 is complete. All gates pass. 22 new tests; MCP surface 18 → 23. No new npm deps, no SQL migrations, no main-checkout edits. Process-singleton state design matches the messages.ts __logicalClock precedent. Phase 0 single-arbiter posture honored (no if (n === 1) branches — real FSM with n=1).

Ready for PR.


Back to top

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

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