P1.3.2 — κ Built-in Functions — Verification Evidence
Step 5 of the 5-step executor chain. Confirms that the
builtins.tsimplementation satisfies every acceptance criterion indocs/contracts/p1-3-2-builtins-contract.md§7.
§1. Gate command + result
cd .worktrees/claude/p1-3-2-builtins
npm run build && npm run lint && npm test
| Stage | Result |
|---|---|
npm run build (tsc + post-build migrations copy) |
exit 0 |
npm run lint (eslint src) |
exit 0 |
npm test (Jest ESM, 36 suites) |
1742 passed / 1742 total |
Test runtime ~50 s (Jest watch off; full coverage gathered).
§2. Test count delta
| Tree state | Tests | Suites |
|---|---|---|
Pre-R86 (post-R85 main d766db59) |
1658 | 35 |
Post-implementation (this branch, 4c142293) |
1742 | 36 |
| Δ added by P1.3.2 | +84 | +1 |
The 84 added tests cover 13 builtins × roughly 5–6 cases each, plus shared groups (surface, bindBuiltins, arity cross-cut, forbidden-op self-scan, determinism harness). The packet projected 70+; the actual final count is 84 because (a) the arity cross-cut runs one parameterised test per builtin (13), and (b) the forbidden-op block also runs one test per builtin (13).
§3. AC ↔ test mapping
| AC# | Statement | Covered by | Status |
|---|---|---|---|
| AC-1 | BUILTINS has exactly 13 entries with expected names | BUILTINS surface > has exactly 13 entries, ... has the expected key set in canonical insertion order |
✓ |
| AC-2 | BUILTIN_COSTS keys === BUILTINS keys | BUILTINS surface > BUILTIN_COSTS keys === BUILTINS keys |
✓ |
| AC-3 | min/max/abs/cap/clamp arithmetic correctness | min, max, abs, cap, clamp describe blocks (positive cases) |
✓ |
| AC-4 | clamp rejects inverted range | clamp > lo > hi throws BuiltinTypeError |
✓ |
| AC-5 | isqrt known values + negative throws | isqrt > isqrt(100/101/99/0/-1) cases |
✓ |
| AC-6 | isqrt correct for arbitrary-precision bigints | isqrt > isqrt(2^100) === 2^50 (large bigint) |
✓ |
| AC-7 | ilog2 known values + edge cases | ilog2 > 8/1024/1/0/-1 cases |
✓ |
| AC-8 | decay delegates to integer-math 1-epoch | decay > decay(1000, 500) === 950 (asserts equality with im_decay(1000n,500n,1n)) |
✓ |
| AC-9 | diminishing(500,1000)===333; k+v=0 throws | diminishing describe block |
✓ |
| AC-10 | bps_mul/bps_div delegation | bps_mul / bps_div describe block (assert equality with im_* versions) |
✓ |
| AC-11 | hash(“hello world”) fixture | hash > hash("hello world") === well-known SHA-256 hex |
✓ |
| AC-12 | hash digest length is 64 hex chars | hash > digest length is exactly 64 hex chars for any input (5 inputs incl. empty) |
✓ |
| AC-13 | hash deterministic | hash > deterministic — same input → identical output |
✓ |
| AC-14 | vrf_verify canonical accept | vrf_verify > canonical valid input → true |
✓ |
| AC-15 | vrf_verify rejects malformed | vrf_verify > pk wrong length / pk non-hex / proof wrong length / empty input / non-string args |
✓ |
| AC-16 | wrong arity → BuiltinTypeError per builtin | arity validation (parameterised over all 13) |
✓ |
| AC-17 | wrong type → BuiltinTypeError per builtin | type-mismatch cases inside each builtin’s describe block | ✓ |
| AC-18 | determinism harness green for every builtin | determinism harness > every numeric builtin is bit-identical across 10 runs (assertDeterministic over all 13) |
✓ |
| AC-19 | bindBuiltins() returns BUILTINS reference-equal | bindBuiltins > returns BUILTINS reference-equal |
✓ |
| AC-20 | forbidden-op self-scan returns [] | forbidden-op self-scan (parameterised over all 13) |
✓ |
§4. Coverage on builtins.ts
src/domains/rules
builtins.ts 99.09 | 100 | 100 | 99.09 | 264
Statement coverage 99.09% (1 line uncovered: line 264 — the unreachable
fallback return x; after the Newton’s-method loop. The loop converges
in O(log log n) which is well under the 256-iteration safety bound, so
this line is unreachable at runtime; documenting it explicitly in the
audit/contract/code so future maintainers don’t try to “fix” the
fallback.). Branch coverage 100%, function coverage 100%, line coverage
99.09% — meets/exceeds the κ module pattern.
§5. Determinism evidence
The determinism harness test runs assertDeterministic(f, [args], {iterations: 10})
over each of the 13 builtins with representative inputs. All pass; no
DeterminismError is thrown. This proves bit-identical output across
10 runs per builtin (130 total deterministic checks).
The forbidden-op self-scan (run as 13 parameterised tests) confirms
that none of the 13 builtin function bodies contain any of the
patterns in determinism.ts §FORBIDDEN_PATTERNS — including the
hash and vrf_verify bodies, which use a bare createHash named
binding (not the syntactic crypto.createHash).
§6. Hash fixture provenance
The “hello world” SHA-256 fixture was cross-checked outside the test runner:
$ node -e "console.log(require('crypto').createHash('sha256').update('hello world','utf8').digest('hex'))"
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
This matches the value asserted in
src/__tests__/domains/rules/builtins.test.ts > hash > hash("hello world").
§7. Out-of-scope (intentional)
- Wiring
BUILTIN_COSTSinto the engine budget — engine slice (a follow-up wave will add aBUILTIN_COSTS.get(expr.name)lookup atengine.ts §3 case 'FuncCall'and bumpinteger_opsaccordingly). - Real ECVRF implementation — Phase 1.5+ per ADR-002 §Decision (TBD). The stub interface (3 hex strings → bool) is stable; only the body changes.
rep(node)and other state-access functions — P1.3.3.- FuncCall validation against BUILTINS at parse/validate time —
the validator (P1.2.3, R85) currently treats FuncCall name as opaque;
a future slice can cross-reference against
BUILTINS.has(name).
§8. Files touched in this slice
| File | Purpose | Lines |
|---|---|---|
src/domains/rules/builtins.ts |
Implementation | 487 |
src/__tests__/domains/rules/builtins.test.ts |
Jest suite | 550 |
docs/audits/p1-3-2-builtins-audit.md |
Step 1 | 211 |
docs/contracts/p1-3-2-builtins-contract.md |
Step 2 | 292 |
docs/packets/p1-3-2-builtins-packet.md |
Step 3 | 167 |
docs/verification/p1-3-2-builtins-verification.md |
Step 5 (this file) | — |
No file outside this slice was modified. Sibling Wave 5 slices
(registry.ts, state-access.ts, policy-gate.ts, versioning.ts)
remain untouched.
§9. Sign-off
5-step chain complete. Writeback proceeds to PR creation, then
thought_record + task_update(status="DONE") per CLAUDE.md §7.