Audit — P1.1.1 Basis Point Arithmetic (R81.A)

Surface inventory

Target directory: src/domains/rules/ Target state: greenfield. The directory does not exist on origin/main @ 77e579b8.

$ ls src/domains/
integrations  proof  router  skills  tasks  trail

No rules/ subdirectory. No integer-math.ts. No pre-existing κ code surface. This is the first Phase 1 κ Rule Engine task to touch src/; every file this task writes is net-new.

Related existing surfaces that must NOT be touched:

  • src/domains/tasks/ (β) — task pipeline; unrelated.
  • src/domains/router/ (δ) — Phase 0 stubs; unrelated (router/scoring.ts, router/fallback.ts).
  • src/domains/proof/ (η) — Merkle surface; consumes κ version hashes in Phase 3 but not Phase 1. Do not edit.
  • src/db/ — κ state is in-memory for P1.1.1; no schema change.
  • src/server.ts — no new MCP tool is registered by P1.1.1; it is a library-only module consumed by later P1 sub-tasks (P1.1.2, P1.1.3, P1.3.2).

Source of authority (reading list)

  1. docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md §P1.1.1 (lines 62–196) — the pre-authored canonical prompt, authoritative spec for files, API, acceptance criteria, gotchas, writeback.
  2. docs/reference/extractions/kappa-rule-engine-extraction.md §3 + §4 — Phoenix-donor algorithmic source: BPS constants, signature table, safe_mul / safe_div pseudocode, int64 bounds (-2^63 .. 2^63 - 1 = -9,223,372,036,854,775,808 .. 9,223,372,036,854,775,807).
  3. docs/3-world/physics/laws/rule-engine.md §Integer-only arithmetic + §Basis-point arithmetic examples + §Forbidden operations — concept doc; confirms floor rounding is required for compounding monotonicity and lists forbidden ops (no Math, no Date, no RNG, no async, no I/O).
  4. CLAUDE.md §3 worktree, §5 triple gate, §6 5-step chain, §7 writeback.
  5. docs/guides/implementation/task-breakdown.md §P1.1.1 — roadmap entry.

File plan

Two files. Both net-new.

Path Role Est. LOC
src/domains/rules/integer-math.ts Public library — bigint bps helpers + typed errors ~170
src/__tests__/domains/rules/integer-math.test.ts 100% branch coverage suite ~300

Test-file location note: the pre-authored prompt (line 126) writes src/domains/rules/__tests__/integer-math.test.ts, but the shipped Phase 0 convention per CLAUDE.md §9.1 is a single root at src/__tests__/ with the source tree mirrored under it (e.g. src/__tests__/domains/router/scoring.test.ts matches src/domains/router/scoring.ts). Every one of the 1085 existing tests follows this layout. jest.config.ts has roots: ['<rootDir>/src'] and picks up **/__tests__/**/*.test.ts, so both layouts would run — but only the centralized src/__tests__/ layout matches the established Phase 0 corpus and CLAUDE.md §9.1 “shipped (Wave A, P0.1.2); 1001 tests passing at 09d462f8”. This audit follows the shipped convention. Gotcha flagged in the packet.

API surface (confirmed against extraction §3 + task-prompt §P1.1.1)

All functions take and return bigint. No number, no Math.*, no Date.*, no async, no RNG.

// Core BPS ops
bps_mul(value: bigint, bps: bigint): bigint     // (value * bps) / 10_000n, floor
bps_div(value: bigint, bps: bigint): bigint     // (value * 10_000n) / bps, floor; throws on bps === 0n
apply_bps(value: bigint, bps: bigint): bigint   // value - bps_mul(value, bps)
decay(value: bigint, rate_bps: bigint, epochs: bigint): bigint
                                                // per-epoch apply_bps; floors each step; throws on epochs < 0n

// Safe arithmetic primitives
safe_mul(a: bigint, b: bigint): bigint          // throws OverflowError when |a*b| > 2^63 - 1
safe_div(a: bigint, b: bigint): bigint          // truncate-toward-zero; throws on b === 0n

// Typed errors (exported)
export class OverflowError extends Error {}
export class DivisionByZeroError extends Error {}
export class UnderflowError extends Error {}

Constants (internal, not re-exported)

const BPS_DENOMINATOR = 10_000n;  // 100% in basis points
const INT64_MAX = 9_223_372_036_854_775_807n;   //  2^63 - 1
const INT64_MIN = -9_223_372_036_854_775_808n;  // -2^63

BPS_100_PERCENT / BPS_1_PERCENT / BPS_50_PERCENT from the extraction are P1.1.3 scope (constants + overflow protection), not P1.1.1. Per gotcha #3 in the prompt: do not introduce branded Bps types here — P1.1.3 concern.

Test strategy

Harness: Jest 29 (ts-jest ESM), per the existing 1085-test suite. No new dev deps. No fast-check/property libs — hand-rolled cases are sufficient for 100% branch coverage on six pure functions.

Coverage target: 100% branches on integer-math.ts. Jest already runs with collectCoverage: true per jest.config.ts, so npm test emits an lcov report the verification step reads.

Test-case matrix (expanded in packet):

Group Cases Purpose
bps_mul identity (10000, 10000), (1000, 500)=50, (0, x), (x, 0), negative value, negative bps, large value × safe bps Every arg sign + zero branch
bps_div inverse of bps_mul, (5000, 2500)=20000, (1000, 2000)=5000, divisor 0 throws, negative divisor Floor / truncate semantics + error branch
apply_bps (1000, 150)=985, (1000, 10000)=0 (full decay), (1000, 0)=1000 (no-op), underflow never goes below 0 for non-negative Invariant check
decay (1000, 150, 1)=985, (1000, 150, 2)=970 (compound floor), (v, r, 0)=v, epochs<0 throws, 100-epoch compounding stability Multi-step flooring
safe_mul 0 × anything, 1 × anything, boundary (INT64_MAX, 1), overflow (2^62 × 2^62), negative × negative (large positive overflow), INT64_MIN × -1 (sign overflow) Overflow + sign branches
safe_div 1 / 1, truncate toward zero, divide-by-zero throws, negative numerator, negative denominator, -7/2 = -3 (not -4 — truncate, not floor) Both signs + zero branch
Error classes instanceof OverflowError/DivisionByZeroError/UnderflowError, .name set, .message present Typed-error surface
Purity same input → structurally identical output; no this-binding required Determinism guardrail (lighter than P1.1.2’s full harness)

Expected test count: ~30 it blocks → ~30 new tests. Brings suite from 1085 → ~1115.

Integration / side-effects

  • No DB writes. Pure library; no import from src/db/*.
  • No MCP tool registration. src/server.ts is untouched. The κ surface ships with zero MCP tools in Phase 0/Phase 1 until P1.4.1 (admission evaluator wiring) — and even then MCP exposure is TBD. Tool-surface count stays at 14.
  • No ADR change. P1.1.1 implements the code counterpart of an existing extraction; no new architecture decision needed. ADR-006 (DSL grammar) is already on record.
  • No docs outside the 5-step-chain quartet. Audits / contracts / packets / verification live under docs/{audits,contracts,packets,verification}/; no concept-doc edit is required because docs/3-world/physics/laws/rule-engine.md already describes the BPS arithmetic at spec granularity. A frontmatter graduation (colibri_code: none → partial) is out of scope for P1.1.1 — the κ concept only graduates once the evaluator loop ships (P1.3.1 at earliest, likely not until the P1.4.1 admission wiring).

Gate expectations

  • npm run build — clean. NodeNext strict mode is already configured; bigint literals are ES2020 and the target: ES2022 tsconfig accepts them natively.
  • npm run lint — clean. eqeqeq: error means always use ===. curly: all means always brace single-line ifs.
  • npm test — pre-existing 1085 tests + ~30 new = ~1115. One known flake: startup — subprocess smoke may fail under load on first run.

Rollback

Single-commit revert is trivial because this task creates net-new files and imports from nowhere else:

git revert <final-sha>

Nothing downstream can regress because nothing else yet imports src/domains/rules/*.

Unblocks

P1.1.2 (Determinism Harness) consumes this module. P1.1.3 (BPS Constants + branded Bps type) wraps this module. P1.3.2 (Built-in functions) delegates to this module.

Done-criteria

  • Target surface confirmed greenfield (no src/domains/rules/ on origin/main).
  • Authority documents read end-to-end.
  • File plan fixed: 2 files, ~470 LOC total.
  • Test strategy + case matrix drafted (expanded in packet).
  • Test-file location divergence from prompt flagged (follows CLAUDE.md §9.1 + shipped convention).
  • Integration surface confirmed (zero wiring; pure lib).
  • Rollback plan defined.

Back to top

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

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