P4.1 — μ Integrity Monitor — Phase 4 Dispatch

Status: ready (R94A graduation, 2026-05-14). T0 autonomous-finish mandate flipped this file from stagedready, mirroring the R89 Phase A λ unblock pattern (which itself mirrored R89.C θ). The 10 sub-tasks below dispatch under R94 across 4 waves; first slice P4.1.1 (envelope) ships Wave 1 and gates Waves 2–4.

Canonical specs:

Round context

This staging file lands during R91 (autonomous post-R89-Phase-B follow-up, 2026-05-13). Predecessor R91A (PR #249, merged at ca9cbda1) ratified the canonical roadmap from 2 → 10 μ sub-tasks in docs/guides/implementation/task-breakdown.md §Phase 4. R91 ships the matching dispatch-ready staging file on top of the ratified roadmap; the two are 1:1 consistent after this PR merges.

The dispatch model is 4-wave dispatch with the 3-safe parallel ceiling validated in R87 (κ Phase 1 close, 8 PRs across 3 waves) and R89 Phase B (θ Phase 3 close, 13 PRs across 5 waves). Status frontmatter is staged, not ready; the first P4.X.Y slice dispatches when T0 grants the Phase 4 mandate (mirrors how R89 Phase A unblocked θ’s staged → ready graduation once the autonomous Phase 2+3 mandate landed).

Dependency map (upstream, by axis)

Every Phase 4 μ dependency ships at main HEAD (ca9cbda1 post-R91A). Zero unmet upstream blockers; only T0 confirmation remains as the soft gate.

Upstream axis Phase shipped Path Used by μ
κ Rule Engine (Phase 1) R87 (f327936b) src/domains/rules/{engine,canonical,versioning,admission,registry,bps-constants,integer-math,denial-reasons,policy-gate,state-access,parity-harness}.ts All detectors + envelope + escalation
λ Reputation (Phase 2) R89 Phase A (0c858381) src/domains/reputation/{schema,compute,decay,penalties,limits,tokens}.ts D2 coercion (reputation_delta < 0 check); D3 drift (parameter-change events from λ penalties)
θ Consensus (Phase 3) R89 Phase B (332feb62 post-#248) src/domains/consensus/{messages,finality,fork-hook,parity-harness}.ts P4.8.1 fork-hook subscriber; D3 may aggregate over θ events
ζ Decision Trail (Phase 0) R75 src/domains/trail/repository.ts D1 circular-logic edges; PASS results logged here
η Proof Store (Phase 0) R75 src/domains/proof/ Future Merkle-anchoring of advisories
β Task Pipeline (Phase 0) R75 src/domains/tasks/ Slice UUIDs for writeback
α Middleware (Phase 0) R75 src/server.ts (inlined) + src/domains/rules/tool-lock-adapter.ts (P1.4.4) HARD BLOCK lands here when D-class severity = HIGH on coercion-in-admission

Downstream consumers (post-Phase-4 horizon):

  • ι State Fork (Phase 5) — P4.8.1 stages the fork-hook subscriber; ι implementation is Phase 5.
  • π Governance (Phase 6) — P4.4.1’s BLOCK output feeds into π proposal-intake denial when π ships.
  • ξ Identity (Phase 7) — no direct dependency on μ.

ADR posture (at R91 staging)

ADR Status Phase 4 impact
ADR-002 (VRF) PROPOSED (R89.C) μ does not consume VRF (μ is observational; no randomness). N/A.
ADR-003 (BFT library) PROPOSED (R89.C, Option C spike) μ subscribes to θ events through θ’s surface; not affected by BFT-library choice. N/A.
ADR-005 (multi-model defer) ACCEPTED μ uses Claude only in Phase 4. N/A.
ADR-006 (concept maturity) ACCEPTED μ stays colibri_code: none until the entire Phase 4 axis ships; graduation to partial lands with the Phase 4 close PR, NOT in this staging PR or any individual slice.

No new ADR is needed for μ Phase 4. The detector algorithms (DFS, option-set, sliding window) are textbook; the schema is pure data; the roles are pure read-side adapters; the MCP tool surface follows the Zod-v3.23 + registerTool pattern already in use across β/ε/ζ/η/λ/θ.

Design invariants (preserved in every sub-task)

  1. No src/ mutation in R91 itself — this is the staging meta PR; first src/ mutation lands when P4.1.1 dispatches in some R94+ post-T0 confirmation.
  2. Pure functions only — detectors are observational; no I/O, no clock, no RNG. Same arithmetic discipline as κ (bigint) + λ (BPS) + θ (canonical).
  3. Lamport timestamp_logical — never Date.now() in detector or advisory output. Inherits θ design invariant 2.
  4. Canonical serialization for hash — reuse κ P1.5.4 canonical for any decision_hash input; matches θ’s pattern (reuse, not duplicate).
  5. Append-only persistencemcp_advisories schema is INSERT-only per AX-01; dedup by decision_hash (NOT by UPDATE).
  6. Read-only roles — Translator/Sentinel/Guide may NEVER mutate state; only emit advisories. Enforced via TypeScript readonly modifiers + no db.run in the role adapter.
  7. Advisory ≠ enforcement — μ records advisories; it never executes denials directly. The escalation FSM emits a typed event; α (tool-lock) and π (proposal intake) consume the event and execute denial. μ produces signals; other axes act on them.
  8. HARD BLOCK is owned by α — μ flags + records an HIGH-severity advisory; the tool-lock middleware reads the advisory and denies. tool-lock itself is in src/domains/rules/tool-lock-adapter.ts (P1.4.4, shipped #220). The Phase 4 work is to flag via an event the adapter already reads.
  9. AX-invariant regression check is per-AX, not aggregate — D3 must distinguish “cumulative drift” (sliding window) from “would-regress-AX-N” (per-axiom semantics check). These are two different code paths.
  10. No new npm deps — μ ships in TypeScript only; no integrity-specific libraries.

The 10 slices (group summary)

Task ID Title Effort Depends on Wave Unblocks
P4.1.1 Advisory Record Schema + Envelope S κ P1.5.4 (canonical), κ P1.5.1 (hash) 1 all of P4.2.x, P4.3.1, P4.4.1, P4.5.1
P4.2.1 Circular Logic Detector (DFS) M P4.1.1, ζ P0.7.1 (thought_records), κ P1.2.4 (registry) 2 P4.4.1, P4.7.1
P4.2.2 Coercion Trap Detector (option-set) M P4.1.1, κ P1.4.1 (admission), κ P1.3.1 (engine), λ P2.1.2 (compute) 2 P4.4.1, P4.7.1
P4.2.3 Axiom Drift Tracker (sliding window) L P4.1.1, λ P2.2.2 (penalties), κ P1.1.1 (BPS math) 2 P4.4.1, P4.7.1, P4.8.1
P4.3.1 Three Advisory Roles (Translator/Sentinel/Guide) M P4.1.1 3 P4.6.1
P4.4.1 Escalation FSM (4-result + 3 invariant mappings) M P4.1.1, P4.2.1, P4.2.2, P4.2.3, κ P1.4.1, κ P1.2.4 3 P4.6.1, P4.7.1
P4.5.1 Advisory Persistence (mcp_advisories migration) S P4.1.1, P0.2.2 (migration runner) 3 P4.6.1, P4.7.1, P4.8.1
P4.6.1 μ MCP Tool Surface (≥4 tools) M P4.3.1, P4.4.1, P4.5.1 4 (closes μ Phase 4)
P4.7.1 Test Corpus + Parity Harness L P4.2.1, P4.2.2, P4.2.3, P4.4.1, P4.5.1 4 (μ Phase 4 seal)
P4.8.1 Fork Hook Subscriber (post-fork invariant sweep) S P4.2.3, P4.5.1, θ P3.9.1 (ForkHookRegistry) 4 ι Phase 5 readiness

Wave structure

Wave 1  ─── P4.1.1 (envelope; solo; gates all)
              │
              ├─────────────────┬─────────────────┐
              ▼                 ▼                 ▼
Wave 2  ─── P4.2.1            P4.2.2            P4.2.3
              circular         coercion          axiom drift
              │                 │                 │
              └─────────┬───────┴─────────┬───────┘
                        ▼                 ▼
Wave 3  ─── P4.3.1   P4.4.1   P4.5.1
            roles    escalation persistence
              │         │         │
              └─────┬───┴─────┬───┘
                    ▼         ▼
Wave 4  ─── P4.6.1   P4.7.1   P4.8.1
            MCP      parity   fork-hook
            tools    harness  subscriber

Waves 2, 3, 4 each have 3 parallel slices — fits inside the 3-safe parallel ceiling validated in R87 (κ close) and R89 Phase B (θ close). Total wallclock estimate at AI pace: ~3–4 hours if all 4 waves run back-to-back. P4.1.1 (envelope) gates everything: every detector returns an envelope; every escalation consumes one; every MCP tool serializes one.


§P4.1.1 — Advisory Record Schema + Envelope — Phase 4 μ Wave 1

Spec source: task-breakdown.md §P4.1.1 Concept reference: integrity.md §Advisory record schema (L129-146) + s14 §Output Worktree: feature/p4-1-1-advisory-envelope Branch command: git worktree add .worktrees/claude/p4-1-1-advisory-envelope -b feature/p4-1-1-advisory-envelope origin/main Estimated effort: S (Small — 2–4 hours) Depends on: κ P1.5.4 canonical serializer (shipped at 799e70a9), κ P1.5.1 version-hash (shipped at 0150dcd1) Unblocks: all of P4.2.x (detectors), P4.3.1 (roles), P4.4.1 (escalation), P4.5.1 (persistence)

Files to create

  • src/domains/integrity/schema.ts — Zod schema for the 8-field envelope + helper functions
  • src/domains/integrity/__tests__/schema.test.ts — roundtrip, determinism, dedup tests

Acceptance criteria

  • Zod v3.23 schema for the 8-field envelope: role, check, result, severity, evidence, recommendation, decision_hash, timestamp_logical
  • role: z.enum(["Translator", "Sentinel", "Guide"])
  • check: z.enum(["circular_logic", "coercion_trap", "axiom_drift", "axiom_regression"])
  • result: z.enum(["PASS", "WARN", "BLOCK"])
  • severity: z.enum(["LOW", "MED", "HIGH"]) — matches λ severity bands (R91 audit Q6 resolution; supersedes legacy s14 INFO/WARNING/CRITICAL)
  • evidence: z.array(z.unknown()) — references to records/events/rules
  • recommendation: z.string() — free-form human-readable
  • decision_hash: z.string().regex(/^[a-f0-9]{64}$/) — SHA-256 hex
  • timestamp_logical: z.bigint() — Lamport uint64 (NOT wall-clock)
  • Hash formula: decision_hash = SHA-256(role || check || canonical(input) || result) (R91 audit Q7 resolution; uses κ P1.5.4 canonical, NOT s14’s older formula)
  • computeDecisionHash(role, check, input, result): string helper — REUSES canonicalSerialize from κ P1.5.4 (do not duplicate)
  • serializeAdvisory(advisory): Buffer — delegates to κ canonical for deterministic bytes
  • Dedup invariant: identical (role, check, canonical(input), result) produces identical decision_hash; tested across 1000 iterations
  • No Date.now(), Math.random() in the module (static scanner clean)

Pre-flight reading

  • CLAUDE.md (§3 worktree, §5 gate, §6 5-step chain, §7 writeback)
  • docs/guides/implementation/task-breakdown.md §P4.1.1
  • docs/3-world/physics/enforcement/integrity.md §Advisory record schema (L129-146)
  • docs/spec/s14-integrity-monitor.md §Output
  • src/domains/rules/canonical.ts (κ P1.5.4 — REUSE; do not duplicate)
  • src/domains/rules/versioning.ts (κ P1.5.1 — pattern for SHA-256 over canonical)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.1.1 — Advisory Record Schema + Envelope
Define the 8-field typed envelope that EVERY downstream μ slice consumes,
including SHA-256 decision_hash + κ-canonical serialization. This is Wave-1
solo — the entire Phase 4 graph fans out from here.

FILES TO READ FIRST:
1. CLAUDE.md (§3 worktree, §5 gate, §6 5-step chain, §7 writeback)
2. docs/guides/implementation/task-breakdown.md §P4.1.1
3. docs/3-world/physics/enforcement/integrity.md §Advisory record schema
4. docs/spec/s14-integrity-monitor.md §Output
5. src/domains/rules/canonical.ts (κ P1.5.4 — REUSE)
6. src/domains/rules/versioning.ts (κ P1.5.1 — pattern)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-1-1-advisory-envelope -b feature/p4-1-1-advisory-envelope origin/main
cd .worktrees/claude/p4-1-1-advisory-envelope

FILES TO CREATE:
- src/domains/integrity/schema.ts
  * Zod v3.23 schema (use existing β/ε/ζ/η/λ/θ patterns):
    - AdvisoryRoleSchema = z.enum(["Translator", "Sentinel", "Guide"])
    - AdvisoryCheckSchema = z.enum(["circular_logic", "coercion_trap", "axiom_drift", "axiom_regression"])
    - AdvisoryResultSchema = z.enum(["PASS", "WARN", "BLOCK"])
    - AdvisorySeveritySchema = z.enum(["LOW", "MED", "HIGH"])
    - AdvisorySchema = z.object({
        role, check, result, severity,
        evidence: z.array(z.unknown()),
        recommendation: z.string(),
        decision_hash: z.string().regex(/^[a-f0-9]{64}$/),
        timestamp_logical: z.bigint(),
      })
  * Helper functions:
    - computeDecisionHash(role, check, input, result): string
      * input is canonical-serialized via κ P1.5.4
      * hash = sha256( role + check + canonical(input) + result ) → hex
    - serializeAdvisory(advisory): Buffer
      * delegates to κ canonical for deterministic bytes
  * REUSE src/domains/rules/canonical.ts (do not duplicate); REUSE κ's SHA-256 pattern from versioning.ts

- src/domains/integrity/__tests__/schema.test.ts
  * Roundtrip: build full advisory, parse, structural equality
  * Determinism: serializeAdvisory × 1000 iterations same bytes
  * Hash determinism: computeDecisionHash × 1000 iterations same hex
  * Dedup invariant: same (role, check, canonical(input), result) → same hash
  * Different result with same input → different hash (preimage check)
  * No Date.now() / Math.random() in module (static scanner)

ACCEPTANCE CRITERIA (headline):
✓ 8-field Zod schema matches integrity.md L133-143
✓ Decision_hash formula matches R91 audit Q7 (NOT s14's older formula)
✓ Severity uses LOW/MED/HIGH (R91 audit Q6 — matches λ bands)
✓ timestamp_logical is bigint (Lamport, NOT wall-clock)
✓ Canonical serialization REUSES κ P1.5.4 (no duplicate)
✓ Dedup invariant tested over 1000 iter

SUCCESS CHECK:
cd .worktrees/claude/p4-1-1-advisory-envelope && npm run build && npm run lint && npm test

WRITEBACK (after success, per CLAUDE.md §7):
task_update(id="<PM-supplied UUID for P4.1.1>", status="done", progress=100)
thought_record(
  thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-1-1-advisory-envelope
worktree: .worktrees/claude/p4-1-1-advisory-envelope
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: Phase 4 μ Wave 1 — defined the 8-field advisory envelope (role, check, result, severity, evidence, recommendation, decision_hash, timestamp_logical). Decision_hash = SHA-256(role || check || canonical(input) || result) via κ P1.5.4 reuse. Lamport timestamp_logical. Dedup invariant tested.
blockers: none"
)

FORBIDDENS:
✗ No floating point anywhere (bigint for timestamp_logical)
✗ No Date.now() / wall-clock
✗ No new npm deps beyond what κ already uses
✗ Do not re-implement κ canonical — REUSE src/domains/rules/canonical.ts
✗ Do not adopt s14's older decision_hash formula — use integrity.md's per R91 audit Q7
✗ Do not edit main checkout (CLAUDE.md §3)

NEXT:
P4.2.1 / P4.2.2 / P4.2.3 — three detectors fan out in Wave 2 (parallel)

Verification checklist (for reviewer agent)

  • 8-field schema present and correctly typed
  • Decision hash formula matches integrity.md / R91 audit Q7
  • Severity uses LOW/MED/HIGH (NOT INFO/WARNING/CRITICAL)
  • Lamport timestamp_logical: bigint
  • Canonical delegation to κ P1.5.4 (no duplicate impl)
  • No wall-clock or randomness (static scanner clean)
  • 1000-iter determinism for serialize + hash
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.1.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-1-1-advisory-envelope
    worktree: .worktrees/claude/p4-1-1-advisory-envelope
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: 8-field μ envelope (role, check, result, severity, evidence, recommendation, decision_hash, timestamp_logical). SHA-256 hash over canonical-serialized input via κ P1.5.4 reuse. Lamport clock (no wall-clock). Dedup tested over 1000 iter.
    blockers: none

Common gotchas

  • Decision hash formula — s14 L36 has SHA-256(check + input + result + model_identity); integrity.md L141 has SHA-256(role || check || canonical(input) || result). R91 audit §Q7 resolves: use integrity.md’s formula (delegates to κ canonical; matches θ pattern of canonical-serialize-then-hash; no model_identity field exists in Phase 0 — μ is Claude-only per ADR-005).
  • Severity bands — s14 leaves severity unenumerated; integrity.md L138 uses LOW | MED | HIGH; task-breakdown.md draft used legacy INFO | WARNING | CRITICAL. R91A ratified LOW | MED | HIGH (matches λ).
  • timestamp_logical is uint64 Lamport — TypeScript has no native uint64; use bigint with non-negative assertion in the schema. Initialize from a module-level counter (NOT Date.now()).

§P4.2.1 — Circular Logic Detector (DFS) — Phase 4 μ Wave 2

Spec source: task-breakdown.md §P4.2.1 Concept reference: integrity.md §1 Circular logic (L22-55) — DFS pseudocode Worktree: feature/p4-2-1-circular-detector Branch command: git worktree add .worktrees/claude/p4-2-1-circular-detector -b feature/p4-2-1-circular-detector origin/main Estimated effort: M (Medium — 4–8 hours) Depends on: P4.1.1 (envelope), ζ P0.7.1 (thought_records substrate), κ P1.2.4 (registry for cross-rule edges) Unblocks: P4.4.1 (invariant→BLOCK mapping), P4.7.1 (parity harness)

Files to create

  • src/domains/integrity/detectors/circular.ts — DFS cycle detection over thought_records.cites graph
  • src/domains/integrity/detectors/__tests__/circular.test.ts — cycle/non-cycle fixtures + cross-rule edges

Acceptance criteria

  • find_cycles(records: ThoughtRecord[]): Cycle[] returns ALL cycles (not just first); ordered, deterministic
  • Graph construction: edge source = parent_hash OR refs[] from ζ thought_records (integrity.md L30)
  • IN_PROGRESS / DONE coloring per integrity.md pseudocode L40-51; correctly distinguishes diamonds (DAG, no cycle) from true back-edges (cycle)
  • Self-loop detection: a record citing itself is a length-1 cycle
  • Length-N cycle: A→B→C→A is a length-3 cycle, returned with full path
  • Cross-rule cycle support: Rule A depends on Rule B depends on Rule A via different parameters — uses κ P1.2.4 rule-dep edges (integrity.md L54)
  • Threshold: ANY cycle → emit advisory check=circular_logic, severity=HIGH, result=WARN (integrity.md L54)
  • Advisory evidence contains the cycle path as ThoughtRecord IDs
  • No state mutation (read-only operator)
  • FP profile claim: < 1% on test corpus (verified in P4.7.1)
  • No Date.now() / Math.random() in detector source

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.2.1
  • docs/3-world/physics/enforcement/integrity.md §1 Circular logic (L22-55)
  • src/domains/integrity/schema.ts (P4.1.1)
  • src/domains/trail/repository.ts (ζ P0.7.1 — ThoughtRecord shape)
  • src/domains/rules/registry.ts (κ P1.2.4 — rule-dep edges)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.2.1 — Circular Logic Detector (DFS)
Implement the DFS cycle-detection over ζ thought_records.cites graph,
extended with κ rule-registry edges for cross-rule cycles. Emits HIGH-severity
WARN advisory on any cycle.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.2.1
3. docs/3-world/physics/enforcement/integrity.md §1 Circular logic (L22-55)
4. src/domains/integrity/schema.ts (P4.1.1)
5. src/domains/trail/repository.ts (ζ P0.7.1)
6. src/domains/rules/registry.ts (κ P1.2.4)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-2-1-circular-detector -b feature/p4-2-1-circular-detector origin/main
cd .worktrees/claude/p4-2-1-circular-detector

FILES TO CREATE:
- src/domains/integrity/detectors/circular.ts
  * type Cycle = { records: string[]; length: number }
  * function buildDag(records, registry?): DirectedGraph
    - edges from record.parent_hash + record.refs[]
    - if registry provided: add rule-dep edges from κ registry
  * function findCycles(records, registry?): Cycle[]
    - DFS with IN_PROGRESS / DONE coloring (per integrity.md L40-51)
    - returns all cycles, deterministically ordered
  * function detectCircularLogic(records, registry?): Advisory[]
    - calls findCycles
    - emits one advisory per cycle:
      * check="circular_logic", severity="HIGH", result="WARN"
      * evidence = cycle.records
      * recommendation = "Cycle detected in citation graph: <path>"
      * decision_hash via P4.1.1 computeDecisionHash
      * timestamp_logical from injected counter (NOT Date.now())

- src/domains/integrity/detectors/__tests__/circular.test.ts
  * Empty graph → no cycles
  * Linear chain A→B→C (no cycle) → []
  * Diamond A→{B,C}→D (DAG, no cycle) → []
  * Self-loop A→A → 1 length-1 cycle
  * Triangle A→B→C→A → 1 length-3 cycle
  * Two disjoint cycles → 2 cycles returned
  * Cross-rule: rule R1 depends on R2 which depends on R1 → 1 cycle via registry edges
  * FP corpus stub: 100 randomly-generated DAGs return 0 cycles (FP rate placeholder; full corpus in P4.7.1)
  * Determinism: same input → same cycle order
  * Advisory shape: check="circular_logic", severity="HIGH", result="WARN", evidence=[path]
  * No Date.now() / Math.random() in detector (static scanner)

ACCEPTANCE CRITERIA (headline):
✓ DFS with IN_PROGRESS/DONE coloring (integrity.md L40-51)
✓ Returns all cycles (not just first)
✓ Self-loops, length-N cycles handled
✓ Cross-rule edges via κ P1.2.4 registry
✓ Advisory shape matches P4.1.1 envelope
✓ FP profile <1% stubbed (full measurement in P4.7.1)

SUCCESS CHECK:
cd .worktrees/claude/p4-2-1-circular-detector && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.2.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-2-1-circular-detector
worktree: .worktrees/claude/p4-2-1-circular-detector
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: DFS cycle detector for μ — finds cycles in ζ thought_records.cites graph + κ rule-dep edges. Emits HIGH-severity WARN advisory per cycle. Self-loops, length-N, cross-rule edges handled. FP profile <1% to be verified in P4.7.1.
blockers: none"
)

FORBIDDENS:
✗ No state mutation (read-only operator only)
✗ No Date.now() / wall-clock
✗ Do not re-implement P4.1.1 envelope — IMPORT and use computeDecisionHash
✗ Do not return only first cycle — return all
✗ Do not edit main checkout

NEXT:
Wave 3 — P4.4.1 (escalation) consumes circular-logic advisories

Verification checklist (for reviewer agent)

  • DFS implementation matches integrity.md L40-51 pseudocode
  • All cycles returned (not first)
  • Cross-rule edges integrated via κ P1.2.4
  • Advisory envelope shape from P4.1.1
  • Self-loop + length-N cycle test fixtures pass
  • No wall-clock / randomness
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.2.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-2-1-circular-detector
    worktree: .worktrees/claude/p4-2-1-circular-detector
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: Circular-logic detector — DFS cycle detection over ζ thought_records.cites + κ rule-registry edges. Emits HIGH-severity WARN advisory per cycle. Self-loops, length-N, cross-rule cases handled. Read-only.
    blockers: none

Common gotchas

  • IN_PROGRESS vs DONE coloringvisited[node] = IN_PROGRESS BEFORE recursing into successors; visited[node] = DONE after path.pop(). Hitting IN_PROGRESS on a successor is a back-edge (cycle). Hitting DONE is a forward / cross edge (no cycle for the DAG case).
  • Diamond is NOT a cycle — A→B→D + A→C→D is a DAG; the DFS coloring distinguishes this from a true back-edge.
  • Cross-rule edges live in κ registrysrc/domains/rules/registry.ts exposes rule dependency edges via P1.2.4’s API. Mock the registry in tests with a fixture; don’t open a real registry instance.
  • Cycle path normalization — for A→B→C→A, the cycle path can be ordered starting at any node (A,B,C,A vs B,C,A,B). Pick a canonical start (lexicographically smallest ID) to make the output deterministic.

§P4.2.2 — Coercion Trap Detector (option-set) — Phase 4 μ Wave 2

Spec source: task-breakdown.md §P4.2.2 Concept reference: integrity.md §2 Coercion trap (L57-85) — option-set enumeration pseudocode Worktree: feature/p4-2-2-coercion-detector Branch command: git worktree add .worktrees/claude/p4-2-2-coercion-detector -b feature/p4-2-2-coercion-detector origin/main Estimated effort: M (Medium — 4–8 hours) Depends on: P4.1.1 (envelope), κ P1.4.1 (admission), κ P1.3.1 (engine), λ P2.1.2 (compute) Unblocks: P4.4.1, P4.7.1

Files to create

  • src/domains/integrity/detectors/coercion.ts — option-set enumeration over κ admission + rule-engine outcomes
  • src/domains/integrity/detectors/__tests__/coercion.test.ts — all-negative, empty-set, mixed-outcome, obligation-overflow fixtures

Acceptance criteria

  • detectCoercion(decisionRecord, admission, engine, scoreCompute): Advisory[]
  • Enumerate available actions for participant via κ admission evaluator (P1.4.1)
  • Simulate each action’s effects via κ rule engine (P1.3.1): collect reputation_delta (via λ P2.1.2 score-compute), obligation_beyond_capacity flag
  • Flag conditions (any of):
    • Every available option produces reputation_delta < 0
    • Every available option produces obligation_beyond_capacity === true
    • Action space is empty
  • On flag: emit advisory check=coercion_trap, severity=HIGH, result=WARN, evidence=[presented, available, outcomes]
  • No veto power — advisory only; the function MUST NOT block the decision (integrity.md L85)
  • presented (from decision record) vs available (enumerated) — both included in evidence for operator review
  • Pure function — no I/O beyond the injected κ/λ adapters
  • FP profile claim: medium (legitimate filtering can look like coercion); verified in P4.7.1

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.2.2
  • docs/3-world/physics/enforcement/integrity.md §2 Coercion trap (L57-85)
  • src/domains/integrity/schema.ts (P4.1.1)
  • src/domains/rules/admission.ts (κ P1.4.1 — enumeration entry point)
  • src/domains/rules/engine.ts (κ P1.3.1 — outcome simulation)
  • src/domains/reputation/compute.ts (λ P2.1.2 — reputation_delta type)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.2.2 — Coercion Trap Detector (option-set)
Enumerate available actions for a participant via κ admission, simulate
outcomes via κ engine + λ compute, flag if every option is negative or if
the action space is empty. Advisory only — no veto.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.2.2
3. docs/3-world/physics/enforcement/integrity.md §2 Coercion trap (L57-85)
4. src/domains/integrity/schema.ts (P4.1.1)
5. src/domains/rules/admission.ts (κ P1.4.1)
6. src/domains/rules/engine.ts (κ P1.3.1)
7. src/domains/reputation/compute.ts (λ P2.1.2)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-2-2-coercion-detector -b feature/p4-2-2-coercion-detector origin/main
cd .worktrees/claude/p4-2-2-coercion-detector

FILES TO CREATE:
- src/domains/integrity/detectors/coercion.ts
  * type Outcome = { reputation_delta: bigint; obligation_beyond_capacity: boolean }
  * type CoercionDeps = {
      admission: (actor, context) => Action[],     // κ P1.4.1 adapter
      engine:    (action, context) => Outcome,     // κ P1.3.1 adapter
      scoreCompute: (delta, baseline) => bigint,   // λ P2.1.2 adapter
    }
  * function detectCoercion(decisionRecord, deps: CoercionDeps): Advisory[]
    1. presented = decisionRecord.options
    2. available = deps.admission(decisionRecord.actor, decisionRecord.context)
    3. outcomes = new Map(); for action in available: outcomes.set(action, deps.engine(action, decisionRecord.context))
    4. negative = available.filter(a => outcomes.get(a).reputation_delta < 0n)
    5. obligates = available.filter(a => outcomes.get(a).obligation_beyond_capacity)
    6. emit advisory if:
       - available.length === 0 (empty action space)
       - OR negative.length === available.length (all-negative)
       - OR obligates.length === available.length (all-obligates)
    7. Advisory:
       * check="coercion_trap", severity="HIGH", result="WARN"
       * evidence=[presented, available, Array.from(outcomes.entries())]
       * recommendation: human-readable explanation of which condition triggered
       * decision_hash via P4.1.1 computeDecisionHash
       * timestamp_logical from injected counter

- src/domains/integrity/detectors/__tests__/coercion.test.ts
  * Empty action space → 1 advisory (empty trap)
  * All-negative reputation → 1 advisory
  * All-obligates → 1 advisory
  * Mixed outcomes (some positive, some negative) → 0 advisories
  * Single positive option → 0 advisories
  * Presented ≠ Available (silent filtering) → evidence captures both; advisory if filtered-set is degenerate
  * Determinism: same inputs → same advisory
  * Pure function (no I/O beyond injected adapters)
  * No Date.now() / Math.random() in detector

ACCEPTANCE CRITERIA (headline):
✓ Three trigger conditions (empty, all-negative, all-obligates)
✓ Advisory-only (no veto); function returns advisory list, does not throw
✓ Uses κ admission (P1.4.1) + κ engine (P1.3.1) + λ compute (P2.1.2) adapters
✓ Evidence captures both presented and available sets
✓ FP profile medium (mixed-outcome cases NOT flagged)

SUCCESS CHECK:
cd .worktrees/claude/p4-2-2-coercion-detector && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.2.2>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-2-2-coercion-detector
worktree: .worktrees/claude/p4-2-2-coercion-detector
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: Coercion-trap detector — enumerates legal actions via κ admission, simulates outcomes via κ engine + λ compute. Flags empty / all-negative / all-obligates action spaces. Advisory only; never vetoes. FP profile medium.
blockers: none"
)

FORBIDDENS:
✗ No veto path — detector emits advisories; never blocks the decision
✗ No I/O beyond injected adapters (pure function)
✗ Do not call into κ / λ directly — accept adapters in CoercionDeps
✗ No Date.now() / Math.random()
✗ Do not edit main checkout

NEXT:
Wave 3 — P4.4.1 (escalation) consumes coercion-trap advisories; HARD BLOCK
maps to coercion-in-admission rejection

Verification checklist (for reviewer agent)

  • Three trigger conditions implemented (empty / all-negative / all-obligates)
  • Advisory shape matches P4.1.1 envelope
  • Pure function (no I/O outside injected adapters)
  • Uses bigint for reputation_delta (matches λ P2.1.2)
  • FP profile: mixed-outcome NOT flagged
  • No wall-clock / randomness in detector source
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.2.2>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-2-2-coercion-detector
    worktree: .worktrees/claude/p4-2-2-coercion-detector
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: Coercion-trap detector via option-set enumeration over κ admission + outcome simulation via κ engine + λ compute. Three flag conditions: empty, all-negative, all-obligates. Advisory-only; never vetoes. Evidence captures both presented and available.
    blockers: none

Common gotchas

  • reputation_delta is bigint — λ P2.1.2 uses BPS (basis-points) bigint math. Compare with 0n, NOT 0. JavaScript’s < mixes bigint and number unsafely; force bigint.
  • presented vs availablepresented is what the decision record contains; available is what κ admission says was legal. They CAN differ (legitimate filtering at skill-tier gate); capture both in evidence so operators can decide.
  • Advisory only — even an HIGH-severity advisory is NOT enforcement. The escalation FSM (P4.4.1) consumes these advisories and decides whether to map to a HARD BLOCK at α tool-lock.
  • Adapter injection over direct import — accept CoercionDeps as a parameter so tests can mock κ admission / κ engine / λ compute independently. Importing live κ from a μ detector would create a circular module dependency.

§P4.2.3 — Axiom Drift Tracker (sliding window) — Phase 4 μ Wave 2

Spec source: task-breakdown.md §P4.2.3 Concept reference: integrity.md §3 Axiom drift (L87-115) + constitution.md §AX-01..AX-07 Worktree: feature/p4-2-3-drift-tracker Branch command: git worktree add .worktrees/claude/p4-2-3-drift-tracker -b feature/p4-2-3-drift-tracker origin/main Estimated effort: L (Large — 1–2 days) Depends on: P4.1.1 (envelope), λ P2.2.2 (penalties — parameter-change events surface), κ P1.1.1 (BPS arith) Unblocks: P4.4.1, P4.7.1, P4.8.1

Files to create

  • src/domains/integrity/detectors/drift.ts — 6-month sliding window over parameter-change events + AX-invariant regression check
  • src/domains/integrity/detectors/__tests__/drift.test.ts — under-threshold / WARN at 8% / BLOCK at 10% / AX regression fixtures + synthetic 12-month corpus

Acceptance criteria

  • checkAxiomDrift(domain, now, parameterChangeEvents): Advisory[]
  • 6-month sliding window over parameter_changes(domain, since=now - window) (integrity.md L98)
  • Cumulative magnitude: sum(abs(c.delta_bps) for c in changes) (integrity.md L99)
  • Threshold 1 (WARN at 8% / 800 bps): emit advisory check=axiom_drift, severity=MED, result=WARN (integrity.md L102-103)
  • Threshold 2 (BLOCK at 10% / 1000 bps): emit advisory check=axiom_drift, severity=HIGH, result=BLOCK — denies new proposals in the domain (integrity.md L104-106)
  • AX-invariant regression check (separate code path per design invariant 9):
    • For each staged proposal in domain, simulate against AX-01..AX-07
    • If any axiom invariant would regress: emit advisory check=axiom_regression, severity=HIGH, result=BLOCK (HARD BLOCK semantics; tool-lock denies)
  • BPS arithmetic via κ P1.1.1 — no floats; integer-only bigint
  • AX-06 cap (10% / 1000 bps) hardcoded per integrity.md L104
  • FP profile claim: high without long history; verified in P4.7.1 with synthetic 12-month corpus
  • No Date.now() in detector source (now is an injected parameter)
  • Pure function — no I/O beyond the injected parameterChangeEvents adapter

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.2.3
  • docs/3-world/physics/enforcement/integrity.md §3 Axiom drift (L87-115)
  • docs/3-world/physics/constitution.md §AX-01..AX-07 (the seven axioms μ watches)
  • src/domains/integrity/schema.ts (P4.1.1)
  • src/domains/reputation/penalties.ts (λ P2.2.2 — parameter-change event shape)
  • src/domains/rules/bps-constants.ts + integer-math.ts (κ P1.1.1 — BPS arith)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.2.3 — Axiom Drift Tracker (sliding window)
Implement the 6-month sliding-window aggregation over parameter changes
plus the per-AX invariant regression check. WARN at 8%, BLOCK at 10%,
HARD BLOCK on AX-N regression. BPS bigint math throughout.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.2.3
3. docs/3-world/physics/enforcement/integrity.md §3 Axiom drift (L87-115)
4. docs/3-world/physics/constitution.md §AX-01..AX-07
5. src/domains/integrity/schema.ts (P4.1.1)
6. src/domains/reputation/penalties.ts (λ P2.2.2)
7. src/domains/rules/bps-constants.ts + integer-math.ts (κ P1.1.1)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-2-3-drift-tracker -b feature/p4-2-3-drift-tracker origin/main
cd .worktrees/claude/p4-2-3-drift-tracker

FILES TO CREATE:
- src/domains/integrity/detectors/drift.ts
  * Constants (bigint, integrity.md L102-104):
    - WINDOW_MS = 15552000000n           // 6 months in Lamport-equivalent units (180d)
    - WARN_THRESHOLD_BPS = 800n          // 8%
    - BLOCK_THRESHOLD_BPS = 1000n        // 10% (AX-06 cap)
  * type ParameterChange = { domain: string; delta_bps: bigint; timestamp_logical: bigint }
  * type StagedProposal = { id: string; domain: string; would_reduce_invariant(ax: AxiomId): boolean }
  * function checkAxiomDrift(domain, now, changes, stagedProposals): Advisory[]
    1. windowed = changes.filter(c => c.domain === domain && c.timestamp_logical >= now - WINDOW_MS)
    2. magnitude = windowed.reduce((s, c) => s + abs(c.delta_bps), 0n)
    3. advisories = []
    4. if magnitude >= WARN_THRESHOLD_BPS && magnitude < BLOCK_THRESHOLD_BPS:
       advisories.push(makeAdvisory("axiom_drift", "MED", "WARN", ...))
    5. if magnitude >= BLOCK_THRESHOLD_BPS:
       advisories.push(makeAdvisory("axiom_drift", "HIGH", "BLOCK", ...))
    6. for prop in stagedProposals.filter(p => p.domain === domain):
       for ax of [AX_01..AX_07]:
         if prop.would_reduce_invariant(ax):
           advisories.push(makeAdvisory("axiom_regression", "HIGH", "BLOCK", evidence=[prop.id, ax]))
    7. return advisories
  * abs(bigint): bigint helper — reuse κ integer-math if available
  * No Date.now() — accept `now: bigint` as parameter (Lamport)

- src/domains/integrity/detectors/__tests__/drift.test.ts
  * Empty changes → 0 advisories
  * Magnitude 500 bps (under WARN) → 0 advisories
  * Magnitude 800 bps (exact WARN) → 1 advisory MED/WARN
  * Magnitude 999 bps (still WARN) → 1 advisory MED/WARN
  * Magnitude 1000 bps (exact BLOCK) → 1 advisory HIGH/BLOCK
  * Magnitude 1500 bps (over BLOCK) → 1 advisory HIGH/BLOCK
  * Out-of-window changes excluded (older than 6 months)
  * Cross-domain changes excluded (only "this domain" counted)
  * AX-01 regression in staged proposal → 1 advisory axiom_regression HIGH/BLOCK
  * Multiple AX regressions → multiple advisories (one per AX)
  * Combined: 1500 bps WARN advisory + 1 AX_03 regression advisory → 2 advisories total
  * Synthetic 12-month corpus stub (full FP measurement in P4.7.1)
  * Determinism: same inputs → same advisories in same order
  * No Date.now() in detector source (static scanner)

ACCEPTANCE CRITERIA (headline):
✓ Sliding window 6 months over parameter changes
✓ WARN at 8% (800 bps), BLOCK at 10% (1000 bps)
✓ AX-01..AX-07 regression check as separate code path (design invariant 9)
✓ BPS bigint math (no floats)
✓ `now` injected (no Date.now)
✓ FP corpus stub present (full in P4.7.1)

SUCCESS CHECK:
cd .worktrees/claude/p4-2-3-drift-tracker && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.2.3>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-2-3-drift-tracker
worktree: .worktrees/claude/p4-2-3-drift-tracker
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: Axiom drift tracker — 6mo sliding-window cumulative bps over parameter changes (WARN 8% / BLOCK 10%) + per-AX invariant regression check (HARD BLOCK on any AX-01..AX-07 regression). BPS bigint math via κ P1.1.1. now-injected (no wall-clock).
blockers: none"
)

FORBIDDENS:
✗ No Date.now() — `now` is a parameter
✗ No floating point — BPS bigint via κ P1.1.1
✗ Do not collapse axiom_drift and axiom_regression — they are two distinct checks per design invariant 9
✗ Do not edit main checkout

NEXT:
Wave 3 — P4.4.1 (escalation) routes axiom_drift BLOCK to π proposal-intake,
axiom_regression HARD BLOCK to α tool-lock; Wave 4 P4.8.1 subscribes drift
to post-fork sweeps

Verification checklist (for reviewer agent)

  • WINDOW / WARN / BLOCK constants match integrity.md
  • Sliding window filter correctness (timestamp_logical comparison)
  • axiom_drift vs axiom_regression as separate paths
  • All AX-01..AX-07 enumerated in the regression check
  • BPS bigint throughout (no number / float)
  • now is injected (no Date.now in source)
  • Determinism over identical inputs
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.2.3>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-2-3-drift-tracker
    worktree: .worktrees/claude/p4-2-3-drift-tracker
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: D3 axiom drift tracker — 6mo sliding window (WARN 800bps / BLOCK 1000bps) + AX-01..AX-07 regression check (HARD BLOCK on any regression). BPS bigint math (κ P1.1.1). `now` injected. Two distinct check codes: axiom_drift (cumulative) and axiom_regression (per-AX).
    blockers: none

Common gotchas

  • Sliding window in Lamport, not wall-clocknow is a bigint Lamport timestamp; subtract WINDOW_MS in the same units. Real wall-clock conversion happens at the call site (β scheduler), NOT inside the detector.
  • axiom_drift vs axiom_regression — these are TWO different check values in the envelope. axiom_drift is aggregate-cumulative (sliding window); axiom_regression is per-axiom semantic (would-reduce-invariant). Per design invariant 9, do NOT collapse them. A staged proposal can trigger both.
  • abs(bigint) — JavaScript native Math.abs does not accept bigint. Use (x < 0n) ? -x : x or import from κ integer-math.
  • BLOCK vs HARD BLOCK — integrity.md L106 calls the 10% cap a BLOCK_NEW_PROPOSALS action; the AX regression at L112 calls it HARD_BLOCK. In our envelope, both are result: "BLOCK" (the envelope schema is 3-valued: PASS / WARN / BLOCK); the HARD-vs-soft distinction is captured by the consumer (P4.4.1 escalation FSM) via the check field — axiom_regression routes to α tool-lock (HARD BLOCK), axiom_drift BLOCK routes to π proposal-intake.

§P4.3.1 — Three Advisory Roles (Translator / Sentinel / Guide) — Phase 4 μ Wave 3

Spec source: task-breakdown.md §P4.3.1 Concept reference: integrity.md §Three advisory roles (L117-127) + s14 §Advisory roles Worktree: feature/p4-3-1-advisory-roles Branch command: git worktree add .worktrees/claude/p4-3-1-advisory-roles -b feature/p4-3-1-advisory-roles origin/main Estimated effort: M (Medium — 4–8 hours) Depends on: P4.1.1 (envelope) Unblocks: P4.6.1 (MCP tool surface — tools per role)

Files to create

  • src/domains/integrity/roles.ts — Translator / Sentinel / Guide role adapters (all read-only)
  • src/domains/integrity/__tests__/roles.test.ts — role-specific input/output + read-only invariant tests

Acceptance criteria

  • Translator role: class Translator { summarize(advisory): string }
    • read-only; summarizes advisory reports for a human operator
    • NO recommendations of its own (integrity.md L123)
    • input: advisory; output: human-readable string
  • Sentinel role: class Sentinel { flag(advisory, severityThreshold): SentinelFlag | null }
    • read-only; flags advisories meeting a severity threshold
    • may escalate to π via emit (NOT direct call) — integrity.md L124
    • input: advisory + threshold; output: SentinelFlag with action=”escalate_to_pi” if severity ≥ threshold
  • Guide role: class Guide { suggest(state, advisories): Suggestion[] }
    • read-only; suggests corrective actions for human review
    • the human (NOT μ) decides whether to act (integrity.md L125)
    • input: current state + accumulated advisories; output: Suggestion[]
  • All three roles are strictly read-only — no mutation API exposed; no db.run, no state writes
  • Enforced via TypeScript readonly modifiers + class fields all private readonly
  • Each role’s output is NOT another advisory (advisories come from detectors) — output is presentation / signal layer on top
  • No “Mutator” role exists (integrity.md L127) — there is no fourth role
  • Static analysis: grep for db.run, db.exec, mutating verbs in role files returns 0 hits
  • Standard input shape across all roles: P4.1.1 Advisory

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.3.1
  • docs/3-world/physics/enforcement/integrity.md §Three advisory roles (L117-127)
  • docs/spec/s14-integrity-monitor.md §Advisory roles
  • src/domains/integrity/schema.ts (P4.1.1)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.3.1 — Three Advisory Roles (Translator / Sentinel / Guide)
Implement three READ-ONLY role adapters that consume P4.1.1 advisories
and produce different output surfaces: human-readable summaries (Translator),
escalation flags (Sentinel), corrective suggestions (Guide). Zero
mutation. No Mutator role.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.3.1
3. docs/3-world/physics/enforcement/integrity.md §Three advisory roles (L117-127)
4. docs/spec/s14-integrity-monitor.md §Advisory roles
5. src/domains/integrity/schema.ts (P4.1.1)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-3-1-advisory-roles -b feature/p4-3-1-advisory-roles origin/main
cd .worktrees/claude/p4-3-1-advisory-roles

FILES TO CREATE:
- src/domains/integrity/roles.ts
  * type SentinelFlag = { action: "escalate_to_pi" | "log_only"; reason: string; advisory: Advisory }
  * type Suggestion = { headline: string; advisory_refs: string[]; rationale: string }

  * class Translator {
      readonly role = "Translator" as const;
      summarize(advisory: Advisory): string {
        // Compose human-readable summary from check + severity + result + recommendation
        // Pure function — no I/O, no mutation
      }
    }

  * class Sentinel {
      readonly role = "Sentinel" as const;
      flag(advisory: Advisory, severityThreshold: "LOW" | "MED" | "HIGH"): SentinelFlag | null {
        // Severity rank: LOW=0, MED=1, HIGH=2
        // If advisory.severity rank >= threshold rank: return flag with action="escalate_to_pi"
        // Otherwise: return null (no flag)
        // EMITS the flag — does NOT call into π directly
      }
    }

  * class Guide {
      readonly role = "Guide" as const;
      suggest(state: unknown, advisories: readonly Advisory[]): Suggestion[] {
        // Group advisories by check; produce one Suggestion per group
        // Each Suggestion references the advisories that triggered it
        // Pure function — no I/O, no mutation
      }
    }

  * Static-analyzability: ensure NO `db.`, `update`, `insert`, `delete`,
    `mutate` symbols appear in this file (CI-grep gate).

- src/domains/integrity/__tests__/roles.test.ts
  * Translator: feed advisory, get string containing check + severity + recommendation
  * Sentinel: HIGH severity advisory with threshold HIGH → flag; threshold above → null
  * Sentinel: returns flag object, never throws, never mutates input
  * Guide: feed N advisories of M check types → at most M suggestions (grouped)
  * Read-only: all three classes have only `readonly` fields
  * Static analysis stub: grep for db.* / mutating verbs → 0 hits in roles.ts
  * No "Mutator" class exists in the module
  * No Date.now() / Math.random() in role source

ACCEPTANCE CRITERIA (headline):
✓ Three classes: Translator, Sentinel, Guide
✓ All read-only (TypeScript `readonly` + no mutation APIs)
✓ Output types distinct per role (string / SentinelFlag / Suggestion[])
✓ No "Mutator" role
✓ Sentinel emits flag, never calls π directly
✓ Guide suggests, never executes

SUCCESS CHECK:
cd .worktrees/claude/p4-3-1-advisory-roles && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.3.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-3-1-advisory-roles
worktree: .worktrees/claude/p4-3-1-advisory-roles
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: Three read-only advisory roles for μ — Translator (summarize), Sentinel (flag with severity threshold), Guide (group advisories into suggestions). All `readonly`; no mutation paths; no Mutator role. Sentinel emits flag, does not call π; Guide suggests, does not execute.
blockers: none"
)

FORBIDDENS:
✗ No mutation APIs in any role
✗ No direct π / α / κ / λ calls from a role — roles are pure presentation
✗ No "Mutator" or fourth role (integrity.md L127)
✗ No Date.now() / Math.random()
✗ Do not edit main checkout

NEXT:
Wave 4 — P4.6.1 MCP tools wrap one tool per role (translator_summarize, sentinel_flag, guide_suggest variants)

Verification checklist (for reviewer agent)

  • Three classes present and exported
  • All classes use readonly for fields
  • No mutation APIs (grep for set, mut, db.run, db.exec)
  • Translator output: string
  • [ ] Sentinel output: SentinelFlag null
  • Guide output: Suggestion[]
  • No “Mutator” class
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.3.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-3-1-advisory-roles
    worktree: .worktrees/claude/p4-3-1-advisory-roles
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ advisory roles — Translator (summarize), Sentinel (flag), Guide (suggest). All read-only; outputs are signal layer, not mutation. No Mutator role per integrity.md L127. Sentinel emits flag (no direct π call); Guide suggests (no execution).
    blockers: none

Common gotchas

  • readonly is a contract, not a runtime guarantee — TypeScript’s readonly is compile-time only. Add a runtime grep gate in CI: search for db.run|db.exec|UPDATE|INSERT|DELETE patterns in roles.ts and fail if any are found.
  • Roles are NOT detectors — they consume advisories produced by P4.2.1/P4.2.2/P4.2.3; they do not detect new conditions. The role layer is presentation / signal-shaping.
  • Sentinel does not call π — it emits a SentinelFlag object; some other surface (the escalation FSM in P4.4.1 or the MCP tool layer in P4.6.1) decides what to do with the flag. Direct π calls would couple roles to governance.
  • No Mutator — integrity.md L127 is explicit. Execution requires a separate π proposal or T0 authorization. A “Mutator” role would violate the entire μ design intent.

§P4.4.1 — Escalation FSM (4-result + 3 invariant mappings) — Phase 4 μ Wave 3

Spec source: task-breakdown.md §P4.4.1 Concept reference: integrity.md §Escalation mapping (L148-157) + s14 §When advisory becomes enforcement Worktree: feature/p4-4-1-escalation-fsm Branch command: git worktree add .worktrees/claude/p4-4-1-escalation-fsm -b feature/p4-4-1-escalation-fsm origin/main Estimated effort: M (Medium — 4–8 hours) Depends on: P4.1.1 (envelope), P4.2.1 (circular), P4.2.2 (coercion), P4.2.3 (drift), κ P1.4.1 (admission), κ P1.2.4 (registry) Unblocks: P4.6.1, P4.7.1

Files to create

  • src/domains/integrity/escalation.ts — 4-result FSM + 3 invariant-mapping rules
  • src/domains/integrity/__tests__/escalation.test.ts — every result path + each invariant mapping fired

Acceptance criteria

  • 4-result FSM per integrity.md L150-157:
    • PASS → log to ζ at thought_type=advisory; no further effect
    • WARN → log + surface in operator console; no rule change
    • BLOCK → record denial event into ζ at thought_type=advisory; π proposal-intake denial (π integration is OUT OF SCOPE for Phase 4 per R91 audit Q5 — μ emits the event; π consumes when π ships)
    • HARD BLOCK → α tool-lock admission denies; downstream κ evaluation never runs (uses src/domains/rules/tool-lock-adapter.ts from P1.4.4, shipped #220)
  • 3 invariant mappings per integrity.md §When advisory becomes enforcement (s14 L38-43):
    • Mapping 1: circular_logic in rule update → rule rejected at κ rule loader (P1.2.4)
    • Mapping 2: coercion_trap in admission gate → event rejected at κ admission (P1.4.1)
    • Mapping 3: axiom_drift beyond limits → governance proposal rejected at π intake (records BLOCK event into ζ; π consumes when π ships)
  • escalate(advisory): EscalationOutcome — returns the action taken (or to-be-taken)
  • EscalationOutcome = { result: "PASS" | "WARN" | "BLOCK" | "HARD_BLOCK"; target_axis: "ζ" | "operator_console" | "π" | "α"; event_id: string }
  • Result encoding: advisory result=PASS → PASS; WARN → WARN; BLOCK → BLOCK unless invariant-mapping fires HARD_BLOCK
  • HARD BLOCK is owned by α (design invariant 8) — escalation emits the event; α tool-lock reads it and denies
  • Idempotent — same advisory fed twice produces same outcome with same event_id
  • No mutation of upstream κ/λ state (μ is read-only; escalation is event-emit only)

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.4.1
  • docs/3-world/physics/enforcement/integrity.md §Escalation mapping (L148-157)
  • docs/spec/s14-integrity-monitor.md §When advisory becomes enforcement
  • src/domains/integrity/schema.ts (P4.1.1)
  • src/domains/integrity/detectors/{circular,coercion,drift}.ts (P4.2.1/2/3)
  • src/domains/rules/admission.ts (κ P1.4.1)
  • src/domains/rules/registry.ts (κ P1.2.4)
  • src/domains/rules/tool-lock-adapter.ts (P1.4.4 — HARD BLOCK consumer)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.4.1 — Escalation FSM (4-result + 3 invariant mappings)
Map advisory result codes onto enforcement targets per integrity.md
§Escalation mapping. PASS → ζ log; WARN → operator console; BLOCK → π
proposal-intake event; HARD BLOCK → α tool-lock denial event. Three
invariant mappings convert specific (check × context) combinations into
HARD BLOCK semantics.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.4.1
3. docs/3-world/physics/enforcement/integrity.md §Escalation mapping (L148-157)
4. docs/spec/s14-integrity-monitor.md §When advisory becomes enforcement
5. src/domains/integrity/schema.ts (P4.1.1)
6. src/domains/integrity/detectors/*.ts (P4.2.1/2/3)
7. src/domains/rules/tool-lock-adapter.ts (P1.4.4)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-4-1-escalation-fsm -b feature/p4-4-1-escalation-fsm origin/main
cd .worktrees/claude/p4-4-1-escalation-fsm

FILES TO CREATE:
- src/domains/integrity/escalation.ts
  * type EscalationResult = "PASS" | "WARN" | "BLOCK" | "HARD_BLOCK"
  * type EscalationTarget = "ζ" | "operator_console" | "π" | "α"
  * type EscalationOutcome = {
      result: EscalationResult;
      target_axis: EscalationTarget;
      event_id: string;     // deterministic from advisory.decision_hash
    }
  * type EscalationContext = { surface: "rule_update" | "admission_gate" | "governance_intake" | "other" }
  * type EscalationDeps = {
      emitZeta(advisory): string;     // returns event_id; thought_type=advisory
      emitOperator(advisory): string;
      emitPi(advisory): string;       // records BLOCK event; π consumes when π ships
      emitAlpha(advisory): string;    // tool-lock-adapter consumes
    }
  * function escalate(advisory, context, deps): EscalationOutcome
    1. If advisory.result === "PASS": deps.emitZeta(advisory); return PASS
    2. If advisory.result === "WARN": deps.emitOperator(advisory); deps.emitZeta(advisory); return WARN
    3. If advisory.result === "BLOCK":
       // Invariant mapping check
       if advisory.check === "circular_logic" && context.surface === "rule_update":
         deps.emitAlpha(advisory); return HARD_BLOCK (mapping 1)
       if advisory.check === "coercion_trap" && context.surface === "admission_gate":
         deps.emitAlpha(advisory); return HARD_BLOCK (mapping 2)
       if advisory.check === "axiom_regression":
         deps.emitAlpha(advisory); return HARD_BLOCK (any context — axiom regression is always HARD)
       if advisory.check === "axiom_drift" && context.surface === "governance_intake":
         deps.emitPi(advisory); return BLOCK (mapping 3 — π intake denial)
       // Default BLOCK: π intake
       deps.emitPi(advisory); return BLOCK
  * event_id is deterministic: derived from advisory.decision_hash + target axis
  * Idempotency: same advisory + same context twice → same event_id

- src/domains/integrity/__tests__/escalation.test.ts
  * PASS path: advisory.result=PASS → emitZeta called, outcome.result=PASS, target_axis=ζ
  * WARN path: advisory.result=WARN → emitOperator + emitZeta called, outcome.result=WARN
  * Plain BLOCK (no invariant mapping): advisory.result=BLOCK, context="other" → emitPi called, outcome.result=BLOCK
  * Mapping 1: circular_logic + rule_update → HARD_BLOCK; emitAlpha called
  * Mapping 2: coercion_trap + admission_gate → HARD_BLOCK; emitAlpha called
  * Mapping 3: axiom_drift + governance_intake → BLOCK (π denial)
  * axiom_regression in any context → HARD_BLOCK; emitAlpha called
  * Idempotency: escalate(advisory, ctx, deps) twice → same event_id both times
  * π/α emitters NOT called when ζ/operator path applies
  * No state mutation of upstream κ/λ — escalation is event-emit only

ACCEPTANCE CRITERIA (headline):
✓ 4-result FSM (PASS/WARN/BLOCK/HARD_BLOCK)
✓ 3 invariant mappings (circular_logic, coercion_trap, axiom_drift)
✓ axiom_regression always HARD_BLOCK (separate from mapping 3)
✓ HARD BLOCK emits to α; tool-lock-adapter consumes
✓ event_id deterministic; idempotent re-escalation
✓ No upstream mutation (μ stays read-only)

SUCCESS CHECK:
cd .worktrees/claude/p4-4-1-escalation-fsm && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.4.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-4-1-escalation-fsm
worktree: .worktrees/claude/p4-4-1-escalation-fsm
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: Escalation FSM — 4 result codes (PASS/WARN/BLOCK/HARD_BLOCK) routed to 4 targets (ζ/operator/π/α). Three invariant mappings: circular_logic + rule_update → HARD via α; coercion_trap + admission_gate → HARD via α; axiom_drift + governance_intake → BLOCK via π. axiom_regression always HARD. Idempotent (deterministic event_id). No upstream mutation.
blockers: none"
)

FORBIDDENS:
✗ μ does NOT directly deny — it emits typed events to α/π/ζ
✗ No direct mutation of κ/λ state from escalation
✗ Do not skip the idempotency check — event_id MUST be deterministic
✗ Do not edit main checkout

NEXT:
P4.6.1 — MCP tool surface exposes escalation as a tool (`integrity_escalate`)

Verification checklist (for reviewer agent)

  • 4-result enum
  • 3 invariant mappings encoded in escalate() logic
  • axiom_regression always HARD_BLOCK (any context)
  • HARD BLOCK emits to α only (tool-lock-adapter is the consumer)
  • event_id is deterministic from advisory.decision_hash
  • Idempotency tested (twice → same event_id)
  • No κ/λ mutation from escalation source
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.4.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-4-1-escalation-fsm
    worktree: .worktrees/claude/p4-4-1-escalation-fsm
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ escalation FSM — 4-result routing (PASS→ζ / WARN→operator+ζ / BLOCK→π / HARD_BLOCK→α). 3 invariant mappings (circular→rule_update, coercion→admission, drift→governance). axiom_regression always HARD. Idempotent.
    blockers: none

Common gotchas

  • HARD BLOCK is α’s job, not μ’s — design invariant 8. μ emits the event; the tool-lock-adapter from P1.4.4 reads mcp_advisories (P4.5.1) and consults the escalation event during admission. μ never calls denyTool() directly.
  • π is not shipped in Phase 4emitPi records the BLOCK into ζ at thought_type=advisory; when π ships in Phase 6, its proposal-intake gate reads this stream. R91 audit Q5 documents this explicitly.
  • axiom_regression is its own check value — it is distinct from axiom_drift. Per integrity.md L112, axiom regression is ALWAYS HARD BLOCK regardless of context. The escalation FSM should branch on check first, then context.
  • event_id determinism — derive event_id from advisory.decision_hash || target_axis (or similar). Idempotent re-escalation is required so re-running the same advisory does not double-emit events.

§P4.5.1 — Advisory Persistence (mcp_advisories migration) — Phase 4 μ Wave 3

Spec source: task-breakdown.md §P4.5.1 Concept reference: integrity.md §Phase 0 posture (L165-170) (table stub) + §Phase 4 scope (L172-178) (activation) Worktree: feature/p4-5-1-advisory-persistence Branch command: git worktree add .worktrees/claude/p4-5-1-advisory-persistence -b feature/p4-5-1-advisory-persistence origin/main Estimated effort: S (Small — 2–4 hours) Depends on: P4.1.1 (envelope), P0.2.2 (SQLite migration runner) Unblocks: P4.6.1, P4.7.1, P4.8.1

Files to create

  • src/db/migrations/NN-create-mcp-advisories.sql — table create + indexes
  • src/domains/integrity/repository.tsinsertAdvisory, getAdvisory, listAdvisories
  • src/domains/integrity/__tests__/repository.test.ts — schema validation, idempotent insert, dedup, filter queries

Acceptance criteria

  • SQLite migration creates mcp_advisories table with 8 envelope fields from P4.1.1:
    • role TEXT NOT NULL CHECK(role IN ('Translator','Sentinel','Guide'))
    • check TEXT NOT NULL CHECK(check IN ('circular_logic','coercion_trap','axiom_drift','axiom_regression')) (quoted; check is a SQL keyword)
    • result TEXT NOT NULL CHECK(result IN ('PASS','WARN','BLOCK'))
    • severity TEXT NOT NULL CHECK(severity IN ('LOW','MED','HIGH'))
    • evidence TEXT NOT NULL (JSON-serialized array)
    • recommendation TEXT NOT NULL
    • decision_hash TEXT NOT NULL UNIQUE — enforces dedup (integrity.md L146)
    • timestamp_logical INTEGER NOT NULL — uint64 Lamport (SQLite has no native uint64; INTEGER affinity holds bigint via better-sqlite3)
  • Indexes: (check, severity) and (role) for typical advisory queries (audit §3.2)
  • Repository functions:
    • insertAdvisory(record): { inserted: boolean; existing?: Advisory } — idempotent
    • getAdvisory(decision_hash): Advisory | null
    • listAdvisories(filter): Advisory[] — filter by { role?, check?, severity?, result?, since? }
  • Idempotent insert: same decision_hash returns existing row, does NOT throw (UNIQUE constraint caught and resolved)
  • Append-only (design invariant 5): no UPDATE or DELETE in repository; only INSERT and SELECT
  • Migration runner uses P0.2.2’s runMigrations() API
  • Migration is reversible-safe (no destructive ALTER on existing tables; just CREATE IF NOT EXISTS pattern from existing migrations)
  • All repository functions use better-sqlite3 prepared statements

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.5.1
  • docs/3-world/physics/enforcement/integrity.md §Phase 0 posture + §Phase 4 scope
  • src/db/migrations/*.sql (existing migration patterns)
  • src/db/index.ts (P0.2.2 — migration runner)
  • src/domains/integrity/schema.ts (P4.1.1 — envelope shape)
  • src/domains/trail/repository.ts (ζ pattern — closest precedent for append-only)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.5.1 — Advisory Persistence (`mcp_advisories` migration)
Create the SQLite table and repository for μ advisories. Append-only.
Dedup on decision_hash. Use better-sqlite3 prepared statements.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.5.1
3. docs/3-world/physics/enforcement/integrity.md §Phase 0 posture + §Phase 4 scope
4. src/db/migrations/*.sql (existing patterns)
5. src/db/index.ts (P0.2.2 — runMigrations)
6. src/domains/integrity/schema.ts (P4.1.1)
7. src/domains/trail/repository.ts (ζ append-only precedent)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-5-1-advisory-persistence -b feature/p4-5-1-advisory-persistence origin/main
cd .worktrees/claude/p4-5-1-advisory-persistence

FILES TO CREATE:
- src/db/migrations/NN-create-mcp-advisories.sql  (NN = next sequence number)
  CREATE TABLE IF NOT EXISTS mcp_advisories (
    role TEXT NOT NULL CHECK(role IN ('Translator','Sentinel','Guide')),
    "check" TEXT NOT NULL CHECK("check" IN ('circular_logic','coercion_trap','axiom_drift','axiom_regression')),
    result TEXT NOT NULL CHECK(result IN ('PASS','WARN','BLOCK')),
    severity TEXT NOT NULL CHECK(severity IN ('LOW','MED','HIGH')),
    evidence TEXT NOT NULL,
    recommendation TEXT NOT NULL,
    decision_hash TEXT NOT NULL UNIQUE,
    timestamp_logical INTEGER NOT NULL
  );
  CREATE INDEX IF NOT EXISTS idx_advisories_check_severity ON mcp_advisories("check", severity);
  CREATE INDEX IF NOT EXISTS idx_advisories_role ON mcp_advisories(role);

- src/domains/integrity/repository.ts
  * import Database from "better-sqlite3"
  * function insertAdvisory(db, advisory): { inserted: boolean; existing?: Advisory }
    - try INSERT OR IGNORE
    - if rowCount === 0: SELECT by decision_hash; return { inserted: false, existing }
    - else: return { inserted: true }
  * function getAdvisory(db, decision_hash): Advisory | null
    - SELECT * WHERE decision_hash = ? LIMIT 1
  * function listAdvisories(db, filter: { role?, check?, severity?, result?, since? }): Advisory[]
    - build SELECT with dynamic WHERE for non-null filter fields
    - ORDER BY timestamp_logical ASC
  * Use prepared statements (db.prepare(...).all() / .run() / .get())
  * timestamp_logical stored/read as bigint via better-sqlite3's bigint mode
  * NO update / delete methods (append-only per design invariant 5)

- src/domains/integrity/__tests__/repository.test.ts
  * Setup in-memory SQLite, run the migration
  * Schema introspection: verify table + 2 indexes + CHECK constraints + UNIQUE on decision_hash
  * Insert: insertAdvisory returns { inserted: true }
  * Idempotent insert: insertAdvisory same record again → { inserted: false, existing }; no throw
  * getAdvisory: returns advisory for known hash; null for unknown
  * listAdvisories: filter by role / check / severity / result / since; ORDER BY timestamp_logical
  * Append-only: assert repository module exports no `updateAdvisory` / `deleteAdvisory` / `clearAdvisories` symbol
  * Roundtrip: insert → get → structural equality with original

ACCEPTANCE CRITERIA (headline):
✓ 8-column schema matches P4.1.1 envelope (with CHECK constraints)
✓ UNIQUE on decision_hash; INSERT OR IGNORE for dedup
✓ Indexes (check,severity) and (role)
✓ Append-only repository (no update/delete exported)
✓ better-sqlite3 prepared statements throughout
✓ Idempotent insertAdvisory returns existing on collision

SUCCESS CHECK:
cd .worktrees/claude/p4-5-1-advisory-persistence && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.5.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-5-1-advisory-persistence
worktree: .worktrees/claude/p4-5-1-advisory-persistence
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: mcp_advisories table + repository — 8 envelope columns with CHECK constraints, UNIQUE on decision_hash for dedup, indexes (check,severity)+(role). Append-only: only insertAdvisory / getAdvisory / listAdvisories exposed. Idempotent insert via INSERT OR IGNORE.
blockers: none"
)

FORBIDDENS:
✗ No UPDATE / DELETE methods on the repository (append-only per AX-01)
✗ No raw SQL strings in repository — use prepared statements
✗ Do not add columns beyond the 8 envelope fields
✗ Do not edit main checkout

NEXT:
Wave 4 — P4.6.1 (MCP tools) wraps the repository for `integrity_query`

Verification checklist (for reviewer agent)

  • Migration creates the 8-column table with all CHECK constraints
  • UNIQUE on decision_hash present
  • Both indexes created
  • Repository exposes only insertAdvisory, getAdvisory, listAdvisories
  • Idempotent insert test passes
  • No update* / delete* exports
  • Prepared statements used throughout (no raw concatenation)
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.5.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-5-1-advisory-persistence
    worktree: .worktrees/claude/p4-5-1-advisory-persistence
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ advisory persistence — mcp_advisories table (8 columns, UNIQUE decision_hash, 2 indexes) + append-only repository (insert/get/list only). Idempotent insertAdvisory via INSERT OR IGNORE. better-sqlite3 prepared statements.
    blockers: none

Common gotchas

  • check is a SQL keyword — must be quoted ("check") in the CREATE TABLE statement and in every SELECT/INSERT. better-sqlite3 will silently mis-parse otherwise.
  • better-sqlite3 bigint modeINTEGER columns return JavaScript number by default, which loses precision above 2^53. Pass { safeIntegers: true } to the Database constructor or use .safeIntegers(true) so timestamp_logical round-trips as bigint.
  • Migration sequence number — look at the latest migration in src/db/migrations/ and use the next sequence number; do NOT pick an arbitrary number.
  • Append-only is a code-level contract — TypeScript can’t prevent someone from later adding updateAdvisory. Add a CI grep gate that fails if the repository module exports any of update* / delete* / clear* symbols.

§P4.6.1 — μ MCP Tool Surface (≥4 tools) — Phase 4 μ Wave 4

Spec source: task-breakdown.md §P4.6.1 Concept reference: integrity.md §Phase 4 scope (L172-178) — three detection jobs, MCP tool registration pattern in existing β/ε/ζ/η/λ/θ surfaces Worktree: feature/p4-6-1-mcp-tools Branch command: git worktree add .worktrees/claude/p4-6-1-mcp-tools -b feature/p4-6-1-mcp-tools origin/main Estimated effort: M (Medium — 4–8 hours) Depends on: P4.3.1 (roles), P4.4.1 (escalation), P4.5.1 (persistence) Unblocks: (closes μ Phase 4 — only the parity harness comes after)

Files to create

  • src/tools/integrity.ts — MCP tool registrations (Zod schemas + handlers)
  • src/tools/__tests__/integrity.test.ts — tool registration + handler behavior

Acceptance criteria

  • ≥4 MCP tools registered, growing the surface 23 → 27+:
    • integrity_check_circular: trigger D1 detector over current thought_records; returns advisory list
    • integrity_check_coercion: trigger D2 detector over a decision_record; returns advisory or PASS
    • integrity_check_drift: trigger D3 detector for a domain; returns advisory or PASS
    • integrity_query: list / fetch advisories from mcp_advisories via P4.5.1 repository
  • All tools use Zod v3.23 schemas (matches existing β/ε/ζ/η/λ/θ surfaces; NOT v4)
  • All tools registered via registerTool pattern from existing surfaces (e.g. mirror src/tools/consensus.ts from P3.7.1)
  • Each tool’s response shape matches the P4.1.1 envelope (where applicable) OR { advisories: Advisory[]; total: number } for query
  • Tools have docstrings explaining the check class + invocation context
  • Zero new MCP tools beyond the 4 (graduation discipline: more tools land in Phase 4.x follow-ups, not this slice)
  • Server registration: src/server.ts registration block updated to include integrityTools (mirroring how lambdaTools / consensusTools were added in P2.5.1 / P3.7.1)
  • No tool may BLOCK directly — tools return advisories; the escalation FSM (P4.4.1) is the only path to enforcement
  • No new npm deps (design invariant 10)

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.6.1
  • docs/3-world/physics/enforcement/integrity.md §Phase 4 scope (L172-178)
  • src/tools/consensus.ts (P3.7.1 — closest tool-registration precedent)
  • src/tools/reputation.ts (P2.5.1 — λ tool-registration precedent)
  • src/server.ts (registration call sites)
  • src/domains/integrity/{schema,roles,escalation,repository}.ts + detectors/*.ts (all of P4.1.1–P4.5.1)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.6.1 — μ MCP Tool Surface (≥4 tools)
Register ≥4 MCP tools for μ on top of the detector / role / escalation /
repository surfaces from P4.1.1–P4.5.1. Grow the MCP surface 23 → 27+.
Zod v3.23. No new npm deps.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.6.1
3. docs/3-world/physics/enforcement/integrity.md §Phase 4 scope
4. src/tools/consensus.ts (P3.7.1 precedent)
5. src/tools/reputation.ts (P2.5.1 precedent)
6. src/server.ts (registration call sites)
7. src/domains/integrity/* (all of P4.1.1–P4.5.1)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-6-1-mcp-tools -b feature/p4-6-1-mcp-tools origin/main
cd .worktrees/claude/p4-6-1-mcp-tools

FILES TO CREATE:
- src/tools/integrity.ts
  * import z from "zod" (v3.23, matching existing tools)
  * Tool 1: integrity_check_circular
    - inputSchema: z.object({ records: z.array(z.unknown()).optional() }) // defaults to recent thought_records
    - handler: calls detectCircularLogic (P4.2.1) with injected registry
    - returns: { advisories: Advisory[]; cycles_found: number }
  * Tool 2: integrity_check_coercion
    - inputSchema: z.object({ decision_record: z.object({...}) })
    - handler: calls detectCoercion (P4.2.2) with injected κ/λ adapters
    - returns: { advisories: Advisory[]; flag_reason: string | null }
  * Tool 3: integrity_check_drift
    - inputSchema: z.object({ domain: z.string(), now: z.bigint().optional() })
    - handler: calls checkAxiomDrift (P4.2.3) with parameterChangeEvents fetched from λ
    - returns: { advisories: Advisory[]; magnitude_bps: bigint }
  * Tool 4: integrity_query
    - inputSchema: z.object({ role?: ..., check?: ..., severity?: ..., result?: ..., since?: z.bigint().optional(), limit?: z.number().int().positive().optional() })
    - handler: listAdvisories (P4.5.1)
    - returns: { advisories: Advisory[]; total: number }
  * export const integrityTools = [tool1, tool2, tool3, tool4] (with `registerTool` shape)
  * Each tool docstring cites integrity.md section for the check it triggers

- Update src/server.ts:
  * import { integrityTools } from "./tools/integrity"
  * In the tools-registration block (where lambdaTools / consensusTools registered): add `integrityTools`
  * Verify the surface count comment updates: 23 → 27 (4 new tools)

- src/tools/__tests__/integrity.test.ts
  * Tool registration: getRegisteredTools() includes the 4 integrity_* names
  * integrity_check_circular: empty records → no advisories
  * integrity_check_circular: known cycle fixture → 1 advisory
  * integrity_check_coercion: all-negative options → 1 advisory with check="coercion_trap"
  * integrity_check_drift: under-threshold magnitude → 0 advisories; over-1000-bps → 1 advisory
  * integrity_query: insert 3 advisories with different severities; query severity=HIGH returns 1
  * Tools never throw on bad input — Zod validation returns structured error
  * Tools never BLOCK — they return advisories; check enforcement happens via P4.4.1 escalation FSM
  * Surface count assertion: total registered tools = 27 (was 23 before P4.6.1)

ACCEPTANCE CRITERIA (headline):
✓ 4 MCP tools registered (integrity_check_{circular,coercion,drift} + integrity_query)
✓ Zod v3.23 (not v4) — matches existing surface
✓ registerTool pattern from P3.7.1 / P2.5.1
✓ Tools return advisories; never throw on bad input
✓ Server registration updated; MCP surface 23 → 27
✓ No new npm deps

SUCCESS CHECK:
cd .worktrees/claude/p4-6-1-mcp-tools && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.6.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-6-1-mcp-tools
worktree: .worktrees/claude/p4-6-1-mcp-tools
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: μ MCP surface — 4 tools (integrity_check_circular / coercion / drift + integrity_query). Zod v3.23 + registerTool pattern from P3.7.1. MCP surface 23 → 27. Tools return advisories; never block. Server registration updated.
blockers: none"
)

FORBIDDENS:
✗ No more than 4 tools in this slice (Phase 4.x follow-ups for additions)
✗ No new npm deps (design invariant 10)
✗ No Zod v4 — match existing v3.23
✗ Tools never call denyTool or block — they return advisories
✗ Do not edit main checkout

NEXT:
P4.7.1 — Parity harness validates the full detector chain end-to-end

Verification checklist (for reviewer agent)

  • 4 tools registered with exact names
  • Zod v3.23 imports
  • registerTool pattern matches P3.7.1
  • server.ts updated with integrityTools import + registration
  • Surface count comment updated to 27
  • All tool handlers return shapes matching the documented return type
  • No tool throws on bad input (Zod validation path)
  • No new npm deps in package.json
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.6.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-6-1-mcp-tools
    worktree: .worktrees/claude/p4-6-1-mcp-tools
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ MCP tool surface — integrity_check_circular / _coercion / _drift + integrity_query. 4 tools added (23 → 27). Zod v3.23. Server registration block updated. Tools return P4.1.1-shaped advisories; never throw or block.
    blockers: none

Common gotchas

  • Zod version pinning — the project uses Zod v3.23 as documented in CLAUDE.md §1. Some Zod v4-style APIs (z.literal with type narrowing changes) silently mis-behave on v3.23. Mirror imports from src/tools/consensus.ts.
  • Tool docstrings affect MCP client UX — the docstring is what shows up in Claude Desktop / other clients. Keep it short, concrete, and cite the integrity.md section. Examples in src/tools/reputation.ts.
  • Surface-count assertion test — there is an existing pattern in the test suite that counts registered tools and asserts the total. After P4.6.1 lands, this assertion needs to update from 23 to 27. If you forget, the build will fail loudly at test time.
  • Tools NEVER block — even integrity_check_circular returning a HARD-severity advisory does NOT deny the caller’s tool invocation. Enforcement happens at the next admission cycle when α tool-lock reads the persisted advisory (P4.5.1 + P4.4.1).

§P4.7.1 — Test Corpus + Parity Harness — Phase 4 μ Wave 4

Spec source: task-breakdown.md §P4.7.1 Concept reference: Precedent: κ P1.5.5 parity harness (src/__tests__/domains/rules/parity-harness.test.ts, R87 #214); θ P3.8.1 4-scenario harness (R89 Phase B #246) Worktree: feature/p4-7-1-parity-harness Branch command: git worktree add .worktrees/claude/p4-7-1-parity-harness -b feature/p4-7-1-parity-harness origin/main Estimated effort: L (Large — 1–2 days) Depends on: P4.2.1, P4.2.2, P4.2.3 (detectors), P4.4.1 (escalation), P4.5.1 (persistence) Unblocks: (μ Phase 4 seal — the close-PR runs this corpus as the proof gate)

Files to create

  • src/__tests__/integrity/parity.test.ts — full parity harness
  • src/__tests__/integrity/corpus/circular.json — D1 cycle-detection corpus
  • src/__tests__/integrity/corpus/coercion.json — D2 coercion corpus
  • src/__tests__/integrity/corpus/drift.json — D3 drift corpus (with synthetic 12-month history)

Acceptance criteria

  • D1 cycle-detection corpus: ≥10 fixtures spanning:
    • True cycles (triangle, length-N)
    • Diamonds (DAG, no cycle)
    • Self-loops
    • Length-2 cycles
    • Two disjoint cycles
    • Cross-rule cycles (via κ P1.2.4 registry edges)
  • D2 coercion corpus: ≥10 fixtures spanning:
    • All-negative reputation outcomes
    • Empty action space
    • Mixed outcomes (no flag)
    • Reputation-loss-only options
    • Obligation-overflow-only options
    • Filtering legitimate-vs-coercive
  • D3 drift corpus: ≥10 fixtures spanning:
    • Under-threshold (no advisory)
    • Exact 8% (WARN)
    • Mid-range WARN
    • Exact 10% (BLOCK)
    • Over 10% (BLOCK)
    • AX-01..AX-07 regression cases (one per axiom)
    • 12-month synthetic history (drift trajectory across multiple windows)
  • FP rate measurement: D1 < 1%, D2 < 5%, D3 < 10% on corpus (per integrity.md §1/§2/§3 declared profiles)
  • Parity harness: runs each detector against its corpus; asserts advisory output matches expected envelope byte-for-byte (via P4.1.1 canonical serialization)
  • Determinism: running the harness twice with the same seed produces byte-identical advisory output
  • End-to-end chain: parity harness exercises one full chain per detector: detector → advisory → escalation FSM → repository insert → repository query (no MCP layer; that’s covered by P4.6.1 tests)
  • Performance budget: full corpus (≥30 fixtures across 3 detectors) runs in < 5s
  • No Date.now() / Math.random() — all corpus is static JSON; all timestamps Lamport bigint

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.7.1
  • src/__tests__/domains/rules/parity-harness.test.ts (κ P1.5.5 precedent — closest pattern)
  • src/domains/consensus/parity-harness.ts (θ P3.8.1 precedent — multi-scenario shape)
  • src/domains/integrity/detectors/*.ts + escalation.ts + repository.ts (the chain under test)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.7.1 — Test Corpus + Parity Harness
Build the canonical test corpus + parity harness that validates every
detector against expected advisory output byte-for-byte, plus measures the
false-positive rate against declared profiles. End-to-end chain coverage.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.7.1
3. src/__tests__/domains/rules/parity-harness.test.ts (κ P1.5.5)
4. src/domains/consensus/parity-harness.ts (θ P3.8.1 — multi-scenario)
5. src/domains/integrity/detectors/*.ts (P4.2.1/2/3)
6. src/domains/integrity/escalation.ts (P4.4.1)
7. src/domains/integrity/repository.ts (P4.5.1)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-7-1-parity-harness -b feature/p4-7-1-parity-harness origin/main
cd .worktrees/claude/p4-7-1-parity-harness

FILES TO CREATE:
- src/__tests__/integrity/corpus/circular.json
  * Array of {fixture_id, records[], registry_edges[], expected_advisories[]}
  * ≥10 fixtures covering: triangle, diamond, self-loop, length-N, two-disjoint, cross-rule
  * Use deterministic record IDs (record_001..record_NNN)

- src/__tests__/integrity/corpus/coercion.json
  * Array of {fixture_id, decision_record, admission_actions[], engine_outcomes{}, expected_advisories[]}
  * ≥10 fixtures covering: all-negative, empty-set, mixed, rep-loss, obligation-overflow, legitimate-filter

- src/__tests__/integrity/corpus/drift.json
  * Array of {fixture_id, domain, now_logical, parameter_changes[], staged_proposals[], expected_advisories[]}
  * ≥10 fixtures including a 12-month synthetic history walking through all thresholds
  * AX-01..AX-07 regression: one fixture per axiom

- src/__tests__/integrity/parity.test.ts
  * describe("μ Parity Harness", () => {
      describe("D1 circular logic corpus", () => {
        const corpus = loadJson("./corpus/circular.json");
        it.each(corpus)("$fixture_id: cycle detection matches expected", (fixture) => {
          const actual = detectCircularLogic(fixture.records, fixture.registry_edges);
          expect(canonicalSerialize(actual)).toEqual(canonicalSerialize(fixture.expected_advisories));
        });
      });
      describe("D2 coercion corpus", () => { ... });
      describe("D3 drift corpus", () => { ... });
      describe("End-to-end chain", () => {
        it("detector → escalation → repository → query roundtrip", async () => {
          // 1. Run D2 on a known all-negative fixture
          // 2. Pass advisory through escalation FSM (mock deps)
          // 3. Insert into in-memory mcp_advisories
          // 4. Query and assert structural equality
        });
      });
      describe("FP rate", () => {
        it("D1 false-positive rate < 1%", () => { ... });
        it("D2 false-positive rate < 5%", () => { ... });
        it("D3 false-positive rate < 10%", () => { ... });
      });
      describe("Determinism", () => {
        it("running corpus twice produces byte-identical output", () => { ... });
      });
      describe("Performance", () => {
        it("full corpus runs in < 5000ms", () => { ... });
      });
    });

ACCEPTANCE CRITERIA (headline):
✓ ≥10 fixtures per detector (≥30 total)
✓ FP rates within declared profiles (<1% / <5% / <10%)
✓ Byte-for-byte advisory equality via canonical serialization
✓ End-to-end chain test (detector → escalation → repo)
✓ Determinism + performance gates pass

SUCCESS CHECK:
cd .worktrees/claude/p4-7-1-parity-harness && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.7.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-7-1-parity-harness
worktree: .worktrees/claude/p4-7-1-parity-harness
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: μ parity harness — 3-corpus (≥30 fixtures across D1/D2/D3) byte-for-byte advisory equality + FP rates D1<1% / D2<5% / D3<10%. End-to-end chain test (detector → escalation → repository → query). Determinism + <5s performance gates.
blockers: none"
)

FORBIDDENS:
✗ No Date.now() / Math.random() — fixtures are static; clocks injected
✗ Do not run corpus in parallel (determinism)
✗ Do not load corpus from outside src/__tests__/integrity/corpus/
✗ Do not edit main checkout

NEXT:
μ Phase 4 seal PR (close round) — graduates μ from colibri_code:none → partial

Verification checklist (for reviewer agent)

  • 3 corpus files present (circular / coercion / drift)
  • ≥10 fixtures per corpus (≥30 total)
  • AX-01..AX-07 regression fixtures present in drift.json
  • FP rate assertions pass within profiles
  • End-to-end chain test present
  • Determinism test passes (2 runs byte-identical)
  • Performance test passes (<5s)
  • No wall-clock / randomness in harness source
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.7.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-7-1-parity-harness
    worktree: .worktrees/claude/p4-7-1-parity-harness
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ parity harness — 3 corpora × ≥10 fixtures each. Byte-for-byte advisory equality via canonical serialization. FP rates (D1<1% / D2<5% / D3<10%) within declared profiles. End-to-end chain (detector → escalation → repository → query) tested. Determinism + 5s performance gate.
    blockers: none

Common gotchas

  • Byte-for-byte equality uses canonical serialization, not deep equal — JavaScript’s expect.toEqual is forgiving on object key order; the P4.1.1 canonical serializer is strict. Use canonicalSerialize(actual) vs canonicalSerialize(expected) for the assertion.
  • FP rate measurement — for “no advisory expected” fixtures, run the detector and assert the returned array length is 0. The FP rate is (fixtures_with_advisory ÷ fixtures_expecting_none) × 100. Keep the math integer; bigint where applicable.
  • 12-month synthetic history for D3 — generate the corpus deterministically from a seed (NOT Math.random). The drift fixture should walk through the magnitude curve: 0 → 400 → 799 (no advisory) → 800 (WARN) → 999 (still WARN) → 1000 (BLOCK) → 1500 (BLOCK still).
  • End-to-end chain isolation — the chain test should use an in-memory SQLite for P4.5.1 to avoid polluting the project’s persistent state. Pattern: new Database(":memory:"); runMigrations(db);.

§P4.8.1 — Fork Hook Subscriber (post-fork invariant sweep) — Phase 4 μ Wave 4

Spec source: task-breakdown.md §P4.8.1 Concept reference: integrity.md §Phase 4 scope (L177) — fork-hook awareness + consensus.md §Interaction with ι (fork) — θ fork-hook surface (P3.9.1 ForkHookRegistry from R89 Phase B #245) Worktree: feature/p4-8-1-fork-hook-subscriber Branch command: git worktree add .worktrees/claude/p4-8-1-fork-hook-subscriber -b feature/p4-8-1-fork-hook-subscriber origin/main Estimated effort: S (Small — 2–4 hours) Depends on: P4.2.3 (drift detector), P4.5.1 (persistence), θ P3.9.1 (ForkHookRegistry — shipped) Unblocks: ι Phase 5 readiness (downstream activation; ι implementation is OUT OF SCOPE for Phase 4)

Files to create

  • src/domains/integrity/fork-hook-subscriber.ts — subscribes μ to θ ForkHookRegistry; runs D3 sweep on POST_FORK
  • src/domains/integrity/__tests__/fork-hook-subscriber.test.ts — register, fire, idempotent dedup, bounded sweep

Acceptance criteria

  • Subscribes to θ ForkHookRegistry (from P3.9.1, file src/domains/consensus/fork-hook.ts) for POST_FORK events
  • class IntegrityForkSubscriber { register(registry: ForkHookRegistry): void; handler(event: ForkTriggerEvent): Promise<void> }
  • On POST_FORK event: triggers a D3 axiom-drift sweep across all domains in the forked sub-tree
  • Records sweep results to mcp_advisories via P4.5.1 insertAdvisory (idempotent via decision_hash dedup)
  • Idempotency: same fork event triggers at most one sweep — use event_id (composed from round_id || divergent_roots) as dedup key
  • Bounded sweep: caps detectors at a configurable budget (default 100 advisories per fork event); stops at cap and emits a separate result=WARN advisory indicating truncation
  • Stages the surface for ι Phase 5 (state-fork) activation; ι integration is OUT OF SCOPE for Phase 4 — this slice only ensures the subscriber + sweep call exists
  • Handler errors do not cascade — wrap detector calls in try/catch; one detector error must not stop the rest
  • No Date.now() / Math.random() in source

Pre-flight reading

  • CLAUDE.md
  • docs/guides/implementation/task-breakdown.md §P4.8.1
  • docs/3-world/physics/enforcement/integrity.md §Phase 4 scope (L172-178; specifically L177 fork awareness)
  • docs/3-world/physics/laws/consensus.md §Interaction with ι (fork)
  • src/domains/consensus/fork-hook.ts (θ P3.9.1 — ForkHookRegistry, ForkTriggerEvent shape)
  • src/domains/integrity/detectors/drift.ts (P4.2.3 — sweep entry point)
  • src/domains/integrity/repository.ts (P4.5.1)

Ready-to-paste agent prompt

You are a Phase 4 builder agent for Colibri (μ Integrity Monitor).

TASK: P4.8.1 — Fork Hook Subscriber (post-fork invariant sweep)
Subscribe μ to θ's ForkHookRegistry (P3.9.1). On POST_FORK events, run a
D3 axiom-drift sweep across all forked-tree domains. Persist advisories
via P4.5.1. Idempotent per fork event. Bounded by a configurable cap.
ι implementation NOT included — this is subscriber surface only.

FILES TO READ FIRST:
1. CLAUDE.md
2. docs/guides/implementation/task-breakdown.md §P4.8.1
3. docs/3-world/physics/enforcement/integrity.md §Phase 4 scope (L172-178)
4. docs/3-world/physics/laws/consensus.md §Interaction with ι (fork)
5. src/domains/consensus/fork-hook.ts (θ P3.9.1)
6. src/domains/integrity/detectors/drift.ts (P4.2.3)
7. src/domains/integrity/repository.ts (P4.5.1)

WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p4-8-1-fork-hook-subscriber -b feature/p4-8-1-fork-hook-subscriber origin/main
cd .worktrees/claude/p4-8-1-fork-hook-subscriber

FILES TO CREATE:
- src/domains/integrity/fork-hook-subscriber.ts
  * import { ForkHookRegistry, ForkTriggerEvent } from "../consensus/fork-hook"
  * type ForkSubscriberConfig = { sweepBudget?: number /* default 100 */ }
  * class IntegrityForkSubscriber {
      private seen = new Set<string>();  // dedup: fork event_id
      private sweepBudget: number;
      constructor(private db: Database, private fetchDomains: () => string[], cfg: ForkSubscriberConfig = {}) {
        this.sweepBudget = cfg.sweepBudget ?? 100;
      }
      register(registry: ForkHookRegistry): void {
        registry.register((event) => this.handler(event));
      }
      async handler(event: ForkTriggerEvent): Promise<void> {
        const eventId = this.computeEventId(event);
        if (this.seen.has(eventId)) return;  // idempotent
        this.seen.add(eventId);

        const domains = this.fetchDomains();
        let advisoryCount = 0;
        for (const domain of domains) {
          if (advisoryCount >= this.sweepBudget) {
            // Emit truncation advisory
            const truncAdvisory = makeAdvisory("axiom_drift", "MED", "WARN", evidence=[domain, eventId, "sweep_truncated"]);
            insertAdvisory(this.db, truncAdvisory);
            return;
          }
          try {
            const advisories = checkAxiomDrift(domain, event.timestamp_logical, fetchChanges(domain), []);
            for (const a of advisories) {
              insertAdvisory(this.db, a);  // idempotent via decision_hash
              advisoryCount++;
              if (advisoryCount >= this.sweepBudget) break;
            }
          } catch (e) {
            // One detector failure does NOT stop the sweep
            // Optionally emit a meta-advisory; do NOT rethrow
          }
        }
      }
      private computeEventId(event: ForkTriggerEvent): string {
        // Deterministic: SHA-256(round_id || JSON.stringify(divergent_roots))
      }
    }

- src/domains/integrity/__tests__/fork-hook-subscriber.test.ts
  * Register subscriber against a fresh ForkHookRegistry → handler reachable
  * Fire fork event → handler called; sweep runs over fetchDomains
  * Idempotent: fire same fork event twice → handler runs once (seen.has)
  * Bounded sweep: 200 domains × 1 advisory each, budget=50 → 50 advisories + 1 truncation advisory
  * Detector error in one domain: sweep continues for other domains
  * Inserts go through P4.5.1 repository (dedup via decision_hash)
  * No state mutation outside `this.seen` and the injected `db`
  * No Date.now() / Math.random() in source

ACCEPTANCE CRITERIA (headline):
✓ Subscribes to ForkHookRegistry (P3.9.1)
✓ POST_FORK → D3 sweep over all domains
✓ Idempotent per fork event_id
✓ Bounded by sweepBudget (default 100)
✓ Truncation emits a meta-advisory
✓ Per-domain error isolation
✓ ι implementation NOT included (subscriber only)

SUCCESS CHECK:
cd .worktrees/claude/p4-8-1-fork-hook-subscriber && npm run build && npm run lint && npm test

WRITEBACK:
task_update(id="<PM-supplied UUID for P4.8.1>", status="done", progress=100)
thought_record(thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-8-1-fork-hook-subscriber
worktree: .worktrees/claude/p4-8-1-fork-hook-subscriber
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: μ fork-hook subscriber — registers handler on θ ForkHookRegistry (P3.9.1); POST_FORK → D3 axiom-drift sweep over all domains in forked sub-tree; persists via P4.5.1; idempotent per event_id; bounded by sweepBudget (default 100, truncation advisory emitted at cap); per-domain error isolation. ι implementation OUT OF SCOPE.
blockers: none"
)

FORBIDDENS:
✗ Do not implement ι fork-creation logic — this is subscriber surface ONLY
✗ Do not skip the idempotency check — same event_id MUST run once
✗ Do not skip the budget cap — unbounded sweeps would DoS the advisory store
✗ No Date.now() / Math.random()
✗ Do not edit main checkout

NEXT:
μ Phase 4 close PR — graduates μ from colibri_code:none → partial; CLAUDE.md
§10 updates 11/15 → 12/15

Verification checklist (for reviewer agent)

  • IntegrityForkSubscriber class exported
  • Subscribes to ForkHookRegistry.register()
  • handler triggers D3 sweep across fetchDomains() output
  • Idempotent: second fire of same event_id is a no-op
  • Bounded sweep: budget cap respected; truncation advisory emitted
  • Per-domain error isolation: one throw does not kill the sweep
  • Inserts use P4.5.1 insertAdvisory (no raw SQL)
  • No Date.now() / Math.random() in source
  • No ι fork-creation logic
  • npm run build && npm run lint && npm test pass

Writeback template

task_update:
  id: "<PM-supplied UUID for P4.8.1>"
  status: done
  progress: 100

thought_record:
  thought_type: reflection
  content: |
    task_id: <UUID>
    branch: feature/p4-8-1-fork-hook-subscriber
    worktree: .worktrees/claude/p4-8-1-fork-hook-subscriber
    commit: <SHA>
    tests: npm run build && npm run lint && npm test (<N>/<T> pass)
    summary: μ subscriber to θ ForkHookRegistry (P3.9.1). POST_FORK → D3 axiom-drift sweep over all forked-tree domains; persists via P4.5.1 with decision_hash dedup; idempotent per event_id; bounded by sweepBudget=100 (truncation advisory at cap); per-domain error isolation. ι integration OUT OF SCOPE.
    blockers: none

Common gotchas

  • ForkHookRegistry.register() accepts an arbitrary handler — pass an arrow function bound to this.handler so the subscriber’s this resolves correctly. Anonymous function literals lose this.
  • Idempotency via seen Set in memory — works for single-process Phase 4. If a future round multi-instances the subscriber, this dedup becomes per-instance; the durable dedup is still the decision_hash UNIQUE on mcp_advisories. Document this in a code comment.
  • Sweep budget protects the advisory store — without it, a fork event with 10000 domains × 10 advisories each = 100k inserts could DoS the SQLite. The default of 100 is conservative; tune in Phase 5+ when ι is real.
  • ForkTriggerEvent.divergent_roots is Buffer[] — when computing the event_id, serialize each Buffer to hex; do NOT pass Buffer directly to JSON.stringify (it produces {0: byte, 1: byte, ...} not the hex string).

Writeback (when each slice is dispatched and shipped)

Per CLAUDE.md §7, every P4.X.Y slice MUST produce:

task_update(id="<PM-supplied UUID>", status="done", progress=100)

thought_record(
  thought_type="reflection",
  content="task_id: <UUID>
branch: feature/p4-X-Y-<slug>
worktree: .worktrees/claude/p4-X-Y-<slug>
commit: <SHA>
tests: npm run build && npm run lint && npm test (<N>/<T> pass)
summary: <slice headline>
blockers: <none or details>"
)

For proof-grade slices (any that touch the legitimacy axis or are marked proof_grade: true), the slice MUST also call audit_session_start + audit_verify_chain + merkle_finalize + merkle_root in the order specified in CLAUDE.md §7 (final thought_record BEFORE merkle_finalize).

Forbiddens (re-asserted in every slice)

  1. No edits to main checkout (CLAUDE.md §3).
  2. No --force-push on any feature branch (CLAUDE.md §3).
  3. No --no-verify, no --amend on commits (CLAUDE.md §13).
  4. No κ/λ/θ frontmatter mutation — those concept docs were graduated in R75 H.2 / R89 Phase A / R89 Phase B and are locked.
  5. No advancement of μ frontmatter to colibri_code: partial in any single slice — graduation lands with the Phase 4 close PR, not individual sub-task PRs (ADR-006).
  6. No ADR-002 / ADR-003 / ADR-005 / ADR-006 mutation in any slice.
  7. No task-breakdown.md mutation — the canonical roadmap was ratified in R91A (PR #249) and the staging file matches 1:1. Any further roadmap edits land in a separate hygiene round.
  8. No new npm dependencies (design invariant 10).
  9. No floating-point in any detector or envelope field — bigint / BPS / SHA-256 hex strings.
  10. No dispatch of any P4.X.Y slice while this file’s frontmatter remains status: staged — explicit T0 mandate required to graduate to status: ready.

Back to index

See also


Back to top

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

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