P4.1 — μ Integrity Monitor — Phase 4 Dispatch
Status:
ready(R94A graduation, 2026-05-14). T0 autonomous-finish mandate flipped this file fromstaged→ready, 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:
task-breakdown.md §Phase 4— ratified 10-slice roadmap (R91A PR #249)docs/spec/s14-integrity-monitor.md— authoritative μ specdocs/3-world/physics/enforcement/integrity.md— concept doc with DFS + option-set + sliding-window pseudocodedocs/spec/s10-admission.md— α tool-lock HARD BLOCK target surfacedocs/3-world/physics/enforcement/governance.md— π proposal intake (BLOCK consumer, Phase 6)docs/3-world/physics/constitution.md— AX-01..AX-07 invariants μ guards againstdocs/5-time/roadmap.md§Phase 4 — R151+ schedulingdocs/agents/executor-contract.md— T3 5-step chainCLAUDE.md— §3 worktree, §5 gate, §6 5-step chain, §7 writeback
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)
- No
src/mutation in R91 itself — this is the staging meta PR; firstsrc/mutation lands when P4.1.1 dispatches in some R94+ post-T0 confirmation. - Pure functions only — detectors are observational; no I/O, no clock, no RNG. Same arithmetic discipline as κ (bigint) + λ (BPS) + θ (canonical).
- Lamport
timestamp_logical— neverDate.now()in detector or advisory output. Inherits θ design invariant 2. - Canonical serialization for hash — reuse κ P1.5.4 canonical for any
decision_hashinput; matches θ’s pattern (reuse, not duplicate). - Append-only persistence —
mcp_advisoriesschema is INSERT-only per AX-01; dedup bydecision_hash(NOT by UPDATE). - Read-only roles — Translator/Sentinel/Guide may NEVER mutate state;
only emit advisories. Enforced via TypeScript
readonlymodifiers + nodb.runin the role adapter. - 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.
- HARD BLOCK is owned by α — μ flags + records an HIGH-severity
advisory; the
tool-lockmiddleware reads the advisory and denies.tool-lockitself is insrc/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. - 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.
- 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 functionssrc/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/rulesrecommendation:z.string()— free-form human-readabledecision_hash:z.string().regex(/^[a-f0-9]{64}$/)— SHA-256 hextimestamp_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): stringhelper — REUSEScanonicalSerializefrom κ P1.5.4 (do not duplicate)serializeAdvisory(advisory): Buffer— delegates to κ canonical for deterministic bytes- Dedup invariant: identical
(role, check, canonical(input), result)produces identicaldecision_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.1docs/3-world/physics/enforcement/integrity.md§Advisory record schema (L129-146)docs/spec/s14-integrity-monitor.md§Outputsrc/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 testpass
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 hasSHA-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; nomodel_identityfield 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 legacyINFO | WARNING | CRITICAL. R91A ratifiedLOW | MED | HIGH(matches λ). timestamp_logicalis uint64 Lamport — TypeScript has no native uint64; usebigintwith non-negative assertion in the schema. Initialize from a module-level counter (NOTDate.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 graphsrc/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_hashORrefs[]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
evidencecontains 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.mddocs/guides/implementation/task-breakdown.md§P4.2.1docs/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 testpass
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 coloring —
visited[node] = IN_PROGRESSBEFORE recursing into successors;visited[node] = DONEafterpath.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 κ registry —
src/domains/rules/registry.tsexposes 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,AvsB,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 outcomessrc/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_capacityflag - Flag conditions (any of):
- Every available option produces
reputation_delta < 0 - Every available option produces
obligation_beyond_capacity === true - Action space is empty
- Every available option produces
- 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) vsavailable(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.mddocs/guides/implementation/task-breakdown.md§P4.2.2docs/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 testpass
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_deltais bigint — λ P2.1.2 uses BPS (basis-points) bigint math. Compare with0n, NOT0. JavaScript’s<mixes bigint and number unsafely; force bigint.presentedvsavailable—presentedis what the decision record contains;availableis 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
CoercionDepsas 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 checksrc/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 (nowis an injected parameter) - Pure function — no I/O beyond the injected
parameterChangeEventsadapter
Pre-flight reading
CLAUDE.mddocs/guides/implementation/task-breakdown.md§P4.2.3docs/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)
nowis injected (no Date.now in source)- Determinism over identical inputs
npm run build && npm run lint && npm testpass
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-clock —
nowis abigintLamport timestamp; subtractWINDOW_MSin 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
checkvalues in the envelope.axiom_driftis aggregate-cumulative (sliding window);axiom_regressionis 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.absdoes not accept bigint. Use(x < 0n) ? -x : xor import from κ integer-math. - BLOCK vs HARD BLOCK — integrity.md L106 calls the 10% cap a
BLOCK_NEW_PROPOSALSaction; the AX regression at L112 calls itHARD_BLOCK. In our envelope, both areresult: "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 thecheckfield —axiom_regressionroutes to α tool-lock (HARD BLOCK),axiom_driftBLOCK 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
readonlymodifiers + class fields allprivate 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.mddocs/guides/implementation/task-breakdown.md§P4.3.1docs/3-world/physics/enforcement/integrity.md§Three advisory roles (L117-127)docs/spec/s14-integrity-monitor.md§Advisory rolessrc/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
readonlyfor 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 testpass
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
readonlyis a contract, not a runtime guarantee — TypeScript’sreadonlyis compile-time only. Add a runtime grep gate in CI: search fordb.run|db.exec|UPDATE|INSERT|DELETEpatterns inroles.tsand 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
SentinelFlagobject; 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 rulessrc/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.tsfrom P1.4.4, shipped #220)
- PASS → log to ζ at
- 3 invariant mappings per integrity.md §When advisory becomes enforcement (s14 L38-43):
- Mapping 1:
circular_logicin rule update → rule rejected at κ rule loader (P1.2.4) - Mapping 2:
coercion_trapin admission gate → event rejected at κ admission (P1.4.1) - Mapping 3:
axiom_driftbeyond limits → governance proposal rejected at π intake (records BLOCK event into ζ; π consumes when π ships)
- Mapping 1:
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.mddocs/guides/implementation/task-breakdown.md§P4.4.1docs/3-world/physics/enforcement/integrity.md§Escalation mapping (L148-157)docs/spec/s14-integrity-monitor.md§When advisory becomes enforcementsrc/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 testpass
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-adapterfrom P1.4.4 readsmcp_advisories(P4.5.1) and consults the escalation event during admission. μ never callsdenyTool()directly. - π is not shipped in Phase 4 —
emitPirecords the BLOCK into ζ atthought_type=advisory; when π ships in Phase 6, its proposal-intake gate reads this stream. R91 audit Q5 documents this explicitly. axiom_regressionis its own check value — it is distinct fromaxiom_drift. Per integrity.md L112, axiom regression is ALWAYS HARD BLOCK regardless of context. The escalation FSM should branch oncheckfirst, then context.- event_id determinism — derive
event_idfromadvisory.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 + indexessrc/domains/integrity/repository.ts—insertAdvisory,getAdvisory,listAdvisoriessrc/domains/integrity/__tests__/repository.test.ts— schema validation, idempotent insert, dedup, filter queries
Acceptance criteria
- SQLite migration creates
mcp_advisoriestable 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;checkis 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 NULLdecision_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 }— idempotentgetAdvisory(decision_hash): Advisory | nulllistAdvisories(filter): Advisory[]— filter by{ role?, check?, severity?, result?, since? }
- Idempotent insert: same
decision_hashreturns 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-sqlite3prepared statements
Pre-flight reading
CLAUDE.mddocs/guides/implementation/task-breakdown.md§P4.5.1docs/3-world/physics/enforcement/integrity.md§Phase 0 posture + §Phase 4 scopesrc/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 testpass
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
checkis 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 mode —
INTEGERcolumns return JavaScriptnumberby default, which loses precision above 2^53. Pass{ safeIntegers: true }to the Database constructor or use.safeIntegers(true)sotimestamp_logicalround-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 ofupdate*/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 currentthought_records; returns advisory listintegrity_check_coercion: trigger D2 detector over adecision_record; returns advisory or PASSintegrity_check_drift: trigger D3 detector for a domain; returns advisory or PASSintegrity_query: list / fetch advisories frommcp_advisoriesvia P4.5.1 repository
- All tools use Zod v3.23 schemas (matches existing β/ε/ζ/η/λ/θ surfaces; NOT v4)
- All tools registered via
registerToolpattern from existing surfaces (e.g. mirrorsrc/tools/consensus.tsfrom 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.tsregistration block updated to includeintegrityTools(mirroring howlambdaTools/consensusToolswere 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.mddocs/guides/implementation/task-breakdown.md§P4.6.1docs/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
registerToolpattern matches P3.7.1- server.ts updated with
integrityToolsimport + 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 testpass
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.literalwith type narrowing changes) silently mis-behave on v3.23. Mirror imports fromsrc/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_circularreturning 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 harnesssrc/__tests__/integrity/corpus/circular.json— D1 cycle-detection corpussrc/__tests__/integrity/corpus/coercion.json— D2 coercion corpussrc/__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.mddocs/guides/implementation/task-breakdown.md§P4.7.1src/__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 testpass
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.toEqualis forgiving on object key order; the P4.1.1 canonical serializer is strict. UsecanonicalSerialize(actual)vscanonicalSerialize(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_FORKsrc/domains/integrity/__tests__/fork-hook-subscriber.test.ts— register, fire, idempotent dedup, bounded sweep
Acceptance criteria
- Subscribes to θ
ForkHookRegistry(from P3.9.1, filesrc/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_advisoriesvia P4.5.1insertAdvisory(idempotent via decision_hash dedup) - Idempotency: same fork event triggers at most one sweep — use
event_id(composed fromround_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=WARNadvisory 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.mddocs/guides/implementation/task-breakdown.md§P4.8.1docs/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)
IntegrityForkSubscriberclass 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 testpass
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 tothis.handlerso the subscriber’sthisresolves correctly. Anonymous function literals losethis.- Idempotency via
seenSet 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 thedecision_hash UNIQUEonmcp_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_rootsisBuffer[]— when computing the event_id, serialize each Buffer to hex; do NOT pass Buffer directly toJSON.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)
- No edits to
maincheckout (CLAUDE.md §3). - No
--force-pushon any feature branch (CLAUDE.md §3). - No
--no-verify, no--amendon commits (CLAUDE.md §13). - No κ/λ/θ frontmatter mutation — those concept docs were graduated in R75 H.2 / R89 Phase A / R89 Phase B and are locked.
- No advancement of μ frontmatter to
colibri_code: partialin any single slice — graduation lands with the Phase 4 close PR, not individual sub-task PRs (ADR-006). - No ADR-002 / ADR-003 / ADR-005 / ADR-006 mutation in any slice.
- No
task-breakdown.mdmutation — 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. - No new npm dependencies (design invariant 10).
- No floating-point in any detector or envelope field — bigint / BPS / SHA-256 hex strings.
- No dispatch of any P4.X.Y slice while this file’s frontmatter remains
status: staged— explicit T0 mandate required to graduate tostatus: ready.
Back to index
See also
- agent-bootstrap.md — Master bootstrap prompt (read FIRST)
- agent-handoff-protocol.md — Multi-agent handoff spec
- task-breakdown.md — Canonical 71-task breakdown (all phases, post-R91A)
- docs/spec/s14-integrity-monitor.md — Authoritative μ spec
- docs/3-world/physics/enforcement/integrity.md — μ concept doc with detector pseudocode
- docs/spec/s10-admission.md — α tool-lock HARD BLOCK surface
- docs/3-world/physics/enforcement/governance.md — π proposal intake (BLOCK consumer)
- docs/3-world/physics/constitution.md — AX-01..AX-07 invariants
- docs/architecture/decisions/ADR-005-multi-model-defer.md — Phase 0 stub precedent
- docs/architecture/decisions/ADR-006-concept-maturity.md —
colibri_codegraduation rules - docs/5-time/roadmap.md — Phase 4 schedule (R151+)
- docs/agents/executor-contract.md — T3 5-step chain
- CLAUDE.md — §3 worktree, §5 gate, §6 5-step chain, §7 writeback