Skip to content

docs(permissions): declaring package capabilities: has no page — the permissions docs only cover consuming them #10216

Description

@os-elon

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 — new
  • content/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.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions