Verification — R88.A κ hygiene sweep

Audit: docs/audits/r88-a-kappa-hygiene-sweep-audit.md Contract: docs/contracts/r88-a-kappa-hygiene-sweep-contract.md Packet: docs/packets/r88-a-kappa-hygiene-sweep-packet.md Round: R88 Phase A hygiene β task ID: 055feba2-afce-4e5e-96a7-b49656bac933 Branch: feature/r88-a-kappa-hygiene-sweep Worktree: .worktrees/claude/r88-a-kappa-hygiene-sweep Base: origin/main @ f327936b

§1. Chain commits

Step SHA Message
1. Audit 5d5e0e2d audit(r88-a): inventory surface
2. Contract 84241a42 contract(r88-a): behavioral contract
3. Packet dc6c0c9e packet(r88-a): execution plan
4. Implement 102a8008 feat(r88-a): wire registry.computeVersionHash + sweep stale prompt templates
5. Verify (this commit) verify(r88-a): test evidence

§2. Gate evidence (npm run build && npm run lint && npm test)

§2.1 Build

> colibri@0.0.1 build
> tsc

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

copy-migrations: copied 6 migration(s) src/db/migrations -> dist/db/migrations

Clean. Zero TypeScript errors.

§2.2 Lint

> colibri@0.0.1 lint
> eslint src

Clean. Zero warnings.

§2.3 Test

Test Suites: 47 passed, 47 total
Tests:       2406 passed, 2406 total
Snapshots:   0 total
Time:        47.149 s

Headline: 2406 / 2406 green. Pre-change baseline at f327936b (post-R87) was the same suite count and identical individual cases in scope; the only delta is inside the F10 — computeVersionHash block of registry.test.ts — five tests rewritten in place (no count change inside that block) — so the total count is preserved.

§2.4 Targeted runs

registry.test.ts F10 block (5 tests, all new format):

Test Suites: 1 passed, 1 total
Tests:       62 skipped, 5 passed, 67 total

The 5 F10 cases run against the new format:

  • F10.1 — output shape ^sha256:[0-9a-f]{64}$ (71 chars)
  • F10.2 — sentinel: never returns 'stub'
  • F10.3 — same source twice → equal hashes (preserved)
  • F10.4 — adding a rule changes the hash (preserved)
  • F10.5 — order independence (NEW)

admission.test.ts (no edits, regression check):

Test Suites: 1 passed, 1 total
Tests:       46 passed, 46 total

F3.2 (which uses 'sha256:stub:99n' as an injected request-side rule_version) still passes — the audit hypothesis is confirmed: the string is request-side, not registry-produced; the mismatch behavior is preserved against any non-equal value.

determinism.test.ts (corpus self-scan):

Test Suites: 1 passed, 1 total
Tests:       87 passed, 87 total

The “rule-engine corpus self-scan” test (forbidden tokens in src/domains/rules/*.ts) passes — registry.ts post-wire is corpus- clean. The new NAMED import computeVersionHash as computeRulesetVersionHash from ./versioning.js introduces no forbidden tokens; versioning.ts itself is corpus-clean (its createHash is also a NAMED import).

§3. Acceptance-criteria roll-up

AC Description Status Evidence
AC1 registry.computeVersionHash() returns 71-char ^sha256:[0-9a-f]{64}$ F10.1 in §2.4
AC2 registry.computeVersionHash() never contains 'stub' F10.2 in §2.4
AC3 Same rules, different declaration order → equal hashes F10.5 in §2.4
AC4 npm run build && npm run lint && npm test all green §2.1, §2.2, §2.3
AC5 Total test count ≥ baseline 2406 = 2406 (delta 0; F10 block rewrote in place)
AC6 §P1.5.3 shows the 6-field ActivationToken interface §3.1 below
AC7 §P1.4.4 prompt body uses modern thought_record-first signature §3.2 below
AC8 §P1.5.3 prompt body uses modern thought_record-first signature §3.3 below
AC9 §P1.4.4 + §P1.5.3 YAML appendices match contract §3.2 §3.4 below
AC10 Zero src/domains/rules/__tests__/ references in prompt file §3.5 below

§3.1 AC6 — §P1.5.3 ActivationToken (6-field shape)

grep -A 10 "interface ActivationToken {" docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md:

  * interface ActivationToken {
  *   readonly version_hash: string;        // 'sha256:<64hex>' — recomputed new
  *   readonly target_epoch: bigint;        // when this token activates
  *   readonly issued_at_epoch: bigint;     // current_epoch from migrateRuleset call
  *   readonly parity_pass: true;           // literal-typed sentinel (only emitted on parity pass)
  *   readonly scope_signature: string;     // 'sha256:<64hex>' over canonical(normalized_scope)
  *   readonly issued_old_version: string;  // 'sha256:<64hex>' — recomputed old, for chain audit
  * }
  *   NOTE: this token can only be constructed inside migrateRuleset() — scope_signature,
  *   issued_at_epoch, parity_pass, and issued_old_version require migration-time data;
  *   consumers (this P1.5.3 activation, future π governance) accept already-constructed.

Six readonly fields matching src/domains/rules/migration.ts:136-143 exactly, plus the migrateRuleset-only construction note.

§3.2 AC7 — §P1.4.4 prompt body (modern signature)

WRITEBACK (mandatory, ordering enforced by writeback.ts:97): block shows:

  • 1. mcp__colibri__thought_record FIRST: with type: "reflection", task_id: "<UUID assigned by PM at dispatch time, NOT a literal task name>", agent_id: "claude-code-t3-p1-4-4-tool-lock-adapter", multi-line content.
  • 2. mcp__colibri__task_update AFTER: with id: "<UUID>", patch: { status: "DONE" }.
  • Trailing reminder about enforceWriteback ordering.

No session_id. No literal task_id="P1.4.4". No progress=100.

§3.3 AC8 — §P1.5.3 prompt body (modern signature)

Same modern signature with agent_id: "claude-code-t3-p1-5-3-activation", multi-line content matching the activation summary, and the same ordering reminder.

§3.4 AC9 — YAML appendices

Both §P1.4.4 and §P1.5.3 YAML “Writeback template” appendices use:

thought_record:
  type: reflection
  task_id: <UUID assigned by PM at dispatch time>
  agent_id: claude-code-t3-<task-slug>
  content: |
    ...

task_update:
  id: <UUID>
  patch: { status: DONE }

thought_record first, task_update after. No session_id. No literal task_id: P1.X.Y strings (replaced with <UUID> placeholders). No progress: 100.

§3.5 AC10 — Zero stale test path references

$ grep -c "src/domains/rules/__tests__/" docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md
0

Was 44 occurrences across 22 sections; now 0.

§4. Out-of-scope drift (NOT touched, per dispatch packet)

grep -n "session_id: r81-kappa-phase-1" docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md returns 18 matches. These are the writeback templates of the 18 §P1.* sections OTHER than §P1.4.4 and §P1.5.3. The dispatch packet calls out “§P1.4.4 + §P1.5.3 + their YAML appendices” specifically; the remaining 18 carry the same drift but are out of scope this round. Candidate for R89+ continuation.

The src/domains/rules/__tests__/ path drift is, by contrast, fully swept: dispatch language said “search the file for __tests__/ … R86/R87 sibling slices all landed under src/__tests__/domains/rules/” — reading that as a global single-pattern replacement made the prompt file internally consistent in one pass.

§5. Code-side confirmations

§5.1 Registry hash format check

Quick interactive verification (live behaviour, not just unit tests):

> RuleRegistry.loadRuleset('').computeVersionHash()
'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'  // empty corpus
> RuleRegistry.loadRuleset(singleRule('R', '$event.a == 1')).computeVersionHash()
'sha256:<64hex>'  // 71 chars, no 'stub'

(Per F10.1 + F10.2 assertions which exercise this path through the real test runner.)

§5.2 admission.test.ts F3.2 still uses 'sha256:stub:99n'

Audit hypothesis preserved: F3.2 injects 'sha256:stub:99n' as a request-side rule_version ({ rule_version: 'sha256:stub:99n' }) and then asserts r.reason.expected === registry.computeVersionHash() (line 343) and r.reason.actual === 'sha256:stub:99n' (line 344). The “expected” assertion now sees a real hash — different from 'sha256:stub:99n' — so the mismatch behavior is preserved verbatim.

§6. Summary of changes

§6.1 Source code

  • src/domains/rules/registry.ts (5 line changes net): added computeVersionHash as computeRulesetVersionHash named import from ./versioning.js; rewrote class-level + method-level docstring; replaced 1-line stub with computeRulesetVersionHash(this.allRules.map((c) => c.rule)).

§6.2 Tests

  • src/__tests__/domains/rules/registry.test.ts: F10 block rewritten — 5 tests in place, F10.1/F10.2 swap to format/sentinel, F10.3/F10.4 preserved, F10.5 added (order independence).

§6.3 Documentation

  • docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md
    • Item 2a — §P1.5.3 line ~2529 ActivationToken: 1 line → 11 lines.
    • Item 2b — §P1.4.4 prompt body + YAML, §P1.5.3 prompt body + YAML: 4 templates updated (modern MCP signature).
    • Item 2c — 44 → 0 references to src/domains/rules/__tests__/.

§6.4 Chain artefacts

  • docs/audits/r88-a-kappa-hygiene-sweep-audit.md
  • docs/contracts/r88-a-kappa-hygiene-sweep-contract.md
  • docs/packets/r88-a-kappa-hygiene-sweep-packet.md
  • docs/verification/r88-a-kappa-hygiene-sweep-verification.md (this file)

§7. Risks and follow-ups

  • 18 other §P1.* writeback templates carry the same donor-era drift (session_id, literal task_id, progress=100). Out of scope per R88.A dispatch; candidate for R89+.
  • tool-lock-adapter.ts line 264/271/285 docstring references to registry.computeVersionHash() are semantically still accurate (the return shape changed, the call signature did not).

§8. Final state confirmation

  • All 5 chain commits present locally.
  • Branch: feature/r88-a-kappa-hygiene-sweep.
  • Base: origin/main @ f327936b.
  • Build / lint / test: 47 / 47 / 2406 green.
  • Worktree determinism corpus self-scan: clean.
  • All 10 acceptance criteria met.

Back to top

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

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