Skip to content

fix(plugin-security): Everyone and Guest position descriptions read as admin copy - #23987

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23919-builtin-position-copy
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23919-builtin-position-copy

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23919
Clause-②: no

What changed

Setup's Positions page (/apps/setup/metadata/position) lists each built-in position with its description. The two audience anchors' descriptions carried design-record ids; they now read as plain admin copy. The docblock above AUDIENCE_ANCHOR_METADATA (packages/plugins/plugin-security/src/builtin-positions.ts), which already cited ADR-0090 D5/D9, now ties each id to the sentence it governs. Wording only: names, labels, bindings and the boot path are unchanged.

Position Before After
everyone "... Permission sets bound to it are the default grants for the tenant (ADR-0090 D5). High-privilege sets cannot be bound here." "... Permission sets bound to it are the default grants for the tenant. High-privilege sets cannot be bound here."
guest "... Bindings face the strictest checks — named objects only, read-mostly, never a wildcard (ADR-0090 D9)." "... Bindings face the strictest checks — named objects only, read-mostly, never a wildcard."

The first read: what the boot path does with a stored built-in row whose description differs

Read at origin/main 68ffa5dc1a, before any edit.

  1. The boot keeps the row and writes nothing. No boot step writes a sys_position row: the built-in and declared position seeders are retired (builtin-positions.ts module doc, lines 16-19; ADR-0131 D2, C3 stage 6a). builtin-positions.boot.test.ts already pins this in its three existing-database scenarios: the census equals LEGACY_ROWS and the write ledger is empty (the case "writes no sys_position row, and leaves an existing database's rows as they were").
  2. No census reports it as drift. The only code that classifies these rows is the upgrade ceremony (os migrate). packages/cli/src/utils/organization-ownership-inventory.ts gives sys_position fate 2, mirror deletion, with the SEEDED selector (managed_by in package / platform / system). Only AUTHORED rows are converted to definitions, so a platform-managed built-in row is deleted, and it is never converted or compared.
  3. No reader shows the stored description. No runtime package declares the sys_position row object any more (C3 stage 8; header of src/__tests__/legacy-catalog-objects.testkit.ts). The Positions nav item is /apps/setup/metadata/position?scope=environment (security-plugin.ts:1587). That is the metadata door, and it lists the declarations. The only runtime read of a sys_position row, idSpellingHints in position-catalog-refusal.ts, reads only the row's name.

So an upgraded tenant's administrator sees the declaration's text, the same as on a fresh install. The stored text is leftover data that nothing reads, and os migrate deletes it. The claim's stop branch assumed that existing tenants would keep the ADR ids if the boot kept the stored text. Read 3 shows that assumption is false, so this PR changes the declaration only and leaves the boot path untouched. A boot-time refresh of these rows would add a sys_position write that ADR-0131 C3 stage 6a retired, and nobody would see the result. The only precedent that refreshed platform-managed rows from code, os meta resync, was deleted in 9e9e6f97fc.

Pins

  • src/objects/admin-copy-vocabulary.test.ts (from the family's precedent, PR fix(setup-copy): approval, security, automation and sharing descriptions read as admin copy; ADR numbers move to comments #23917) adds securityBuiltinPositions to its population. The label and description of all six built-in positions are judged, and the population is pinned to include everyone, guest, platform_admin and org_admin.
  • src/builtin-positions.boot.test.ts adds one case to each existing-database scenario (single, single + organization, walled). For everyone and guest it asserts three things: the stored row's text (LEGACY_ROWS, unchanged) differs from the declaration; the census row still holds that stored text; and the security catalog read answers the declaration's text. The LEGACY_ROWS docblock now says that the anchors' stored text is older than their declarations.

Verification

The implementation commit is bebfe4cf6a. The final head d3a4d92391 merges origin/main 2054a0888c into it. None of the four incoming commits touches plugin-security, its dependencies, or packages/spec source.

  • Build first: pnpm --filter '@objectstack/plugin-security^...' build (the upstream dependency closure) — exit 0. After the merge, turbo run build --filter=!@objectstack/docs — 75/75 tasks, exit 0.
  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/objects/admin-copy-vocabulary.test.ts src/builtin-positions.boot.test.ts: 2 files, 63 passed. That is 55 boot cases (12 scenarios, 3 of them new existing-database cases) plus 8 vocabulary cases (1 new). Re-run on d3a4d92391 together with builtin-positions.test.ts and builtin-positions.declaration-phase.test.ts: 4 files, 73 passed.
  • pnpm --filter @objectstack/plugin-security test (at bebfe4cf6a): 188 files passed; 3736 passed, 45 skipped.
  • pnpm --filter @objectstack/plugin-security typecheck: exit 0 (tsc --noEmit, the scripts project, and check:test-typecheck over tsconfig.test.json: 0 errors).
  • Ablation (one time, through scripts/ablation-replace.mjs, on the committed tree): I put (ADR-0090 D9) back into the guest declaration. Anchor never a wildcard.', went x1 to x0, and the blob went 9a36e5037bb6 to 1e2122e49ffb. Result: 4 failed, 59 passed. The vocabulary case failed on guest.description (an ADR number, a decision id), and all three new existing-database cases failed. Restore: blob 9a36e5037bb6 == HEAD, and git diff HEAD is empty. The test imports the module from src, so no dist/ leg was needed.
  • Translation bundles: pnpm check:i18n exit 0. plugin-security has 7 bundles, all in sync, and nothing to regenerate: the bundles hold object copy, and position descriptions never reach them.
  • Gates, on the final head d3a4d92391, after the build: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands for this diff, the same list as at bebfe4cf6a. All 65 exited 0, with each exit captured before any pipe. --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. On the first run at bebfe4cf6a, check:i18n and check:dual-build-cjs-loads answered exit 3 (PREREQUISITE NOT MET: no dist/) until the build had run.
  • Not run locally (CI's): test:repo, the dogfood shards, and the workspace type-check lanes.

Acceptance notes

  • What existing tenants see after the upgrade: the new text, the same as fresh installs. It is pinned by the existing-database case above. Their stored everyone/guest rows keep the older text until os migrate deletes them as seeded mirrors. This PR does not touch those rows.
  • The four identity positions' descriptions come from BUILTIN_IDENTITY_METADATA in @objectstack/spec. They carry no design-record vocabulary today, and the new population case now guards them too.

Generated by Claude Code

…s admin copy

The two audience anchors' descriptions drop their design-record ids; the
references stay in the docblock above AUDIENCE_ANCHOR_METADATA. The
admin-copy vocabulary pin now judges every built-in position, and the
boot test's existing-database scenarios pin that the catalog read answers
the declared text while the rows a retired seeder wrote keep theirs.

Claude-Session: https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 022c6e799e88ae192e124c80e1cc343bda54d7bf → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T16:47Z

Reviewed against GitHub at head d3a4d92391, from the diff and the dev's report on #23919 (6219869399).

Form

  • Draft, base main. The first line is Fixes #23919, then Clause-②: no.
  • 4 files, +77 / −11, all on claim 6218389904's surface:
    • builtin-positions.ts;
    • its boot test;
    • objects/admin-copy-vocabulary.test.ts;
    • one patch changeset.
  • check-governed-merges.mjs --pr 23987: NOT governed. mergeable_state: clean.

The change, read in the diff

  • The Everyone and Guest descriptions in AUDIENCE_ANCHOR_METADATA drop (ADR-0090 D5) / (ADR-0090 D9).
  • The docblock above them ties each decision id to its sentence. It also states that a stored sys_position row keeps the older text until the upgrade ceremony deletes it as a seeded mirror.

The claim's stop branch, measured false. Accepted.

  • The claim said to stop if the boot keeps stored text, because existing tenants would then keep the ids. The boot does keep stored rows: the seeders are retired, ADR-0131 C3 stage 6a, builtin-positions.ts:16-19 on main.
  • But nothing shows a stored row's description:
    • the sys_position row object is retired (C3 stage 8);
    • Setup's Positions entry is /apps/setup/metadata/position?scope=environment (security-plugin.ts:1587), the metadata door, and it lists the declarations;
    • the seat re-read both on main.
  • So an upgraded tenant reads the declared text, the same as a fresh install. The declaration-only route is the one the card's Acceptance 2 asks to be stated and pinned. Nothing refreshes stored rows, and none is wanted.

Pins

  • admin-copy-vocabulary.test.ts now judges all six securityBuiltinPositions.
  • The boot test's three existing-database scenarios assert two things: the stored anchor rows keep the LEGACY_ROWS text, and the security catalog read answers the declared text.
  • Ablation: putting (ADR-0090 D9) back turned 4 tests red. Restore proven by blob.

Gates and CI

  • Gates: 65 derived, 65 exit 0 after a full build, reconciled with --ran. check:i18n reports all bundles in sync; these strings reach no bundle.
  • CI: 40 check-runs, latest per name: 34 success, 0 failure, 6 skipped. The skipped six are all on the roster:
    • Build Docs;
    • Console Pin Gate;
    • the Dogfood Regression Gate matrix;
    • Dogfood Verify CLI;
    • Packed-tarball smoke;
    • Temporal Conformance.

ACCEPT. This act marks the PR ready and enables auto-merge through the fleet relay. It lands through the merge queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 16:49
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 16:49
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 5fb8d15 Oct 16, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23919-builtin-position-copy branch October 16, 2026 17:21
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup copy: the Everyone and Guest built-in positions' descriptions carry ADR-0090 decision ids, and stored rows hold the same text

2 participants