R83.A verification — P1.1.2 Determinism Verification Harness

Scope recap

Second Phase 1 κ Rule Engine sub-task: greenfield src/domains/rules/determinism.ts — pure N-run equality harness + function-body forbidden-op regex scanner that every downstream κ sub-task (P1.1.3, P1.2.x, P1.3.x, P1.4.x, P1.5.x) will call to self-assert their helpers stay integer-only, RNG-free, clock-free, async-free. Authoritative spec: the pre-authored prompt at docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md §P1.1.2 (lines 199–318) + docs/3-world/physics/laws/rule-engine.md §Forbidden operations + the R83 orchestrator dispatch prompt (splits the task-prompts doc’s single test file into harness library + test pair).

Deliverables

File LOC Purpose
src/domains/rules/determinism.ts 276 N-run harness + forbidden-op scanner + deep-equal helper
src/__tests__/domains/rules/determinism.test.ts 622 87 tests, 100% branch coverage on determinism.ts

Public API (confirmed in impl)

// Typed error
class DeterminismError extends Error

// N-run harness — default iterations = 10, throws DeterminismError on drift
assertDeterministic<A, R>(fn, args, opts?): R

// Forbidden-op regex scanner over Function.prototype.toString()
inspectFunctionForbidden(fn: Function): readonly string[]

// Throwing wrapper — throws DeterminismError when inspect returns non-empty
assertNoForbiddenOps(fn: Function, opts?: { label?: string }): void

// Deep structural equality with bigint + Error awareness
deepEqualDeterministic(a: unknown, b: unknown): boolean

All signatures are synchronous. No outbound imports from determinism.ts — self-standing to preserve invariant I1 (the library itself uses none of the forbidden ops it scans for).

Forbidden-op patterns enforced

13 regexes, manifest-frozen in determinism.ts lines 55–70 (= contract §4):

  1. Math.* — any static Math access
  2. Date.* — any static Date access
  3. new Date — constructor form
  4. setTimeout|setInterval|setImmediate — timers
  5. fetch|XMLHttpRequest — network
  6. require('fs') / require('node:fs')
  7. from 'fs' / from 'node:fs' — ESM fs
  8. crypto.* — any crypto randomness
  9. process.hrtime / process.nextTick — process timers
  10. await
  11. async function / async (
  12. Float literal \d+\.\d+ (does NOT match 1n or 1_000_000)
  13. [native code] — native-code bodies

Quality gates (run 2026-04-19 on commit 34ffa8d7 against origin/main @ 657d4ef4)

Gate Result
npm run build ✓ exit 0
npm run lint ✓ exit 0
npm test (first run) 1209 / 1210 — 1 pre-existing startup — subprocess smoke flake hit
npm test (second run — rerun-once policy per prompt) 1210 passed / 1210 total across 28 suites in 23.6 s (0 failures)

Coverage on the new library file

--------------------------|---------|----------|---------|---------|-------------------
File                      | % Stmts | % Branch | % Funcs | % Lines | Uncovered
--------------------------|---------|----------|---------|---------|-------------------
 src/domains/rules        |   99.23 |    98.46 |     100 |   99.22 |
  determinism.ts          |     100 |      100 |     100 |     100 |
  integer-math.ts         |   96.42 |    83.33 |     100 |   96.29 | 180
--------------------------|---------|----------|---------|---------|-------------------

AC#9 satisfied: 100% branch coverage on determinism.ts. (integer-math.ts coverage numbers carry over from R81.A untouched — line 180 is the safe_div: b === 0n branch covered by P1.1.1 tests in the co-located integer-math test file but the new test-path filter reports it as “uncovered” because the filter ran only determinism tests. Full-suite coverage under npm test without filter shows it fully exercised; unchanged vs R81.A.)

Test-count delta

  • Baseline (R81.A main): 1123 tests
  • Post-R83.A: 1210 tests
  • Delta: +87 new tests, all in src/__tests__/domains/rules/determinism.test.ts

Acceptance-criteria traceability

AC Status Test reference
AC#1 — five exports + DeterminismError Group 1 (DeterminismError), every import in the test file
AC#2 — harness wraps all six P1.1.1 helpers, 10 iterations each Group 2 (happy path over P1.1.1 helpers) — 7 tests
AC#3 — FAIL loudly on non-deterministic mock Group 4 (fails on non-deterministic functions) — 7 tests
AC#4 — inspectFunctionForbidden returns [] for P1.1.1 helpers Group 6 (clean P1.1.1 helpers) — 6 tests
AC#5 — inspect detects each of 13 forbidden patterns Group 7 (detects every forbidden pattern) — 20 tests
AC#6 — assertNoForbiddenOps wrapper Group 8 (assertNoForbiddenOps) — 5 tests
AC#7 — deepEqualDeterministic all branches Group 9 (deepEqualDeterministic) — 18 tests
AC#8 — corpus self-scan over src/domains/rules/*.ts Group 12 (rule-engine corpus self-scan) — 1 test
AC#9 — 100% branch coverage on determinism.ts Jest lcov shows 100/100/100/100 on determinism.ts
AC#10 — all three gates green on 1123+ baseline 1210/1210 second-run

Scope divergence from task-prompts canonical doc

The canonical docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md §P1.1.2 lists one file (src/domains/rules/__tests__/determinism.test.ts) with the harness inline inside the test. The R83 orchestrator dispatch prompt (authoritative for this execution) splits that into two files:

  • src/domains/rules/determinism.ts — reusable library
  • src/__tests__/domains/rules/determinism.test.ts — tests (repo-convention path per R81.A)

The orchestrator’s reason: “This is a guardrail for all of Phase 1” — future sub-tasks (P1.1.3, P1.3.1, P1.3.2) can import { assertDeterministic, assertNoForbiddenOps } from their own test files rather than re-rolling the harness. The library shape is strictly more capable than the inline-only shape; the test surface remains equivalent.

Additional deliberate deferrals recorded in contract §9:

  • No seeded-PRNG fuzz over 10k tuples. The task-prompts doc mentions fuzz; the orchestrator scoped this task to “N times (configurable, e.g. 10 iterations)”. Property-style fuzz will land in later P1 sub-tasks that operate on larger input spaces (P1.3.1 eval loop).
  • No fast-check dev-dep. Not in package.json; adding it would be scope-creep.
  • No MCP tool registration. Library-only, same as P1.1.1.

What was NOT changed

  • src/server.ts — untouched. 14-tool Phase 0 MCP surface preserved.
  • src/db/* — untouched. No schema migration.
  • src/domains/{tasks,router,skills,trail,proof,integrations}/ — untouched.
  • src/domains/rules/integer-math.ts — untouched.
  • No ADR authored or amended.
  • No concept-doc graduation. κ stays colibri_code: none; graduation waits on the evaluator (P1.3.1 earliest, P1.4.1 most likely).
  • No frontmatter edits outside this chain’s own quartet.
  • No changes under .claude/, .agents/, docs/5-time/, CLAUDE.md.

Worktree discipline

  • Worktree: E:\AMS\.worktrees\claude\r83-a-determinism
  • Branch: feature/r83-a-determinism
  • Base: origin/main @ 657d4ef4 (post-R82 seal)
  • Pre-round state verified clean: zero commits ahead of main before audit.

Commit chain on feature/r83-a-determinism

34ffa8d7 feat(r83-a-determinism): P1.1.2 determinism verification harness
506ad720 packet(r83-a-determinism): execution plan
c411644c contract(r83-a-determinism): behavioral contract
2973a340 audit(r83-a-determinism): inventory harness surface

Plus this verification commit.

Gate-capture transcript

npm run build

> colibri@0.0.1 build
> tsc

(exit 0; no output)

npm run lint

> colibri@0.0.1 lint
> eslint src

(exit 0; no output)

npm test (second run, rerun-once policy)

Test Suites: 28 passed, 28 total
Tests:       1210 passed, 1210 total
Snapshots:   0 total
Time:        23.633 s, estimated 34 s
Ran all test suites.

Residual risks / notes for reviewer

  • Pre-existing startup — subprocess smoke flake — first-run only, clears on rerun. Not introduced by this task. Known pre-R83 flake (memory file). Rerun policy per prompt.
  • P1.1.1 integer-math.ts coverage numbers — unchanged vs R81.A (96.42/83.33/100/96.29). R83.A does not modify this file.
  • Lint @typescript-eslint/no-unsafe-function-type disables — five inline disables for the intentional Function type in inspectFunctionForbidden / assertNoForbiddenOps
    • four in the test file for the { toString() } as unknown as Function forgery pattern. Documented in contract §R2. Narrowing Function to (...args: unknown[]) => unknown would reject class-method refs; we accept the lint exemption for the inspector’s introspection surface.
  • Line 180 in integer-math.ts shown uncovered only under the filtered --testPathPattern run because integer-math.test.ts doesn’t match the filter. Under full npm test the line is covered by R81.A’s tests. No R83.A regression.

R83 Wave 2 context

  • Round: R83 — kappa-phase-1-wave-2
  • Manifest PR: https://github.com/LastEld/AMS/pull/187
  • Parallel tasks in this wave: R83.B (P1.1.3 BPS constants) and R83.C (P1.2.1 lexer retry); this PR (R83.A) is independent of both — P1.1.3 depends on P1.1.1 only, P1.2.1 depends on nothing.
  • Sigma seal (Phase B) expected to aggregate A + B + C into the R83 seal commit.

Writeback

The formal writeback lands in the PR body at push time (see packet §8). A live-MCP thought_record is not possible in the current round environment (no MCP client attached); the PR body + this verification doc carry the writeback until Colibri MCP client attaches per CLAUDE.md §4 branch-on-MCP-client-presence.


Back to top

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

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