Part of #10206
The gap
capability is a metadata kind (CapabilityDeclarationSchema, ADR-0066 D1), authorable as defineStack({ capabilities }). AppPlugin registers stack-declared capabilities under that name and bootstrapDeclaredCapabilities reads them back to seed sys_capability.
The permissions section is thorough about consuming capabilities — permission-sets.mdx, authorization.mdx, permission-metadata.mdx, access-recipes.mdx, administrator-guide.mdx all reference them. Nothing tells a package author how to declare one. The only shape documentation is a section inside the generated references/security/misc.mdx, a page titled "misc" that a developer will never search for.
This is an authorization surface: systemPermissions / requiredPermissions resolve capabilities by name string, so a declared capability lands in the same namespace a permission set grants from. #5961 closed the enforcement half (before it, PUT /meta/capability/:name accepted any JSON); the authoring half is still undocumented.
File surface
content/docs/permissions/capabilities.mdx — newcontent/docs/permissions/index.mdx — edit (Cards + overview)content/docs/permissions/meta.json — one added entry
⛔ Nothing outside content/docs/permissions/.
What to write
Read CapabilityDeclarationSchema (packages/spec/src/security/capabilities.ts), the capabilities: docblock in stack.zod.ts, the DEFAULT_METADATA_TYPE_REGISTRY entry, and bootstrapDeclaredCapabilities:
- What a declared capability is and its lifecycle: declared in a package → registered by
AppPlugin → seeded into sys_capability → granted through a permission set → checked by requiredPermissions. One diagram or one ordered list; the reader needs the whole loop, because every existing page shows only its own segment. - The declaration shape with a worked example.
- The name is the contract. Resolution is by string, so naming and namespacing decide whether a grant lands where you meant it.
capability is code-only: allowRuntimeCreate: false — validated on the artifact route, refused at the runtime write door. State it, so the 403 is legible.- ⚠️The trap, and the reason this page has to be careful: the
requires: array on a stack is a different namespace — canonical kebab-case tokens from PLATFORM_CAPABILITY_TOKENS, declaring what the package needs from the platform, fail-fast at startup if missing. capabilities: declares authorization capabilities the package offers. Two arrays, similar words, unrelated vocabularies. Disambiguate them explicitly and early — a reader who conflates them will write something that validates and does nothing. - Cross-link
permission-sets.mdx and authorization.mdx for the consuming half.
⛔ Non-goals
Gates (derived at dispatch time via node scripts/pm/dispatch-gates.mjs, not recalled)
check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:published-readme-links, check:role-word, check:cross-package-test-inputs, and the spec liveness set (check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs). Re-derive against your actual diff; ADR anchor checks apply once you cite ADR-0066 decision letters.
Acceptance
- A package author can declare a capability, grant it, and check it, from one page.
capabilities: versus requires: is impossible to confuse after reading it.- Named gates green.
Part of #10206
The gap
capabilityis a metadata kind (CapabilityDeclarationSchema, ADR-0066 D1), authorable asdefineStack({ capabilities }).AppPluginregisters stack-declared capabilities under that name andbootstrapDeclaredCapabilitiesreads them back to seedsys_capability.The permissions section is thorough about consuming capabilities —
permission-sets.mdx,authorization.mdx,permission-metadata.mdx,access-recipes.mdx,administrator-guide.mdxall reference them. Nothing tells a package author how to declare one. The only shape documentation is a section inside the generatedreferences/security/misc.mdx, a page titled "misc" that a developer will never search for.This is an authorization surface:
systemPermissions/requiredPermissionsresolve capabilities by name string, so a declared capability lands in the same namespace a permission set grants from. #5961 closed the enforcement half (before it,PUT /meta/capability/:nameaccepted any JSON); the authoring half is still undocumented.File surface
content/docs/permissions/capabilities.mdx— newcontent/docs/permissions/index.mdx— edit (Cards + overview)content/docs/permissions/meta.json— one added entry⛔ Nothing outside
content/docs/permissions/.What to write
Read
CapabilityDeclarationSchema(packages/spec/src/security/capabilities.ts), thecapabilities:docblock instack.zod.ts, theDEFAULT_METADATA_TYPE_REGISTRYentry, andbootstrapDeclaredCapabilities:AppPlugin→ seeded intosys_capability→ granted through a permission set → checked byrequiredPermissions. One diagram or one ordered list; the reader needs the whole loop, because every existing page shows only its own segment.capabilityis code-only:allowRuntimeCreate: false— validated on the artifact route, refused at the runtime write door. State it, so the 403 is legible.requires:array on a stack is a different namespace — canonical kebab-case tokens fromPLATFORM_CAPABILITY_TOKENS, declaring what the package needs from the platform, fail-fast at startup if missing.capabilities:declares authorization capabilities the package offers. Two arrays, similar words, unrelated vocabularies. Disambiguate them explicitly and early — a reader who conflates them will write something that validates and does nothing.permission-sets.mdxandauthorization.mdxfor the consuming half.⛔ Non-goals
role/profile/policyas declarable kinds.capability无 DEFAULT_METADATA_TYPE_REGISTRY / schema 条目 ——PUT /api/v1/meta/capability/:name接受任意 JSON 且/meta/types报 allowRuntimeCreate:true(与 #5271 的api同形) #5961 ruled them explicitly out: noPLURAL_TO_SINGULARmapping, no declaration schema, no read-back seam. (permissions/profiles.mdxis titled "Profiles (removed)" — leave it alone.)packages/spec/**.Gates (derived at dispatch time via
node scripts/pm/dispatch-gates.mjs, not recalled)check:doc-anchors,check:docs-audit-scope,check:docs-redirects,check:published-readme-links,check:role-word,check:cross-package-test-inputs, and the spec liveness set (check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs). Re-derive against your actual diff; ADR anchor checks apply once you cite ADR-0066 decision letters.Acceptance
capabilities:versusrequires:is impossible to confuse after reading it.