Skip to content

docs(permissions): add a page for declaring package capabilities - #10335

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10216-declaring-capabilities
Aug 21, 2026
Merged

docs(permissions): add a page for declaring package capabilities#10335
os-elon merged 1 commit into
mainfrom
claude/issue-10216-declaring-capabilities

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10216

Sub-issue of the #10206 docs-coverage epic.

What this adds

content/docs/permissions/capabilities.mdx — the package author's half of the
capability story. The permissions section was already thorough about
consuming a capability (permission-sets.mdx, authorization.mdx,
permission-metadata.mdx, access-recipes.mdx, administrator-guide.mdx all
reference 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:

  1. capabilities: vs requires:, disambiguated first, as a comparison
    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 the
    package needs from the platform (unknown token = defineStack error;
    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.
  2. The whole loop, as a diagram plus an ordered list — declare →
    registered by AppPlugin → seeded by bootstrapDeclaredCapabilities
    granted through a permission set → checked by requiredPermissions. Every
    existing page shows only its own segment.
  3. The declaration shape, with an os:check-marked worked example (so
    check:skill-examples type-checks it against the built spec) plus the
    field table and the three named near-miss key refusals
    (permissionSets / requiredPermissions / inputs).
  4. The name is the contract — resolution is by string, so a declared name
    lands in the same flat namespace systemPermissions grants from and
    requiredPermissions requires from. Covers namespacing, the fail-closed
    typo, and the validateCapabilityReferences authoring warning.
  5. The seeder's four refusal paths, including the one an author can cause
    themselves: a declaration with no resolvable owning package writes no
    row at all
    .
  6. capability is code-onlyallowRuntimeCreate: 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 refuses
    PUT /api/v1/meta/capability/:name with 403 NOT_CREATABLE before the
    body is validated, on every kernel. The OS_METADATA_WRITABLE escape hatch
    and the 422 invalid_metadata behind it are named too.

index.mdx gains an overview paragraph plus a module-list entry; meta.json
gains 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_capability row is a catalogue, not a
gate
: permission-set grants and resource requiredPermissions match
capability names as strings and never load the row, the only production
readers of the table are the two boot seeders, and the row's active flag has
no 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-only

Adding the page turns check:docs-audit-scope red by design. Remedy applied:

node scripts/docs-audit/check-audit-scope.mjs --write

which writes exactly one line into .claude/workflows/docs-accuracy-audit.js
(+ "content/docs/permissions/capabilities.mdx" in ALL_HANDWRITTEN). Committed
verbatim; never hand-edited.

Because .claude/** is governed (#9866), this PR is human-merge-only. Auto-merge
is 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 --write on the
merged base rather than resolving it by hand.

Verification

Gate union run after the final commit, at b95286d9a, all green — each line
below is the gate's own verdict:

GateVerdict
check:doc-anchors256 internal #fragment link(s) across 400 source file(s) all resolve to a real heading
check:doc-authoringdoc authoring guard: 381 files clean — no bare metadata literals
check: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 assertions
check:skill-frame-sync4 copies of the decision frame are structurally isomorphic across 3 files
check:doc-formula-expressions22 record-scoped formula example(s) across 407 files / 1417 TS blocks judged clean
check: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 exempt
check:skill-examples210 prose examples type-check against @objectstack/spec

check:skill-examples extracted the new block as
content/docs/permissions/capabilities.mdx:86 → docs__permissions_capabilities__1.ts,
so the defineCapability example is type-checked against the built
@objectstack/spec declarations 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 merge
base itself). The .claude/ line added three families the dispatch list did
not 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.js line. No packages/spec/**. No
existing authorization statement weakened, restated or reinterpreted —
profiles.mdx ("Profiles (removed)") is untouched, and the page documents no
declarable kind other than capability. No changeset: this PR publishes
nothing, so it carries skip-changeset.


Generated by Claude Code

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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation labels Aug 20, 2026
@os-elonos-elon added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed documentation Improvements or additions to documentation labels Aug 20, 2026 — with Claude
@os-elon
os-elon requested a review from os-zhuangAugust 20, 2026 16:05
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 00:55
@os-elon
os-elon added this pull request to the merge queueAug 21, 2026
@os-elonClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landed by the PM under the maintainer's explicit instruction — audit record.

This is a governed PR (.claude/workflows/docs-accuracy-audit.js, the #9866 collision), so under the standing regime the merge is the maintainer's own act and their click is the review record. It is being landed by an agent instead, on instruction. This comment exists so the entry is legible in check-governed-merges rather than reading as an unexplained agent merge.

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 (405 Changes must be made through the merge queue), so this PR was flipped ready and entered the merge queue via auto-merge — the only sanctioned path. It lands only if the full-suite queue build passes; the queue has been dequeuing on an unrelated @objectstack/example-showcase failure tonight (see #10253), so a dequeue here would not be this PR's defect.


Generated by Claude Code

Merged via the queue into main with commit 99f1f91Aug 21, 2026
27 checks passed
@os-elon
os-elon deleted the claude/issue-10216-declaring-capabilities branch August 21, 2026 01:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@os-elon@claude