Skip to content

Commit 3263400

Browse files
committed
docs(adr): ADR-0099 two-axis amendment — Layer 1 tier stays the per-object scope bit; P2 superseded by P2' (#3211)
The pre-P2 verification found D2's Layer 1 row was not behavior-preserving as written: Layer 1's tier input is the per-object super-bit, a per-principal-x-per-object delegation primitive (custom set granting viewAllRecords on one object to a non-admin position — the auditor pattern, the industry-standard shape). Posture is per-principal and structurally cannot carry per-object scope; enforcing the MEMBER cell literally would inert a declared grantable bit (the ADR-0049 declared-but-unenforced class) and delete a mainstream delegation capability. Amendment (maintainer, 2026-07-18): two orthogonal axes, each with a single authority — posture is the boundary axis (Layer 0 exemption at PLATFORM_ADMIN, EXTERNAL replacement; both ladder ends), the per-object super-bits are the scope axis (the only Layer 1 tier input, unchanged). D1's single-input principle refines to single-authority-per-axis; new invariant I7 (scope never crosses a boundary posture has not opened — enforced by P1); I2 clarified as a per-rung floor. Original P2 rejected; P2' = documentation cells only (seeded-face agreement, the delegation cell, I7), zero behavior change. Inline markers added at the D1 bullet, D2 table, and Sequencing P2 entry; full rationale in the Amendment section. Docs-only; no changeset. check:role-word OK - check:doc-authoring 200 files clean. Refs #3211 - ADR-0099 / #3109 / #3218 / #3226 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu
1 parent a2795f6 commit 3263400

1 file changed

Lines changed: 80 additions & 6 deletions

File tree

docs/adr/0099-posture-adjudicated-tiering-and-external-rung.md

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0099: Posture-Adjudicated Tiering — one axis for tier decisions, the EXTERNAL rung's enforcement path, no explicit deny
22

3-
**Status**: Accepted (2026-07-18; proposed 2026-07-17) — P0 equivalence gate landed with the acceptance (see the Acceptance addendum); P1/P2 flips gated on the #3211 G1 delta adjudication
3+
**Status**: Accepted (2026-07-18; proposed 2026-07-17) — P0 equivalence gate landed with the acceptance (see the Acceptance addendum); P1 shipped (#3226). Amended 2026-07-18: **two-axis correction** — D2's Layer 1 tier input corrected to the per-object scope bits, original P2 superseded by P2′ (see the Amendment)
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0095](./0095-authz-kernel-tenant-layer-and-posture-ladder.md) (the posture ladder + Layer 0 — Accepted, implemented; this ADR is its adjudication follow-through), [ADR-0066](./0066-unified-authorization-model.md) (superuser bypass ①; precedence), [ADR-0090](./0090-permission-model-v2-concept-convergence.md) (D10 principal taxonomy / `audience`, D11 external OWD), [ADR-0093](./0093-tenancy-mode-and-membership-lifecycle.md) (membership lifecycle — where an external principal type would come from), [ADR-0094](./0094-sys-permission-set-pure-projection.md) (one-authority precedent)
66
**Composes with**: [ADR-0096](./0096-execution-surface-identity-admission.md) — 0096 governs **admission** (may this call reach the engine, as whom); this ADR governs **tiering** (given an admitted principal, which tier of rows each layer grants). Orthogonal axes, deliberately separate ADRs.
@@ -121,9 +121,13 @@ Every enforcement-time **tier** decision reads `ctx.posture`:
121121
ADR-0096 D3 is progressively eliminating). When both signals are present
122122
the carried rung wins; a disagreement is logged as a defect signal, and the
123123
**narrower** verdict is enforced (fail-safe).
124-
- **Layer 1 short-circuit**: the *tier* question ("does business RLS apply at
125-
all inside the org") reads posture (`>= TENANT_ADMIN` short-circuits, per
126-
today's semantics); the *side* question keeps its per-side capability bit —
124+
- **Layer 1 short-circuit** *(superseded by the two-axis Amendment below)*: the
125+
original text read the tier from posture (`>= TENANT_ADMIN`) and claimed
126+
today's semantics. The pre-P2 verification (framework#3211) showed that is
127+
NOT today's semantics: the tier input is the **per-object** super-bit, which
128+
a custom set can delegate to a non-admin principal — a per-principal ×
129+
per-object fact the per-principal posture cannot represent. Layer 1's tier
130+
input therefore **stays the per-side, per-object capability bit**;
127131
`viewAllRecords` gates the read bypass, `modifyAllRecords` the write bypass,
128132
exactly as today. Posture never grants a side the bits don't.
129133
- **Explain** reports the carried rung it read — the same field, not a
@@ -148,6 +152,12 @@ it would have caught).
148152
| **Write path** | tier does not bypass write guards: readonly strip (#2957), identity write guard (ADR-0092), Layer 0 write wall (#2946) all still apply | same | same | shares with edit access only |
149153
| **Explain** | reports the carried rung — identical input as enforcement, by construction | same | same | same |
150154

155+
> **Amendment note (2026-07-18):** the **Layer 1** row above is corrected by
156+
> the two-axis Amendment — the tier at Layer 1 is *not* selected by posture;
157+
> it is selected by the per-object super-bits (a `MEMBER` holding a delegated
158+
> per-object `viewAllRecords` short-circuits business RLS on that object,
159+
> tenant-walled as ever). The Layer 0 and EXTERNAL rows stand as written.
160+
151161
Invariants (each carries a matrix cell):
152162

153163
- **I1**`TENANT_ADMIN` never crosses Layer 0. No capability composition may
@@ -233,8 +243,11 @@ Strictly serial, each step behind the matrix:
233243
rung (probe demoted to fallback; stale comment at
234244
`security-plugin.ts:71-77` retired). Behavior-preserving under P0's gate.
235245
Unblocked today (#2956); small.
236-
- **P2** — Layer 1 short-circuit reads posture for tier (side bits unchanged).
237-
Behavior-preserving under the gate.
246+
- **P2** *(superseded — rejected at adjudication, replaced by P2′; see the
247+
Amendment)* — the original "Layer 1 short-circuit reads posture for tier"
248+
turned out NOT to be behavior-preserving: it would inert the delegated
249+
per-object super-bit for non-admin principals. P2′ is documentation cells
250+
only (seeded-face agreement, the delegation cell, I7), zero behavior change.
238251
- **P3 (demand-gated)** — EXTERNAL derivation + injection when the external
239252
principal type ships (portal track). New behavior for a new principal type;
240253
no existing principal's visibility changes. If any breaking surface
@@ -333,3 +346,64 @@ D1's behavior contract with one finding:
333346
doing its job as specified ("any cell where they disagree … must be
334347
resolved before the flip") — the resolution is D1's rung authority, not a
335348
probe repair.
349+
350+
## Amendment — the two-axis correction (2026-07-18)
351+
352+
**Trigger.** P1 shipped behavior-preserving on the seeded face (#3226). The
353+
pre-P2 verification (framework#3211) then found that D2's Layer 1 tier claim
354+
was **not** behavior-preserving as written. Layer 1's tier input today is the
355+
per-object super-bit — a **per-principal × per-object delegation primitive**:
356+
a custom permission set may grant `viewAllRecords` on one object (all invoices
357+
to an auditor position) without granting any admin stature. This is the
358+
industry-standard shape (Salesforce object-level View All / Modify All,
359+
org-level per-entity access in comparable platforms). Posture is
360+
**per-principal** — one rung per principal — and structurally cannot carry
361+
per-object scope: a MEMBER with a delegated invoice view-all and a plain
362+
MEMBER sit on the same rung. Enforcing D2's MEMBER cell literally would have
363+
(a) made a declared, grantable bit conditionally inert — the
364+
declared-but-unenforced class ADR-0049 forbids and the silent-miss failure
365+
mode this platform removes everywhere else — and (b) deleted a mainstream
366+
delegation capability, forcing over-grants (promote the auditor to org admin)
367+
to recover it.
368+
369+
**Decision (maintainer, 2026-07-18).** The model is corrected to **two
370+
orthogonal axes, each with a single authority** — the table's Layer 1 row is
371+
amended rather than enforced:
372+
373+
- **Posture is the boundary axis** (per-principal). It exclusively governs the
374+
ladder's *ends*: the Layer 0 cross-tenant exemption at `PLATFORM_ADMIN`
375+
(P1, shipped) and the EXTERNAL rung's wholesale replacement of the member
376+
pipeline (P3, demand-gated). No other enforcement site consults posture for
377+
tiering.
378+
- **The per-object super-bits are the scope axis** (per-principal ×
379+
per-object). `viewAllRecords` / `modifyAllRecords` remain the only Layer 1
380+
tier input — per side, per object, delegable to any position; today's
381+
semantics, unchanged.
382+
- **D1's "single adjudication input" refines to "single authority per
383+
axis."** The disease this ADR treats — one fact re-derived at multiple
384+
sites — stays dead: the boundary fact has one derivation (the resolver) and
385+
one enforcement reader (the Layer 0 gate); the scope fact has one evaluator
386+
(`PermissionEvaluator.hasSuperuser*Bypass`). Neither is derived anywhere
387+
else.
388+
- **I7 (new — the I5 dual):** the scope axis never crosses a boundary the
389+
posture axis has not opened. A super-bit holder below `PLATFORM_ADMIN`
390+
stays tenant-walled (enforced by P1; pinned in the matrix), and an EXTERNAL
391+
principal's explicit-share rule replaces Layer 1 regardless of any held
392+
bits (pinned when P3 lands).
393+
- **I2 clarification:** nesting is a per-rung *floor* invariant. Additive
394+
per-object grants widen visibility within a rung (two MEMBERs may differ)
395+
without violating the ladder; no rung's floor exceeds the rung above.
396+
- **P2 → P2′.** The original P2 flip is rejected. P2′ lands documentation
397+
cells only: the seeded-face agreement pin (every seeded super-bit holder
398+
sits ≥ `TENANT_ADMIN`, so the axes agree on the seeded surface), the
399+
**delegation cell** (a MEMBER holding a custom per-object `viewAllRecords`
400+
on a private object sees all rows in-org and remains tenant-walled — the
401+
pattern is load-bearing and must not be "cleaned up" by a future
402+
convergence attempt), and the I7 cells. Zero behavior change. An
403+
authoring-time advisory for high-privilege bit grants (via
404+
`describeHighPrivilegeBits`) is tracked separately.
405+
406+
The AI-authoring rule this yields is one sentence — *bits pick your row scope
407+
on an object; posture picks which tenant boundaries exist for you at all*
408+
which is the smallest mental model the platform can offer an agent writing
409+
permissions, with no conditionally-dead declarations.

0 commit comments

Comments
 (0)