Skip to content

fix(plugin-security): withdraw the sys_capability Deactivate dialog's false grant-revocation claim (#8535) - #8619

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-8535-deactivate-claim
Aug 14, 2026
Merged

fix(plugin-security): withdraw the sys_capability Deactivate dialog's false grant-revocation claim (#8535)#8619
os-zhuang merged 2 commits into
mainfrom
claude/issue-8535-deactivate-claim

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8535

A shipped confirmation dialog declared behaviour no code path enforces. This withdraws the claim.

The claim, and why it was false

deactivate_capability told the admin, verbatim:

Deactivate this capability? Grants and resource requirements that reference it stop resolving until re-activated.

Re-verified on origin/main at b45c71e85a (measured, not inherited from the card):

  • PermissionEvaluator.getSystemPermissions() unions permissionSets[].systemPermissions — plain strings — and a resource's requiredPermissions is matched against that string set. Neither loads a sys_capability row.
  • The table's only two production readers are seeders (bootstrap-system-capabilities.ts:214/291, bootstrap-declared-capabilities.ts:210). Both writeactive: true on insert; neither reads it back. No reader of active exists anywhere.
  • The authoring lint resolves capability names against PLATFORM_CAPABILITY_NAMES, stack declarations and seed rows — never the table.

The direction of the falsehood was the dangerous one: an admin withdrawing a capability was told the withdrawal took effect and it silently did not. The escalation is what they believed they had prevented.

Shape taken: reword (option B default), not the retirement playbook

Per the maintainer ruling of 2026-08-13 (ADR-0049 enforce-or-remove). Enforcement was rejected there and is not attempted here.

Removal was considered and rejected. The spec-property-retirement playbook governs authorable packages/spec properties — liveness ledger, ADR-0087 conversion, retiredKey() tombstones, the eight generated artifacts. sys_capability.active is none of those: it is a shipped database column with its own index on a plugin-owned system object, written by two seeders. Removing it is a data migration plus two action deletions, not a spec retirement — heavier, and it would touch bootstrap-system-capabilities.ts, which #8470 landed in and which this PR deliberately leaves untouched.

What changed

All presentation and text. There is no behaviour change because there was no behaviour.

  • Dialog now states the non-effect outright rather than merely omitting the promise — an admin who remembers the old wording has to be told it was wrong, not left to infer it.
  • All four shipped locales corrected (en, es-ES, ja-JP, zh-CN). See the note below.
  • active gains a description it never had. Its absence is precisely how the dialog became the only place the field's meaning was stated — and that statement was false.
  • Demoted from prominence, the ruling's other half: out of highlightFields, out of the danger variant, out of the two scoped list views. It stays in all_capabilities, the full-catalogue view — hiding a flag the product still lets an admin set is the opposite error, not a stronger fix.

The translation-bundle trap, confirmed live

Editing the source object does not rewrite shipped bundles. Running node scripts/check-i18n-bundles.mjs --write printed regenerated and, measured against a before-snapshot:

  • it added the new fields.active.help key to all four bundles (filled with the English string in the three non-en locales);
  • it left the changedconfirmText stale in all four bundles, en included.

So the eight leaf values were corrected by hand, per the bundle header's own documented workflow. check:i18n reports "in sync" either way, so that gate is not evidence here — the sweep test below is.

Verification

The deliverable is that no surface makes the claim, so the test is a sweep, not a pin. It walks every string in the object definition and in all four locale bundles and applies two independent checks: a negative one (the withdrawn claim's own words, per locale, appear nowhere) and a positive one (every locale's dialog and field help actually state the non-effect — catching the cheap non-fix of deleting the sentence and leaving the admin to infer the rest). 19 cases.

Ablations, each with the colour predicted before running, all matching:

AblationPredictedMeasured
old English text back in the source only2 red, all bundle cases stay greenexactly that
old ja-JP text back in the bundle only2 red (ja-JP only), source + other locales greenexactly that
active back in highlightFields1 redexactly that

The first two are the ones that matter: they prove the bundle assertions read the bundle files independently of the source, which is exactly the blind spot that let a corrected source ship beside a stale en bundle in the #8601 case. Harness non-vacuity is further pinned by the case count — 1+4+1+4+1+4+1+2+1 = 19 — so no it.each silently expanded to zero.

Local gates green after the final commit: check:i18n (on a built CLI — it reports a meaningless prerequisite failure otherwise), check:nul-bytes plus a control-byte self-scan of every touched file, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-dispatch-gates, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (plain, --self-test, --re-measuresurplus: none, no ledger growth), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, and plugin-security in full: 57 files / 1103 tests passed, tsc --noEmit clean.

Adjacent finding, filed not fixed

#8613 records the same unenforced-active shape on sys_permission_set and sys_position — both Deactivate dialogs promise access stops, and no active predicate exists anywhere in the resolution chain. On those two it is worse, since a permission set is the grant itself. Filed unassigned; deliberately out of scope here, and its direction is not assumed to follow this card's ruling.


Generated by Claude Code

… false grant-revocation claim (#8535)
The deactivate_capability confirmation dialog told the admin that grants and
resource requirements referencing the capability 'stop resolving until
re-activated'. No code path enforces that: getSystemPermissions() unions
permissionSets[].systemPermissions as plain strings and requiredPermissions is
matched against that string set — neither loads a sys_capability row. The
table's only two production readers are seeders, which write active: true on
insert and never read it back.
Per the maintainer ruling of 2026-08-13 (ADR-0049 enforce-or-remove, option B),
the claim is withdrawn rather than enforced; putting the registry on the
authorization hot path is an architectural change needing its own card.
- reword the dialog in the source object and in all four shipped locale bundles
(editing the source does not rewrite bundles — they were corrected by hand)
- declare active's real semantics in a field description it never had
- demote active from highlightFields, the danger variant, and the two scoped
list views; keep it in the full-catalogue view where it belongs
- add a sweep asserting the withdrawn claim survives on no surface in any locale
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
@vercel

vercelBot commented Aug 14, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 14, 2026 2:33am

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.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 14, 2026
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

PM review — accepted. Landing option A (the shape shipped here) on the open question about how far the demotion goes.

The ruling said "demote from highlightFields / list-view prominence accordingly" and left the boundary open, so this was delegated latitude, not a gap. A is a faithful reading of it, and the argument against B is the stronger one: a flag the product still lets an admin set, shown in no grid at all, is the opposite dishonest surface — the fix should stop the field over-claiming, not make it unobservable. C (renaming the label) is out of proportion: "Active" is not itself a claim about grant resolution — the dialog was, and the dialog is what changed.

Recording it because the boundary is a judgement rather than a mechanical consequence, and the next seat should be able to see that it was chosen. It is also cheap to revisit: the demotion is pinned by three cases in the sweep, so flipping to B or C is a visible, reviewable edit rather than an archaeology exercise.

Two things verified independently of the PR body before enqueue:

  • Surface completeness.deactivate_capability appears in exactly the five files this PR touches — the object definition and the four locale bundles. No generated reference page carries the dialog text, so the sweep's five surfaces are the whole set rather than the subset that was easy to reach. (The sys_capability hits elsewhere in content/docs are about the permission model in general; the content/docs/permissions/sharing-rules.mdx deactivation claim is about sharing rules, where deactivation genuinely does revoke.)
  • Path fork. No docs/adr/**, no .claude/skills/**, no skills/**, no content/docs/releases/**.

The i18n half is the part worth keeping for the next card in this shape: --write reported regenerated while adding the new fields.active.help key to all four bundles and leaving the changedconfirmText stale in all four, en included — so check:i18n reporting "in sync" is not evidence that a reworded string shipped. #8601 is the same trap with the opposite outcome.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 14, 2026 02:45
@os-zhuang
os-zhuang added this pull request to the merge queueAug 14, 2026
Merged via the queue into main with commit 779bab3Aug 14, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8535-deactivate-claim branch August 14, 2026 02:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sys_capability.active is read by nothing — the Deactivate action tells the admin grants stop resolving, and they do not

2 participants

@os-zhuang@claude