Every read through /osl/query is limited by a
capability + permission model: who is asking (a delegation chain of
subjects), what each policy grants them over the semantic graph, and how
those grants compose. It is fail-closed by construction — the default is
deny, and anything unresolved is dropped, never guessed.
Access is evaluated over a chain of principals, root → leaf. The acting
subject is the last link. Composition has two levels:
Within one principal
All policies binding that principal combine: grants union, the strongest
obligation wins (deny > mask > redact), an explicit deny overrides,
and conditions (IP range, time window) gate ALLOW only — a deny is
unconditional.
Across the chain
The links combine by MEET (intersection): an entity or column is granted
only if every link grants it. The tier cap is the minimum (most
restrictive) and the required redaction is the union of what each link
requires. An empty or un-authored link collapses the meet to deny.
Grants are positive: a column that isn’t granted is redacted, never returned
as NULL or silently dropped from the schema. deny removes a column from the
projection entirely; a policy-global deny overrides any other binding’s grant.
Content facets are governed by named redaction versions — a decoupling of a
human-facing name from a physical, byte-stable identity:
A RedactionVersion has a name (the console handle, e.g. "PII básica")
and a set of tags (the content classes it redacts, e.g. {email, phone}).
The tags are the physical identity (byte-unchanged, used as the table key);
the name is only for authoring.
The implicit original version (no tags) is the base table.
A facetGrant binds a required version name per facet. Selection is
exact-or-deny: the engine serves the stored version whose tags exactly
match, or denies the facet — it never falls back to a more- or less-redacted
version.
Across a chain, the required versions compose by union of tag-sets (more
redaction is always safe).
Enforcement is server-side, in the cell. The outcomes:
Situation
Value returned
In the result?
Granted cleartext
original
yes
mask obligation
***
yes
redact obligation
⟦redacted⟧
yes (survives for ORDER BY/GROUP BY)
deny / not granted
—
dropped from the projection
The ⟦redacted⟧sentinel means the column survives with a governed value
(so downstream shape is stable), rather than becoming NULL or vanishing — which
is what makes cross-modal joins fail-closed and the model composable.
The chain isn’t a trusted header — it’s a signed token. The cell mints and
verifies an HS256 JWT whose payload contains the chain itself, so a caller can’t
shorten or forge it without breaking the signature (an absent/invalid chain →
deny). See Authentication.
Two dry-run operations compute the real composition without serving data
(Enterprise console surfaces them; the APIs are in the cell):
:simulate — preview one policy against a query as a single subject.
:simulate-chain — compile the tenant’s full authored policy set over a
root → leaf chain (the MEET), returning the composed scope plus a
per-link contribution breakdown. An un-authored link is flagged; the meet
empties to a fail-closed empty_meet.