Skip to content

docs(permissions): name where a high-privilege anchor binding is refused; spec patch changeset for the anchor docblock - #24006

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23887-anchor-docblock-changeset
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23887-anchor-docblock-changeset

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23887
Fixes #23935
Clause-②: no

What

The contract review of the anchor-docblock change (6f7ad7900f, review 6214176883 on #23373) left two release-text items. This PR handles both:

  1. .changeset/23887-anchor-docblock-guest-no-door.md: '@objectstack/spec': patch for the docblock that change landed on EVERYONE_POSITION / GUEST_POSITION (packages/spec/src/identity/position.zod.ts:159-167). The docblock now says packages suggest bindings to everyone only and the admin confirms each one, and that no door binds a set to guest until ADR-0138 D3 item 5's bind door is built.
  2. content/docs/permissions/authorization.mdx:68-78: the page said "The everyone/guest audience-anchor bindings reject high-privilege sets at the data layer for every caller". That stopped being true when the data-layer gate retired with sys_position_permission_set. The new text names each refusal point measured below.

This PR changes no code, no other docs page and nothing under packages/spec/src/**. It touches no governed surface.

Why the changeset is owed (measured on this tree's build)

  • packages/spec/package.json files[] = ["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json","protocol-upgrade-guide.md"]. So src/identity/position.zod.ts ships in the tarball.
  • After pnpm --filter @objectstack/spec build, the new sentence (No door binds a set to `guest` ) appears in dist/membership-role-BpDGLF1o.d.ts and dist/membership-role-a9eypi2I.d.mts. Positive control: the older sibling sentence ("its bindings face the strictest lint tier") appears in the same two files.

Where a high-privilege set bound to an anchor is refused (measured)

Tree: packages/** byte-identical to origin/main 5fb8d158b5. Search: git grep -n describeAnchorForbiddenBits gives 38 hits. Only 3 are production call sites, and all 3 pass everyone. The other production anchor check is the one describeHighPrivilegeBits caller in security-plugin.ts. The anchor seal paths were read separately. The line numbers the card gave (:512, :865) have since moved to :659 and :1040.

# Refusal point Code Answer Measured by
1 Accepting a package isDefault suggestion: POST /api/v1/security/suggested-bindings/:id/confirm calls confirmAudienceBindingSuggestion plugin-security/src/suggested-audience-bindings.ts:1040 (everyone tier, with the stack's declared capabilities) 403 PERMISSION_DENIED; the row stays pending and nothing is recorded suggested-audience-bindings.test.ts "refuses a set carrying anchor-forbidden bits — 403, nothing recorded", plus the undeclared-token and platform-capability controls: passed
2 Each derivation of everyone's definition, acceptance half: readAcceptedEveryoneSets, for record rows and for a walled host's acceptedDefaultPermissionSets suggested-audience-bindings.ts:659 the set is withheld and reported as forbidden; declareEveryone logs it at error tests "withholds an accepted set whose published body now carries a forbidden bit" and "a declared name passes the everyone anchor's high-risk gate": passed
3 Each derivation, baseline half: everyoneBaseline (describeHighPrivilegeBits, which is the whole everyone tier) plugin-security/src/security-plugin.ts:4755-4765, called from declareBuiltinPositions (init()) and declareEveryone (kernel:ready, and rederiveEveryone on accept, revoke, uninstall and package publish) the baseline set is left off everyone, with a warn builtin-positions.declaration-phase.test.ts and the security-plugin.test.ts block "audience-anchor bindings read the stack's declared capabilities": passed
4 Author time: security-anchor-high-privilege in validateSecurityPosture, keyed on isDefault: true only lint/src/validate-security-posture.ts:940; registry row lint/src/authoring-rules.ts:2010-2019 (gating, commands validate/build/lint, surfaces cli + runtime-publish, runtimeTypes include permission) an error finding on the CLI. At the metadata door's publish it is 422 INVALID_METADATA (metadata-protocol/src/runtime-authoring-gate.ts:1194-1195). Draft saves are not gated scratch probe below and validate-security-posture.test.ts (166 passed)
5 guest no production caller of the guest tier no door binds a set: the confirm door refuses a guest row with 409 SUGGESTION_STATE (suggested-audience-bindings.ts:1009), and the sync never produces one test "refuses a guest-anchor row loudly rather than record a grant it does not deliver": passed
6 The anchor seal (any set, high-privilege or not) package door: objectql/src/security-catalog-namespace.ts:122 BUILT_IN_SECURITY_CATALOG_NAMES; metadata door: the sealed-managed-item refusal at metadata-protocol/src/protocol.ts:18279 package door: refused. Metadata door: 403 NOT_OVERRIDABLE registry-security-catalog-namespace.test.ts "the built-in holders" (9 passed, position everyone among them). The metadata-door half is pinned for guest by packages/qa/dogfood/test/guest-grants-channel.dogfood.test.ts:479-491, which I read and did not run (it needs the whole-workspace dogfood boot). everyone goes through the same predicate, which I also read and did not run

No engine (data-layer) write gate on anchor bindings remains: the plugin-security write gates mention no anchor refusal. delegated-admin-gate.ts ANCHOR_POSITIONS refuses sys_user_position assignments to an anchor, which is a user assignment and not a set binding. The /security/_activation door refuses switching an anchor (400), which is not a binding either.

Scratch probe (not committed; run with tsx against packages/lint/src over the built closure). Calls runRuntimeAuthoringRules({ type: 'permission', item }) and validateSecurityPosture({ permissions: [item] }):

runtime rules for permission: validateRetiredPermissionResidue,validateSecurityPosture,validateSecurityRoleWord,validateRlsPredicateEnforceability
isDefault_with_delete {"runtimeErrors":["security-anchor-high-privilege"],"runtimeAdvisories":[],"cliFindings":["security-anchor-high-privilege:error"]}
isDefault_read_only {"runtimeErrors":[],"runtimeAdvisories":[],"cliFindings":[]}
not_default_with_delete {"runtimeErrors":[],"runtimeAdvisories":[],"cliFindings":[]}

Tests run locally (each through scripts/pm/os-verify-lock.sh, every VERDICT command-exit 0, after building the plugin-security and lint dependency closures):

  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/suggested-audience-bindings.test.ts src/builtin-positions.declaration-phase.test.ts src/audience-anchors.test.ts: 3 files, 65 passed
  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/security-plugin.test.ts -t 'audience-anchor bindings read the stack': 3 passed, 217 skipped
  • pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-security-posture.test.ts: 166 passed
  • pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/registry-security-catalog-namespace.test.ts -t 'built-in holders': 9 passed, 22 skipped

Gates (head 240caf9b61)

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 55 commands for this diff. All 55 exited 0. One of them, pnpm --filter @objectstack/spec run check:skill-examples, first exited 3 (PREREQUISITE NOT MET, no client-react declarations). I built @objectstack/client-react and its closure, and the rerun exited 0 with 263 examples type-checked.
  • dispatch-gates --ran gave: 55 derived, 55 run, 0 NOT-MEASURED, 0 UNRUN.
  • Changeset family:
    • check-empty-changeset: "1 declaring changeset(s) added"
    • check-adr-0087-registration: "no declared-breaking changeset (1 non-breaking changeset(s) seen)"
    • check-changeset-no-major: protocol lockstep holds. Its clause-② level axis prints NOT APPLICABLE locally (no pull_request payload), so this PR's Check Changeset run reads that axis from the line above.
  • Docs:
    • check:doc-anchors: 531 fragment links resolve
    • check:doc-authoring: clean
    • check:docs-transcript-drift: clean
    • check:docs-audit-scope: clean
    • check:nul-bytes: OK
  • Run outside the derived list:
    • node scripts/docs-audit/check-affected-docs.mjs exits 0, and node scripts/docs-audit/affected-docs.mjs origin/main reports "0 docs name something this change touched … 0 changed package(s)". The Docs Drift Check workflow triggers on packages/** only, so it does not run on this PR.
    • node scripts/check-adr-links.mjs: 765 links resolve. Lychee runs in CI.

Acceptance notes

  • The changeset will be collected by the next Version Packages PR as though it had shipped with the docblock change.
  • NOT MEASURED locally: the metadata-door 403 NOT_OVERRIDABLE for the anchors (row 6), pinned by dogfood that only main's full battery runs. Also not run: check:dev-prereqs and check:skill-frame-freshness (local-only families; this diff edits no packages/spec source and no four-axis framework file).
  • No label written: the dispatch named none, and skip-changeset does not apply.

Generated by Claude Code

…sed; spec patch changeset for the anchor docblock

The authorization page said the everyone/guest anchor bindings reject
high-privilege sets "at the data layer for every caller". That gate
retired with sys_position_permission_set. The sentence now names the
measured refusal points: the confirm door (403), every derivation of the
everyone definition, and the author-time rule on the CLI and the runtime
publish gate (422); no door binds a set to guest yet.

Adds the '@objectstack/spec' patch changeset the anchor docblock on
EVERYONE_POSITION / GUEST_POSITION owed: the text ships in src/**/*.zod.ts
and in dist/*.d.ts.

Claude-Session: https://claude.ai/code/session_01LFqdRsuM5nkqoJdHd5ztVk
Co-authored-by: Claude <noreply@anthropic.com>
…d the metadata door refuses the publish

Claude-Session: https://claude.ai/code/session_01LFqdRsuM5nkqoJdHd5ztVk
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Oct 16, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Review: ACCEPT · #23887 + #23935 · PR #24006 @ 240caf9b612b08e6286caf99462c07e69d78fa2e · domain:spec seat 2 (#18549) · 2026-10-16T17:56Z

Session session_01LFqdRsuM5nkqoJdHd5ztVk · os-bill. This is the reviewer of record, checked against GitHub rather than the report. The chain: claims 6220950693 (#23887) and 6220975123 (#23935); the os-dev subagent's report is the os-dev-report on #23887.

  • Shape.
    • Draft against main. Lines 1 and 2 are Fixes #23887 and Fixes #23935; line 3 is Clause-②: no.
    • 2 files, +24/−3: one .changeset and one docs paragraph. No governed path, no code, no packages/spec/src/**. No contract-tier review is owed.
  • Changeset. '@objectstack/spec': patch, Clause-②: no. The seat checked it against position.zod.ts:159–:167 on main: its description matches the docblock PR docs: guest has no bind door until ADR-0138 D3 item 5's door is built (ADR-0090 D9 line, two docblocks) #23780 landed (6f7ad7900f) word for word, so it closes contract review 6214176883's ② ground for PR docs: guest has no bind door until ADR-0138 D3 item 5's door is built (ADR-0090 D9 line, two docblocks) #23780.
    • Why it is owed. files[] ships src/**/*.zod.ts, and the dev measured the sentence in both built dist/membership-role-*.d.ts and .d.mts, with the older sentence as a positive control.
  • Docs. authorization.mdx:68–:78 replaces "reject high-privilege sets at the data layer for every caller" with the refusal points measured on main. The seat spot-checked the source sites:
    • describeAnchorForbiddenBits on the confirm door (suggested-audience-bindings.ts:1040, 403) and on the derivation filter;
    • the author-time rule security-anchor-high-privilege (validate-security-posture.ts:940), which the runtime publish gate also runs (422);
    • and that no door binds guest.
    • The one site read but not run, the metadata door's anchor seal, is not claimed in the sentence. The link anchor resolves (check:doc-anchors, 531 resolve).
  • Gates. 55 derived families, 55 run, 0 NOT-MEASURED; one prerequisite miss (check:skill-examples) re-run green on this head. CI on this head: in progress at this act, 0 failed so far. Auto-merge lands it only on green.
  • Accepted deviations. The one sentence became three in the same paragraph, each carrying a measured point. The extra closures were built only to measure.
  • Landing terms. Ready, then auto-merge. Fixes #23887 and Fixes #23935 close both cards on merge.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment