CI enforcement of the audit contract
The named audit gates that make the auditability and replayability contract load-bearing in CI — what each clause promises, the gate that fails the build when it breaks, and where the gates run.
The auditability and replayability contract isn't a promise in
prose. Each clause is backed by a named npm run audit:* gate that
fails CI when the invariant breaks. This page reads the
full gate catalog through the lens of those two
contracts: it pairs every clause with the gate that enforces it and
the condition that turns the gate red.
For the exhaustive, subsystem-organized list — package shape, plugin contracts, tool coverage, graph lattice — see Audit gates. This page is the contract-first index into that catalog.
@pleach/coreSourcescripts/audit/How a gate runs
Every gate is an .mjs or .ts script invoked through npm:
npm run audit:<name>Most are pure source-text scans that finish in under a second. They fall into two shapes:
- Source-text regression. The gate asserts a set of canonical anchors (a flag name, an exported symbol, a column literal) still exist in the production files. Drift in any anchor signals the invariant is being silently rolled back. The script names the anchor in its failure output, so the fix is the file and symbol to restore.
- Soak ledger. The gate reads a rolling window of runtime probe emissions and passes only when the last N batches are clean. Soak gates verify a behavior reached production, not just that the code still compiles.
Suffix conventions are stable across the suite. :strict is the CI
mode that fails on novel drift; :json emits machine-readable
output; :list dumps the inventory; :update-baseline regenerates
the allowlist after intentional drift. Gates exit 0 clean, 1 on
a contract failure, 2 on a config or IO error.
The auditability contract
The promise: every model call writes one typed, tenant-scoped, reconstructable row to the event log. Six gates hold the clauses up.
| Contract clause | Gate | What turns it red |
|---|---|---|
| The row shape is locked and versioned | audit:auditable-call | A fingerprint diff in AuditableCall.ts without a matching AuditRecordVersion bump — the silent-breaking-change detector. Composite of audit:auditable-call-shape (TS wire-format fingerprint) + audit:auditable-call-version. |
| Persisted rows match the typed wire format | audit:auditable-call-soak | A DB-side row missing a required cluster field, a pre-projected flag disagreeing with its input, an audit_record_version below the per-cluster floor (familyLock ≥ 2, cacheBreakpoint ≥ 3, fallbackStep ≥ 4, providerCascade ≥ 5), or a gap in seq_within_turn (the dropped-write tamper signal). Runs --print-sql by default — no DB connection, CI-safe. |
| Every row can reconstruct its substrate | audit:event-log-manifest-hash-completeness | A harness_event_log write site in SessionRuntime.ts or EventLogWriter.ts that neither stamps manifest_hash, routes through the eventToRow wire-layer stamper, nor carries a // @no-manifest-hash-allowlist annotation. manifest_hash is the foreign reference replay uses to recover the runtime config active when the event fired. |
| The referenced manifest actually exists | audit:config-manifest-referential-integrity | An event row pointing at a manifest that isn't present. |
| A referenced manifest survives GC | audit:config-manifest-retention-completeness | A surviving event referencing a manifest that retention dropped. |
| Every event type is scrubber-covered | audit:c8-event-type-allowlist-coverage | An EventLogInput union member with no SCRUBBABLE_FIELDS allowlist entry — an event type that could slip past redaction. |
| Every event type is actually produced | audit:c8-union-member-has-producer | An EventLogInput union member with zero producer call sites. |
| Multi-tenant rows are tenant-scoped | audit:tenant-scoping + audit:harness-event-log-tenant-id-required | A tenant_id-bearing table without an RLS policy, or a harness_event_log write that doesn't thread tenant_id. |
The row shape itself is locked at AuditRecordVersion (currently
18), and AUDIT_RECORD_VERSION_HISTORY records every additive
bump. See the AuditableCall row for the
field-by-field shape.
The replayability contract
The promise: a recorded session re-executes deterministically against an untampered log, and the chain proves the log wasn't mutated after the fact. Four gates hold these clauses up.
| Contract clause | Gate | What turns it red |
|---|---|---|
| The hash-chain wire-in stays intact | audit:c9-hash-chain-integrity | Drift in a C9 anchor (c9PhaseBEnabled, chainStep, verifyChainForChat, generateProof) across EventLogWriter.ts / hashChain.ts / index.ts. Also evaluates a 3-batch soak when probe batches are staged: [UXParity:c9-hash-chain-row-stamp] count > 0, every [UXParity:c9-hash-chain-verify] carrying chainValid: true, and zero legacy-prefix diagnostics. The soak ledger ships empty, so the gate is informational today; :strict is the verifier-CLI promotion gate. |
| Canonicalization is versioned | PLEACH_C9_CANONICALIZATION_VERSION | Pinned at "pleach.c9.v1". verifyChain returns { ok: true } or a row-precise { ok: false, failedIndex, expected, actual, reason }. The verifier and writer share the canonicalization helper by construction. |
| The replay surface can't regress before its next phase | audit:eval-phase-a-coverage | A drift in the 19 Phase A anchors across @pleach/eval's four subpaths (./, ./scorers, ./replay, ./report), or test coverage falling below the floor in __tests__/eval/. :strict gates the Phase B dispatch — Phase B can't land until this holds clean. |
| The graph stays a fixed lattice | audit:graph-stages + audit:edge-inventory-completeness | A compiled cross-stage edge that isn't in the allowed-edge inventory. Replay can only reproduce a graph whose edges are fixed; an out-of-lattice edge breaks deterministic re-execution. |
See Tamper-evident hash chain for what the chain proves and the verifier surface, and Eval and replay for the replay path that reads the chain.
Where the gates run
Three CI jobs carry the contract gates on every merge:
| CI job | What it runs |
|---|---|
graphnoderef | npm run ci:graphnoderef — chains audit:auditable-call, audit:event-log-manifest-hash-completeness, the three audit:config-manifest-* gates, and the graph-lattice gates. |
local-clone | npm run audit:harness-package:local-clone — packs via npm pack, installs into an isolated scratch dir, and smoke-imports SessionRuntime + createPleachRuntime. |
consumer-rehearsal | A per-SKU matrix running npm run audit:consumer-rehearsal:<sku> — the six-phase end-to-end publish rehearsal. |
The per-SKU consumer rehearsal also runs on a weekly cron (Sundays 08:00 UTC) and nightly, so a packaging break that a PR didn't touch still surfaces within a day rather than at the next publish.
Reproducing the gates as a consumer
A host embedding @pleach/core inherits the contract but not the
CI. Two gates are built to be run from outside the source repo:
audit:consumer-rehearsal:corewalks six phases against a packed tarball — build,npm pack+ install of the target and its peers into a scratch dir, facet smoke imports, one subpath import perpackage.json:exportskey, a TypeScript.d.tsshape check understrict, and example runs. It writes only outside the repo and is idempotent across runs.audit:harness-package:local-cloneproves the published surface imports cleanly from an isolated install — the check that catches an unconditional-import blocker before a first publish.
For the wider mirroring pattern — one invariant per script, structured failure output, documented next to the gate — see Audit gates → Mirroring gates in your own repo. The discipline is that adding a new invariant means adding a new gate, not a line on a review checklist.
Where to go next
Audit gates
The full subsystem-organized gate catalog this page indexes by contract.
AuditableCall row
The typed per-call row shape locked at AuditRecordVersion.
Tamper-evident hash chain
What the chain proves and the verifier surface the C9 gate protects.
Eval and replay
The replay surface that reads the chain to assert deterministic re-execution.
Config manifest
The content-addressable substrate snapshot manifest_hash points back to.
Audit gates
The CI-time invariant checks that protect the `@pleach/*` substrate — package shape, plugin contracts, tool coverage, event-log integrity, multi-tenant scoping, and graph lattice.
Gate failures → fixes
Each audit gate's verbatim failure string mapped to what it means and the doc that explains the fix — the lookup an agent reads straight from red CI output to the change.