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
feat(spec,plugin-security): A5 — package-level capability declaration API (#2920)
Give packages a formal, EXPLICIT entry point to DEFINE their own authorization
capabilities, so package-owned capabilities flow into the sys_capability
registry with managed_by:'package' + package_id provenance instead of relying on
the implicit "derive an untitled capability from a permission set's
systemPermissions[]" back-door (ADR-0066 D1; aligns with ADR-0094 D5).
- spec: new defineCapability / CapabilityDeclarationSchema
({ name, label?, description?, scope, packageId? }); new `capabilities`
field on the stack definition (+ compose concat).
- plugin-security: new bootstrapDeclaredCapabilities seeds declared capabilities
with package provenance (new package_id field + index on sys_capability).
Idempotent, upgrade-aware; refuses to hijack curated platform capabilities or
a foreign package's rows, never clobbers admin rows, and CLAIMS a pre-existing
derived placeholder. bootstrapSystemCapabilities gains declaredCapabilityNames
so its back-compat derivation skips (never clobbers) declared capabilities.
- runtime: stack-declared `capabilities` registered into the metadata registry
(type `capability`) for the boot seeder to read.
- lint: validateCapabilityReferences treats stack.capabilities as a known source.
- docs: authorization.mdx + ADR-0066 D1 note; app-showcase example (define +
grant the showcase.export_data capability).
Note: corrects the tracking issue's premise — capability was already a single
source of truth (no app→framework duplication); this task adds the missing
explicit package DECLARATION path + provenance, not a de-dup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
Copy file name to clipboardExpand all lines: docs/adr/0066-unified-authorization-model.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,6 +42,8 @@ The design rule that resolves the recurring confusion: **a resource declares the
42
42
### D1 — Capability registry [new]
43
43
Promote capabilities from bare strings to **first-class records** (`sys_permission` / capability definition) with `name`, `label`, `description`, `scope` (platform | org), and a `managedBy` (platform | package | admin). `systemPermissions[]` on permission sets and `requiredPermissions[]` on resources become **references** to these records. Packages declare their capabilities; admins add new ones in Setup. Back-compat: existing string capabilities are seeded as records with the same `name`, so all current references keep resolving.
44
44
45
+
**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-0094 D5 (retire implicit `managed_by`-guessing).
46
+
45
47
### D2 — Secure-by-default object/field posture [new] (data-model posture, NOT a permission)
46
48
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).
0 commit comments