Skip to content

Commit a581c57

Browse files
docs(adr): record the stage-8 retirement of the four security-catalog row objects in the ADRs that name them (#23362)
Fixes #22978 Clause-②: no docs only — no package ships docs/adr/**. This diff touches only `docs/adr/**`, a Tier H governed surface: the PR stays a draft and lands on the maintainer's approval. ## What this changes ADR-0131 D2/D3/D13, carried out by #15204 stage 8 (`66949b7729`, PR #23222, merged 2026-10-13), stopped registering `sys_position`, `sys_permission_set`, `sys_position_permission_set` and `sys_capability`, and unwired the ADR-0094 `permission` projector, its boot reconcile and the data-door write-throughs. The tables and their rows stay until the ADR-0131 D10 migration. Nine of the eleven ADR files on this card still named those objects or that machinery as live. Each now records the retirement in the form AGENTS.md rule 13 names, an amended status line, plus a dated note at each section that names them: - `· **Amended** (2026-10-14, ADR-0131 …)` on the status line. ADR-0017 used this form for ADR-0131 D13's `sys_view_definition` retirement. - `> **RETIRED 2026-10-13 (#15204 stage 8, by execution of ADR-0131 …)**` where a mechanism the ADR itself decided is gone. ADR-0006 v4's D1 is the precedent. - `> **Note (2026-10-14) — …**` where the decision stands and only the stored object it names is gone. ADR-0061 D5 is the precedent. **No decision is rewritten: every edit is a pure insertion.** Each file was compared with `difflib.SequenceMatcher` against the merge base `18cd410dc4`: 36 insert opcodes, 0 replace, 0 delete. No heading changes (the diff has no `+#` or `-#` line), so no anchor that other files cite moves. ADR-0131 is linked with the sibling spelling `./0131-total-organization-ownership-no-null-organization-id.md`, without a fragment, as all 25 existing links to it in the corpus are written. This is a leaf of the #15194 epic subtree. `content/docs/**` (the devx lane's own card) and `packages/**` are untouched. ## Per-file record | File | Passage | Note written | Why | |---|---|---|---| | `0029-kernel-object-ownership-…` | D1 ownership table, `plugin-sharing` row: `sys_permission_set`, `sys_*_permission_set` | status segment; Note under D1 | the row names two retired row objects; D1's ownership rule stands | | `0057-erp-authorization-core-…` | status line: "realizes them as flat `sys_position`"; the path note's `sys-position.object.ts` | status segment | the status line itself names the retired object, and stage 8 deleted that file | | `0057-system-data-lifecycle-…` | none | nothing | `git grep -c -E 'sys_position\|sys_permission_set\|sys_capability\|permission-set-projection' -- docs/adr/0057-system-data-lifecycle-and-retention.md` exits 1 (zero hits). The control is the same grep on the ERP 0057, which counts 2 | | `0066-unified-authorization-model` | principle item 2 (records edited in Setup); D1 "Landed" (materialized into `sys_capability`); D5 (sets land as `sys_permission_set`) with "What stays unchanged"; refinement ⑨ (the `sys_capability` seed row) | status segment; 4 Notes | each passage names a retired row object as a live store; the three-way separation stands | | `0086-authz-metadata-config-…` | Re-evaluation: the status table's D5/P1 seeder rows; ⚠️ item 1 (projection and write-through); the restated D1 table's Assignment row (`sys_position_permission_set`) | status segment; 2 Notes; 1 RETIRED inside item 1 | this ADR's status line sends readers to the Re-evaluation for "the current, code-grounded status of every decision", so the notes go there | | `0090-permission-model-v2-…` | D3 rename table (`sys_position`, `sys_position_permission_set`); D5 (`everyone` bound through `sys_position_permission_set`; `sys_capability`); D8 (`sys_position_permission_set`-like tables) | status line; 3 Notes | the renames and rules stand, but the tables they name are retired | | `0091-grant-lifecycle-…` | D1 (binding table left undated); Non-goals bullet 1; Phasing L2 (`sys_position.delegatable`) | status segment; 3 Notes | the reasoning stands; the table and the column are gone | | `0094-sys-permission-set-pure-projection` | TL;DR, D1, D2, D3, D4, Consequences (the open question), the 2026-07-14 addendum's per-type table and the `scope` tradeoff, the 2026-09-04 amendment's "one major left to live" | status segment; RETIRED under the TL;DR, D2, D3, D4 and at the end of the 2026-09-04 amendment; Notes under D1, the Consequences and the addendum | ADR-0131 D2/D3 retires the record's whole projection; D1 stands, generalized by ADR-0131 D3 | | `0103-managedby-write-policy-…` | Context item 2 and its "Not to be confused with" block (provenance-column objects); the v16 addendum's writable-set list; the #4671 addendum's three link tables | status segment; 3 Notes | the policy stands; one link table and two provenance-column objects are retired | | `0126-packaged-metadata-customization-model` | §4 (D2): "the permission projection keeps its own row-state door"; §8 (D5) item 3: how `sys_permission_set.active` and the ledger relate | status segment; 2 Notes | the projection and its row are gone; ADR-0131 §4 moves `active` into the definition's lifecycle | | `0131-total-organization-ownership-…` | none written | nothing | this is the deciding record. D2, D3, D13 and §4 already describe these objects as retiring, §1 and §7 are dated context, and §8's C3 row is the plan. Its status line says execution state lives on the epic, not on that line | **Left alone and listed, not noted (dated context, or text that already reads as retired):** - 0057 ERP: the Context axis table ("ObjectStack today: `sys_permission_set`"), and D5's `sys_role_permission_set`, which the status line already marks superseded (D4–D7). - 0086: the original body (Context "What is already true", D3, D5, P1, Consequences). The ADR keeps every decision's current status in its Re-evaluation, where the notes go. The Net assessment points back to ⚠️ item 1. - 0094: Context (dated: "had two writable stores"), D5 (already RETIRED 2026-08-09), Alternatives considered, References. - 0126: §1.1, "What exists today — measured" (dated context). - 0131: §1.2 item 4, §1.3, §5 and §7 (dated); D2, D3, D13 and §4 (the retiring text); §8's C3 row. ## Facts the notes state, and where each was read (`main` at `18cd410dc4`) - **Stage 8** (`66949b7729`): its own message says the objects are "no longer registered" and "Tables and rows stay for the organization-ownership migration (ADR-0131 D10)". Of the unwired modules it says "The modules stay for 7a/7b to delete". The ADR-0087 entry is `packages/spec/src/migrations/entries/semantic/18.catalog-row-objects-retired.ts`. - **The projection module:** `permission-set-projection.ts` is still in the tree. `security-plugin.ts` does not register it, and its deletion is #15204 stage 7a (#22975, open). Stage 7b (`5c9bfb0d3f`, PR #23302) already deleted the position write-through and the row-only backfill. None of the eleven ADRs names those two. - **The projector seam stays.** `git grep -c "this.runMutationProjector(" -- packages/metadata-protocol/src/protocol.ts` answers 7. `security-catalog-echo.ts` (PR #23334, `6d96721faa`) registers the seam for `permission` and `position` and writes no row. ADR-0094's D2 note and its 2026-09-04 note both say this, so neither can be read as "the seam is gone". - **The seeder:** `bootstrap-declared-permissions.ts` was deleted by stage 6b-2 (`c8a9783eca`, PR #22900). - **`PositionSchema`** has the keys `name`, `label`, `description`, `permissionSets`, `delegatable` and `protection`, and no team key. The ADR-0090 D8 note depends on this. - **The D12 gate** reads `delegatable` "from the security catalog by name" (`delegated-admin-gate.ts`). The ADR-0091 L2 note depends on this. - **Outside the eleven:** `git grep -c -E 'sys_position|sys_permission_set|sys_capability' -- 'docs/adr/*.md'` lists exactly the ten numbers on this card. No other ADR names the four objects. ## Gates — head `abc9f1aa3d` (this branch merged with `origin/main` `18cd410dc4`) Every exit code was captured before any pipe. - **The five named gates**, all exit 0: `pnpm check:adr-anchors`, `pnpm check:adr-links` (758 relative link destinations under `docs/adr/`, all found), `pnpm check:adr-symbol-anchors` (2336 anchors across 142 records, all found), `pnpm check:nul-bytes` and `pnpm check:doc-authoring`. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` lists 19 commands. 18 exit 0. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exits 3 with `PREREQUISITE NOT MET` (`@objectstack/formula` is not built). That is **NOT MEASURED**, and it was not built for, because no package source is in this diff. - `--ran` reconciliation: "19 derived famil(ies) accounted for — 18 run, 1 NOT-MEASURED", exit 0. - **No build and no test suite ran:** the diff holds no package source. ## 维护者速读(草稿) **改了什么** - 9 份 ADR 提到了岗位、权限集、岗位-权限集绑定、能力这四个“目录行对象”,或提到了 ADR-0094 的权限集投影机制。这些地方现在都标注了“已退役”:状态行追加一段 Amended 修订记录,受影响的章节下各加一段带日期的说明。 - 原文没有改动,全部是插入,标题也没动,其他文件引用这些 ADR 的锚点不受影响。 - 有两份没改:ADR-0131 是做出退役决定的那份,原文本来就写着“退役”;ADR-0057 的数据生命周期那份没有提到这四个对象。 **为什么改** - 第 8 阶段(#15204)已在 10 月 13 日合并,这四个对象已经从系统中摘除,但上面这些 ADR 还把它们写成在用的数据表。按 AGENTS.md 第 13 条,被后来决定退役的内容,要在原 ADR 上留下修订记录。不留的话,人和 AI 读到旧 ADR,会以为这些表还在,照着写出错误的代码或元数据。 **风险与代价(含回滚)** - 只改文档,不涉及任何发布的包,对运行时没有影响。回滚就是 revert 本 PR。 - 说明里的每条事实都对照当前 main 核实过:哪个提交退役了什么;表和数据保留到 D10 迁移;投影模块仍在代码树里,等 7a 阶段删除。7a 落地后,“模块仍在树上”这句会变成历史描述,但说明带了日期,不会误导读者。 **席位意见** **你要做的** - `docs/adr/**` 是受管路径(Tier H),需要你审阅后批准,或亲手合并。 - 一个可选决定:ADR-0131 是否也要在状态行记一笔“C3 第 8 阶段已执行”?本 PR 没有加,因为它的状态行明确写着执行进度由 epic 记录,不放在状态行。 --- _Generated by [Claude Code](https://claude.ai/code/session_013zGKMG1cRedVzWtqGGGZW9)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 02a691a commit a581c57

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)