diff --git a/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md b/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md index c568f7d7a4f..8c4203350fa 100644 --- a/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md +++ b/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md @@ -1,6 +1,6 @@ # ADR-0029: Kernel Object Ownership — First-Party Capabilities as Plugins That Own Their Data, and Decomposing the `platform-objects` Monolith -**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.) +**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.) **Deciders**: ObjectStack Protocol Architects **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) **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* (Exact assignment of security objects — under `plugin-sharing` vs a dedicated `plugin-rbac` — to settle in implementation.) +> **Note (2026-10-14) — two security objects in the `plugin-sharing` row are retired.** +> `sys_permission_set` and `sys_position_permission_set` are no longer registered since #15204 +> stage 8 (`66949b7729`, 2026-10-13), by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13: a permission set is a definition in the metadata registry, and a position names its sets +> in its own definition. `sys_user_permission_set` stays; D1's ownership rule is unchanged. + ### D2 — Small core; everything else is a capability plugin Split kernel objects into two tiers by a clear criterion: diff --git a/docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md b/docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md index 8929007ccff..ca158a853e4 100644 --- a/docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md +++ b/docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md @@ -1,6 +1,6 @@ # ADR-0057: ERP-Grade Authorization Core — Business-Unit Partitioning, Scope-Depth Grants, and Hierarchy Rollup -**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. +**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. **Deciders**: ObjectStack Protocol Architects **Path note (2026-09), paths only — no decision changes**: this record cites the **pre-rename** vocabulary, and those four paths no longer resolve. `sys-department.object.ts` and `department-graph.ts` diff --git a/docs/adr/0066-unified-authorization-model.md b/docs/adr/0066-unified-authorization-model.md index 01878adb8de..8bd709c0be3 100644 --- a/docs/adr/0066-unified-authorization-model.md +++ b/docs/adr/0066-unified-authorization-model.md @@ -1,6 +1,6 @@ # ADR-0066: Unified authorization model — capability registry, secure-by-default posture, resource→capability contracts, dual-surface gates -**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. +**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 ⑨. **Deciders**: ObjectStack Protocol Architects **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) **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: 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". +> **Note (2026-10-14) — of item 2's records, only the assignments remain.** `sys_permission_set`, +> and `sys_role` / `sys_role_permission_set` under their ADR-0090 D3 names `sys_position` / +> `sys_position_permission_set`, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by +> execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a permission set, and +> the sets a position carries, are definitions in the metadata registry; the user assignments +> `sys_user_permission_set` and `sys_user_position` stay runtime records. + ## Authorization needs (taxonomy: need × status) | # | Need | Status | @@ -44,6 +51,13 @@ Promote capabilities from bare strings to **first-class records** (`sys_permissi **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). +> **Note (2026-10-14) — `sys_capability` is retired.** The row object this paragraph materializes +> declared capabilities into is no longer registered since #15204 stage 8 (`66949b7729`, 2026-10-13), by execution of +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13, and no seeder writes a +> declared capability into any table (ADR-0131 D2): `PLATFORM_CAPABILITIES` and a stack's +> `defineCapability` declarations are served by the metadata registry. DEFINE / GRANT / REQUIRE stay +> decoupled. + ### D2 — Secure-by-default object/field posture [new] (data-model posture, NOT a permission) 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). @@ -62,6 +76,11 @@ An action declaring `requiredPermissions` is enforced in **one place, two surfac ### D5 — Package-seeded, admin-maintainable policies [existing mechanism, fill the gap] 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. +> **Note (2026-10-14) — a package's sets no longer land as `sys_permission_set` records.** That row +> object is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by execution of +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a package's sets are served from +> the metadata registry. Of the runtime records the next section keeps, only the assignments remain. + ## What stays unchanged (existing strengths) 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. @@ -112,3 +131,8 @@ Captured for the record; **out of scope for Phases 1–4 above**. Each is anchor - **⑦ 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). - **⑧ 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. - **⑨ 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. + +> **Note (2026-10-14) — ⑨'s seed-row source is gone.** With `sys_capability` retired by #15204 +> stage 8 (`66949b7729`, 2026-10-13; [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13), `validateCapabilityReferences` no longer counts a `sys_capability` seed row as a known +> capability, because no runtime reader consults one. diff --git a/docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md b/docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md index 7f655be34a9..a1dede8a1c0 100644 --- a/docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md +++ b/docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md @@ -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. +**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. **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`) @@ -382,6 +382,13 @@ flagged inline below. | **P1** framework `packageId` + seeder | **Done** | = D3 + D5 above. | | **P2** two doors + overlay/subtract | **Two doors done; the overlay subtract layer is RETIRED for `permission`** | Two doors shipped (D6/D7). The subtract layer had landed as an **ADR-0005 first-class overlay** (ADR-0094 D5) rather than the Salesforce-muting/explicit-deny path this ADR sketched — **retired 2026-08-09 (ADR-0094 D5-R)**, so for an artifact-backed set there is no overlay customization channel at all. The ADR-0066 precedence-step-4 explicit-deny layer remains deferred and **evidence-gated**; with the overlay channel withdrawn, a package set is customized by editing the package and re-publishing. | +> **Note (2026-10-14) — the D5/P1 seeder and the row it seeded are gone.** +> `bootstrap-declared-permissions.ts` was deleted by #15204 stage 6b-2 (`c8a9783eca`; ADR-0131 D2: +> no seeder writes a declared item into any table), and the `sys_permission_set` row object it +> filled is retired by stage 8 (`66949b7729`, 2026-10-13; +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13). A package's sets reach a +> deployment as metadata-registry definitions; D3's `packageId` / `managedBy` stay on the definition. + ### ⚠️ Two mechanisms in the original text are superseded by a better realization 1. **P0's "slice-merge the shared *data record*" premise → ADR-0094 projection + overlay.** @@ -396,6 +403,11 @@ flagged inline below. the store, not the body shape — so this ADR's load-bearing claim that namespaced keys make a package's slice unambiguous still holds and underpins both slice-merge and overlay.* + > **RETIRED 2026-10-13 (#15204 stage 8, by execution of ADR-0131 D2/D3/D13).** The projection + > and the data-door write-through this item describes are unregistered, and the + > `sys_permission_set` row object is retired: there is no derived row any more, so "no second + > writable authority" now holds with no row at all. + 2. **D4's "Salesforce muting / clone-to-customize" subtract layer → ADR-0005 whole-document overlay — ⛔ RETIRED 2026-08-09, see ADR-0094 D5-R.** ADR-0094 D5 (revised 2026-07-14) had made an environment overlay of a package-owned set a **first-class ADR-0005 customization** @@ -434,6 +446,13 @@ except where noted: | Delegated-admin **`adminScope`** on a set | **metadata (definition) + config (grant)** | NEW (ADR-0090 D12): admin capability itself is scoped (BU subtree + assignable-set allowlist + no self-escalation); `authorEnvironmentSets` keeps package-managed rows publish-only per this ADR. | | Organization / tenancy / actual users & teams | **config / data** | Unchanged. Tenancy is now an explicit kernel service (ADR-0093) and tenant isolation an always-first Layer 0 (ADR-0095). Teams *receive* sharing, never carry capability (ADR-0090 D8). | +> **Note (2026-10-14) — the position → permission-set binding is a definition now.** +> `sys_position_permission_set` is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by +> execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a position names its +> sets in its own definition (`PositionSchema.permissionSets`), so the binding leaves the +> **Assignment** row for the **Position** definition row. `sys_user_position` and +> `sys_user_permission_set` remain the assignment line. + ### Net assessment ADR-0086 is **done and correct**, not pending. Its provenance-axis classification and Shape-B diff --git a/docs/adr/0090-permission-model-v2-concept-convergence.md b/docs/adr/0090-permission-model-v2-concept-convergence.md index c0d56959280..2fb593ae51e 100644 --- a/docs/adr/0090-permission-model-v2-concept-convergence.md +++ b/docs/adr/0090-permission-model-v2-concept-convergence.md @@ -2,6 +2,7 @@ **Status**: Accepted (2026-07-09) · **Amended** (2026-10-13, ADR-0138 D3/D4) — D9's `guest` anchor bindings resolve for anonymous requests, in the organization ADR-0138 D4 resolves; an empty set denies all. +· **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. **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), @@ -209,6 +210,12 @@ collaboration = `team`. The word "role" does not exist here.** `permission_set` is deliberately **not** renamed — see *Alternatives considered*. +> **Note (2026-10-14) — two of the renamed tables are retired.** `sys_position` and +> `sys_position_permission_set` are no longer registered since #15204 stage 8 (`66949b7729`, +> 2026-10-13), by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a +> position is a metadata-registry definition that names its sets in `PositionSchema.permissionSets`, +> and `sys_user_position` stays as the assignment. The renames and the word ban stand. + ### D4 — OWD vocabulary: canonical four, aliases removed The `sharingModel` enum shrinks to `private | public_read | public_read_write | @@ -286,6 +293,13 @@ Replaces both the `member_default` builtin-set fallback and ADR-0056 D7's defaul > supply the declared list they keep the pre-revision behaviour — the predicate's default — so the > narrowing reaches a binding only where a caller can name what this stack declared. +> **Note (2026-10-14) — `everyone`'s bindings are no longer rows.** With +> `sys_position_permission_set` and `sys_capability` retired by #15204 stage 8 (`66949b7729`, +> 2026-10-13; [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13), a set reaches +> `everyone` through the position's definition — ADR-0131 D3 keeps the baseline derived from +> `isDefault`, and an accepted suggestion edits that definition instead of inserting a row — and a +> package-declared capability lives in the registry. D5's rules stand. + ### D6 — Explain engine is P0; access-matrix snapshots gate publishes A contract `explain(principal, operation, object, record?) → decision + granting path` (which @@ -334,6 +348,11 @@ receive shared records **horizontally** (fluid, deal/project-shaped). This is a lint-checked at the spec level (no `team_id` on `sys_position_permission_set`-like tables), so the Dataverse owner-team auditability failure mode is unrepresentable rather than discouraged. +> **Note (2026-10-14).** `sys_position_permission_set` is retired by #15204 stage 8 (`66949b7729`, +> 2026-10-13; [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13); a position's sets +> are its definition's `PositionSchema.permissionSets`, a list of permission-set names with no team +> key. The constraint stands. + ### D9 — Audience anchors: `everyone` + `guest` positions; packages suggest, never own App developers legitimately need the universal audiences — admin, authenticated user, anonymous — diff --git a/docs/adr/0091-grant-lifecycle-and-recertification.md b/docs/adr/0091-grant-lifecycle-and-recertification.md index d10335e41d5..8a19343f057 100644 --- a/docs/adr/0091-grant-lifecycle-and-recertification.md +++ b/docs/adr/0091-grant-lifecycle-and-recertification.md @@ -1,6 +1,7 @@ # ADR-0091: Grant lifecycle — effective-dated assignments, delegation, break-glass, recertification substrate - **Status:** Accepted + · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13): `sys_position_permission_set`, which D1 and the non-goals leave undated, and the `sys_position` row L2 adds `delegatable` to, are retired by #15204 stage 8 (`66949b7729`, 2026-10-13); a position's sets and its `delegatable` flag are its metadata-registry definition. D1's dated assignment rows stand. - **Date:** 2026-07-09 (proposed) · 2026-07-10 (accepted) - **Deciders:** jack@objectstack.ai - **Relates to:** ADR-0090 (Permission Model v2 — named follow-up #1), ADR-0057 (assignment tables), ADR-0049 (no unenforced security properties), ADR-0016 (open-core boundary) @@ -76,6 +77,12 @@ which belongs to the D7 human-gated publish track, not to per-person grants) and `sys_record_share` (time-boxed record access is what share links already do). Existing rows carry nulls — zero migration. +> **Note (2026-10-14) — the binding table is retired; the reasoning stands.** +> `sys_position_permission_set` is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), by +> execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13: a position's sets +> are part of its definition (`PositionSchema.permissionSets`), so a dated binding change is a +> definition change, the publish track this paragraph already sends it to. + ### D2 — Correctness = resolution-time filtering, fail-closed A grant row outside its validity window **does not resolve**, everywhere, @@ -195,6 +202,9 @@ every edition); *convenience and compliance workflow* are the product. request-approval flows belong to the existing approvals plugin composed at the product layer. +> **Note (2026-10-14).** The first non-goal's table is retired (see the note under D1); a scheduled +> binding change is a scheduled change to a position's definition and stays out of scope here. + ## Phasing 1. **L1 (spec + filtering)** — columns, zod shapes, resolver filtering + tests @@ -216,6 +226,10 @@ every edition); *convenience and compliance workflow* are the product. 3. **L3 (enterprise)** — cloud-side campaign/notification/activation product design doc, consuming L1/L2 substrate only. +> **Note (2026-10-14) — L2's `sys_position.delegatable` left with its object.** `sys_position` is +> retired by #15204 stage 8 (`66949b7729`, 2026-10-13; [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13); the D12 gate reads `PositionSchema.delegatable` from the security catalog by name. + ## References - ADR-0090 §Non-goals #1 (this ADR); ADR-0090 D6/D10/D12 diff --git a/docs/adr/0094-sys-permission-set-pure-projection.md b/docs/adr/0094-sys-permission-set-pure-projection.md index 47bf28e9627..e4c2618c6e6 100644 --- a/docs/adr/0094-sys-permission-set-pure-projection.md +++ b/docs/adr/0094-sys-permission-set-pure-projection.md @@ -1,6 +1,6 @@ # ADR-0094: Permission-Set Definitions Have One Authoritative Store — `sys_permission_set` Becomes a Pure Projection -**Status**: Accepted (2026-07-14); **D5 retired 2026-08-09** (see D5-R) +**Status**: Accepted (2026-07-14); **D5 retired 2026-08-09** (see D5-R) · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13): **the projection is retired by execution** — #15204 stage 8 (`66949b7729`, 2026-10-13) unregistered the `sys_permission_set` row object, the `permission` projector with its boot reconcile, and the data-door write-through; D1 stands, generalized by ADR-0131 D3. See the notes under the TL;DR, D1–D4, the Consequences, the 2026-07-14 addendum and the 2026-09-04 amendment. **Deciders**: ObjectStack Protocol Architects **Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (overlay store), [ADR-0056](./0056-permission-model-landing-verification.md) (landing verification), [ADR-0086](./0086-authz-metadata-config-boundary-and-cross-package-composition.md) (two doors / provenance) **Closes**: framework#2875 (root cause behind the #2857 display-freshness class) @@ -13,6 +13,12 @@ ADR-0005 rollback landed by #6483 / PR #6608) ## TL;DR +> **RETIRED 2026-10-13 (#15204 stage 8, `66949b7729`, by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13).** +> The `sys_permission_set` row object is no longer registered, and the machinery that kept it a +> projection — the write-through (D3), the `permission` projector (D2) and the boot reconcile and +> backfill (D4) — is unwired; its module stays in the tree until #15204 stage 7a deletes it. D1 +> stands. Read the rest of this summary, D2–D4 and the Consequences in the past tense. + A permission-set **definition** (label, description, the six facet groups, `adminScope`, `active`) now has exactly **one authoritative store: the metadata layer** — packaged declarations plus the `sys_metadata` overlay, merged overlay-wins by the protocol's @@ -93,8 +99,20 @@ recreates ids. **Assignments and bindings stay data-plane.** Which users/positions hold a set is environment *state*, not part of the definition; those tables are unchanged. +> **Note (2026-10-14) — D1 stands; the row and the binding table it names do not.** +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3 generalizes D1: the metadata registry +> is the one store, and #15204 stage 8 (`66949b7729`, 2026-10-13) retired both the projected record +> and `sys_position_permission_set` (a position's sets are now its definition's `permissionSets`). +> `sys_user_permission_set` stays. + ### D2 — The record is written only by the projector, awaited by the protocol (door enumeration amended 2026-09-04 — see the amendment at the end) +> **RETIRED 2026-10-13 (#15204 stage 8, by execution of ADR-0131 D2/D3).** The `permission` +> projector is unwired and the record it wrote is retired; a metadata-door save reaches the security +> catalog through the door's own registry write-through. The protocol seam `registerMutationProjector` +> stays: since #23334 (`6d96721faa`) the security catalog echo uses it for `permission` and +> `position` to register environment-wide definitions in memory, and it writes no row. + `@objectstack/metadata-protocol` gains `registerMutationProjector(type, fn)`: an awaited, best-effort per-type hook invoked after persistence inside `saveMetaItem` (active saves), `publishMetaItem`, and `deleteMetaItem`, receiving @@ -132,6 +150,10 @@ when the protocol predates `registerMutationProjector`. ### D3 — Data-door writes are redirected into metadata (write-through) +> **RETIRED 2026-10-13 (#15204 stage 8, by execution of ADR-0131 D3/D13).** The write-through is +> unwired with the `sys_permission_set` row object, whose data route is gone; a permission set is +> written through the metadata door alone. + An engine middleware (registered by `plugin-security`, object-filtered to `sys_permission_set`, running **inside** the security middleware so all existing authorization — the ADR-0086 two-doors gate, the ADR-0090 D12 delegated-admin gate, @@ -184,6 +206,10 @@ store there is no split brain to prevent. ### D4 — Boot reconciliation and the migration/backfill path +> **RETIRED 2026-10-13 (#15204 stage 8, by execution of ADR-0131 D2/D13).** The boot reconcile and +> backfill are unwired; legacy `sys_permission_set` rows stay in their table until the ADR-0131 D10 +> migration takes them. + At `kernel:ready`, after the ADR-0086 D5 package seeding, `plugin-security` runs a convergence pass: @@ -341,6 +367,11 @@ other write. metadata) stays open; junction FKs and Setup's list/query surface make the projection the pragmatic shape today. +> **Note (2026-10-14) — the open question is answered.** +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3 drops the record store, and #15204 +> stage 8 (`66949b7729`, 2026-10-13) executed it: the security catalog is served from the metadata +> registry, with no `sys_permission_set` row object. + ## Alternatives considered - **Keep hardening the #2867 subscriber** (more events, more call sites). Rejected — @@ -427,6 +458,14 @@ Only `sys_permission_set` was both metadata-authoritative **and** carried a harmful, actively-drifting split-brain, which is why it was fixed first and in full. The follow-up work above landed via framework#2909. +> **Note (2026-10-14) — three of the four types have no record object any more.** +> `sys_permission_set`, `sys_position` and `sys_capability` are retired as row objects by #15204 +> stage 8 (`66949b7729`, 2026-10-13), by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13, and no seeder writes a declared item into any table (ADR-0131 D2): their definitions live +> in the metadata registry alone, so the `sys_position` and `sys_capability` rows of the table above, +> and the `sys_capability.scope` tradeoff below, describe row objects that are no longer registered. +> `sys_sharing_rule` is not one of the four row objects stage 8 retired. + #### Recorded tradeoffs (#2909) - **Why `sys_sharing_rule` chose seed-not-clobber over a write gate @@ -587,3 +626,9 @@ tracked on #15204. So this amendment describes a mechanism with one major to live. It is written for whoever has to keep the seven doors correct until then — not as an invitation to build something new on top of them. + +> **RETIRED 2026-10-13 — the major ran out.** #15204 stage 8 (`66949b7729`) executed C3's +> retirement of this mechanism: the `permission` projector is unwired and the `sys_permission_set` +> row object is retired. The seven call sites remain as the generic seam; the security catalog echo +> (#23334, `6d96721faa`) now registers the `permission` and `position` projectors behind them and +> writes no row. diff --git a/docs/adr/0103-managedby-write-policy-and-engine-write-guard.md b/docs/adr/0103-managedby-write-policy-and-engine-write-guard.md index 489c72df789..6d27e67a08a 100644 --- a/docs/adr/0103-managedby-write-policy-and-engine-write-guard.md +++ b/docs/adr/0103-managedby-write-policy-and-engine-write-guard.md @@ -2,6 +2,7 @@ - **Status**: Accepted (D5 completed in v17 — see the #3355 addendum at the end: the residual `system` bucket is renamed `system-data` and the bare value retired) + · **Amended** (2026-10-14, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D3/D13): `sys_position_permission_set`, one of the RBAC link tables this record files as writable data, and `sys_position` / `sys_capability`, two of the provenance-column objects it sets apart, are retired as row objects by #15204 stage 8 (`66949b7729`, 2026-10-13); the policy stands. See the notes in the Context and the two addenda. - **Date**: 2026-07-19 - **Issue**: #3220 (root cause surfaced by the #1591 / #3213 better-auth guard work; safe slice shipped in #3222) - **Relates to**: ADR-0049 (no unenforced security properties), ADR-0092 (identity @@ -52,6 +53,13 @@ write policy **expressible and enforced** without breaking the writable set. > per-row provenance axis; this ADR is about the per-object schema bucket. The > shared word is a coincidence. +> **Note (2026-10-14) — three objects named above are retired.** `sys_position_permission_set` +> (item 2), `sys_position` and `sys_capability` are no longer registered since #15204 stage 8 +> (`66949b7729`, 2026-10-13), by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13: positions, their sets and capabilities are metadata-registry definitions. The RBAC link +> tables are now `sys_user_position` and `sys_user_permission_set`, and of the three +> provenance-column objects only `sys_sharing_rule` remains. + ## Decision ### D1 — The bucket is a default; the *resolved affordance* is the policy @@ -181,6 +189,11 @@ successor to the engine-owned-DEFAULT overload of `system`. "engine-managed schema, writable via `userActions`" — the residual meaning of the bucket after the engine-owned rows move out. +> **Note (2026-10-14).** `sys_position_permission_set` has left the writable set with its object, +> retired by #15204 stage 8 (`66949b7729`, 2026-10-13; +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13); the relabel's reasoning is unchanged. + **Not a behaviour or enforcement change.** Resolved affordances, the guard verdict, the 405 reconciliation and the permissions clamp are byte-identical before and after; this is a self-documenting relabel. No data migration (`managedBy` is schema @@ -277,6 +290,12 @@ model. Under v16 all eight resolved `import: false` (locked `system` plus would have put a "bulk-bind permission sets from a spreadsheet" entry point in the admin console for the first time. +> **Note (2026-10-14).** Two of the three link tables remain: `sys_position_permission_set` is +> retired by #15204 stage 8 (`66949b7729`, 2026-10-13; +> [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) +> D3/D13), and a position's sets are edited in its definition through the metadata door, not +> imported as rows. The decision below is unchanged. + **What is NOT at stake.** No authorization boundary. `import` is an affordance — it decides whether a UI entry point renders. Every row a CSV import writes still goes through `DelegatedAdminGate`, RLS and permission-set adjudication one at a diff --git a/docs/adr/0126-packaged-metadata-customization-model.md b/docs/adr/0126-packaged-metadata-customization-model.md index 88252904649..f822b261d98 100644 --- a/docs/adr/0126-packaged-metadata-customization-model.md +++ b/docs/adr/0126-packaged-metadata-customization-model.md @@ -1,6 +1,6 @@ # ADR-0126: The platform customization model for packaged metadata — three regimes, one activation ledger -**Status**: **Amended** (2026-09-04) by [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — maintainer, verbatim: 「所以 ADR-0126 不能简单的关掉」「hotcrm 是标准的软件包,客户安装之后能根据业务需求实现具体的定制」「同意」. The three regimes stand at **environment scope only**: O's per-organization reading is retired (ADR-0005 amended), C's activation ledger stays as tenant-less deployment state (D3's reserved `organization_id`, written NULL, is withdrawn — ADR-0131 D1 forbids a nullable tenant column), E stands; the unreleased implementation (#12185, #12190, #12296, #12348, #12419, framework half of #12491) ships in 17.3 with that column dropped. The install mode ADR-0131 D6 adds (managed = sealed definitions + these regimes; template = copied once, fully editable, refused on shared-DB multi-tenant) is the frame the regimes now sit in. Original status line follows. +**Status**: **Amended** (2026-09-04) by [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — maintainer, verbatim: 「所以 ADR-0126 不能简单的关掉」「hotcrm 是标准的软件包,客户安装之后能根据业务需求实现具体的定制」「同意」. The three regimes stand at **environment scope only**: O's per-organization reading is retired (ADR-0005 amended), C's activation ledger stays as tenant-less deployment state (D3's reserved `organization_id`, written NULL, is withdrawn — ADR-0131 D1 forbids a nullable tenant column), E stands; the unreleased implementation (#12185, #12190, #12296, #12348, #12419, framework half of #12491) ships in 17.3 with that column dropped. The install mode ADR-0131 D6 adds (managed = sealed definitions + these regimes; template = copied once, fully editable, refused on shared-DB multi-tenant) is the frame the regimes now sit in. · **Amended** (2026-10-14, ADR-0131 D2/D3/D13): the permission projection §4 and §8 name is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), which unwired the ADR-0094 projector and retired the `sys_permission_set` row object; see the notes in §4 and §8. Original status line follows. **Original status**: Proposed (2026-08-25) — awaiting the maintainer's hand-merge, which is itself the acceptance act for a governed surface (Prime Directive #14) **and the ruling that settles the tentative flow-instance directions in §7** (chartered on @@ -234,6 +234,12 @@ read at each runtime's own consult point; an empty ledger changes nothing anywhe convergence (§8). Each consumer documents its consult point; the ledger imposes no global dispatch layer. +> **Note (2026-10-14) — the permission projection's row-state door is gone.** #15204 stage 8 +> (`66949b7729`, 2026-10-13) unwired the ADR-0094 projection and retired the `sys_permission_set` +> row object, by execution of [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D2/D3/D13, so +> the row-state door the bullet above names no longer exists; per ADR-0131 §4, a permission set's +> `active` state moves into the environment-authored definition's lifecycle. + ## 5. Decision (D3): scope and write authority - **Install-level rows now.** Every row is written with `organization_id NULL`. For the default @@ -386,6 +392,11 @@ callers first, or don't), and preserves "packaged code calls what it names". (active row state + free clone, no linkage). The convergence card decides one thing only: how `sys_permission_set.active` and the generic ledger relate (projection vs migration). Nothing about it is urgent. + + > **Note (2026-10-14).** The convergence question has no row left to relate: + > `sys_permission_set` is retired by #15204 stage 8 (`66949b7729`, 2026-10-13; ADR-0131 + > D3/D13), and ADR-0131 §4 moves `active` into the environment-authored definition's lifecycle. + 4. **The five overlay types are untouched.** No overlay-type work is chartered, implied, or permitted by this ADR. 5. **Docs**: the §1.3 promise pages are corrected to promise what each regime actually delivers