P1.5.3 — Audit

Step 1 of the 5-step executor chain. Inventories the surface this slice creates, the integration points it consumes, and the doc/spec drift it must respect.

1. Mandate

Per docs/guides/implementation/task-prompts/p1.1-kappa-rule-engine.md §P1.5.3 (lines 2478–2617):

Implement activation scheduling, apply, and rollback flows with an append-only journal.

P1.5.3 is the operational counterpart to P1.5.2. P1.5.2 produces ActivationTokens (proof-grade migration receipts); P1.5.3 consumes them by registering them in a journal and swapping rule versions when the runtime epoch crosses target_epoch. P1.5.3 also adds rollback — a way to restore a prior version at the current epoch — and a stub seam for π governance review during dispute windows.

2. Files this slice creates

Path Purpose
src/domains/rules/activation.ts Public surface: ActivationJournal class, JournalEntry interface, scheduleActivation, applyActivation, rollback, governance_review_hook stub, ActivationError.
src/__tests__/domains/rules/activation.test.ts Test fixtures covering all six prompt-required scenarios + ActivationToken consumption parity + governance hook routing.
docs/audits/p1-5-3-activation-audit.md This file.
docs/contracts/p1-5-3-activation-contract.md Behavioral contract.
docs/packets/p1-5-3-activation-packet.md Execution plan.
docs/verification/p1-5-3-activation-verification.md Post-implementation evidence.

No file is mutated outside this list. The 5-step chain artefacts live in docs/{audits,contracts,packets,verification}/. The runtime surface is one new module in src/domains/rules/ colocated with peers (engine.ts, migration.ts, versioning.ts, etc.).

3. Integration points (read-only consumers)

3.1. P1.5.2 migration.ts — ActivationToken (CRITICAL)

The dispatch prompt’s source template at p1.1-kappa-rule-engine.md line 2529 declares a stale 3-field shape:

interface ActivationToken { new_version: string, target_epoch: bigint, proposal_id: string }

This is WRONG. The dispatch prompt’s CRITICAL OVERRIDE block establishes that P1.5.2 (Wave 7, merged at 1a3ea59b) shipped the canonical 6-field shape exported from src/domains/rules/migration.ts. The shape, verified by direct read of migration.ts:136-143:

export interface ActivationToken {
  readonly version_hash: string;        // 'sha256:<64hex>' — recomputed new ruleset
  readonly target_epoch: bigint;        // when this token activates
  readonly issued_at_epoch: bigint;     // current_epoch at issue time
  readonly parity_pass: true;           // literal-typed sentinel — only emitted on parity pass
  readonly scope_signature: string;     // 'sha256:<64hex>' over canonical(normalized_scope)
  readonly issued_old_version: string;  // 'sha256:<64hex>' — recomputed old, for chain audit
}

Properties verified by reading migration.ts:617-624:

  • Token is Object.frozen at construction.
  • Exactly six own enumerable keys (no extras).
  • parity_pass === true literal — sentinel proving parity-pass provenance.
  • All three hash fields are 71 chars total (7-char 'sha256:' prefix + 64 lowercase hex).

P1.5.3 must consume this canonical shape verbatim. The journal entry’s version_hash field maps to token.version_hash (NOT the stale token.new_version). The audit ledger’s chain audit lookup, when added later, will use token.issued_old_version for old→new continuity verification.

P1.5.3 does not re-construct an ActivationToken. The token is produced exclusively by migrateRuleset (which has the parity-harness output, the recomputed hashes, and the scope-signature inputs). scheduleActivation accepts an already-constructed token and registers it for later application, which keeps the parity-pass invariant intact: a token cannot exist without parity having passed.

Re-export: P1.5.3 re-exports ActivationToken from ./migration.js so consumers can import it from one place (./activation.js). This avoids cross-module import noise downstream.

3.2. P1.5.1 versioning.ts

Used indirectly. ActivationToken.version_hash and JournalEntry.version_hash are the strings versioning.ts produces (71-char 'sha256:<64hex>'). No direct call from activation.ts to versioning.ts — the migration module already routes through it before issuing a token.

3.3. P1.5.4 canonical.ts

Not consumed directly. Indirect dependency through ActivationToken.scope_signature (computed at migration time using computeScopeSignature which uses canonicalize).

4. Spec & contract anchors

4.1. Acceptance criteria from dispatch prompt

The dispatch prompt enumerates eight acceptance items. They are restated here for traceability with the contract that follows. Quoting items 1–6 verbatim:

  1. class ActivationJournal — append-only; entries private; methods: append(entry: JournalEntry): void (rejects non-monotonic epoch), current(): JournalEntry, at(epoch: bigint): JournalEntry, all(): readonly JournalEntry[].
  2. interface JournalEntry { epoch: bigint, version_hash: string, cause: 'initial' | 'migration' | 'rollback' }.
  3. scheduleActivation(journal, new_version, target_epoch, current_epoch): ActivationTokentarget_epoch MUST be strictly greater than current_epoch; throws on violation.
  4. scheduleActivation returns the canonical 6-field ActivationToken from P1.5.2 (import { ActivationToken } from './migration.js').
  5. applyActivation(token, journal, current_epoch): void — applies only when current_epoch >= token.target_epoch; appends JournalEntry with cause='migration' and version_hash = token.version_hash.
  6. rollback(journal, target_version, current_epoch, dispute_window_open): void — looks up target_version in journal (must exist prior to current entry); appends JournalEntry { epoch: current_epoch, version_hash: target_version, cause: 'rollback' }; if dispute_window_open === true invokes governance_review_hook stub for π.
  7. Rollback does NOT retroactively invalidate events admitted under rolled-back version.
  8. Test fixtures cover all six scenarios + non-monotonic + ActivationToken verbatim consumption.

4.2. Spec sources

  • docs/spec/s11-rule-engine.md — references Epoch number, rule version hash in §1 context. No direct activation flow text; activation is operational, not part of the law-library spec.
  • docs/3-world/physics/laws/rule-engine.md §Rule versioning (lines 139–156) — describes rule_version_hash, θ consensus signing, ι fork-id derivation, and the parity-run prerequisite. P1.5.3’s journal embodies the post-parity activation lifecycle: after migrateRuleset says “yes,” activation happens here.
  • docs/reference/extractions/kappa-rule-engine-extraction.md §5 — referenced by dispatch prompt; describes the activation lifecycle at extraction time.

4.3. Constitutional considerations

  • Append-only journal (AX-04 informally — “no retroactive change”). Past JournalEntry records are never rewritten or removed; rollback adds a new entry rather than replacing the offending one. The consequence: journal.at(historical_epoch) returns the version that was actually active at that epoch, regardless of later rollbacks. This preserves event history integrity.
  • Synchronous operations — no async in any public method. Per the prompt’s “Common gotchas” section: “an async append leaves a window where two rollbacks race.”
  • Determinism — same inputs to scheduleActivation produce same outputs (the input itself comes from migration.ts which is fully deterministic). Object.freeze on every emitted token.

5. Determinism corpus self-scan compatibility

activation.ts will be scanned by src/__tests__/domains/rules/determinism.test.ts §Group 12. The scanner forbids these tokens (post-comment-strip):

Pattern Avoidance strategy
Math.* None used; the body has no math (epoch comparisons via > < operators on bigint).
Date.*, new Date None. No timestamps.
setTimeout, setInterval, setImmediate None. Synchronous only.
fetch, XMLHttpRequest None. Pure module.
require\(['"]fs['"]\), from 'fs' None. No file I/O.
crypto.<member> None. No hashing in this module — token already carries hashes. (Pre-existing peers like versioning.ts:72 use a NAMED import { createHash } from 'node:crypto' to avoid the pattern; this module imports nothing from crypto.)
process.hrtime, process.nextTick None.
await, async function None. Synchronous only.
\d+\.\d+ float literal None. No float math; only bigint.

JSDoc comments are stripped before the scan, so verbatim references to Math.floor-style tokens in prose comments are tolerated; the body itself stays clean.

6. Public surface inventory

Export Kind Purpose
ActivationToken re-export The 6-field ActivationToken from ./migration.js, re-exported for ergonomics.
JournalEntry interface Single tuple in the journal: { epoch: bigint, version_hash: string, cause: 'initial' \| 'migration' \| 'rollback' }.
JournalCause type alias 'initial' \| 'migration' \| 'rollback'.
ActivationJournal class Append-only log with monotonic-epoch invariant.
ActivationError error class Thrown for invariant violations (non-monotonic epoch, target_epoch <= current_epoch, apply-too-early, rollback-version-not-found, rollback at non-monotonic epoch, governance-hook violation).
GovernanceReviewHook type alias Function shape for the π governance seam ((snapshot: GovernanceReviewSnapshot) => void).
GovernanceReviewSnapshot interface Read-only payload passed to the governance hook (target_version, current_epoch, prior_current_entry, journal_length).
governance_review_hook const function Default no-op stub implementation; replaceable per call.
scheduleActivation(journal, token, current_epoch) function Validates token.target_epoch > current_epoch; returns the same token (or throws). Token shape is left unchanged — the function is a guard, not a constructor.
applyActivation(token, journal, current_epoch) function Validates current_epoch >= token.target_epoch; appends JournalEntry { epoch: current_epoch, version_hash: token.version_hash, cause: 'migration' }.
rollback(journal, target_version, current_epoch, dispute_window_open, hook?) function Validates target_version is present in a prior entry; appends rollback entry; invokes hook on dispute window.

A note on the prompt signature for scheduleActivation. The source prompt says:

scheduleActivation(journal, new_version, target_epoch, current_epoch): ActivationToken

Per the CRITICAL OVERRIDE in the dispatch prompt, the canonical 6-field token has 5 of 6 fields that depend on data only migrateRuleset has access to (scope_signature requires the declared scope; issued_old_version requires a recomputed old hash; parity_pass is the migration-pass sentinel). It is therefore impossible for scheduleActivation to construct a valid token from a string + epoch + journal alone.

The contract resolves this by having scheduleActivation accept a pre-constructed ActivationToken (already issued by migrateRuleset) and validate its target_epoch > current_epoch invariant. This is documented in §4 of the contract.

7. Out of scope

  • Persisting the journal to disk — Phase 1.5+.
  • Wiring the governance hook to a real π verifier — Phase 5.
  • Concurrent application coordination across replicas — θ governance concern; Phase 1.5+.
  • Multi-version coexistence (canary deployments) — not part of the κ contract.
  • Engine-side actually swapping the active ruleset at the runtime layer — handled by ι; this slice owns only the journal.

8. Risks & mitigations

Risk Mitigation
Activation applied at exact target_epoch>= boundary off by one. Test fixture apply at target_epoch === target_epoch (boundary-equal case). The implementation uses current_epoch >= token.target_epoch.
Mutating frozen ActivationToken after issuance. Don’t write to the token. Re-export (typeof unchanged); pass by reference; trust Object.freeze enforcement from migration.ts.
Rollback to a version that was activated at the same epoch as current_epoch. The contract requires “prior to current entry,” interpreted as: target_version must appear in a JournalEntry whose epoch < current_epoch (or in the unique entry that is < journal.current().epoch if current_epoch === journal.current().epoch). Test fixture covers both cases.
Hook throwing under dispute window halts rollback partway. The hook is invoked after the rollback entry is appended. If the hook throws, the journal is already updated. This matches the prompt’s “log that it was called” framing — the hook is a notification seam, not a veto.
Non-monotonic epoch from rollback at exact current_epoch == journal.current().epoch. The journal’s append rejects non-monotonic. Rollback at an epoch equal to the current journal head must be rejected by rollback before reaching append.
at(epoch) lookup before any entry exists. at throws ActivationError. The journal must have at least one 'initial' entry before any other operation runs; this is part of the construction contract.

9. Greek-letter alignment

  • κ (kappa) — primary axis. Activation lifecycle is a κ runtime concern.
  • π (pi) — governance review hook is the seam to π. Stub-only in Phase 1; full wiring is later.
  • θ (theta) — consumes version_hash for consensus signing; the journal is the canonical source of “what version was active at epoch E.”
  • ι (iota) — fork-id derivation reads rule_version_hash from the journal at fork time. Not part of this slice.

10. Step 1 sign-off

Surface inventoried. Integration with P1.5.2 confirmed (canonical 6-field ActivationToken consumed verbatim, NOT the stale 3-field template at p1.1-kappa-rule-engine.md:2529). Determinism scan compatible. No external module is mutated. Step 2 (contract) follows.


Back to top

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

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