ADR 0001 — Audit row schema and PII taxonomy
- Status: Accepted
- Date: 2026-05-15
- Charter version referenced: v1.0.1
Context
SchemaBrain today describes databases — it never executes queries against them. Every MCP tool shipped through v0.5 returns derived metadata (column descriptions, join paths, suggested example queries) and the audit trail is implicit in the MCP transport logs. That shape is about to change. The v2 line addsexecute and
validate_query — tools that compile a MetricDefinition into a
parameterised SQL statement and run it against the source database. The
moment that lands, three new properties become load-bearing:
- Decisions need a record. “This query was refused because it
touched
pii:contactand the caller’s role isanalyst_no_pii” is only useful if the record survives, is queryable, and can be produced for audit review months later. - Records need to be tamper-evident. An audit trail that can be silently rewritten is worse than no audit trail — it implies guarantees that don’t hold.
- Records need privacy boundaries that match real regulation. “PII” as a single flag conflates GDPR special categories with PCI DSS payment data with HIPAA PHI. Regulators audit on the categorical distinctions; the schema needs to express them.
get_example_queries) ships as the first charter-v1.0.1-compliant
tool. Tool #5’s description, recovery contract, and event stream all
need to reference the final audit row and the final PII taxonomy. Once
locked, the v1 implementation lands the table, and v2 widens the
writers; no retrofit required.
The fingerprint primitive used by audit rows has a separate visibility
trajectory (see Privacy-by-construction below) — this ADR documents
what the fingerprint excludes, not how it’s computed.
Decision
1. The mcp_audit table — 14-field DDL
Every MCP tool invocation that touches the source database, returns a
refusal, or completes a charter-defined status writes exactly one row
to mcp_audit. The table lives in the same SQLite store as the rest of
SchemaBrain’s state.
2. Append-only invariant
The audit table is append-only by three independent mechanisms. Any single one being weak is acceptable; all three failing simultaneously is what we design against. Mechanism A — SQL triggers reject UPDATE and DELETE.chain_hash column). Each row’s
chain_hash is computed as sha256(prev_row.chain_hash ‖ canonical_serialisation(this_row, fields 1–13)). The genesis row uses
32 zero bytes for the previous hash. A schemabrain audit verify CLI
command (deferred to a later release) walks rows in order, recomputing
each chain hash and reporting the first mismatch.
The chain is detection, not prevention — an attacker with write
access to the file can rewrite the chain coherently. The defense is
that any external archive (file-system snapshot, S3 backup, off-host
copy) that captured a prior chain_hash can be compared against the
current row to detect tampering between the snapshot and now.
3. PII taxonomy — 4 sensitivities, 12 categories
- Sensitivity (Layer 1) drives role-based access decisions. “Role
analystmay readinternalandconfidential; neverpii.” One axis, four values, orderedpublic < internal < confidential < pii. - Categories (Layer 2) drive refusal patterns. “Role
customer_supportmay readpii:contactbut notpii:financialorpii:government_id.” Set-valued (frozenset[PIICategory]), composable across joins.
public | confidential | pii) some
implementations use:
The list is closed at 12 today but additive — extending it is a minor
charter bump. The current set covers GDPR, CCPA/CPRA, HIPAA, PCI DSS,
and ISO 27018 categorical distinctions without conflating
regulator-distinguished categories.
A Pydantic validator enforces the cross-layer invariant:
4. Propagation across joins and views
Tool authors will need a deterministic rule for “what PII categories does this joined result carry?” The rule is:- Sensitivity propagates by MAX under the ordering
public < internal < confidential < pii. A view that pulls one column from apublictable and one column from apiitable ispii. A view combininginternalandconfidentialisconfidential. - Categories propagate by UNION. A view selecting
users.email(pii_categories={"contact"}) andtransactions.amount(pii_categories={"financial"}) has effective categories{"contact", "financial"}.
schemabrain/compiler/ — the audit writer reads the compiler’s already-
resolved category set rather than recomputing. This keeps the
compiler’s grain check and the audit writer’s PII bookkeeping aligned
by construction.
5. Privacy-by-construction
The fingerprint primitive (column 12,fingerprint) is the load-bearing
audit field for any future fleet-level aggregation. Its visibility
posture is explicit and asymmetric: this ADR documents what the
fingerprint excludes; the exact composition is not public until the
hosted fleet-aggregation product ships.
What the fingerprint excludes, and how the design enforces each
exclusion:
The single-paragraph guarantee — quotable verbatim:
Fingerprints contain no row content, no column values, no identifying schema information. We publish what the fingerprint excludes rather than how it’s computed — the same posture Cloudflare uses for WAF signatures and Snyk uses for vulnerability patterns. The exact composition will be published when our hosted fleet-aggregation product ships.The CI lint that enforces the fingerprint-input field count is part of the audit-implementation deliverable; the test asserts the field count at the dataclass definition and fails on any addition. Adding a fingerprint field is therefore a deliberate, reviewed action — not an accidental drift.
Implementation sequence
This ADR is design-only. The implementation deliverables, in order:- PII taxonomy module first.
schemabrain/pii/categories.pylands with theSensitivityandPIICategoryLiteral types and the propagation helpers. First consumers are the PII classifier and the entity blueprint — both of which need the taxonomy before the audit table needs it. - Audit module next.
schemabrain/audit/lands with themcp_auditDDL, the writer, the chain-hash computation, and the fingerprint primitive. Tool authors begin emitting audit events through a singleaudit_writehelper. - Chain verification CLI.
schemabrain audit verifywalks the chain and reports the first mismatch. Deferred to a later release once real audit volume justifies it; the chain hash makes verification mathematically possible from day one of the audit module. - v2 widens the writers.
executeandvalidate_queryare the first tools to populate therefusal_reason,ast_shape_hash, andrule_idfields for refusal paths. Non-refusal v2 rows userefusal_reason IS NULLandast_shape_hash IS NULL.
get_example_queries) can
reference final field names, final refusal reasons, and final PII
category names in its description and recovery contract.
Consequences
What becomes possible:- Charter-v1.0.1 tools can name
refusal_reasonvalues in their recovery contracts (e.g.Recovery(suggested_tool="describe_table")paired withrefusal_reason="pii_blocked") and the values won’t drift between tools. - Enterprise legal review has a quotable privacy guarantee and a closed schema to validate against, separately from any code-level audit.
- The fleet-aggregation product can ship without renegotiating the
audit shape — the schema is forward-compatible by construction
through
fingerprint_version.
- Every new MCP tool must define which
refusal_reasonvalues its refusal paths produce (subset of the six listed) and document them in the tool’s docstring. - Tools that touch source-database data compute their effective
pii_categoriesthrough the compiler’s propagation helper rather than hand-rolling set-union logic at the tool surface. - Tools must not log row content into any other surface (stderr, MCP transport, exception messages) — the audit row is the only sanctioned per-call record, and its column constraints already forbid value fields.
- The exact write path for the audit row (single helper vs decorator) is an implementation choice at audit-module landing time.
- The
schemabrain audit verifyCLI shape is deferred to a later release; the chain hash makes verification mathematically possible from day one of the audit module. - Cross-deployment fingerprint aggregation is the hosted product (v3+); the local audit table never aggregates beyond a single deployment.
References
- SchemaBrain MCP Charter v1.0.1 — status enum, refusal kinds, recovery contracts.
- GDPR Articles 4 (definitions) and 9 (special categories).
- CCPA / CPRA §1798.140(o) personal information categories.
- HIPAA Safe Harbor — 18 identifiers, 45 CFR §164.514(b)(2).
- PCI DSS scope guidance (payment card data isolation).
- ISO 27018:2019 (PII in public clouds).
- Cloudflare WAF signature posture (public exclusion list, private composition) — the analogous visibility model.