contract authority · 50-authority.md · 51-enforcement.md · guide · card
Enforcement and inference placement
Three layers stand between a stored row and a caller: removal at write time, a redistribution bound at push, and a compiled relation at query time. Together they form the reference monitor. This file states what each layer does, the order the compiled relation applies, column classification and masking, and the grammar of an inference zone.
The three layers of the reference monitor and the contracts feeding each:
redact
Removal and transformation inside the writer, ahead of columnar bytes and the run path’s durable record.
write-timetestRedaction runs inside the writer before columnar encoding. Each rule drops its value or transforms it into a policy-defined substitute: keyed digest, token, prefix or generalized band.ruletestA rule names a table, a column, one operation and that operation’s argument.pipeline-widetestA rule set declared on a pipeline binds every table the pipeline lands, child tables included.after-normalizationtestRedaction runs after relational normalization; a rule naming a parent column reaches values shredded into child tables.before-the-recordtestA canonical source writer shapes, normalizes and removes values before recording an opaque admitted payload; neither its inline record nor its blob contains the removed source value.rules-per-pipelinetestA pipeline carries at most 256 entries in its removal rule set.Why
because a longer rule set is a policy the operator cannot reviewpattern-bytestestOne removal pattern carries at most 4 KiB of source text.Why
because pattern compilation cost must remain bounded before a writer startscompiled-pattern-sizetestEach Thompson NFA compiled for one removal pattern occupies at most 1 MiB.Why
because expanded repetitions must not allocate unbounded matcher statepattern-refusaltestUnsupported syntax, Unicode word boundaries, empty matches or a pattern pastpattern-bytesorcompiled-pattern-sizeraisesEnforceRedactionInvalidat declaration.Why
because a failed matcher cannot silently leave a declared sensitive value intactin-valuetestPattern removal rewrites the union of all matches in one linear pass, merging overlapping matches into one span; whole-value removal rewrites one cell. JSON paths select object keys, array indices or every array item; unselected values remain unchanged.every-land-entrytestA canonical project declaration binds direct and staged writers even when their callers omit its rules.combined-authoritytestA destination’s ordered canonical rule union obeysrules-per-pipeline; conflicting declared types, classes or policies and duplicate rules refuse before writing parts.Why
because an additional declaration cannot replace an earlier rule or its type and class authoritynormalized-authoritytestProjects declaring protected relational normalization require canonical table bindings for ordinary writes; an undeclared destination enters through a declared root’s complete normalization lineage or refuses before durable bytes.Why
because a child name cannot prove which source column its values descend fromrewritten-identitiestestRelational parent and child identities derive from rewritten source values; removed values contribute no retained identity digest.child-selector-lineagetestA normalized child mask selecting a sibling class requires typed sibling-row lineage; a group lacking that lineage refuses before writing any part.Why
because a source-row index cannot distinguish sibling values from different child rowspinned-plantestA source plan’s explicit removal list equals its complete canonical destination rules; an unbound or mismatched list refuses before source execution. Canonical removal still binds an omitted list.Why
because a content-hashed plan cannot promise rules that its writer ignorescanonical-journaltestCanonical writer rules requirebefore-the-recordeven when a caller’s source plan omits its removal list;redacting-sourcegoverns absent admission.Why
because caller omission cannot weaken a durable declaration’s recording boundaryrelational-journaltestA protected relational source records its concrete rewritten root and child group, preserving declared root types, child lineage andrewritten-identitiesthroughprepared-replay.prepared-payloadtestA protected source cannot supply an admitted recording envelope; modified rows, types, destination or normalization fail canonical payload admission before staged parts.Why
because connector-controlled bytes cannot claim a writer’s recording authorityprepared-continuationtestProtected source continuations refuse before recording unless absent, exact terminal{ next: null }without more pages, or text unchanged by every canonical pattern; any canonical whole-value or JSON-path rule refuses text continuations.Why
because a source-controlled continuation can otherwise retain a removed value beside its rewritten rowsprepared-clocktestA protected monotonic source proves complete clock-output lineage before execution opens; lineage reaching removed columns, including JSON-path targets, refuses. Protected relational clocks refuse without complete descendant lineage; independent clocks retainwatermark-shape.Why
because progress bytes cannot retain a value that its row removesrecorded-bodytestA store-driven job targeting canonical removal rules refuses before body execution without typed effect-result removal lineage; body calls record results before output landing.Why
because later row rewriting cannot remove earlier recorded effect resultsprepared-effectstestCanonical effect preparation produces opaque rewritten root and child groups; the paid record and staged group carry the same rewritten values andrewritten-identities.effect-scopetestA prepared effect binds canonical authority to its execution, row and effect entry key and composite projection identity; replay and staging require the independently issued scope, never authority inferred from decoded bytes.
compose
The three layers as one reference monitor, and the order a registered relation applies.
complete-mediationtestEvery read of stored rows traverses the layers. A path reaching stored rows outside a registered relation is a defect, never a documented limitation.Why
P5protection-is-a-rewritetestProtection is a rewrite in the data plane: predicates compiled into the statement, rewritten projections, a per-credential filter at the sync edge, zone floors. Admission decides entry alone.Why
P5registered-relationtestThe compiler emits one relation per granted table, bound to the table’s bare name for the session’s lifetime. Every retrieval arm reads through it.Why
P5relation-ordertesttestA registered relation applies, in order: mirrored permission semi-join, tenant equality, credential predicate, table policy, project default, table zone, then the column projection carrying masks and zone nulls.Why
P5conjunctive-narrowingtestSteps conjoin: a surviving row survives each step alone, and appending a step removes rows and adds none.Why
P5before-the-cuttestEvery step, zone exclusion included, completes before a ranked result is cut to its requested size; visible positions carry no trace of a withheld row.Why
P5vector-armtestA sidecar vector index returns candidate identifiers, which re-join through the registered relation before contributing to a result.Why
P5session-buildtestA session builds its relations at admission and rebuilds them when a revocation or a manifest change invalidates a grant it holds.Why
P5relations-per-sessiontestA session carries at most 1024 entries in its relation set.Why
because a larger grant set is a mis-scoped credential
filter-rows
The typed row-predicate grammar, subject binding, the tenant equality and the project default.
grammartestA row predicate is the typed boolean subset of SQL in Shapes: column references, comparisons, conjunction, disjunction, negation, membership lists, pattern matching, null tests and declared-safe scalar functions.Why
P1outside-grammartestPredicates parse at manifest load; a node outside the grammar raisesEnforcePredicateOutsideGrammar, naming the node.Why
P1subject-relationtestSubject claims reach a predicate as prepared-statement parameters through a session-scoped subject relation. A claim value carrying SQL syntax occupies a parameter slot and reaches no parser.Why
P1exceptiontestAn exception condition is a boolean expression over subject fields in the same grammar, bound the same way.Why
P1overridetestA table-policy predicate overriding for a matching subject condition replaces that one predicate; it reaches neither the tenant equality, the mirrored join nor the credential predicate.Why
P5default-denytestA table reached with no matching grant yields no rows.Why
P5tenant-equalitytestA tenant-scoped grant compiles a bound-parameter byte equality on the tenant column, conjoined by the engine; no consumer statement carries it.Why
P5predicate-sizetestA declared predicate holds at most 4 KiB of source text.Why
because a longer predicate is unreviewable policymembership-listtestA membership list inside a predicate holds at most 512 entries.Why
because a longer list belongs in a mirrored table
mask
Column classes, strategies and combines, guardrails over an exhaustible value space, the pepper, and masked behavior under caller SQL.
column-policytestA column under[pipeline.tables.policy.columns]declaresRawColumnPolicy; an undeclared column carries no class.class-registrytestclasstakes a value from the class registry in Shapes.phimarks protected health data;ssn,phone,emailandmrnare exhaustible, each carrying a registered domain size.row-classtestA row-classRawColumnPolicydeclaresclass = { from = ... }, astrategiesmap naming registered classes, andfallback = "drop"; each row’s sibling text selects its strategy, independently of other rows.row-class-selectortestfrom_columnaliasesfrominrow-class; declaring both names or any unknown selector field refuses before reading rows.Why
because ambiguous selector declarations cannot choose which class controls a rowrow-class-dependencytestA row-class selector names another Utf8 column in the same table;absent-columnchecks its presence, and every mapped strategy retainstyped-strategyanddigest-alone.row-class-fallbacktestUnknown and null row-class values receive strict drop; no unspecified branch yields the original column value.Why
because an unrecognized class cannot grant weaker protection than any declared branchunknown-classtestAclassoutside the registry raisesEnforceUnknownClassat manifest check.strategiestestThe strategies aredrop,hash(keyed),tokenize(format-preserving),truncate,bucketandrange.droptestdropnulls the cell, or yields an empty string for a string column.projected-typetestA masked column reads as text under every strategy butdrop, which keeps the stored type; an exception beside a row-selected mask reads as text.Why
because both arms of an exception share one projected type, and a digest or token is text whatever it digestsone-valuetestA column mask is one value whose primary and combine halves are private; it drives both the write-time and query-time layers, and no consumer applies the primary alone.digest-alonetesthashortokenizestanding alone over an exhaustible class raisesEnforceDigestAloneOnExhaustibleClass.high-entropytestA column of random identifiers or opaque tokens carrieshashwith no combine.combine-generalizestestAbucketorrangecombine behindhashortokenizeraisesEnforceCombineWithoutGeneralization;truncateis the one combine that generalizes a digest.subject-exceptiontestA mask carries exceptions, each a subject condition in the row-predicate grammar bound through the subject relation.Why
P1in-placetestThe masked projection substitutes named columns where they stand; the caller receives the column set and order the unmasked statement produces, and*expands to it.equalitytestAn equality filter on adropcolumn matches nothing; on ahashcolumn it matches equal digests.aggregatestestAggregation reads the masked column.summarize-onlytestsummarize_onlyis a flag, not a strategy: the value stays legible to grounding and citation.typed-strategytestA binary column masks bydropor byhashover its bytes, a vector or nested column bydropalone over the whole column; another strategy on either raisesEnforceStrategyOutsideTypeat manifest load.absent-columntestA mask naming a column the table’s schema omits raisesEnforceMaskOnAbsentColumnat manifest load, refusing the whole manifest.Why
because a skipped declaration serves reads under a policy its author believed covered a columnmasks-per-tabletestA table carries at most 128 entries in its column mask set.Why
because a table beyond it needs a drop, not a mask per column
keyed digest
hash and tokenize digest under one pepper, truncated within the class’s crowd.
hash-widthtesthashemits 32 chars of hexadecimal.token-widthtesttokenizeemits 20 chars.cuts-nothingtestAtruncatecombine at or past its primary’s output width raisesEnforceTruncationCutsNothing.crowdtestA column’scrowd, the fewest inputs one masked output covers, is at least 1000 values and defaults to that floor.truncation-ceilingtestBehind a keyed digest over an exhaustible class, atruncatewidth above floor(log_b(domain ÷crowd)), b being 16 forhashand 36 fortokenize, with domain from the class registry, raisesEnforceTruncationTooWide.peppertestOne environment variable supplies the key forhashandtokenize, read by both layers as one resolved value.development-peppertestLeft unset, the pepper resolves to a constant in the source tree and the process emits one signal per run.pseudonymoustesthashoutput is an HMAC-SHA-256 under the pepper and is pseudonymous; no surface labels a masked value anonymous.native-digesttestBoth layers compute the keyed digest natively; the query layer calls a scalar function holding the pepper in process memory, and a value masked at either layer joins the other.key-materialtestA compiled relation, query plan or session catalog entry carrying pepper-derived key state raisesEnforceKeyMaterialInRelation.
refuse
The two shapes an unauthorized read takes, the scope guard, and what a refusal discloses.
scope-deniedtestA tenant-scoped credential reading another tenant’s rows on a granted table raisesEnforceScopeDenied, as wire codescope_denied, HTTP403or an in-band tool error, naming the table and both scopes.Why
P2not-emptytestA scope refusal is a typed error, never an empty result.Why
P2echotestThe requested scope in a refusal echoes the statement’s value, never a stored value.Why
P2ungranted-tabletestA relation the caller holds no grant on, whether or not a table of that name exists, raisesEnforceUnknownRelationand is absent from listings and descriptions.Why
P2payloadtestA refusal payload carries at most 512 B.Why
P2scope-guardtestThe scope guard walks the engine’s parse once, deciding literal equalities and membership lists over the tenant column and bound template parameters destined for it.Why
P2top-leveltestThe guard reads top-level conjuncts of a statement whose own FROM names a scoped table.Why
P2guard-walktestThe guard visits at most 4096 entries of one parse tree.Why
P2undecidabletestA constraint the guard cannot settle composes as an ordinary conjunct; the engine-applied equality isolates.Why
P5drifted-scopetestA tenant value matching no partition reads empty, like a tenant that wrote nothing; the read path repairs no transformed scope value.
bound-redistribution
The per-table licence bound applied at push, the root-object allowlist, and the per-credential edge filter.
withholdtestA cleared redistribution flag withholds every object of the table at push and omits the table’s name from the bucket manifest.before-difftestThe exclusion applies before the object diff is computed.outranks-grantstestThe bound holds against a credential granted everything.envelopestestThe bound covers the table’s columnar files and each pull’s verbatim record.root-allowlisttestWhile any table is withheld, a root-level object ships only when the allowlist in Shapes names it.root-not-allowlistedtestA root-level object absent from the allowlist raisesEnforceRootObjectNotAllowlistedat push.left-behindtestClearing the flag over a table an earlier push uploaded raisesEnforceStaleRedistributedObjects, naming each object and the remedy, deleting none.manifest-is-an-indextestAn object stays fetchable by its key after the manifest stops naming it, untildrop-firstdeletes it; a manifest authorizes nothing.reporttestA left-behind report names at most 100 files and states the total found.two-checkstestThe left-behind check runs before and after the upload loop; the guarantee holds for one bucket under one pushing configuration.edge-filtertestA serving edge in front of the bucket filters a materialized replica by the pulling credential’s grants.edge-whole-tabletestThe edge serves a table’s objects only under a read grant covering it with no tenant scope, aggregate bound or horizon, and a root object outsideaccess/,control/andstale_memory.idxonly under such a grant on*.Why
because a table’s files hold every row a narrowing grant removes, run states and cursor logs name tables, and a query re-applies only what the replica holds
place
Inference zones: grammar, composition across grains, floors, incognito, and the serve-time outcome.
zone-stringtestA zone string parses, after trimming, aslocal:device,on-prem:<id>,private-cloud:<id>orpublic-cloud:<id>; any other text is undeclared. The identifier is part of the value.allow-set-entrytestAn allow-set entry is*,local:device,on-prem:*,private-cloud:*,public-cloud:*, or a category carrying a concrete identifier.unparsed-patterntestAn allow-set entry matching no entry form raisesEnforceZonePatternUnparsed, naming the entry.Why
because an unparsed entry matching nothing hides a policy error; a new zone category updates the grammar firstdisjunctivetestA zone is admitted when any entry matches. A bare category matches every identifier in it; an entry carrying an identifier matches that identifier alone.undeclaredtestOnly*admits an undeclared zone.caller-zonetestThe calling process declares its zone per request; the store carries none.asserted-zonetestA zone a request asserts stands only where it equals the zone its credential signs; any other assertion raisesEnforceZoneAssertionWidens, naming both.Why
because an unsigned argument able to widen placement makes the signed zone decorativefail-closedtestA table declaring no zone policy resolves to the fail-closed pairlocal:deviceandon-prem:*, declared and discovered tables alike.protected-floortestAphicolumn or table carries a floor at the fail-closed pair; a wider declared set resolves down to it at serve time unless the override names that class or column.floor-widenedtestA manifest widening a protected-class surface past its floor without an override naming that class raisesEnforceProtectedFloorWidened.removal-with-public-cloudtestA surface declaring write-time removal on a column and an allow-set admitting a public cloud raisesEnforceRemovalWithPublicCloud.Why
P1narrowest-graintestA column’s set narrows its table’s; a principal’s set narrows both.row-zone-columntestA table’spolicy.zone.columnnames a column whose value, one allow-set entry, narrows its row’s set as a column’s set narrows its table’s; a null keeps the table’s set, and an unparsed value admits no zone.Why
because the store owner declares the column in the table’s policy and its value only narrows, unlike the binding’s advisory keyadvisory-zonerefusesexcluded-rowtestWhere the table’s effective set omits the session’s zone, the row leaves the result.excluded-celltestA cell whose effective set omits the session’s zone arrives null, a mask exception notwithstanding.per-principaltestA[[principal]]allow-set narrows every row whose_authored_agentor_authored_byequals itsid, wherever the row lands; a row whose authors no principal lists keeps its table’s set.unauthored-rowtestA row the engine landed with no authenticated subject takes its table’s set; only a row whose author the engine cannot verify resolves as an undeclared zone.Why
because a pipeline-landed row carries no author to narrow by, and treating it as undeclared withholds every such row from any table not admitting*evidence-floortestA synthesized row declaring a set wider than the intersection of its evidence tables’ sets raisesEnforceEvidenceFloorExceededand resolves to that intersection.empty-evidencetestA synthesized row naming no evidence table raisesEnforceEvidenceListEmptyand resolves to the fail-closed pair offail-closed.Why
because an empty list intersects to every zone, and a row citing nothing then resolves to the widest setincognitotestIncognito pins the session to the fail-closed pair, the uncredentialed local owner’s reads included.contextful mcp --incognitoserves a credential signing no zone atlocal:deviceand refuses one signing a zone outside[local:device, on-prem:*].incognito-wideningtestA session asserting a zone wider than the incognito pin, or an incognitocontextful token mintsigning one, raisesEnforceIncognitoWidening.team-defaulttest[project.zone]declaresdefault,users_may_widenandincognito_default;contextful token mintnaming no--zonesignsdefault, and underincognito_default = truemints incognito.default-widenedtestUnderusers_may_widen = false, acontextful token mintsigning a zone other than the project’sdefaultorlocal:deviceraisesEnforceZoneDefaultWidened, naming both.Why
because a default any minter replaces bounds nothing the project owner choseserve-outcometestServing one row yields a drop flag and the list of zone-masked columns.envelopetestA response envelope carries the zone’s removals, the asserted zone and the incognito flag inrestriction-block, and no removed value.excluded-disclosedtestrows_droppedcounts the rows the zone step removes from the whole relation, every earlier step applied; the caller’s statement, filter and requested size never enter it, and no removed value crosses.symbolic-inclusiontestInclusion against a floor is decided over every constructor and identifier, never by probing sample zones.allow-set-entriestestAn allow-set holds at most 32 entries.identifier-lengthtestA zone identifier holds at most 128 chars.
unsettled: Does the restriction block also carry the rows a row predicate removes and the column names a mask policy nulls, given both disclose how much of a table the caller’s scope withholds? owner: authority affects: authority.place
resist
The paths that stay closed to an injected instruction.
credential-shaped-valuetestA claim write, direct or synthesized, raisesEnforceCredentialShapedValueon a field matching a credential shape, before it reaches storage.read-only-facetestAn organization-wide face serves the read subset of the tool surface; conversational writes are server-authored.write-tooltestRegistering a write tool on an organization-wide face raisesEnforceWriteOnReadOnlyFace.
unsettled: Does a source writer refuse a credential-shaped value per value, landing the surrounding rows, and which operator-declared exemption admits a source carrying such text? owner: authority affects: authority.resist
Shapes
The class registry. The width ceiling assumes the default crowd:
class exhaustible domain hash truncate ceiling
phi no - -
ssn yes 1e9 4
phone yes 1e10 5
email yes 1e10 5
mrn yes 1e8 4
prompt no - -
completion no - -
A column policy using optional health-data classes (mrn, medical record number, and phi, protected health information), and a zone declaration:
[pipeline.tables.policy.columns]
record_number = { class = "mrn", strategy = "hash", combine = "truncate:4" }
contact_email = { class = "email", strategy = "tokenize", combine = "truncate:4" }
restricted_text = { class = "phi", summarize_only = true }
value_band = { strategy = "bucket:10000" }
internal_id = { strategy = "hash" }
[pipeline.tables.policy.zone]
allow = ["local:device", "on-prem:*"]
protected_class_override = []
[pipeline.tables.policy.rows]
predicate = "region = subject.region AND classification <= subject.clearance"
[[pipeline.tables.policy.rows.exception]]
when = "subject.role = 'auditor'"
predicate = "true"
[pipeline.tables.policy.redistribution]
replicate = false
[[principal]]
id = "reader-loop"
zone = { allow = ["local:device"] }
[project.zone]
default = "on-prem:hq"
users_may_widen = false
incognito_default = true
The row-predicate grammar:
predicate = disjunction ;
disjunction = conjunction , { "OR" , conjunction } ;
conjunction = negation , { "AND" , negation } ;
negation = [ "NOT" ] , primary ;
primary = comparison | membership | pattern | null-test | "(" predicate ")" ;
comparison = operand , ( "=" | "<>" | "<" | "<=" | ">" | ">=" ) , operand ;
membership = column , [ "NOT" ] , "IN" , "(" , literal , { "," , literal } , ")" ;
pattern = column , [ "NOT" ] , "LIKE" , string ;
null-test = column , "IS" , [ "NOT" ] , "NULL" ;
operand = column | subject-field | literal | safe-call ;
subject-field = "subject" , "." , identifier ;
safe-call = declared-function , "(" , [ operand , { "," , operand } ] , ")" ;
The root-object allowlist while a table is withheld:
tables/<withheld>/ withheld: no object, no manifest entry
access/ ships: mirrored permission tables
nodes/ ships: per-node run state, no rows
control/ ships: the applied control chain, no rows
stale_memory.idx ships
manifest.json ships, naming no withheld table
catalog.db withheld
records/ withheld: verbatim pull records
memory.db withheld
The serve-time outcome and the response envelope:
{
"row": { "drop": false, "masked_columns": ["restricted_text"] },
"envelope": {
"rows_removed_by_predicate": 41,
"rows_removed_by_zone": 7,
"columns_masked_by_policy": ["record_number", "contact_email"],
"columns_masked_by_zone": ["restricted_text"],
"asserted_zone": "on-prem:team-a",
"incognito": true
}
}
The two refusals on the wire:
{ "error": { "code": "scope_denied", "http": 403, "table": "records", "granted_scope": "team-a", "requested_scope": "team-b" } }
{ "error": { "code": "unknown_relation", "http": 404, "relation": "restricted_records" } }