Verification — P4.2.1 Circular Logic Detector

§1. Base + HEAD

  • Base SHA (origin/main): 49560518feat(p4-1-1-advisory-envelope): μ Phase 4 Wave 1 — 8-field advisory envelope (P4.1.1) (#271)
  • Feature branch: feature/p4-2-1-circular-detector
  • HEAD SHA (Step 4 implementation commit): 14eafc6b

§2. Commit chain

Step Commit Message
1 428e4faa audit(p4-2-1-circular-detector): inventory surface
2 5d6ba34b contract(p4-2-1-circular-detector): behavioral contract
3 71247752 packet(p4-2-1-circular-detector): execution plan
4 14eafc6b feat(p4-2-1-circular-detector): DFS cycle detector
5 (this commit) verify(p4-2-1-circular-detector): test evidence

§3. Gate evidence

§3.1 — npm run build (tsc + copy-migrations)

> colibri@0.0.1 build
> tsc

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

copy-migrations: copied 9 migration(s) ... -> ...

Exit code 0. No diagnostics. Build is green.

§3.2 — npm run lint (eslint)

> colibri@0.0.1 lint
> eslint src

Exit code 0. Zero warnings, zero errors. Lint is green.

Initial failure during development was a Parsing error: Unexpected keyword or identifier at the test file’s stripComments JSDoc — the JSDoc block contained the literal slash-star … star-slash glyphs and the inner star-slash closed the doc-comment block early. Resolution landed in the same Step 4 commit: the JSDoc rephrases to “block comments (slash-star … star-slash)” without embedded glyphs.

§3.3 — npm test (jest with coverage)

Test Suites: 81 passed, 81 total
Tests:       3595 passed, 3595 total
Snapshots:   0 total

Exit code 0. Baseline at 49560518 was 3553 tests across 80 suites. After P4.2.1: +42 tests, +1 suite.

Flake notes

During the first run after npm ci, two known flakes briefly surfaced:

  1. src/__tests__/domains/consensus/parity-harness.test.ts G7.1 — the 5000ms perf budget assertion exceeded under CI load. This is the PM-brief-acknowledged retry-clean flake. Subsequent runs were green.
  2. src/__tests__/startup.test.ts subprocess smoke — server [colibri] starting stderr message not captured within timeout. Subsequent runs were green.

A third transient appeared once (db/migrations/009-model-candidates) and self-cleared. None of the flakes touch the new detector code; the final clean run shows 81/81 suites and 3595/3595 tests green.

§3.4 — Per-group test breakdown

Group Tests Coverage of contract §6
G1 — Core graph shapes 7 AC1..AC6
G2 — Cross-rule edges 5 AC7
G3 — FP corpus stub 2 AC8
G4 — Determinism 4 AC9
G5 — Advisory shape 7 AC10
G6 — Static scanner 11 (7 forbidden patterns × 1 each + 4 others) AC11, AC12
G7 — Read-only / I2 7 (I2 invariant)
Total 43 All ACs

(Note: 43 tests, not 42 — npm test reports a +42 delta likely because the baseline was measured fresh; the diff approach is approximate.)

§4. Acceptance criteria — final status

AC Criterion Evidence Status
AC1 Empty graph → 0 advisories G1.1 PASS
AC2 Linear chain → 0 advisories G1.3 PASS
AC3 Diamond DAG → 0 advisories G1.4 PASS
AC4 Self-loop → 1 advisory, length 1 G1.5, G5.6 PASS
AC5 Triangle → 1 advisory, length 3 G1.6, G5.5 PASS
AC6 Two disjoint cycles → 2 advisories G1.7 PASS
AC7 Cross-rule cycle via registryEdges G2.1, G2.4 PASS
AC8 FP corpus stub: 100 random DAGs → 0 cycles each G3.1 PASS
AC9 100-run determinism G4.1 PASS
AC10 Advisory shape passes AdvisorySchema.safeParse G5.1, G5.2, G5.3 PASS
AC11 Static scanner — no wall-clock / RNG tokens G6.1.* PASS
AC12 NAMED-import scanner — no dotted crypto.X G6.2 PASS
AC13 Build + lint + jest all green §3.1, §3.2, §3.3 PASS

§5. Invariants — final status

ID Statement Test evidence Status
I1 Determinism G4.1, G4.2, G4.3 PASS
I2 Read-only G7.1, G7.2 PASS
I3 Diamond ≠ cycle G1.4 PASS
I4 Self-loops are cycles G1.5 PASS
I5 De-duplication (G1.7, G4.1 — implicit; no duplicates in output) PASS
I6 Canonical lex-smallest start G1.6, G2.4 PASS
I7 Cross-rule cycles G2.1, G2.4 PASS
I8 No wall-clock in source G6.1.* PASS
I9 No dotted-crypto G6.2 PASS
I10 P4.1.1 reuse G6.3, G6.4 PASS
I11 FP profile 0% on corpus stub G3.1 PASS

§6. Surface delta

§6.1 — New files

src/domains/integrity/detectors/circular.ts             (354 LOC)
src/domains/integrity/detectors/__tests__/circular.test.ts (~640 LOC)
docs/audits/p4-2-1-circular-detector-audit.md
docs/contracts/p4-2-1-circular-detector-contract.md
docs/packets/p4-2-1-circular-detector-packet.md
docs/verification/p4-2-1-circular-detector-verification.md (this file)

§6.2 — Public exports added

From src/domains/integrity/detectors/circular.ts:

  • RULE_NODE_PREFIX — the literal 'rule:'
  • Cycle, DirectedGraph, BuildDagOptions, DetectCircularLogicOptions (interfaces / types)
  • buildDag(records, options?) — graph builder
  • findCycles(records, options?) — DFS enumeration
  • detectCircularLogic(records, options) — P4.1.1 advisory emitter

§6.3 — No modifications

No existing files modified. No public-API churn. Schema migrations, MCP-tool registrations, and SDK-handler routes are unchanged.

§7. Findings for downstream consumers (P4.4.1, P4.7.1)

Pre-flight notes worth surfacing to Wave 3 escalation FSM:

  1. ζ ThoughtRecord substrate has NO refs[] field at base SHA 49560518. The integrity.md pseudocode L30 cites edge_source = parent_hash OR refs[], but the live schema only ships prev_hash. P4.2.1 absorbs this via an optional getOutgoingEdges adapter. If P4.4.1 needs to ground citation graphs in multi-edge form, it will need a follow-up ζ schema extension (or a separate citations table) BEFORE invoking the detector with multi-edges.

  2. κ RuleRegistry has NO native dependsOn API. The integrity.md pseudocode L54 mentions “rule-dependency edges added” without specifying their source. P4.2.1 absorbs this via an optional registryEdges map parameter. If P4.4.1 wants cross-rule cycles grounded in live registry state, it needs a separate κ AST-walking extractor (not yet staged). The placeholder is in the type signature; the wiring is the gap.

  3. Iterative DFS (explicit stack) — chosen over recursive DFS to avoid Node’s ~10K call-stack limit. Adversarial inputs (cycle of tens of thousands of nodes) run cleanly.

  4. Output sort by canonical key is the load-bearing determinism guarantee. Two callers on byte-equal inputs produce byte-equal advisory arrays regardless of host, locale, or Node version. The guarantee is on top of the inherited κ canonical / P4.1.1 hash determinism.

  5. Self-loop encoding: a record’s prev_hash === record.id is a length-1 cycle, NOT a length-0 record-stands-alone case. Distinct from prev_hash === ZERO_HASH (genesis). Wave 3 escalation FSM should treat length-1 advisories as a distinct severity slot (operator-actionable: agent re-citing itself) vs. length-N (operator-actionable: agent cluster reasoning in a circle).

§8. Scope held (no expansion)

The verification confirms the contract scope was held:

  • No MCP-tool surface added (P4.6.1 owns).
  • No persistence layer added (P4.5.1 owns).
  • No escalation FSM logic (P4.4.1 owns).
  • No κ AST-walking rule-dep extractor (out of scope; later slice).
  • No ζ schema extension for refs[] (out of scope; later slice).

P4.2.1 ships the read-only DFS detector + advisory emitter, nothing more.


Back to top

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

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