Audit — P4.2.3 Axiom Drift Tracker (μ Phase 4 Wave 2 detector)

Purpose

Inventory the surface that P4.2.3 consumes before writing one line of detector code: (1) the P4.1.1 envelope shape + helpers, (2) the κ P1.1.1 bigint BPS primitives, (3) the λ P2.2.2 parameter-change event surface (or its absence), (4) the AX-01..AX-07 enumeration from the constitution, (5) the integrity.md §3 algorithm pseudocode, and (6) the sibling-detector scope boundary (P4.2.1 / P4.2.2 own circular.ts and coercion.ts in parallel — P4.2.3 owns ONLY drift.ts).

Base SHA

49560518feat(p4-1-1-advisory-envelope): μ Phase 4 Wave 1 — 8-field advisory envelope (P4.1.1) (#271). Branched from origin/main.

What exists upstream (consumed via REUSE, never duplicated)

P4.1.1 envelope — src/domains/integrity/schema.ts

Public surface confirmed live at 49560518:

  • AdvisoryRoleSchemaz.enum(['Translator','Sentinel','Guide'])
  • AdvisoryCheckSchemaz.enum(['circular_logic','coercion_trap','axiom_drift','axiom_regression']) — both P4.2.3 check codes (axiom_drift and axiom_regression) are pre-declared in the closed enum. No schema change is required by this slice.
  • AdvisoryResultSchemaz.enum(['PASS','WARN','BLOCK']) (3-valued; the HARD-vs-soft distinction is carried by check per design invariant 9).
  • AdvisorySeveritySchemaz.enum(['LOW','MED','HIGH']) (R91 audit Q6, λ-aligned).
  • AdvisorySchema — z.object with 8 fields including evidence: z.array(z.unknown()), decision_hash: z.string().regex(/^[a-f0-9]{64}$/), timestamp_logical: z.bigint().nonnegative().
  • Advisoryz.infer<typeof AdvisorySchema> (TS interface).
  • computeDecisionHash(role, check, input, result): stringsha256(role || '||' || check || '||' || canonical(input) || '||' || result).
  • DECISION_HASH_REGEX/^[a-f0-9]{64}$/.

P4.2.3 will call computeDecisionHash exactly once per advisory (one for each axiom_drift WARN/BLOCK, one for each axiom_regression BLOCK). The input preimage is the canonical-JSON-encodable subset of the call inputs that identifies the advisory semantically; for axiom_drift it is { domain, magnitude_bps, window_ms, threshold_bps }; for axiom_regression it is { proposal_id, axiom }. This keeps the dedup key discriminating without re-encoding the full envelope (κ canonical handles the rest at serialize time).

κ P1.1.1 bigint BPS primitives — src/domains/rules/integer-math.ts + bps-constants.ts

  • BPS_MAX = 10_000n, BPS_MIN = 0n (the integer-math range for BPS values).
  • OverflowError, UnderflowError — thrown by bps(...) validator outside [BPS_MIN, BPS_MAX]. P4.2.3 does NOT use the bps(...) validator — delta_bps in a parameter-change row may be negative (parameter was reduced) or positive (parameter increased), and the magnitude calculation is the absolute value, sum-bounded by the 10% AX-06 cap. The detector operates on raw bigint and uses the existing (x < 0n ? -x : x) idiom to take absolute value — JavaScript Math.abs does NOT accept bigint (gotcha §Common gotchas in p4.1-mu-integrity.md L843).

No new κ primitive is required. P4.2.3 will not import bps-constants.ts because the WARN/BLOCK thresholds are domain-specific to μ axiom drift (separate from the λ damage-tier constants like DAMAGE_MINOR); they live as module-local constants in drift.ts.

Constitution — docs/3-world/physics/constitution.md §AX-01..AX-07

The seven immutable axioms μ guards (file confirmed live at 49560518, identical text in docs/3-world/physics/laws/constraints.md §Constitutional Axioms L583-624):

  1. AX-01 — Append-Only Events: events are never deleted; corrections are new events.
  2. AX-02 — Reputation is Derived: reputation is computed from history, never assigned administratively.
  3. AX-03 — No Absolute Authority: no role (admin/owner/arbitrator) can bypass consequences or reset reputation.
  4. AX-04 — Consequence Windows: subjects get an admission window to contest a proposed sanction.
  5. AX-05 — Subjective Finality: events become fact when valid + signed + accepted by counterparties.
  6. AX-06 — Right to Exit: participants may fork with reputation penalty ≤ 10% (this is where the 10% / 1000 bps drift cap derives — exit cost ceiling).
  7. AX-07 — Technical Sovereignty: each node validates independently; no centralized arbiter can force states.

P4.2.3’s AxiomId union must enumerate exactly these seven values. The would_reduce_invariant(ax) callback on a StagedProposal is provided by the caller (governance / β scheduler) — μ does NOT simulate axioms itself in this slice. P4.2.3 enumerates the seven axiom IDs and queries the callback once per (proposal × axiom) pair.

The 10% / 1000 bps BLOCK threshold and 8% / 800 bps WARN threshold are derived from AX-06 per integrity.md L102-105 (“AX-06-derived cap: cumulative change must stay within 1000 bps (10%)”). These numbers are governance parameters that may be tuned in Phase 6 π governance — for Phase 4 they are module-local constants per the dispatch packet.

integrity.md §3 — docs/3-world/physics/enforcement/integrity.md L87-115

The algorithm reference (confirmed live at 49560518):

fn check_axiom_drift(domain, now):
    window    = 6_months
    changes   = parameter_changes(domain, since=now - window)
    magnitude = sum(abs(c.delta_bps) for c in changes)

    if magnitude >= 800:  # 8% warning threshold
        emit ADVISORY(check="axiom_drift", severity="MEDIUM", domain=domain)
    if magnitude >= 1000:  # 10% = cap
        emit ADVISORY(check="axiom_drift", severity="HIGH", domain=domain)
        return BLOCK_NEW_PROPOSALS(domain)

    for proposal in staged_proposals(domain):
        if proposal.would_reduce_invariant(AX_01 through AX_07):
            emit ADVISORY(check="axiom_regression", severity="HIGH")
            return HARD_BLOCK(proposal)

The pseudocode says MEDIUM and the envelope says MED — P4.2.3 emits MED per the P4.1.1 closed enum (R91 audit Q6 λ-aligned). The Phase 4 narrative MEDIUM is a doc-only label; the code uses MED.

The pseudocode’s return BLOCK_NEW_PROPOSALS / return HARD_BLOCK are escalation-FSM actions consumed by P4.4.1 (Wave 3). In our 3-valued envelope both surface as result: 'BLOCK'. The HARD-vs-soft distinction is carried by the check field — axiom_regression routes (in P4.4.1) to α tool-lock, axiom_drift BLOCK routes to π proposal intake. P4.2.3’s job is to emit the correct check; routing is downstream.

λ P2.2.2 parameter-change events — src/domains/reputation/penalties.ts

Live audit at 49560518: P2.2.2 defines ReputationHistoryRow and apply_penalty(...), which writes delta (a NEGATIVE number per AX-09 “history_event.delta <= 0”) into reputation_history. reputation_history is for REPUTATION decay/penalty events on node_id × domain — it is NOT a parameter-change feed.

There is no parameter_changes table in the shipped schema at 49560518. The integrity.md pseudocode’s parameter_changes(domain, since=...) is a future surface — a governance-rule-change ledger fed by π proposals ENACTED into rule version bumps. The shape used by P4.2.3 is the dispatch-packet shape verbatim:

type ParameterChange = {
  domain: string;
  delta_bps: bigint;
  timestamp_logical: bigint;
};

This is a feed adapter, injected by the caller (β scheduler / P4.4.1 escalation FSM in Wave 3). The detector is pure with respect to its inputs — no DB read, no clock read. The caller is responsible for filtering / paginating / persisting the events.

This is the key finding for Wave 3 P4.4.1 — the escalation FSM must construct the ParameterChange[] feed from whatever shipped persistence exists at the time (π governance rule-version history, or a fresh mcp_parameter_changes table created by P4.5.1’s persistence slice). The detector contract decouples from the source.

θ P3.1.1 messaging — src/domains/consensus/messages.ts (pattern reference only)

Pattern reference only — P4.2.3 does NOT import from θ. The Lamport bigint timestamp_logical injection idiom is borrowed; the createHash + canonicalize pattern is borrowed by way of computeDecisionHash already shipped in P4.1.1.

What does NOT exist (P4.2.3 ships these — only its files)

src/domains/integrity/detectors/ directory

Confirmed absent at 49560518. src/domains/integrity/ contains only schema.ts from P4.1.1. P4.2.3 creates the detectors/ subdirectory and its single file drift.ts. Two sibling files (circular.ts, coercion.ts) are owned by parallel agents P4.2.1 / P4.2.2 in different worktrees / different branches — P4.2.3 MUST NOT touch them (Wave 2 file-disjoint pattern, mirrors R92 W3 swarm).

Test directory layout

The repo convention (per jest.config.ts roots: ['<rootDir>/src'] + testMatch: ['**/__tests__/**/*.test.ts', ...]) places tests under src/__tests__/domains/<axis>/. P4.1.1’s test sits at src/__tests__/domains/integrity/schema.test.ts. P4.2.3 will place its test at src/__tests__/domains/integrity/detectors/drift.test.ts mirroring the source path under __tests__.

(The dispatch prompt §FILES TO CREATE suggests an in-tree src/domains/integrity/detectors/__tests__/drift.test.ts layout. Both locations satisfy the Jest testMatch glob; we follow the repo’s established src/__tests__/domains/... layout to keep colocation consistent with the P4.1.1 envelope test.)

Boundary recap — what P4.2.3 OWNS

  • src/domains/integrity/detectors/drift.ts — exports WINDOW_MS, WARN_THRESHOLD_BPS, BLOCK_THRESHOLD_BPS, AXIOM_IDS, type AxiomId, type ParameterChange, type StagedProposal, function checkAxiomDrift(...).
  • src/__tests__/domains/integrity/detectors/drift.test.ts — Jest suite covering 13+ acceptance cases plus 100× determinism sweep plus static-scanner forbids.

Boundary recap — what P4.2.3 DOES NOT TOUCH

  • src/domains/integrity/schema.ts — frozen at 49560518. P4.2.3 consumes its exports; it does not edit them.
  • src/domains/integrity/detectors/circular.ts — owned by P4.2.1 (parallel).
  • src/domains/integrity/detectors/coercion.ts — owned by P4.2.2 (parallel).
  • κ / λ / θ / β / α / ζ / η / ε / δ / ν / γ source — read-only references.
  • docs/3-world/physics/enforcement/integrity.md — spec source, read-only.
  • docs/3-world/physics/constitution.md — axiom source, read-only.
  • Any file outside src/domains/integrity/detectors/drift.ts + src/__tests__/domains/integrity/detectors/drift.test.ts + the four 5-step doc files under docs/{audits,contracts,packets,verification}/.

Unresolved questions for the contract step

  1. Severity for the WARN advisory: pseudocode says MEDIUM, envelope enum has MED. Resolution: emit MED (P4.1.1 schema is the truth).
  2. recommendation text contract: the envelope requires a recommendation: z.string(). The detector emits a deterministic fixed-string recommendation per check code so two callers with identical inputs produce byte-identical advisories — non-determinism in recommendation text would break the dedup invariant (P4.1.1 contract §I1).
  3. evidence shape: must be canonical-encodable. The detector packs [{ kind: 'parameter_change', domain, timestamp_logical, delta_bps_abs }, ...] for axiom_drift, and [{ kind: 'staged_proposal', proposal_id, axiom }] for axiom_regression. Bigints are canonical-encodable (κ P1.5.4 emits them as decimal strings).
  4. AXIOM_IDS export: needed by P4.4.1 (escalation FSM iterates the same set) and P4.8.1 (fork-hook subscriber may reset cumulative drift on FINAL fork). Exported as a readonly tuple in drift.ts.

The contract step pins each of these into invariants.

Phase 4 Wave 2 swarm context

P4.2.3 is one of three parallel detectors in Wave 2:

  • P4.2.1circular.ts (cycles in evidence-chain DAG)
  • P4.2.2coercion.ts (no-escape decision traps)
  • P4.2.3drift.ts (this task — sliding-window cumulative + per-AX regression)

File-disjoint. No shared imports between detectors. Each agent owns exactly one .ts under detectors/ plus its test mirror. Wave 3 P4.4.1 will compose the three into an escalation FSM; until then, each is a pure standalone library.

Conclusion

P4.2.3 ships a single pure-function module + its test. The envelope, the hashing, the canonical serializer, the closed enums, and the AX-01..AX-07 list are all live upstream — P4.2.3 consumes them. The only new vocabulary is ParameterChange, StagedProposal, AxiomId, AXIOM_IDS, WINDOW_MS, WARN_THRESHOLD_BPS, BLOCK_THRESHOLD_BPS, and the checkAxiomDrift function. Wave 3 P4.4.1 will consume these without modification.


Back to top

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

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