Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0086: Authorization metadata↔config boundary + cross-package composition — DEFINITION travels, ASSIGNMENT stays, packages ship their own sets

**Status**: Accepted (2026-07-04) — **substantially implemented**; parts refined/superseded by [ADR-0090](./0090-permission-model-v2-concept-convergence.md) and [ADR-0094](./0094-sys-permission-set-pure-projection.md). See the **Re-evaluation (2026-07-16)** addendum for the current, code-grounded status of every decision. · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13): the ADR-0094 projection the Re-evaluation describes, and the `sys_permission_set` and `sys_position_permission_set` row objects, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13); the DEFINITION↔ASSIGNMENT thesis stands, and ADR-0131 completes it. See the notes in the Re-evaluation.
**Status**: Accepted (2026-07-04) — **substantially implemented**; parts refined/superseded by [ADR-0090](./0090-permission-model-v2-concept-convergence.md) and [ADR-0094](./0094-sys-permission-set-pure-projection.md). See the **Re-evaluation (2026-07-16)** addendum for the current, code-grounded status of every decision. · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13): the ADR-0094 projection the Re-evaluation describes, and the `sys_permission_set` and `sys_position_permission_set` row objects, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13); the DEFINITION↔ASSIGNMENT thesis stands, and ADR-0131 completes it. See the notes in the Re-evaluation. · **Amended** (2026-10-14, [ADR-0138](./0138-guest-model-anonymous-principal-doors-grants-and-organization.md) D3 item 5, ruling C of record 6159279205 on #23267): the Re-evaluation table's `everyone` / `guest` row ("Packages *suggest* bindings; admins confirm") holds for `everyone` alone; no door binds a set to `guest` (no package suggestion, no confirmation) until ADR-0138 D3 item 5's bind door is built, an act of the environment's administrator, never of a package. Read the row's `guest` half as superseded by this line.
**Deciders**: ObjectStack Protocol Architects
**Builds on**: [ADR-0066](./0066-unified-authorization-model.md) (three-way separation: Capability / Assignment / Requirement — this ADR operationalizes its **D5** for the packaging axis), [ADR-0056](./0056-permission-model-landing-verification.md) (whole-model enforce/prove; **D7** app-declared default profile), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (`readScope`/`writeScope` depth), [ADR-0005](./0005-metadata-customization-overlay.md) (per-org overlay = the subtract/adjust layer), [ADR-0028](./0028-metadata-naming-and-namespace-isolation.md) / [ADR-0048](./0048-cross-package-metadata-collision.md) (namespaced api names = collision-proof keys), [ADR-0078](./0078-no-silently-inert-metadata.md) (no declarable-but-never-seeded metadata), [ADR-0016](./0016-studio-package-authoring-and-publish.md) / [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (draft/publish), [ADR-0084](./0084-application-builder-information-architecture.md) (Access pillar = matrix; defers composition), [ADR-0025](./0025-plugin-package-distribution.md) (manifest `permissions` = install-consent scopes, **not** RBAC grants), [ADR-0003](./0003-package-as-first-class-citizen.md) / [ADR-0070](./0070-package-first-authoring.md) (package-first), [ADR-0006](./0006-project-environment-split.v4.md) (environment split)
**Consumers**: `@objectstack/spec` (`security/permission.zod.ts`, `stack.zod.ts`, `system/metadata-persistence.zod.ts`), `@objectstack/plugin-security` (`bootstrap-declared-*`, `permission-evaluator`), `@objectstack/core` (`resolve-authz-context`), `@objectstack/metadata` (overlay), `../objectui` (`AccessPillar`, `PermissionMatrixEditor`, `metadata-admin`)
Expand Down
1 change: 1 addition & 0 deletions docs/adr/0090-permission-model-v2-concept-convergence.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
· **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13) — two of the row objects D3 renames into, `sys_position` and `sys_position_permission_set`, and the `sys_capability` D5 names, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13): a position is a metadata-registry definition that names its sets in `PositionSchema.permissionSets`; `sys_user_position` stays. See the notes under D3, D5 and D8.
· **Amended** (2026-10-14, ruling of record 6159300530 on #23268, letter A) — D11's external org-wide default is retired: external visibility is the [ADR-0095](./0095-authz-kernel-tenant-layer-and-posture-ladder.md) `EXTERNAL` rung, explicitly shared rows only. See the note under D11.
· **Amended** (2026-10-15, ruling of record 6181988435 on #23488, letter A) — D12's position↔set binding right and its environment-set authoring right are retired (`AdminScope.manageBindings` and `AdminScope.authorEnvironmentSets`, ADR-0049 enforce-or-remove): a position's sets and a permission set's body are environment definitions, and the definition write is `manage_metadata`'s per [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3, never a delegate scope's. D12's *what* is user↔position assignments (and direct set grants) inside the subtree; read its bindings and authoring clauses as superseded by this line.
· **Amended** (2026-10-14, [ADR-0138](./0138-guest-model-anonymous-principal-doors-grants-and-organization.md) D3 item 5, ruling C of record 6159279205 on #23267) — D9's last bullet holds for `everyone` alone: a package suggests `everyone` bindings and the admin confirms each, while no door binds a set to `guest` (no package suggestion, no confirmation, no operator bind) until ADR-0138 D3 item 5's bind door is built, an act of the environment's administrator, never of a package. Read that bullet's `guest` half as superseded by this line.
**Deciders**: ObjectStack Protocol Architects
**Builds on**: [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove),
[ADR-0056](./0056-permission-model-landing-verification.md) (permission-model landing verification),
Expand Down
24 changes: 12 additions & 12 deletions packages/lint/src/validate-security-posture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* | security-owd-unset (error) | objectui#2348 leave_request 事故 |
* | security-owd-alias (error) | ADR-0090 D4 canonical enum — UNPARSED intakes only, see § Intake |
* | security-wildcard-vama (error) | ADR-0066 superuser wildcard |
* | security-anchor-high-privilege(error) | ADR-0090 D5/D9 anchors — declared `everyone` suggestions (`isDefault: true`) only; a `guest`-bound set is outside a package-time linter's sight and is the bind-time gate's alone (#16110) |
* | security-anchor-high-privilege(error) | ADR-0090 D5/D9 anchors — declared `everyone` suggestions (`isDefault: true`) only; no door binds a set to `guest` until ADR-0138 D3 item 5's bind door is built (#16110) |
* | security-role-word (error) | ADR-0090 D3 vocabulary freeze — own function/registry entry since #8310; CLI + runtime publish gate since commit a227afa41 |
* | security-book-audience-unknown-set(warn)| ADR-0046 §6.7 { permissionSet } |
* | security-private-no-readscope (info) | admin-intent mismatch class |
Expand All @@ -28,13 +28,13 @@
* — the lint moves the failure from runtime-deny to author-time fix-it.
* `security-anchor-high-privilege` mirrors that gate for the one suggestion a
* package can actually declare (`isDefault: true` → the `everyone` anchor,
* `suggested-audience-bindings.ts`'s "the only declarable suggestion"); a set
* an operator binds to `guest` at install time is a decision ADR-0090 D9
* ("a package may suggest bindings … the admin confirms each individually")
* puts past authoring, so this rule is not — and cannot be — coverage for an
* app-authored anchor set bound to `guest` (#16110). No runtime door binds a
* set to `guest` today; ADR-0138 D3 item 5's bind door runs
* `describeAnchorForbiddenBits(set, 'guest')` when it is built.
* `suggested-audience-bindings.ts`'s "the only declarable suggestion"). A
* package never suggests a `guest` binding, and no door binds a set to
* `guest` until ADR-0138 D3 item 5's bind door is built (it amends ADR-0090
* D9's "a package may suggest bindings to `everyone` or `guest`"), so this
* rule is not — and cannot be — coverage for a set bound to `guest` (#16110).
* That door, when built, is an act of the environment's administrator, never
* of a package, and runs `describeAnchorForbiddenBits(set, 'guest')` itself.
*
* `security-cbp-ambiguous-relation` used to be the one exception: the runtime
* did not refuse two candidate masters, it silently PICKED one by field
Expand Down Expand Up @@ -932,10 +932,10 @@ export function validateSecurityPosture(stack: AnyRec, opts?: { nowMs?: number }
// D5: an isDefault set is a SUGGESTED binding to the `everyone` anchor —
// hold it to the anchor tier at author time (the runtime gate enforces the
// same predicate at bind time; this moves the failure to the author).
// Scope: `isDefault: true` is the only declarable suggestion today — a set
// an operator binds to `guest` at install carries no author-time flag for
// this rule to key off, so that binding is outside what a package-time
// linter can see and is judged by the bind-time gate alone (#16110).
// Scope: `isDefault: true` is the only declarable suggestion, and it names
// `everyone` alone. No door binds a set to `guest` until ADR-0138 D3 item
// 5's bind door is built (an administrator's act, never a package's), so
// no `guest` binding exists for a package-time linter to see (#16110).
if (ps.isDefault === true) {
const offending = describeAnchorForbiddenBits(ps, 'everyone', anchorContext);
if (offending) {
Expand Down
4 changes: 3 additions & 1 deletion packages/spec/src/identity/position.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ export const PositionSchema = lazySchema(() => strictObject(
* tenant's default grants (resolved per-request; additive, no fallback
* cliff). `guest` is held implicitly (and exclusively) by unauthenticated
* principals; its bindings face the strictest lint tier. Packages SUGGEST
* bindings to these anchors at install time — never auto-bind.
* bindings to `everyone` at install time and the admin confirms each — never
* auto-bind. No door binds a set to `guest` until ADR-0138 D3 item 5's bind
* door is built, an act of the environment's administrator, never a package's.
*/
export const EVERYONE_POSITION = 'everyone';
export const GUEST_POSITION = 'guest';
Expand Down
Loading