You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a581c57
Browse filesBrowse the repository at this point in the historyBrowse 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>
Copy file name to clipboardExpand all lines: docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# ADR-0029: Kernel Object Ownership — First-Party Capabilities as Plugins That Own Their Data, and Decomposing the `platform-objects` Monolith
2
2
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.)
4
4
**Deciders**: ObjectStack Protocol Architects
5
5
**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)
6
6
**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*
100
100
(Exact assignment of security objects — under `plugin-sharing` vs a dedicated
101
101
`plugin-rbac` — to settle in implementation.)
102
102
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
+
103
109
### D2 — Small core; everything else is a capability plugin
104
110
105
111
Split kernel objects into two tiers by a clear criterion:
**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.
4
4
**Deciders**: ObjectStack Protocol Architects
5
5
**Path note (2026-09), paths only — no decision changes**: this record cites the **pre-rename**
6
6
vocabulary, and those four paths no longer resolve. `sys-department.object.ts` and `department-graph.ts`
**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 ⑨.
4
4
**Deciders**: ObjectStack Protocol Architects
5
5
**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)
@@ -19,6 +19,13 @@ Authorization splits into three concerns that must stay decoupled:
19
19
20
20
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".
21
21
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
+
22
29
## Authorization needs (taxonomy: need × status)
23
30
24
31
| # | Need | Status |
@@ -44,6 +51,13 @@ Promote capabilities from bare strings to **first-class records** (`sys_permissi
44
51
45
52
**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).
46
53
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
+
47
61
### D2 — Secure-by-default object/field posture [new] (data-model posture, NOT a permission)
48
62
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).
49
63
@@ -62,6 +76,11 @@ An action declaring `requiredPermissions` is enforced in **one place, two surfac
62
76
### D5 — Package-seeded, admin-maintainable policies [existing mechanism, fill the gap]
63
77
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.
64
78
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
+
65
84
## What stays unchanged (existing strengths)
66
85
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.
67
86
@@ -112,3 +131,8 @@ Captured for the record; **out of scope for Phases 1–4 above**. Each is anchor
112
131
-**⑦ 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).
113
132
-**⑧ 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.
114
133
- **⑨ 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
0 commit comments