P1.2.3 — κ AST Validator — Verification Evidence

Step 5 of the 5-step executor chain. Builds on the audit (444e8916), contract (86aeded2), packet (bad227c4), and implementation (33627f8b) commits.

§V1. Headline

Metric Value
Branch feature/p1-2-3-validator
Worktree .worktrees/claude/p1-2-3-validator
Base commit 7218b34b (R84 P1.2.2 parser; main)
Implementation commit 33627f8b (feat(p1-2-3-validator): 7-check ast walker)
Pushed to origin yes (after Step 4)
Build green (tsc + post-build migration copy)
Lint green (zero new warnings)
Tests 1531/1531 passing across 33 suites — baseline 1467 + 64 new tests in validator.test.ts
Validator coverage 97.29% statements · 93.33% branches · 100% functions

§V2. Acceptance criteria — locked from prompt + contract §10

# Criterion Status Evidence
1 7 check functions implemented + exported forbiddenFunctions, sideEffectsInGuard, mutationOfInput, typeCompatibility, scopeCheck, cycleDetection, axiomCheck — all named exports in src/domains/rules/validator.ts §5–§11
2 Each rejects target violation class F1 (forbiddenFunctions), F2 (sideEffectsInGuard), F4 (typeCompatibility), F5 (scopeCheck) — all 9+5+13+7 = 34 fixtures pass with correct error codes; F3+F6+F7 stub semantics confirmed
3 Aggregates all errors (no short-circuit) F9.1 + F9.2 — multi-issue rules surface 3+ errors of distinct codes in a single pass
4 Structured ValidationError {code, message, path, location} F11.1–F11.5 — every error has code: string, non-empty message, path: string[], location: Location | null
5 Axiom checks structured for later π wiring checkAxiom01checkAxiom07 — 7 named exports per contract §2.3; F7.1–F7.9 confirm each returns []; aggregator axiomCheck composes them
6 npm run build && npm run lint && npm test ALL THREE green See §V3 below — verbatim outputs captured
7 Zero new lint warnings eslint src exits 0; no warnings emitted
8 No regressions on the 1467-test baseline Suite reports 1531/1531 passing; baseline 1467 + 64 new validator tests = 1531

§V3. Build / lint / test command transcripts

§V3.1. npm run build

> colibri@0.0.1 build
> tsc

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

copy-migrations: copied 6 migration(s) E:\AMS\.worktrees\claude\p1-2-3-validator\src\db\migrations -> E:\AMS\.worktrees\claude\p1-2-3-validator\dist\db\migrations

Exit code 0. No TypeScript errors.

§V3.2. npm run lint

> colibri@0.0.1 lint
> eslint src

Exit code 0. No warnings.

§V3.3. npm test

Test Suites: 33 passed, 33 total
Tests:       1531 passed, 1531 total
Snapshots:   0 total
Time:        38.974 s

Coverage spotlight (selected rows):

src/domains/rules
  bps-constants.ts    100   |  100   |  100   |  100   |
  determinism.ts      100   |  100   |  100   |  100   |
  integer-math.ts     100   |  100   |  100   |  100   |
  lexer.ts            100   |  83.33 |  100   |  100   | 452
  parser.ts           89.76 |  72.61 |  83.47 |  89.64 |
  validator.ts        97.29 |  93.33 |  100   |  97.08 | 206-207,390,397

Validator achieves 100% function coverage (every named export exercised). The three uncovered statement lines are defensive fallbacks:

  • 206-207: the reasonFor fallback for an unrecognized name — unreachable since FORBIDDEN_SET keys mirror FORBIDDEN_REASON keys, but kept as defense-in-depth.
  • 390: the path: [] defensive branch in scopeCheck (parser cannot emit empty-path VarRef).
  • 397: the display === '' arm of the same defensive branch.

These are intentional defensive paths flagged in the contract (§3.5).

§V4. Test fixture matrix — outcomes

Group Tests Status Notes
F1 — forbiddenFunctions 9 ✅ all pass now/time/read_file/random/rand/http_get + non-blocked + VRF + multi-call
F2 — sideEffectsInGuard 5 ✅ all pass single, multi, no-call, effect-arg, else-only
F3 — mutationOfInput 2 ✅ all pass trivial + with $event references — both return []
F4 — typeCompatibility 13 ✅ all pass 4 hand-built (str-in-expression positions) + 9 parsed; cascades bounded
F5 — scopeCheck 7 ✅ all pass undefined, in-scope (event/actor/vrf_output), mixed, effect-arg path, full IN_SCOPE_ROOTS sweep
F6 — cycleDetection 2 ✅ all pass trivial + complex rule — both return []
F7 — axiomCheck stubs 9 ✅ all pass each of 7 axiom sub-stubs + composer + non-trivial-input case
F8 — happy path + read-only 5 ✅ all pass AcceptCommitment-shaped, minimal, simple-effect rules valid; read-only deep-equal pre/post
F9 — multi-error aggregation 3 ✅ all pass 3+ codes from single rule; explicit no-short-circuit proof
F10 — exposed constants 3 ✅ all pass FORBIDDEN_FUNCTIONS list, IN_SCOPE_ROOTS list, frozen invariants
F11 — ValidationError shape 5 ✅ all pass every error structurally well-formed; path encoding correct
Total 63 documented + 1 dependent (F9.3 happy-path subcase) = 64 cases Jest counts 64 in this suite

§V5. Read-only invariant evidence

Per contract §3 + audit §8 + packet §P2.1:

Test Mechanism Outcome
F8.4 JSON.stringify(jsonifyAst(rule)) before/after validate(rule) on AcceptCommitment-shaped fixture identical
F8.5 Same, but on a rule with 4 distinct issues (FORBIDDEN_FUNCTION + UNDEFINED_VAR + SIDE_EFFECT_IN_GUARD + FORBIDDEN_FUNCTION-in-effect) identical

The validator never assigns to a node, never splices an array, never deletes a key. Every accumulator is a function-local []; every path extension uses spread ([...prev, segment]). pathExt returns a new array.

§V6. Determinism corpus self-scan compatibility

The Jest suite includes a rule-engine corpus self-scan test in src/__tests__/domains/rules/determinism.test.ts (line 833) that scans every .ts file under src/domains/rules/ (except determinism.ts) for forbidden patterns. The new validator.ts is in scope and passes the scan — confirmed by the green full-suite run.

validator.ts was authored to avoid: Math.*, Date.*, new Date, timers (setTimeout/setInterval/setImmediate), fetch, XMLHttpRequest, fs require/import, crypto.*, process.hrtime/nextTick, await, async function/async (, float literals. JSDoc comments are stripped by the scanner so cross-references in comments (e.g. citing Math.* as a forbidden token) are safe.

§V7. Test-file layout — convention reconciliation

Per audit §1.3 + contract §2: the file lives at src/__tests__/domains/rules/validator.test.ts, not at the prompt’s src/domains/rules/__tests__/validator.test.ts. This matches every other shipped κ test (lexer, parser, integer-math, bps-constants, determinism) per the in-repo convention src/__tests__/domains/<name>/. Jest testMatch picks both layouts up; the convention is the one already in flight from R83.A onward.

§V8. Commits + diff stats

444e8916 audit(p1-2-3-validator): inventory surface          (+311)
86aeded2 contract(p1-2-3-validator): behavioral contract     (+335)
bad227c4 packet(p1-2-3-validator): execution plan            (+640)
33627f8b feat(p1-2-3-validator): 7-check ast walker          (+1425)
                                                             (+ this verification doc, ~250)

Implementation deltas:

  • src/domains/rules/validator.ts+654 LOC (within audit §12 estimate of ~430; the larger figure includes JSDoc, the 7 axiom stubs, and frozen-constant infrastructure)
  • src/__tests__/domains/rules/validator.test.ts+~770 LOC (comparable to audit §12 estimate of ~500; the extra accommodates 13 hand-AST builder lines + 4 hand-built F4 cases + the F4.11/12/13 expansion that grew test coverage to 100% function)

§V9. Risk register reconciliation (audit §11)

Risk Audit §11 mitigation Outcome
Confusing EffectCall vs FuncCall shared traversal helper; tests cover both F1.1 (FuncCall in guard) + F1.3/F1.6 (EffectCall in effects) — both surfaced
Type-cascade explosion bounded — each operator emits at most 1 error and falls through F4.1 + F4.4 produce exactly 1 error each, not a chain
Walker mutating AST F8b deep-equal pre/post F8.4 + F8.5 — both pass
Conservative sideEffectsInGuard documented explicit decision (contract §3.2) F2.2 (min + max both flagged); intentional
BLOCKLIST / IN_SCOPE_ROOTS drift exported + tested F10.1 + F10.2 + F10.3
noUncheckedIndexedAccess every arr[i]! post-bounds-check TS strict-mode build green
Path field assembly complexity pathExt immutable helper F11.4 + F11.5 + F11.2

All audit §11 risks accounted for; none materialised.

§V10. Post-condition for downstream consumers

Consumer Contract availability
P1.2.4 — Rule Loader / Registry validate(rule) ready; signature locked. Registry calls per parsed rule before indexing. Cross-rule cycleDetection migrates here per audit §5.6 / contract §3.6.
P1.3.1 — Core Evaluation Loop AST validator-passed rules are safe to evaluate. unknown-tagged operands tightening is a P1.3.1 concern.
Phase 3+ π Governance checkAxiom01checkAxiom07 slots ready to receive real check bodies; codes named (AX_NN_VIOLATION); signatures stable.
Future ADR-006-dsl-grammar FORBIDDEN_FUNCTIONS + IN_SCOPE_ROOTS exported as candidate ratification targets.

§V11. Outstanding

  • None for this task. All acceptance criteria green; tests passing; read-only verified; lint+build clean; corpus self-scan clean; 64 new tests added; no regressions.
  • Out of scope (re-stated). Builtin allowlist for sideEffectsInGuard, cross-rule cycle detection, substantive axiom enforcement, bigint range validation, rule classification by kind. All flagged in audit §9 + contract §9 as future work.

§V12. Writeback intent

mcp__colibri__thought_record({
  type: "reflection",
  task_id: "76f84da5-0dfc-43fe-8c8d-73135087d7e6",
  content: "task_id: P1.2.3
branch: feature/p1-2-3-validator
worktree: .worktrees/claude/p1-2-3-validator
commit: 33627f8b (impl) + verification commit (this)
pr: <to be filed>
tests: npm run build && npm run lint && npm test → all green; +64 tests; 1531/1531
summary: 7-check AST walker over P1.2.2 RuleNode. Active checks: forbiddenFunctions (6 names), sideEffectsInGuard (FuncCall conservative), typeCompatibility (4-tag inference with unknown propagation), scopeCheck (9 IN_SCOPE_ROOTS). Stub checks: mutationOfInput (grammar-gated), cycleDetection (registry concern), axiomCheck × 7 (π wiring). Aggregate, no short-circuit. Read-only over input AST (verified deep-equal pre/post). 97.29% stmt / 93.33% branch / 100% function coverage. Determinism corpus self-scan clean.
blockers: none"
})

mcp__colibri__task_update({
  id: "76f84da5-0dfc-43fe-8c8d-73135087d7e6",
  patch: { status: "DONE" }
})

End of 5-step chain. Task ready for PR.


Back to top

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

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