@pleach/compliance
Regulated-environment bundle — four shipping scrubbers (SSN-US, Luhn, US-DL, KeyedRegex) plus tenant-scoping and event log audit gates that hosts inherit by adopting the package.
Pruning shears at the garden wall — what crosses the boundary, in
what shape, with what scrub. @pleach/compliance is the
regulated-environment add-on to @pleach/core. The first release ships
four Scrubber implementations that gate event log writes, plus two
CI audit gates that hosts inherit at adoption time. The package is aimed at deployments where the audit
ledger is evidence — healthcare, finance, govtech — and where every
harness_event_log row needs both a tenant scope and a redaction
guarantee before it lands on disk.
Beta. While
@pleach/coreships stable,@pleach/complianceships beta for the first release. The scrubbers, tenant-scoping, and CI audit gates below are production-solid and battery-proven; the attestation / audit-report surface is the beta part — envelopes are unsigned by default unless a real key-store is wired, and the version maps are Phase-A-empty (see Attestation). A stable@pleach/complianceattestation claim lands at the 1.0 cut.
The canonical use case is alongside @pleach/core's
EventLogWriter: scrubbers run on the persistence boundary, the CI
gates enforce that no code path writes an untenanted row, and the
schema check guarantees tenant_id NOT NULL after migration.
Install
npm install @pleach/compliancepnpm add @pleach/complianceyarn add @pleach/compliancebun add @pleach/complianceimport {
SsnUsScrubber,
CreditCardScrubber,
UsDriverLicenseScrubber,
KeyedRegexScrubber,
} from "@pleach/compliance";The package exports the four scrubber classes (instantiate with new),
the complianceProfilePlugin(profile) factory that bundles them by
named profile ("hipaa" / "gdpr" / "pci-dss" / "soc2"), the
createComplianceRuntime wrapper for transparent interception, and
the lower-level scrubbersForProfile(profile) helper for hand-rolled
plugins. The published README is the source of truth for export names
and constructor options — check it before pinning a version.
The four bundled scrubbers
Each implements the Scrubber contract from @pleach/core/scrubbers. The
contract — scrub(input, ctx) => ScrubResult returning redacted and
matched spans for audit reporting — is documented in Scrubbers;
this section covers what ships in the bundle.
| Scrubber | Pattern | Redaction key |
|---|---|---|
SsnUsScrubber | US Social Security numbers (NNN-NN-NNNN; opt-in unseparated NNNNNNNNN) with SSA invalid-range guards | [REDACTED:SSN] |
CreditCardScrubber | Credit-card-shaped digit runs validated by Luhn check digit AND IIN range gate | [REDACTED:CARD] |
UsDriverLicenseScrubber | US driver's license numbers — per-state format table, anchored or per-state mode | [REDACTED:DL] |
KeyedRegexScrubber | Generic pattern + key adapter — the building block hosts extend | caller-supplied |
SsnUsScrubber matches the canonical hyphenated form and (when opted
in) the unhyphenated nine-digit form, with SSA invalid-range guards
(area ≠ 000/666/900–999, group ≠ 00, serial ≠ 0000). The redacted
form replaces the matched span with [REDACTED:SSN]; the original
digits never reach storage.
CreditCardScrubber validates against the Luhn check digit AND the
IIN range before redacting. The double gate cuts the false-positive
rate that a naive 16-digit regex would produce against arbitrary
numeric content — order numbers, session ids, and similar digit runs
pass through untouched. Opt out of the IIN gate via
new CreditCardScrubber({ validateIin: false }) if you process exotic
card-network ranges.
UsDriverLicenseScrubber composes a state-keyed pattern table because
the driver's license format varies per state. The bundle ships the
table; consumers that need a state the upstream table doesn't cover
yet should open an issue on the package repo rather than fork the
scrubber.
KeyedRegexScrubber is the parameterized scrubber — the other three
are specializations that bind a fixed pattern and redaction key. The
next section covers it as the extension hook.
KeyedRegexScrubber — the extension hook
KeyedRegexScrubber is the one scrubber consumers commonly instantiate
directly. It's not a custom scrubber — it's a parameterized one — so
it stays inside the contract and inherits the matched-spans audit
reporting that ships with the bundle.
// lib/compliance/tenantMrnPlugin.ts
import { KeyedRegexScrubber } from "@pleach/compliance";
import type { HarnessPlugin } from "@pleach/core";
const mrnScrubber = new KeyedRegexScrubber({
id: "tenant-mrn",
entries: [
{
name: "mrn",
pattern: /\b\d{7}\b/g,
replacement: "[REDACTED:MRN]",
},
],
});
export const tenantCompliancePlugin: HarnessPlugin = {
name: "tenant-mrn-compliance",
contributeScrubbers: () => [mrnScrubber],
};The name (here "mrn") is what shows up in the matched-spans report
and in any downstream audit consumer — pick a name that reads in a
compliance review, not the regex itself. The replacement may be a
string or a function (match, name) => string if the redacted form
needs to be derived from match groups (e.g. preserving the last 4
chars).
For a host with multiple regulated identifier families — MRN,
employee ID, internal account number — register them as separate
entries on a single KeyedRegexScrubber, or use one scrubber per
family if you want the audit report grouped per-scrubber-id. The
per-entry name field is the attribution unit either way.
Inherited audit gates
Adopting @pleach/compliance adds two CI audit gates to the consumer
build. The gates are the load-bearing piece for regulated deployments:
they fail the build before a code path that violates tenant scoping
can reach production.
audit:tenant-scoping
Enforces that every event log write threads tenant_id. The gate
walks the consumer's source for call sites against EventLogWriter
(and its registered wrappers) and fails if any call site can reach
append without a tenant_id resolved from the runtime.
The check is structural — it flags the absence of the threading, not
the value. A code path that resolves tenantId from a request and
then drops it before calling the writer fails the gate at the drop
site, with the drop site named in the failure output.
audit:harness-event-log-tenant-id-required
Schema-level check that harness_event_log.tenant_id is NOT NULL
after migration. The gate reads the consumer's applied schema (via
the standard migration introspection helper) and fails if the column
is nullable.
Run it as part of the post-migration smoke step. A nullable
tenant_id column is the silent-isolation failure mode: writes
succeed, rollups miss rows, and the regulator's "show me tenant X's
events" query under-counts.
Deployment pattern
Adopt the package once at the top of your SessionRuntime
construction via the standard plugin registration. The scrubbers wire
into EventLogWriter automatically; consumer code doesn't call them
directly.
import { SessionRuntime } from "@pleach/core";
import { complianceProfilePlugin } from "@pleach/compliance";
const runtime = new SessionRuntime({
storage,
checkpointer,
userId: req.user.id,
tenantId: req.user.orgId,
plugins: [complianceProfilePlugin("hipaa") /* , gatewayPlugin, ... */],
});complianceProfilePlugin(profile) accepts "hipaa" | "gdpr" | "pci-dss" | "soc2"
and returns a HarnessPlugin whose contributeScrubbers resolves to
the scrubber bundle for that profile. Use scrubbersForProfile(profile)
directly if you need to hand-roll the plugin shape (e.g. composing
your own name + id fields). Custom scrubbers (additional
KeyedRegexScrubber instances for tenant-specific identifiers)
register through their own plugin alongside this one — composition is
additive.
The CI gates are picked up by the consumer's existing audit harness; no per-build wiring beyond installing the package and listing it in the audit config.
What's NOT in this package today
Honest about the scope of the 1.0 cut:
- No gateway-level auth or rate limiting. That lives in
@pleach/gateway@0.1.0. Today's compliance package does not see the outbound provider call. - No SBOM or attestation bundle.
@pleach/trust-packremains reserved at0.0.1 · UNLICENSED. Supply-chain attestation is out of scope for this cut. (Cryptographic attestation of individual audit rows ships in@pleach/core/attestation— see Attestation.) - Deterministic replay over the event log ships in
@pleach/replay@0.1.0(replayTurn,fromSnapshot,fork, andaggregateMultiTenantbodies all land at the0.1.0cut) and@pleach/eval@0.1.0(scoring + diff). The compliance package writes the rows; the replay/eval packages read them. - Tamper-evidence hash chain stamping ships in
@pleach/core/eventLog(chainStep,computeRowHash) behind thec9PhaseBEnabledflag; verification ships in the same@pleach/core/eventLogbarrel (verifyChainForChat,generateProof). TheTamperEvidenceplug-point in@pleach/core/auditkeeps its no-op default for hosts that route attestation elsewhere. - No broader scrubber set. Four scrubbers ship in 1.0. Additional
patterns (international tax IDs, EU formats, sector-specific
identifiers) are planned but not present. Use
KeyedRegexScrubberfor anything outside the bundled four.
Check the package README for the current roadmap before assuming a feature.
Adopting in stages
A host migrating an existing deployment toward mandatory tenant scoping may want the scrubbers immediately but defer the CI gates until the codebase is ready. The gates fail fast — a multi-week migration with the gates on means a broken build for the duration.
Register only the scrubbers via contributeScrubbers from a custom
plugin, and skip the package's full plugin export:
// lib/compliance/scrubbersOnlyPlugin.ts
import {
SsnUsScrubber,
CreditCardScrubber,
UsDriverLicenseScrubber,
} from "@pleach/compliance";
import type { HarnessPlugin } from "@pleach/core";
export const scrubbersOnlyPlugin: HarnessPlugin = {
name: "compliance-scrubbers-only",
contributeScrubbers: () => [
new SsnUsScrubber(),
new CreditCardScrubber(),
new UsDriverLicenseScrubber(),
],
};The scrubbers run; the CI gates stay off. Track the migration as a
real ticket — running the scrubbers without the tenant-scoping
guarantee means redacted rows can still land untenanted, which is a
partial-isolation posture, not a finished one. Flip to the full
complianceProfilePlugin("hipaa") (or the profile matching your
posture) once the codebase passes the gates locally.
Related SKUs
@pleach/core— the substrate this package wraps. Scrubbers run atEventLogWriter's persistence boundary; the CI gates check call sites against@pleach/core/eventLog.@pleach/gateway— emitsdomain.gateway.cost.recordedevents with the fullRoutingDecisionshape. Compliance's attestation runtime reads those events without round-tripping the audit ledger.@pleach/replay—verifyChainIntegritywalks the hash-chained event log. Compliance writes the rows; replay attests them.@pleach/eval— scoring + diff over replayed, scrubbed event logs.
For the full SKU map see Which SKU do I need?.
Where to go next
Scrubbers
The `Scrubber` contract — event-type allowlists, matched-spans, and the `contributeScrubbers` hook.
Multi-tenant
Where `tenantId` lives in the substrate and the production tenant-isolation checklist.
Tenant facet
`runtime.tenant.id` — reading the substrate's tenant identity inside plugins and tool handlers.
Audit ledger
The `ProviderDecisionLedger` and the three compliance plug-points the package will extend.