Skip to content

docs(plugin-security): restate the upsertEnvPermissionSet JSDoc for ADR-0094 D5-R (#7082) - #7346

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7082-permission-set-projection-jsdoc
Aug 10, 2026
Merged

docs(plugin-security): restate the upsertEnvPermissionSet JSDoc for ADR-0094 D5-R (#7082)#7346
os-zhuang merged 1 commit into
mainfrom
claude/issue-7082-permission-set-projection-jsdoc

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7082

Prose only. No behaviour change, no test-logic change, no gate added.

The anchor, re-verified at this branch point

The card measured the stale block at :332 and triage re-confirmed it on b88f5e8. My branch point is origin/main @ f3f855ac154f202fa9f1497abd3e0dff60436e1a, where the block has shifted to :323-338 but is otherwise byte-identical. The card's :332 still lands inside it, on the direction confirmed 2026-07-14 line. Quoted verbatim at that SHA:

323: /**
324:  * Project a PermissionSet body onto its `sys_permission_set` row from the
...
330:  * #2867 band-aid declined to create). A PACKAGE-OWNED row is also projected —
331:  * an env-scope overlay is the platform's standard customization of a packaged
332:  * definition (ADR-0005; direction confirmed 2026-07-14, reversing the earlier
333:  * refuse-the-env-door rule): the facets update to the EFFECTIVE (overlay-wins)
334:  * body while the `managed_by:'package'` + `package_id` provenance is
335:  * PRESERVED — the row still belongs to the package; the overlay is a
336:  * customization of it, and deleting the overlay resets the row to the shipped
337:  * declaration (the layered read reveals the baseline again).
338:  */

The header copy at :30 already reads "The 2026-07-14 direction confirmation that used to sit here… is RETIRED", so PR #6962 fixed exactly one of the two copies, as filed.

The two false claims, and what replaced each

Claim 1 — "an env-scope overlay is the platform's standard customization of a packaged definition (ADR-0005; direction confirmed 2026-07-14…)".

Replaced by the bullet beginning "no new overlay of a packaged set can be minted": a metadata write against a code-declared (artifact-backed) set is refused by the producer with 403 NOT_OVERRIDABLE, so for those names the body reaching this function is the declared one and the projected facets are the shipped declaration; the supported channel is ADR-0086's (edit the package, re-publish); what survives is the neighbouring allowRuntimeCreate tier, which edits the single stored sys_metadata definition in place, with no layering and nothing to reset to.

True against: packages/spec/src/kernel/metadata-plugin.zod.ts:923, which is now { type: 'permission', … supportsOverlay: true, allowOrgOverride: false, allowRuntimeCreate: true, … } (#6483 / PR #6608); and ADR-0094 D5-R, whose "What survives, stated narrowly so it is not mistaken for a re-route" paragraph is the source of the surviving-neighbour wording — including its explicit "no delete = reset to the shipped declaration".

Claim 2 — "deleting the overlay resets the row to the shipped declaration (the layered read reveals the baseline again)".

Replaced by the bullet beginning "delete = reset must NOT be read back into that", which carries #6960's measurement as the statement of record: on an environment-scoped kernel deleteMetaItem throws NOT_OVERRIDABLE / 403 for an artifact-backed target of a non-overridable type before it probes for the row, and a kernel with no environmentId refuses the same write as override-artifact intent — leaving OS_METADATA_WRITABLE=permission as the only documented removal. With no overlay to lift (the normal case) the delete is a no-op success and the row keeps projecting the declaration.

True against: #6960 (cited by number in the comment), and ADR-0094 D5-R's own carry-over paragraph plus its "Negative / behavior changes" bullet, both of which say the same thing.

The history is kept rather than deleted — the block names the retired direction, quotes it, and says where the retirement is recorded (ADR-0094 D5-R, #6609 ruling A, #6858) — matching the :30 header's voice so the two copies read as one author.

The third-copy sweep

grep for the direction's phrasings (2026-07-14, standard customization, standard ADR-0005, delete = reset, resets the row, reset target, refuse-the-env-door) across the repo found more copies. Handled by scope:

Fixed here (same file / same package — 4 more sites):

  • permission-set-projection.ts:290readDeclaredBody's "Used as the reset target when an env overlay is deleted off a declared set" is claim 2 in miniature. Now: it is the body for a declared set with no overlay at all, and the reset target when an overlay is lifted — which since D5-R means a legacy pre-ADR-0005:57 白名单表与注册表的分歧不止 flow:page/app/action/permission/tool/skill 六类 ADR 写 ❌ 而 allowOrgOverride:true,另有 dataset/book/position 三类表里没有却默认成了 true #6483 row removed through the operator hatch, not a data-door delete.
  • security-plugin.ts:891 — "…translates them into env-scope OVERLAY operations (customize / reset via the standard ADR-0005 layering) — the boot re-seed can no longer revert an admin's change, because the change lives in the overlay". The last clause is false for artifact-backed sets since the change never lands. Now states that the middleware translates and ADR-0005 decides.
  • security-plugin.ts:3192 — opened [ADR-0094, direction confirmed 2026-07-14] and cited the retired direction as this gate's reason for letting update/delete through. Now [ADR-0094 D5-R], with the refusal named as the producer's.
  • the two test rationales that repeated it (permission-set-projection.test.ts:336, security-plugin.test.ts:1989). Comments and one test title only — no assertion changed.

Filed, not fixed (outside this package): see the finding linked in the thread below. It covers packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts (header 块2 states both retired halves verbatim while its own body at :75 already asserts the refusal — literally #7082's header-vs-body split one file over) and docs/adr/0094-…md:109.

Verification

This is prose, so it is verified as prose. Commands and real output are in the thread comment below (check:adr-anchors, check:nul-bytes, check:empty-changeset, and the package's typecheck / lint / build / test).

Reverse verification, and its honest result. Prediction, written before running: check:adr-anchors cannot see this. It is a presence check by its own documentation — the file exists, each registered ADR id names a record under docs/adr/, and each id still appears somewhere in the file. Nothing relates what the file says about an ADR to what the ADR currently decides. Measured: with the JSDoc mutated back to the retired direction verbatim, the gate stays green. That is the result, reported as-is; it is the measured reason option B exists, and it is not a failure of the gate, which was never built to catch it. No red/green table is manufactured for a check that has no opinion here.

Option B (a #6628-idiom currency pin) is deliberately not built — it was not dispatched, and it is a new gate surface.


Generated by Claude Code

…DR-0094 D5-R (#7082)

PR #6962 retired the 2026-07-14 env-overlay direction and fixed this file's
header; the function-level JSDoc of `upsertEnvPermissionSet` kept the same two
claims D5-R retired -- "an env overlay is the platform's standard customization
of a packaged definition" and "deleting the overlay resets the row to the
shipped declaration". #6483 / PR #6608 rolled `permission` back to
`allowOrgOverride: false` (the first is 403 NOT_OVERRIDABLE) and #6960 measures
the delete path refusing to lift even a legacy overlay (the second).

Prose only, no behaviour change. Three neighbouring copies in the same package
are corrected with it so the package no longer states the direction in two
voices.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 7:39am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security.

12 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authorization.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-security)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security)

1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-security)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

Verification — real output

All run in a dedicated worktree off origin/main @ f3f855ac154f202fa9f1497abd3e0dff60436e1a, heavy steps serialized on the shared verify lock with NODE_OPTIONS=--max-old-space-size=4096.

The gate that is registered for this file

$ pnpm check:adr-anchors
✓ check-adr-anchors --self-test: 52 assertions over the real auditAdrDirectory() / auditCitedNumbers() / assembleAnchors() paths.
check-adr-anchors: OK (47 anchored file(s), every governing ADR still referenced; 118 decision
number(s), each naming one decision or an allowlisted pair; 20946 citation(s) across 3497 file(s) resolve).

What that gate does check, from its own header: for each entry under scripts/adr-anchors/, the file exists; every ADR id listed for it names a real record under docs/adr/; every one of those ids still appears somewhere in the file. Plus, repo-wide, that a cited ADR-NNNN resolves and that a number names one decision. The registry entry for this file lists ADR-0094 / ADR-0005 / ADR-0086, and all three still appear in it after the rewrite.

What it does not check — also from its own header, verbatim: "It does NOT verify the code still obeys the ADR; no static check can. It guarantees the next author is TOLD which decision they are standing on." It is a presence check. Nothing in it relates what this file says about ADR-0094 to what ADR-0094 currently decides, which is the gap #7082 named.

Other gates that can bite

$ pnpm check:nul-bytes
check-nul-bytes: OK (scanned 6738 text file(s) -- 6737 tracked, 1 untracked-not-ignored;
skipped 5 binary; no raw ASCII control bytes).

$ pnpm check:empty-changeset
✓ check-empty-changeset --self-test: 118 assertions over real temp git repos (real scan() path)
Diffing HEAD from f3f855ac1 (merge base with origin/main).
✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).

Package typecheck / build / lint / test

$ pnpm --workspace-concurrency=2 --filter "@objectstack/plugin-security^..." build
… packages/plugins/plugin-sharing build: Done

$ pnpm --filter @objectstack/plugin-security typecheck
> tsc --noEmit
(no diagnostics)

$ pnpm --filter @objectstack/plugin-security build
DTS ⚡️ Build success in 5212ms
DTS dist/index.d.mts 757.60 KB
DTS dist/index.d.ts  757.60 KB

$ npx eslint --no-inline-config <the 4 changed files>
(clean, exit 0)

$ pnpm --workspace-concurrency=2 --filter @objectstack/plugin-security test -- --maxWorkers=2
 Test Files  43 passed (43)
      Tests  878 passed (878)
   Duration  14.70s

The changeset bump is patch, and it is warranted rather than skippable: upsertEnvPermissionSet is exported from the package index, so the JSDoc is emitted into the published declaration file. Confirmed on the built artifact —

$ grep -n "delete = reset" packages/plugins/plugin-security/dist/index.d.ts
16017: *  - **"delete = reset" must NOT be read back into that.** #6960 measures the

— i.e. the stale text was shipping to consumers' editors, and the corrected text now ships instead.

Reverse verification — prediction, then measurement

Predicted, before running (also stated in the PR body): check:adr-anchors cannot see this at all, so a mutation that makes the prose false again leaves it green. There is no before-red/after-green table to draw, because no check in this repo has an opinion on whether this paragraph is current — that is the finding, not a gap in the run.

Mutation — the fix taken out with git checkout origin/main -- <path> (never git stash; the stash stack is shared across every worktree of this repo), restoring the retired-direction text byte for byte:

$ git checkout origin/main -- packages/plugins/plugin-security/src/permission-set-projection.ts
$ grep -n "direction confirmed 2026-07-14, reversing the earlier" …/permission-set-projection.ts
332: * definition (ADR-0005; direction confirmed 2026-07-14, reversing the earlier

Measured under the mutation:

$ pnpm check:adr-anchors
✓ check-adr-anchors --self-test: 52 assertions …
check-adr-anchors: OK (47 anchored file(s), every governing ADR still referenced; 118 decision
number(s), each naming one decision or an allowlisted pair; 20941 citation(s) across 3497 file(s) resolve).
adr-anchors exit=0

$ npx eslint --no-inline-config …/permission-set-projection.ts
eslint: green under the mutation

Green, as predicted. The only thing that moved is a count20946 citations with the fix, 20941 without, because the rewrite adds five ADR references — and a count is not a verdict: the gate reports OK either way. So the file can state the exact direction ADR-0094 retired and every registered check stays green, which is the measured reason #7082's disposition B exists. Option B was not dispatched and is not built here; recording the measurement is the deliverable.

The tree was restored with git checkout <branch> -- <path> and verified clean (git status --short empty) before pushing.

Out-of-scope finding filed

Filed #7351 (finding, unassigned, no domain:* — routing is triage's): two more copies of the same retired direction survive outside packages/plugins/plugin-security, so they are deliberately not touched here — packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts:12-17 (header states both retired halves verbatim while its own body at :75 already asserts the refusal — #7082's header-vs-body split one file over) and docs/adr/0094-…md:109 (a D2 cross-reference restating the direction as live, pointing at a section whose own title says RETIRED).


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants