P1.5.3 — Verification

Step 5 of the 5-step executor chain. Records the evidence that the implementation in src/domains/rules/activation.ts and src/__tests__/domains/rules/activation.test.ts satisfies the behavioral contract.

1. Build, lint, test gate

Command Result Notes
npm run build green tsc + postbuild migration copy. No type errors.
npm run lint green eslint over src/. Zero warnings, zero errors.
npm test green 46 suites, 2361 tests passed. ~26s wall time.

Test count progression:

  • Pre-Wave-8 baseline (post-Wave-7 with PR #216 + #217 + #218 merged): 2305 tests.
  • Post P1.5.3: 2361 tests (+56 from activation.test.ts).

2. Acceptance criteria — line-by-line

Per the dispatch prompt’s ACCEPTANCE CRITERIA section.

Criterion Evidence
class ActivationJournal — append-only; private entries; append, current, at, all methods src/domains/rules/activation.ts:213-303. Group 1–5 tests (F1–F5, 25 cases).
interface JournalEntry { epoch: bigint, version_hash: string, cause: 'initial' \| 'migration' \| 'rollback' } activation.ts:101-105. Validated in F2 and across the test suite.
scheduleActivationtarget_epoch > current_epoch strictly enforced activation.ts:329-360. F6 (happy path) + F7 (target == current rejection per prompt fixture 1) + F8 (target < current rejection).
scheduleActivation returns the canonical 6-field ActivationToken from P1.5.2 The function takes a pre-constructed ActivationToken (re-exported from ./migration.js) and returns the same reference unchanged. The contract documents this strategy (sec 4.2). F12 verifies end-to-end that the token returned by migrateRuleset flows verbatim through scheduleActivation.
applyActivation(token, journal, current_epoch) — applies only when current_epoch >= token.target_epoch; appends migration entry with version_hash = token.version_hash activation.ts:368-400. F11.a (boundary case current==target) + F11.b (delayed application) + F11.c (length grows by 1) + F11.d (verbatim version_hash).
rollback(journal, target_version, current_epoch, dispute_window_open) — looks up target_version in prior journal entries; appends rollback entry; invokes hook if dispute window activation.ts:413-481. F13 (rollback + past events stand) + F14 (hook invoked w/ correct snapshot) + F15 (no hook outside dispute) + F16 (target not found rejection) + F17 (non-monotonic rejection).
Rollback does NOT retroactively invalidate events admitted under rolled-back version F13 explicitly asserts journal.at(150n).version_hash === HASH_B after rollback to HASH_A at epoch 200n. The B-era entry is preserved.
Test fixtures cover all six scenarios + non-monotonic + ActivationToken verbatim All six prompt fixtures present (F7 = prompt #1, F10 = #2, F11 = #3, F13 = #4, F14+F18 = #5, F2.b = #6). F12 covers ActivationToken verbatim end-to-end.
Determinism scanner clean The src/__tests__/domains/rules/determinism.test.ts Group 12 corpus self-scan (87 tests) passes. activation.ts contains no forbidden tokens.
ActivationToken consumed verbatim from migration.ts import type { ActivationToken } from './migration.js' in activation.ts:74. Re-exported at line 80. F12 asserts journal.current().version_hash === token.version_hash end-to-end.

3. Test evidence — group-by-group

3.1. Group 1 — Construction (F1) — 6 tests

ActivationJournal construction (F1)
  √ default initial epoch is 0n (X ms)
  √ custom initial epoch is preserved
  √ rejects empty initial_version_hash
  √ rejects non-string initial_version_hash
  √ rejects non-bigint initial_epoch
  √ initial entry is frozen

All passing. Boot integrity confirmed: every journal starts with exactly one frozen 'initial' entry.

3.2. Group 2 — append (F2) — 8 tests

ActivationJournal.append (F2)
  √ accepts strict-greater epoch
  √ rejects equal epoch (non-monotonic)
  √ rejects lesser epoch (non-monotonic)
  √ rejects non-bigint epoch
  √ rejects non-string version_hash
  √ rejects bad cause
  √ frozen entry shape — appended entry is frozen
  √ rejects 'initial' cause after construction

All passing. Append-only + monotonic epoch invariants enforced. Initial cause cannot be re-inserted.

3.3. Group 3 — current (F3) — 2 tests

ActivationJournal.current (F3)
  √ returns last entry after construction
  √ updates after each append

All passing.

3.4. Group 4 — at (F4) — 6 tests

ActivationJournal.at (F4)
  √ returns initial for epoch == initial_epoch
  √ returns initial for epoch < first migration epoch
  √ returns migration entry at exactly its epoch
  √ returns rollback entry at exactly its epoch
  √ throws for epoch < initial_epoch
  √ throws for non-bigint

All passing. Inter-era lookup behaves correctly: initial era (0–99 if migration is at 100), migration era (100–199), rollback era (200+).

3.5. Group 5 — all (F5) — 3 tests

ActivationJournal.all (F5)
  √ returns frozen array
  √ mutation of returned array is rejected
  √ returns shallow copy — subsequent appends do not appear in earlier returns

All passing. Defensive copy behaviour confirmed.

3.6. Group 6 — scheduleActivation happy path (F6) — 2 tests

scheduleActivation happy path (F6)
  √ target_epoch > current_epoch returns same token reference
  √ preserves all 6 fields verbatim

All passing. The 6-field shape is checked field-by-field.

3.7. Group 7 — scheduleActivation rejections (F7+F8+F9) — 5 tests

scheduleActivation rejections (F7+F8+F9)
  √ rejects target_epoch == current_epoch (F7 / prompt fixture 1)
  √ rejects target_epoch < current_epoch (F8)
  √ rejects journal not an ActivationJournal instance (F9.a)
  √ rejects token missing version_hash (F9.b)
  √ rejects token.parity_pass not literal true (F9.c)

All passing. The parity_pass !== true rejection guards against malicious parity-fail bypass.

3.8. Group 8 — applyActivation rejections (F10) — 2 tests

applyActivation rejections (F10)
  √ rejects current < target (prompt fixture 2)
  √ rejects journal not an ActivationJournal instance

All passing.

3.9. Group 9 — applyActivation happy path (F11) — 4 tests

applyActivation happy path (F11)
  √ current == target succeeds (boundary case)
  √ current > target succeeds (delayed application)
  √ journal grows by exactly 1
  √ journal head's version_hash === token.version_hash (verbatim consumption)

All passing. Boundary-equal case (current_epoch === target_epoch) is the earliest legal application — confirmed.

3.10. Group 10 — ActivationToken consumed verbatim end-to-end (F12) — 1 test

ActivationToken consumed verbatim end-to-end (F12)
  √ migrateRuleset token flows verbatim through scheduleActivation + applyActivation

Passing. This is the load-bearing test that proves:

  1. migrateRuleset returns the canonical 6-field shape (not the stale 3-field template).
  2. scheduleActivation accepts that shape and returns the same reference.
  3. applyActivation consumes token.version_hash (not a fictional token.new_version).
  4. journal.current().version_hash === token.version_hash end-to-end.

3.11. Group 11 — rollback happy path + past events stand (F13) — 3 tests

rollback happy path + past events stand (F13)
  √ rollback to prior version appends a rollback entry
  √ past events stand — at(epoch in B-era) still returns B
  √ rollback updates journal length

All passing. The “past events stand” assertion is the contract-layer guarantee that protects event-history integrity.

3.12. Group 12 — rollback governance hook (F14+F15+F18+F19) — 5 tests

rollback governance hook (F14+F15+F18+F19)
  √ F14 — dispute_window_open=true invokes hook with correct snapshot
  √ F15 — dispute_window_open=false does NOT invoke hook
  √ F18 — explicit hook param overrides default
  √ F18 — default hook is invoked when no explicit hook is provided
  √ F19 — hook errors propagate; journal already updated

All passing. The hook is a notification seam, not a veto. F19 explicitly asserts that the journal is in its post-rollback state even when the hook throws.

3.13. Group 13 — rollback rejections (F16+F17) — 8 tests

rollback rejections (F16+F17)
  √ F16 — rejects target_version not found in prior entries
  √ F16 — rejects rolling back to current head version (head excluded)
  √ F17 — rejects non-monotonic epoch (prompt fixture 6)
  √ F17 — rejects non-bigint current_epoch
  √ F17 — rejects non-boolean dispute_window_open
  √ F17 — rejects non-function hook
  √ F17 — rejects empty target_version
  √ F17 — rejects journal not an ActivationJournal instance

All passing. Exhaustive shape validation.

3.14. Smoke imports — 1 test

smoke imports
  √ intLit / effect helpers are reachable (mirrors migration.test.ts)

Passing. Keeps the test file’s import surface honest.

4. Determinism scanner — corpus self-scan

src/__tests__/domains/rules/determinism.test.ts Group 12 (“rule-engine corpus self-scan”) iterates every .ts file in src/domains/rules/ (excluding determinism.ts) and applies twelve forbidden-pattern regexes to a comment-stripped source. The scan is part of the standard npm test run.

Result: green. activation.ts introduces zero new forbidden tokens.

Test Suites: 1 passed, 1 total
Tests:       87 passed, 87 total

(The 87 tests come from determinism.test.ts overall — covering integer-math, BPS-constants, the corpus self-scan, etc. Group 12 is the corpus self-scan group.)

5. Determinism — pure module guarantee

activation.ts is a pure synchronous library:

  • No I/O. No DB access. No network. No filesystem.
  • No Date, no Math.random, no clock.
  • No async, no await, no Promise. Synchronous only.
  • No float math. All epoch comparisons are bigint.
  • No hashing. The 71-char 'sha256:'-prefixed strings are passed through opaquely.
  • No env reads. No globals.
  • The governance_review_hook is a void function; the seam is sync.

Two callers on any host (Node ≥ 20, any platform, any locale) produce byte-identical journal-state outputs for byte-identical inputs.

6. ActivationToken consumption strategy — explicit note

Per the dispatch prompt’s CRITICAL OVERRIDE block, the source-prompt template (line 2529) declares a stale 3-field ActivationToken shape that does NOT match the canonical 6-field shape exported by migration.ts:136-143 (P1.5.2, PR #216, merged at 1a3ea59b).

This implementation consumes the canonical 6-field shape verbatim. The strategy is accept-already-constructed:

  • scheduleActivation accepts an ActivationToken as input (not raw fields). The token must already carry parity_pass: true, scope_signature, and issued_old_version — fields only migrateRuleset can populate correctly.
  • activation.ts re-exports ActivationToken from ./migration.js (line 80) so external consumers can import { ActivationToken } from './activation.js'.
  • F12 verifies end-to-end that a token from migrateRuleset flows through scheduleActivation (returns same reference) and applyActivation (writes token.version_hash to the journal).
  • The validateActivationTokenShape validator (activation.ts:175-191) checks all 6 fields including the literal parity_pass === true invariant.

The contract (docs/contracts/p1-5-3-activation-contract.md sec 4.2) documents the rationale for accept-already-constructed in detail.

7. Pre-existing flakiness check

The pre-existing startup — subprocess smoke flake noted in memory was NOT hit during R87 Wave 8 verification. The full test suite ran clean on first attempt.

8. File summary

File LOC Purpose
src/domains/rules/activation.ts 604 Public surface implementation.
src/__tests__/domains/rules/activation.test.ts 790 56 test cases across 13 describe blocks.
docs/audits/p1-5-3-activation-audit.md 179 Step 1 surface inventory.
docs/contracts/p1-5-3-activation-contract.md 337 Step 2 behavioral contract.
docs/packets/p1-5-3-activation-packet.md 258 Step 3 execution plan.
docs/verification/p1-5-3-activation-verification.md this file Step 5 evidence.

Total new lines: ~2200 (1394 source + tests + 800 docs).

9. Commit chain

Step SHA Message
1 ddcde9cb audit(p1-5-3): inventory surface
2 9dfa6092 contract(p1-5-3): behavioral contract
3 de323800 packet(p1-5-3): execution plan
4 4d1e8e29 feat(p1-5-3): activation epoch + rollback
5 (this commit) verify(p1-5-3): test evidence

Base SHA: 69bc4714 (post-Wave-7 main). The five-commit chain rebases cleanly on origin/main.

10. Phase 1 close

P1.5.3 is the final task of Phase 1 κ rule engine. With this slice merged:

  • κ Phase 1 progress: 20 / 20 tasks done (100%).
  • Total phase coverage:
    • P1.1.x — base library (constants, determinism, IntegerMath) — Wave 0–1
    • P1.2.x — DSL pipeline (lexer, parser, validator, registry) — Waves 2–5
    • P1.3.x — runtime (engine, builtins, state-access, policy-gate) — Waves 4–5
    • P1.4.x — admission (eval, denial reasons, budget, history) — Waves 6–7
    • P1.5.x — versioning (hash, migration, activation (this slice), canonical, parity) — Waves 5–8

Operational rollout (the slice’s downstream gate) is now unblocked.

11. Step 5 sign-off

All criteria met. Build green, lint green, 2361 tests pass (+56 new). Determinism scanner clean. ActivationToken consumed verbatim from migration.ts. Past-events-stand guarantee verified by F13. Governance hook seam wired and tested.

Ready for Gate (already passed locally) → push → PR → merge → writeback.


Back to top

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

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