Explain Explain protocol schemas
[ADR-0090 D6] Access-explanation contract — explain(principal, object, operation) as a first-class API.
The explain engine (@objectstack/plugin-security) walks the SAME code
paths as the enforcement middleware — the same permission-set resolution,
the same evaluator, the same RLS compiler — and reports what each layer of
the evaluation pipeline contributed to the final decision. "Explained by
construction": the report can never drift from enforcement because it IS
enforcement, minus the throw.
Layer order mirrors the runtime pipeline:
tenant_isolation → principal → required_permissions → object_crud → fls →
owd_baseline → depth → sharing → vama_bypass → rls.
[C2 / ADR-0095] Record-grained explanation. The contract carries an optional
recordId on the request and, when present, a per-layer record attribution
plus a top-level record verdict on the response — so the sharing / rls / owd
layers can report the ROW-LEVEL story for one concrete record (which share
admitted it, which filter excluded it, whether the effective row filter
matches). Object-level requests (no recordId) stay byte-compatible.
[ADR-0095 D1/D2] The contract also reserves the kernel-chain vocabulary the
β engine + UI will fill: the always-first tenant wall as tenant_isolation
(Layer 0), a per-layer kernelTier marking Layer 0 vs. business RLS
(Layer 1), and the monotonic posture ladder
(PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL) on the resolved principal.
Source: packages/spec/src/security/explain.zod.ts
import { AccessMatrixSchema, AccessMatrixEntrySchema, AuthzPostureSchema, ExplainDecisionSchema, ExplainLayerSchema, ExplainMatchedRuleSchema, ExplainOperationSchema, ExplainRecordAttributionSchema, ExplainRequestSchema } from '@objectstack/spec/security' ;
import type { AccessMatrix, AccessMatrixEntry, AuthzPosture, ExplainDecision, ExplainLayer, ExplainMatchedRule, ExplainOperation, ExplainRecordAttribution, ExplainRequest } from '@objectstack/spec/security' ;
// Validate data
const result = AccessMatrixSchema. parse (data);
Property Type Required Description version 1optional (default: 1) entries { permissionSet: string; object: string; create: boolean; read: boolean; … }[]optional (default: [])
Property Type Required Description permissionSet string✅ object string✅ create boolean✅ read boolean✅ edit boolean✅ delete boolean✅ viewAllRecords boolean✅ modifyAllRecords boolean✅ readScope stringoptional writeScope stringoptional sharingModel stringoptional
ADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
PLATFORM_ADMIN
TENANT_ADMIN
MEMBER
EXTERNAL
Property Type Required Description allowed boolean✅ object string✅ operation Enum<'read' | 'create' | 'update' | 'delete' | 'transfer' | 'restore' | 'purge' | 'export'>✅ principal { userId: string | null; positions: string[]; permissionSets: string[]; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'>; … }✅ layers { layer: Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …>; kernelTier?: Enum<'layer_0_tenant' | 'layer_1_business'>; verdict: Enum<'grants' | 'denies' | 'narrows' | 'widens' | 'neutral' | 'not_applicable'>; detail: string; … }[]✅ readFilter anyoptional record { recordId: string; visible: boolean; decidedBy?: Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …> }optional Row-level verdict for the specific record; set only for singular record-grained requests. records { recordId: string; visible: boolean; decidedBy?: Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …> }[]optional Per-record verdicts for a batch request — records[i] answers recordIds[i]; set only when the request carried recordIds.
Property Type Required Description userId string | null✅ positions string[]optional (default: []) permissionSets string[]optional (default: []) principalKind Enum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional onBehalfOf { userId: string }optional posture Enum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optional ADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
Property Type Required Description layer Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …>✅ kernelTier Enum<'layer_0_tenant' | 'layer_1_business'>optional ADR-0095 kernel layer: layer_0_tenant = the always-first org wall; layer_1_business = business RLS/sharing/ownership. verdict Enum<'grants' | 'denies' | 'narrows' | 'widens' | 'neutral' | 'not_applicable'>✅ detail string✅ contributors { kind: Enum<'permission_set' | 'position' | 'system'>; name: string; via?: string; state?: Enum<'active' | 'expired' | 'deactivated'> }[]optional (default: []) record { outcome: Enum<'admitted' | 'excluded' | 'not_evaluated'>; rowFilter?: any; matchesRecord?: boolean; rules: object[]; … }optional Row-level determination for the specific record under explanation; set only for record-grained requests.
Property Type Required Description recordId string✅ The concrete record id this verdict is about (echoes the request recordId or recordIds[i]). visible boolean✅ Whether the operation is permitted on this specific record after all layers. decidedBy Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …>optional The pipeline layer that decided the record-level outcome (excluded it, or last admitted it); omitted for a missing record.
Property Type Required Description recordId string✅ The concrete record id this verdict is about (echoes the request recordId or recordIds[i]). visible boolean✅ Whether the operation is permitted on this specific record after all layers. decidedBy Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | …>optional The pipeline layer that decided the record-level outcome (excluded it, or last admitted it); omitted for a missing record.
Property Type Required Description layer Enum<'tenant_isolation' | 'principal' | 'required_permissions' | 'object_crud' | 'fls' | 'owd_baseline' | 'depth' | 'sharing' | 'vama_bypass' | 'rls'>✅ kernelTier Enum<'layer_0_tenant' | 'layer_1_business'>optional ADR-0095 kernel layer: layer_0_tenant = the always-first org wall; layer_1_business = business RLS/sharing/ownership. verdict Enum<'grants' | 'denies' | 'narrows' | 'widens' | 'neutral' | 'not_applicable'>✅ detail string✅ contributors { kind: Enum<'permission_set' | 'position' | 'system'>; name: string; via?: string; state?: Enum<'active' | 'expired' | 'deactivated'> }[]optional (default: []) record { outcome: Enum<'admitted' | 'excluded' | 'not_evaluated'>; rowFilter?: any; matchesRecord?: boolean; rules: object[]; … }optional Row-level determination for the specific record under explanation; set only for record-grained requests.
Property Type Required Description outcome Enum<'admitted' | 'excluded' | 'not_evaluated'>✅ This layer's row-level outcome for the record: admitted, excluded, or not_evaluated (skipped/not row-scoped). rowFilter anyoptional The effective row predicate this layer contributed for the record set (null = unrestricted, deny_all = zero rows). matchesRecord booleanoptional Whether the specific record satisfies rowFilter — the judgement behind outcome. rules { kind: Enum<'tenant_filter' | 'owd_baseline' | 'ownership' | 'record_share' | 'sharing_rule' | …>; name: string; grants?: Enum<'read' | 'edit' | 'full'>; via?: string; … }[]optional (default: []) Concrete rules, shares, or policies this layer evaluated against the record, in evaluation order. detail stringoptional Human-readable, record-specific explanation of this layer's outcome.
Property Type Required Description kind Enum<'tenant_filter' | 'owd_baseline' | 'ownership' | 'record_share' | 'sharing_rule' | 'team' | 'territory' | 'rls_policy'>✅ The row-visibility source kind evaluated for this record at this layer. name string✅ Stable identifier of the concrete rule, share, or policy that was evaluated. grants Enum<'read' | 'edit' | 'full'>optional Access level a sharing source grants on the record (authorable: read/edit; full appears only for legacy rows pending normalisation). via stringoptional How the rule reached the principal — recipient group/position, ownership, or the matching criteria. predicate anyoptional The row predicate this rule contributed, when it is filter-shaped (null = unrestricted). effect Enum<'admits' | 'excludes' | 'neutral'>✅ The rule's effect on THIS record: admits, excludes, or neutral.
read
create
update
delete
transfer
restore
purge
export
Property Type Required Description outcome Enum<'admitted' | 'excluded' | 'not_evaluated'>✅ This layer's row-level outcome for the record: admitted, excluded, or not_evaluated (skipped/not row-scoped). rowFilter anyoptional The effective row predicate this layer contributed for the record set (null = unrestricted, deny_all = zero rows). matchesRecord booleanoptional Whether the specific record satisfies rowFilter — the judgement behind outcome. rules { kind: Enum<'tenant_filter' | 'owd_baseline' | 'ownership' | 'record_share' | 'sharing_rule' | …>; name: string; grants?: Enum<'read' | 'edit' | 'full'>; via?: string; … }[]optional (default: []) Concrete rules, shares, or policies this layer evaluated against the record, in evaluation order. detail stringoptional Human-readable, record-specific explanation of this layer's outcome.
Property Type Required Description kind Enum<'tenant_filter' | 'owd_baseline' | 'ownership' | 'record_share' | 'sharing_rule' | …>✅ The row-visibility source kind evaluated for this record at this layer. name string✅ Stable identifier of the concrete rule, share, or policy that was evaluated. grants Enum<'read' | 'edit' | 'full'>optional Access level a sharing source grants on the record (authorable: read/edit; full appears only for legacy rows pending normalisation). via stringoptional How the rule reached the principal — recipient group/position, ownership, or the matching criteria. predicate anyoptional The row predicate this rule contributed, when it is filter-shaped (null = unrestricted). effect Enum<'admits' | 'excludes' | 'neutral'>✅ The rule's effect on THIS record: admits, excludes, or neutral.
Property Type Required Description object string✅ operation Enum<'read' | 'create' | 'update' | 'delete' | 'transfer' | 'restore' | 'purge' | 'export'>✅ recordId stringoptional Optional id of one concrete record to explain at row granularity; omitted = object-level (pre-C2) request. Mutually exclusive with recordIds. recordIds string[]optional Batch of record ids (1–200) to answer at row granularity in one round trip; records[i] answers recordIds[i]. Mutually exclusive with recordId. userId stringoptional