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. |
scheduleActivation — target_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:
migrateRulesetreturns the canonical 6-field shape (not the stale 3-field template).scheduleActivationaccepts that shape and returns the same reference.applyActivationconsumestoken.version_hash(not a fictionaltoken.new_version).journal.current().version_hash === token.version_hashend-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, noMath.random, no clock. - No
async, noawait, noPromise. 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_hookis avoidfunction; 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:
scheduleActivationaccepts anActivationTokenas input (not raw fields). The token must already carryparity_pass: true,scope_signature, andissued_old_version— fields onlymigrateRulesetcan populate correctly.activation.tsre-exportsActivationTokenfrom./migration.js(line 80) so external consumers canimport { ActivationToken } from './activation.js'.- F12 verifies end-to-end that a token from
migrateRulesetflows throughscheduleActivation(returns same reference) andapplyActivation(writestoken.version_hashto the journal). - The
validateActivationTokenShapevalidator (activation.ts:175-191) checks all 6 fields including the literalparity_pass === trueinvariant.
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.