Skip to content

Commit 9a26455

Browse files
committed
docs(adr): record the stage-8 retirement of the four security-catalog row objects in the ADRs that name them
ADR-0131 D2/D3/D13, executed by #15204 stage 8 (66949b7, 2026-10-13), retired sys_position, sys_permission_set, sys_position_permission_set and sys_capability as row objects and unwired the ADR-0094 projector, its boot reconcile and the data-door write-throughs. Nine ADRs that name them gain an amended status-line segment (AGENTS.md rule 13) and a dated note at each affected section; no decision text is rewritten and no heading changes. ADR-0131 (the deciding record) and ADR-0057's data-lifecycle record are left unchanged: the first already reads as retiring them, the second names none of them. Claude-Session: https://claude.ai/code/session_013zGKMG1cRedVzWtqGGGZW9 Co-authored-by: Claude <noreply@anthropic.com>
1 parent e790cfc commit 9a26455

9 files changed

Lines changed: 163 additions & 6 deletions

‎docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0029: Kernel Object Ownership — First-Party Capabilities as Plugins That Own Their Data, and Decomposing the `platform-objects` Monolith
22

3-
**Status**: Accepted — K0/K1/K2/D7 implemented; K3 (pending ADR-0030/storage) + K4 cleanup remaining (proposed 2026-06-01 · calibrated 2026-06-12) · **Amended** (2026-08-09, [#6853](https://github.com/objectstack-ai/objectstack/issues/6853) — **D9**: a tenant `sys_metadata` overlay of an `object` registers as its own `overlay` contributor layer instead of taking the packaged `own` slot and splicing the packaged definition out. D3's single-owner invariant is unchanged. **Design only — nothing is implemented yet**; see **"Amendment (2026-08-09, #6853)"** at the end for the measurement, the semantics, the blast radius, and what is deliberately left open.)
3+
**Status**: Accepted — K0/K1/K2/D7 implemented; K3 (pending ADR-0030/storage) + K4 cleanup remaining (proposed 2026-06-01 · calibrated 2026-06-12) · **Amended** (2026-08-09, [#6853](https://github.com/objectstack-ai/objectstack/issues/6853) — **D9**: a tenant `sys_metadata` overlay of an `object` registers as its own `overlay` contributor layer instead of taking the packaged `own` slot and splicing the packaged definition out. D3's single-owner invariant is unchanged. **Design only — nothing is implemented yet**; see **"Amendment (2026-08-09, #6853)"** at the end for the measurement, the semantics, the blast radius, and what is deliberately left open.) · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13 — two security objects in D1's `plugin-sharing` row, `sys_permission_set` and `sys_position_permission_set` (one of the `sys_*_permission_set` tables), are retired as row objects by #15204 stage 8 (`66949b7729`, 2026-10-13); see the note under D1.)
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package as first-class citizen), [ADR-0019](./0019-app-as-consumer-unit.md) (app as the consumer-facing unit), [ADR-0025](./0025-plugin-package-distribution.md) (plugin package distribution + dependencies)
66
**Related**: [ADR-0028](./0028-metadata-naming-and-namespace-isolation.md) (metadata naming & namespace isolation) **depends on** this ADR — its D5/D6 (reserved `sys` namespace, single-owner-per-object, apps-cannot-define-kernel) assume the kernel is properly owned. This ADR is sequenced **first** and is independently valuable; ADR-0028 owns the naming model, this ADR owns kernel object *ownership*.
@@ -100,6 +100,12 @@ data monolith. Ownership *declaration* (`manifest`) and object *definition*
100100
(Exact assignment of security objects — under `plugin-sharing` vs a dedicated
101101
`plugin-rbac` — to settle in implementation.)
102102

103+
> **Note (2026-10-14) — two security objects in the `plugin-sharing` row are retired.**
104+
> `sys_permission_set` and `sys_position_permission_set` are no longer registered since #15204
105+
> stage 8 (`66949b7729`, 2026-10-13), by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md)
106+
> D3/D13: a permission set is a definition in the metadata registry, and a position names its sets
107+
> in its own definition. `sys_user_permission_set` stays; D1's ownership rule is unchanged.
108+
103109
### D2 — Small core; everything else is a capability plugin
104110

105111
Split kernel objects into two tiers by a clear criterion:

‎docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0057: ERP-Grade Authorization Core — Business-Unit Partitioning, Scope-Depth Grants, and Hierarchy Rollup
22

3-
**Status**: Accepted in part (2026-06-21) — D1–D3/D8 implemented (scope-depth enum + evaluator, `sys_business_unit` tree, hierarchy-resolver seam, conformance rows); **D4–D7 superseded by ADR-0090 D3 / ADR-0095**: the `sys_role`/`sys_user_role`/`role_and_subordinates` vocabulary never shipped — the code realizes them as flat `sys_position`/`sys_user_position` with rollup on the BU tree. This ADR's own implementation-status table predates that supersession; read it with the rename applied.
3+
**Status**: Accepted in part (2026-06-21) — D1–D3/D8 implemented (scope-depth enum + evaluator, `sys_business_unit` tree, hierarchy-resolver seam, conformance rows); **D4–D7 superseded by ADR-0090 D3 / ADR-0095**: the `sys_role`/`sys_user_role`/`role_and_subordinates` vocabulary never shipped — the code realizes them as flat `sys_position`/`sys_user_position` with rollup on the BU tree. This ADR's own implementation-status table predates that supersession; read it with the rename applied. · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13): the flat `sys_position` row object named above is itself retired by #15204 stage 8 (`66949b7729`, 2026-10-13), and its `sys-position.object.ts`, which the path note below names, is deleted; a position is a metadata-registry definition, and the `sys_user_position` assignment stays.
44
**Deciders**: ObjectStack Protocol Architects
55
**Path note (2026-09), paths only — no decision changes**: this record cites the **pre-rename**
66
vocabulary, and those four paths no longer resolve. `sys-department.object.ts` and `department-graph.ts`

‎docs/adr/0066-unified-authorization-model.md‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0066: Unified authorization model — capability registry, secure-by-default posture, resource→capability contracts, dual-surface gates
22

3-
**Status**: Accepted (2026-06-23) — D1–D5 + refinements ⑤/⑨ implemented (capability registry + `defineCapability` seeding, `access.default` posture with posture-gated superuser bypass, `requiredPermissions` AND-gates on object/field/action, `crudBucketForOperation`, `validateCapabilityReferences` lint); D4's UI half enforced in objectui ActionRunner.
3+
**Status**: Accepted (2026-06-23) — D1–D5 + refinements ⑤/⑨ implemented (capability registry + `defineCapability` seeding, `access.default` posture with posture-gated superuser bypass, `requiredPermissions` AND-gates on object/field/action, `crudBucketForOperation`, `validateCapabilityReferences` lint); D4's UI half enforced in objectui ActionRunner. · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13): the stored records this record names — `sys_permission_set` and `sys_capability` — are retired as row objects by #15204 stage 8 (`66949b7729`, 2026-10-13), and the seeders that filled them are retired under ADR-0131 D2; permission sets and capabilities are metadata-registry definitions, and the `sys_user_permission_set` assignment stays. The three-way separation stands; see the notes under the principle, D1, D5 and ⑨.
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (`readScope`/`writeScope` depth: own/unit/unit_and_below/org), [ADR-0058](./0058-expression-and-predicate-surface.md) (CEL predicate surface used by RLS); relates to cloud ADR-0016 (authz open/paid boundary)
66
**Consumers**: `@objectstack/spec` (object/app/permission schemas), `@objectstack/plugin-security` (RLS/FLS compiler + enforcement), `../objectui` (ActionRunner + app/nav gating), `../cloud` (control-plane objects, e.g. `sys_license`)
@@ -19,6 +19,13 @@ Authorization splits into three concerns that must stay decoupled:
1919

2020
The design rule that resolves the recurring confusion: **a resource declares the capability it requires (a stable contract); the capability and its assignment are dynamic, admin-maintained records.** A resource never bakes "who" — only "what is required".
2121

22+
> **Note (2026-10-14) — of item 2's records, only the assignments remain.** `sys_permission_set`,
23+
> and `sys_role` / `sys_role_permission_set` under their ADR-0090 D3 names `sys_position` /
24+
> `sys_position_permission_set`, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by
25+
> execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a permission set, and
26+
> the sets a position carries, are definitions in the metadata registry; the user assignments
27+
> `sys_user_permission_set` and `sys_user_position` stay runtime records.
28+
2229
## Authorization needs (taxonomy: need × status)
2330

2431
| # | Need | Status |
@@ -44,6 +51,13 @@ Promote capabilities from bare strings to **first-class records** (`sys_permissi
4451

4552
**Landed (2026-07):** the curated platform set is `@objectstack/spec` `PLATFORM_CAPABILITIES` (`security/capabilities.ts`), seeded `managed_by:'platform'` by `bootstrap-system-capabilities.ts`. "**Packages declare their capabilities**" is now an EXPLICIT API — `defineCapability` + a stack's `capabilities` array (`{ name, label?, description?, scope, packageId? }`) — materialized into `sys_capability` with `managed_by:'package'` + `package_id` provenance by `bootstrap-declared-capabilities.ts` (idempotent, upgrade-aware; refuses to hijack curated/foreign capabilities, never clobbers admin rows, and *claims* a pre-existing derived placeholder). This retires the implicit back-door where a capability existed only as an untitled placeholder derived from a permission set's `systemPermissions[]`; that derivation still runs for back-compat but skips any explicitly-declared name. A capability is **not** a contract and carries no `inputs`: DEFINE (`defineCapability`) / GRANT (`systemPermissions`) / REQUIRE (`requiredPermissions`) stay decoupled. Aligns with ADR-0066 D1 (retire implicit `managed_by`-guessing).
4653

54+
> **Note (2026-10-14) — `sys_capability` is retired.** The row object this paragraph materializes
55+
> declared capabilities into is no longer registered since #15204 stage 8 (`66949b7729`, 2026-10-13), by execution of
56+
> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13, and no seeder writes a
57+
> declared capability into any table (ADR-0131 D2): `PLATFORM_CAPABILITIES` and a stack's
58+
> `defineCapability` declarations are served by the metadata registry. DEFINE / GRANT / REQUIRE stay
59+
> decoupled.
60+
4761
### D2 — Secure-by-default object/field posture [new] (data-model posture, NOT a permission)
4862
Add an object (and field) flag that opts it **out of blanket wildcard grants** — e.g. `access: { default: 'private' }` (vs the implicit `'public'`). A `private` object is **not** covered by `'*': {allowRead:true}`; access requires an **explicit** permission-set grant. Mirrors Salesforce "new object = no access until granted." This is a posture like `tenancy`, declared on the object — it is **not** an assignment and names no principal. `admin_full_access` (the superuser `'*'` grant) still covers private objects unless it too is excluded (rare).
4963

@@ -62,6 +76,11 @@ An action declaring `requiredPermissions` is enforced in **one place, two surfac
6276
### D5 — Package-seeded, admin-maintainable policies [existing mechanism, fill the gap]
6377
A package may seed permission-set policies (incl. per-object grants for **its own** objects) via `stack.permissions` **[existing]**; these land as `sys_permission_set` records **admins can edit in Setup** **[existing]**. The gap to close: make a package's per-object secure default (D2 + an admin-only grant) expressible + seedable so a sensitive package object is locked on install and tunable thereafter.
6478

79+
> **Note (2026-10-14) — a package's sets no longer land as `sys_permission_set` records.** That row
80+
> object is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by execution of
81+
> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a package's sets are served from
82+
> the metadata registry. Of the runtime records the next section keeps, only the assignments remain.
83+
6584
## What stays unchanged (existing strengths)
6685
Permission sets / roles / bindings as runtime records (assignment layer); the full RLS spectrum (ownership, hierarchy depth ADR-0057, tenant isolation, sharing, CEL); `tenancy`; `App.requiredPermissions`; anti-escalation; union grant semantics.
6786

@@ -112,3 +131,8 @@ Captured for the record; **out of scope for Phases 1–4 above**. Each is anchor
112131
- **⑦ Permission-set groups + subtractive *muting*.** Pure union does not scale governance ("permission-set explosion"); Salesforce added permission-set-group *muting* precisely to allow taking access away. Roles→permission-sets already bundle; a subtractive/deny layer (precedence step 4) is the missing piece for large-org administration. Pairs with delegated admin (#9).
113132
- **⑧ FLS posture: unlisted fields are visible, and field grants union without deny** *(recorded 2026-07 pre-launch architecture assessment)*. Runtime FLS is block-list-shaped: `field-masker.ts` hides a field only when an explicit rule marks it `readable:false`, so an **undeclared** sensitive field is exposed by default, and `getFieldPermissions` merges most-permissively, so one set's `readable:true` permanently out-votes another's `false`. Consequences: protecting a salary/ID-number field relies on discipline (grant it only where needed), not mechanism. The object-level `private` posture (D2/④) mitigates the unlisted-default-visible half for objects that adopt it; the union half is the field-level face of ⑦ — a muting/deny layer must cover **field** grants, not just object grants, when it lands. Until then, treat "sensitive field on a `public` object" as a smell in review.
114133
- **⑨ Authoring-time validation for capability references** *(recorded 2026-07 pre-launch architecture assessment)*. `systemPermissions` / `requiredPermissions` are free strings today; a typo (`mange_users`) fails closed at runtime — the safe direction — but is undiscoverable: nothing reports that the referenced capability exists nowhere. When D1 lands the `sys_permission` registry, add the authoring/publish-gate lint alongside it: referencing an unregistered capability warns at author time (consistent with ADR-0049 honesty and the contract-first Prime Directive — reject at the producer, don't tolerate at the consumer). **Landed (2026-07):** `validateCapabilityReferences` in `@objectstack/lint` warns (never errors — cross-package capabilities and the fail-closed runtime make a hard gate wrong) when a `requiredPermissions` reference resolves against neither the built-in set (`@objectstack/spec` `PLATFORM_CAPABILITY_NAMES`), nor a capability the stack grants via a permission set's `systemPermissions`, nor a `sys_capability` seed row. Wired into `os validate` and `os lint`. `systemPermissions` (the declaration side) is deliberately not flagged.
134+
135+
> **Note (2026-10-14) — ⑨'s seed-row source is gone.** With `sys_capability` retired by #15204
136+
> stage 8 (`66949b7729`, 2026-10-13; [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md)
137+
> D3/D13), `validateCapabilityReferences` no longer counts a `sys_capability` seed row as a known
138+
> capability, because no runtime reader consults one.

0 commit comments

Comments
 (0)