Uh oh!
There was an error while loading. Please reload this page.
docs(permissions): add a page for declaring package capabilities - #10335
Conversation
The permissions section is thorough about CONSUMING a capability — granting one through a permission set, requiring one on a resource — but the package author's half had no findable home. The only shape documentation was a section inside the generated `references/security/misc.mdx`, a page titled "misc" that nobody searches for. `content/docs/permissions/capabilities.mdx` covers the declaration side: - `capabilities:` vs `requires:`, disambiguated first, as a table. Two arrays with similar words and unrelated vocabularies: authorization capabilities the package OFFERS (`CapabilityDeclarationSchema`, ADR-0066 D1) versus the closed kebab-case `PLATFORM_CAPABILITY_TOKENS` vocabulary declaring what it NEEDS from the platform (fail-fast at startup). Conflating them produces metadata that validates and does nothing. - The whole loop as a diagram plus an ordered list — declare → register by `AppPlugin` → seed by `bootstrapDeclaredCapabilities` → grant through a permission set → check by `requiredPermissions` — because every existing page shows only its own segment. - The declaration shape, with an `os:check`-marked worked example, and the field table. - The name is the contract: resolution is by string, so a declared name lands in the same flat namespace `systemPermissions` / `requiredPermissions` grant and require from. - The seeder's four refusal paths, including the one an author can cause: a declaration with no resolvable owning package writes no row at all. - `sys_capability` is a CATALOGUE, not a gate — grants and requirements match names as strings and never read the row, and its `active` flag has no authorization effect (maintainer ruling, 2026-08-13). - `capability` is code-only (`allowRuntimeCreate: false` + `allowOrgOverride: false`), so `PUT /meta/capability/:name` is refused by the #5086 inlet with 403 `NOT_CREATABLE` before the body is validated. `index.mdx` gains an overview paragraph and a Cards-list entry; `meta.json` gains one page. Nothing outside `content/docs/permissions/`, except the one generated line `check:docs-audit-scope --write` adds to `.claude/workflows/docs-accuracy-audit.js`. Part of #10216 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GawRwpD44VwBDVy3hs77AX
os-elon
commented
Aug 21, 2026
Landed by the PM under the maintainer's explicit instruction — audit record. This is a governed PR ( Provenance — who, what, where. The repository owner, in the PM session driving epic #10206, 2026-08-20. Authorization verbatim: 「你帮我处理合并吧」. Their per-PR verdict on this one, verbatim: 「可合」 — 「「sys_capability 是目录不是闸」引您 08-13 裁定带源码出处,处理得干净」. PM position, recorded rather than dropped. I stated before acting that an agent-executed governed merge removes the human click the fence treats as the review record, and that the audit would flag it. The maintainer instructed anyway. The decision is theirs; this is the record of it, not a silent reinterpretation of the rule. Landing route. A direct merge is refused by repository rules ( Generated by Claude Code |
Fixes#10216
Sub-issue of the #10206 docs-coverage epic.
What this adds
content/docs/permissions/capabilities.mdx— the package author's half of thecapability story. The permissions section was already thorough about
consuming a capability (
permission-sets.mdx,authorization.mdx,permission-metadata.mdx,access-recipes.mdx,administrator-guide.mdxallreference them); the shape of a declaration lived only in the generated
references/security/misc.mdx, a page titled "misc".The page carries, in this order:
capabilities:vsrequires:, disambiguated first, as a comparisontable. Two arrays with similar words and unrelated vocabularies:
authorization capabilities the package offers
(
CapabilityDeclarationSchema, ADR-0066 D1) versus the closedkebab-case
PLATFORM_CAPABILITY_TOKENSvocabulary declaring what thepackage needs from the platform (unknown token =
defineStackerror;declared-but-missing provider = fail-fast at startup). A reader who
conflates them writes something that validates and does nothing, which is
why this leads rather than sits in a footnote.
registered by
AppPlugin→ seeded bybootstrapDeclaredCapabilities→granted through a permission set → checked by
requiredPermissions. Everyexisting page shows only its own segment.
os:check-marked worked example (socheck:skill-examplestype-checks it against the built spec) plus thefield table and the three named near-miss key refusals
(
permissionSets/requiredPermissions/inputs).lands in the same flat namespace
systemPermissionsgrants from andrequiredPermissionsrequires from. Covers namespacing, the fail-closedtypo, and the
validateCapabilityReferencesauthoring warning.themselves: a declaration with no resolvable owning package writes no
row at all.
capabilityis code-only —allowRuntimeCreate: false+allowOrgOverride: false, so the metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 inlet refusesPUT /api/v1/meta/capability/:namewith403 NOT_CREATABLEbefore thebody is validated, on every kernel. The
OS_METADATA_WRITABLEescape hatchand the
422 invalid_metadatabehind it are named too.index.mdxgains an overview paragraph plus a module-list entry;meta.jsongains one page (placed after
permission-metadata, in the authoring cluster).One statement that is new to the docs, and where it comes from
The page states plainly that the
sys_capabilityrow is a catalogue, not agate: permission-set grants and resource
requiredPermissionsmatchcapability names as strings and never load the row, the only production
readers of the table are the two boot seeders, and the row's
activeflag hasno authorization effect. That is not a reinterpretation of anything in the
permissions section — it is the maintainer's 2026-08-13 ruling as recorded in
packages/plugins/plugin-security/src/objects/sys-capability.object.ts(ADR-0049 enforce-or-remove: the claim was withdrawn rather than enforced,
because putting the registry on the authorization hot path is an architectural
change needing its own card). It is included because the loop this page leads
with would otherwise read as if step ③ were a gate.
.claude/change — declared, and why the PR is human-merge-onlyAdding the page turns
check:docs-audit-scopered by design. Remedy applied:which writes exactly one line into
.claude/workflows/docs-accuracy-audit.js(
+ "content/docs/permissions/capabilities.mdx"inALL_HANDWRITTEN). Committedverbatim; never hand-edited.
Because
.claude/**is governed (#9866), this PR is human-merge-only. Auto-mergeis not armed, the PR is not enqueued, and it stays a draft. Batch-2 siblings #10213
and #10215 append to that same generated block — on conflict, re-run
--writeon themerged base rather than resolving it by hand.
Verification
Gate union run after the final commit, at
b95286d9a, all green — each linebelow is the gate's own verdict:
check:doc-anchors256 internal #fragment link(s) across 400 source file(s) all resolve to a real headingcheck:doc-authoringdoc authoring guard: 381 files clean — no bare metadata literalscheck:role-wordcheck-role-word: OK, no new occurrences of the reserved word.check:docs-redirectscheck-docs-redirects: OK (apps/docs/redirects.mjs: 92 entries …)check:docs-audit-scopedocs-accuracy-audit scope is in sync with content/docs/: 181 hand-written doc(s).check:published-readme-links152 outbound link(s) across 60 published markdown file(s) …check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared …check:nul-bytesOK (scanned 6091 text file(s) … no raw ASCII control bytes)check:pm-governed-mergescheck-governed-merges --self-test: 81 assertionscheck:skill-frame-sync4 copies of the decision frame are structurally isomorphic across 3 filescheck:doc-formula-expressions22 record-scoped formula example(s) across 407 files / 1417 TS blocks judged cleancheck:empty-stateall classified (1 closed, 2 open, 4 output, 9 scope)check:livenessevery governed-type property … is classified …check:strictness-ledgerstrictness ledger: 61 file(s) across 5 triaged director(ies) …check:variant-docsvariant/doc gate: 18 discriminated union(s) — 8 governed, 10 exemptcheck:skill-examples210 prose examples type-check against @objectstack/speccheck:skill-examplesextracted the new block ascontent/docs/permissions/capabilities.mdx:86 → docs__permissions_capabilities__1.ts,so the
defineCapabilityexample is type-checked against the built@objectstack/specdeclarations rather than merely proofread.The gate list was re-derived from the real change set after the final
commit (
node scripts/pm/dispatch-gates.mjs, no paths — it reads the mergebase itself). The
.claude/line added three families the dispatch list didnot name —
check:doc-formula-expressions,check:pm-governed-merges,check:skill-frame-sync— all three run above.Scope
Nothing outside
content/docs/permissions/except the one generated.claude/workflows/docs-accuracy-audit.jsline. Nopackages/spec/**. Noexisting authorization statement weakened, restated or reinterpreted —
profiles.mdx("Profiles (removed)") is untouched, and the page documents nodeclarable kind other than
capability. No changeset: this PR publishesnothing, so it carries
skip-changeset.Generated by Claude Code