Skip to content

feat(plugin-security): the boot writes no sys_position row — delete the built-in and declared position seeders and the everyone baseline junction writer (ADR-0131 D2, #15204 stage 6a) - #22793

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-15204-s6a-position-seeders
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-15204-s6a-position-seeders

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of #15204 (ADR-0131 cutover, stage 6a). This PR deletes the built-in and declared position seeders and the everyone baseline junction writer. The card stays open for the remaining stages.
Clause-②: no

Size: 36 files, +443 / −2,045 (2,488 changed lines) against main at b7cd1af9df. That is under the 3,000 line.

Landing: this PR stays a draft until stage 5 (C9) lands, per order correction 3 of the stage-0 record on #15204. Setup's position pickers still read rows, so on a fresh database they would list no built-in position.

What is deleted

  • plugin-security/src/bootstrap-builtin-positions.ts (bootstrapBuiltinRoles, 166 lines)
  • plugin-security/src/bootstrap-declared-positions.ts (bootstrapDeclaredPositions, 391 lines), with its test file (410 lines)
  • In security-plugin.ts:
    • the seedCatalogPositions, seedCatalogBuiltins and bindBaselineToEveryone closures;
    • their calls in the boot sweep and in the organization-creation hook;
    • the two imports, and the resolveOwnOrganizationRow / seedCtx imports that only they used.
    • seedCatalogForOrganization now runs the one catalog step left (the permission-set seeder, which 6b-2 retires).
  • isBuiltinPositionName in builtin-positions.ts. Its only caller was the deleted declared-positions seeder's exclusion.

per-organization-catalog.ts is untouched: 6a never imported it directly, and 6c retires it. bootstrap-platform-admin.ts imported nothing from the deleted modules (measured: no import, two comments only), so there is no seam with 6b-2.

Why no grant moves

Since stage 1 the resolver reads position definitions:

  • everyone's baseline is the anchor's declared permissionSets. declareEveryoneBaseline registers it at kernel:ready.
  • The set list is this.baselinePermissionSets, which is the app's default set composed with member_default.
  • It is filtered by describeHighPrivilegeBits against the same declared-capability context bindBaselineToEveryone used.

So the junction rows this PR stops writing granted nothing. Measured:

  • builtin-positions.boot.test.ts boots the real plugin over SQLite in 3 postures × 4 variants, including a new existing database variant that holds the rows the retired seeders wrote.
    • Every principal's grant envelope equals the unchanged golden: platform admin, organization admin, member and agent, fresh and existing.
    • Zero-write census: no sys_position row and no ledger write in any variant. An existing database keeps exactly the rows it had.
    • Positive control: one organization-authored create reads back as exactly one row.
  • security-plugin.test.ts: the everyone definition names the app set when its token is declared, and does not when the token is undeclared or names a platform capability. No junction row is written for either token.
  • dogfood declared-position-provenance: a real showcase boot writes no position row, and a cold boot of an existing database re-stamps, refreshes and adds nothing.
  • The resolver's batch-equivalence golden (resolve-authz-context.batch-equivalence.golden.json) is not touched.

Tests

Every test that imported a deleted module or pinned a deleted row:

file disposition
bootstrap-declared-positions.test.ts deleted with its module
audience-anchors.test.ts 3 seeding cases deleted; predicate cases kept
bootstrap-seed-round-trips.test.ts 5 position cases deleted; permission-set cases kept
builtin-positions.boot.test.ts rewritten: zero-row census + ledger with a positive control; new existing-database variant; grant golden unchanged; built-in-row refusals read on the existing database; the door-authored-rows case deleted
builtin-positions.test.ts isBuiltinPositionName case deleted with the function
catalog-reference-report.test.ts precondition rewritten: the built-in copies are staged as existing-database rows
objects/reserved-identity-names.test.ts platform-seed case rewritten as a direct platform-provenance write; the exemption stays until stage 8 retires the object
per-organization-catalog.test.ts rewritten onto the permission-set seeder; case 1 adds the zero-position census; case 6 stages the everyone rows
security-plugin.test.ts the #18535 junction cases rewritten onto the definition; a no-junction-row case added
seed-write-refusal.test.ts 8 seeder-driven cases deleted; the redaction canary (#8682) rewritten at the accumulator
dogfood showcase-declarative-rbac-seeding positions read through the security catalog; the re-seed case deleted
dogfood declared-position-provenance rewritten: the gate legs run on staged existing-database rows; the fresh boot writes no row; a cold boot re-stamps, refreshes and adds nothing
dogfood me-apps-and-everyone-baseline, showcase-d7-default-profile the baseline is read off the everyone definition; no row is written
dogfood showcase-invoice-seed-isolation the position route rides the contributor declaration; no row or junction row is written
dogfood showcase-permission-zoo the junction-gate cases stage the row an existing database holds
dogfood position-held-name-setup-create the precondition expects no seeded row

Docs and ledgers

  • Docs: authorization.mdx (the declared-positions bullet and the lifecycle row), positions.mdx, permission-metadata.mdx, system-context.mdx (rows 11b and 23b), and tenant-audit-census.mdx. The census is regenerated (238 → 233 write sites: the 5 deleted writes) and its prose figures are updated.
  • Code comments: TSDoc that named the seeders in builtin-positions.ts, reserved-identity-names.ts, position-catalog-refusal.ts, suggested-audience-bindings.ts and bootstrap-platform-admin.ts.
  • Ledgers: engine-double-contract.baseline.json, engine-double-contract.pinned.json and objectql-double-limit.baseline.json drop the doubles that went with the deleted cases.
  • Not touched: content/docs/releases/ and any packages/spec/src path. 6b-1c, now on main, already rewrote high-privilege.ts's sentence naming bindBaselineToEveryone.

Open question for the seat: the audience-binding suggestion confirm

Settled: the seat answered A in 6106681412 (no change in 6a). The confirm went to decision card #22801, ruled A (6108478638), and stage U2 builds it.

Measured on a real showcase boot of this head, with the app default CLI-wired:

  • showcase_member_default is a pending everyone suggestion.
  • The database holds no everyone row and no junction row.
  • confirmAudienceBindingSuggestion needs the everyone row, so a confirm on a fresh database answers SuggestionStateError ("not seeded yet").

On main, by reading the code (this before-leg was NOT MEASURED in that wiring):

  • the boot's junction row made the reconcile record the suggestion as confirmed (observed);
  • a confirm of any other pending suggestion wrote a junction row that grants nothing.

In the default wiring both trees measured pending. This is the suggestion-confirm path that release-cut condition (d) of the stage-1 ACCEPT already holds out of every release. No fix is built here (only delete; U2 belongs to its own stage). The report on #15204 carries the options.

Acceptance notes

  • Four test comments still describe rows the retired seeder wrote: driver-sql sql-driver-sys-position-organization-unique.test.ts:310, bootstrap-platform-admin-seeded-provenance.test.ts:204, objects/sys-position.organization-unique.test.ts:44 and suggested-audience-bindings-install-path.test.ts:139. Each reads as history of an existing database's rows. Not edited.
  • Retired modules are not edited, so their comments still name the deleted seeders: objects/sys-position.object.ts (stage 8), seed-name-lookup.ts (7a), bootstrap-declared-permissions.ts (6b-2) and position-write-through.ts (7b).
  • scripts/measure-position-name-fold-census.mjs still models bindBaselineToEveryone as a junction producer. It is a measurement instrument whose question (name-fold vs junction) ADR-0131 retires. Carrier: none.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at head ac2d982b (merge base b7cd1af9df). Each command was run with its exit code recorded, and the record was reconciled with --ran: 105 derived, 104 run, 1 NOT MEASURED, 0 unrun.

  • 104 commands exited 0.
  • NOT MEASURED: check-plugin-teardown-shape --self-test exited 3. A shallow clone cannot reach its pinned fixture commit, as on the sibling stages.

Suites on the merged head ac2d982b, each after a dependency-closure build (plugin-auth and the example apps were measured at 68ba543e and not re-run on ac2d982b; CI runs them):

package files tests
@objectstack/plugin-security (typecheck + suite) 197 4,005 passed, 45 skipped
@objectstack/plugin-auth 136 2,769 passed, 10 skipped
@objectstack/runtime 352 5,789 passed, 19 skipped
@objectstack/dogfood 246 (+1 skipped) 1,994 passed, 9 skipped
example apps: showcase / crm / todo / embed-objectql 33 / 6 / 7 / 1 409 / 46 / 238 / 2 passed

Before-leg of the deletion: with the deletion in place and only the five importing test files fixed, the full plugin-security suite was red, with 22 failures in 3 files (the boot census, the catalog report's built-in precondition, and the #18535 junction cases). The first dogfood run was red too, with 15 failures in 6 files. Every one of them pinned a row the deleted seeders wrote. CI was not awaited.

Seat edits at landing round 1 (ac2d982b, a merge of main b7cd1af9df plus the census regeneration): the size base, the census figures, the gate head and the suite rows above, from the dev's report 6108751935. Lines 1–2 are unchanged.


Generated by Claude Code

…rs and the everyone baseline junction writer

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
…umulator, the exemption and the per-organization permission-set pins

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
…base's unchanged rows and grants, and the everyone baseline as a declaration

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
…es a sys_position row; drop the deleted test from two gate baselines

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
…existing database's rows where a gate judges a row; refresh the census and the double ledgers

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
…a-position-seeders

# Conflicts:
#	packages/plugins/plugin-security/src/bootstrap-seed-round-trips.test.ts
#	packages/plugins/plugin-security/src/security-plugin.test.ts
#	packages/plugins/plugin-security/src/security-plugin.ts
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 11, 2026
@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 26 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/objects/reserved-identity-names.ts, packages/plugins/plugin-security/src/position-catalog-refusal.ts, packages/plugins/plugin-security/src/suggested-audience-bindings.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json b7cd1af9df7ade29e165e838503cbbef05e4299e.

⛔ 15 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/objects/reserved-identity-names.ts, packages/plugins/plugin-security/src/position-catalog-refusal.ts, packages/plugins/plugin-security/src/suggested-audience-bindings.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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 b7cd1af9df7ade29e165e838503cbbef05e4299e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e82fcd5c0b5c730793dbee25a47a4d5d85b04b30 — the merge of head ac2d982b92d7931aefa2d3205dc9d5088e4be044 into base b7cd1af9df7ade29e165e838503cbbef05e4299e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e82fcd5c0b5c730793dbee25a47a4d5d85b04b30 && git checkout e82fcd5c0b5c730793dbee25a47a4d5d85b04b30
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b7cd1af9df7ade29e165e838503cbbef05e4299e ac2d982b92d7931aefa2d3205dc9d5088e4be044 && git checkout -B drift-repro b7cd1af9df7ade29e165e838503cbbef05e4299e && git merge --no-ff ac2d982b92d7931aefa2d3205dc9d5088e4be044

node scripts/docs-audit/affected-docs.mjs --json b7cd1af9df7ade29e165e838503cbbef05e4299e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b7cd1af9df7ade29e165e838503cbbef05e4299e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…a-position-seeders

# Conflicts:
#	content/docs/permissions/tenant-audit-census.mdx
#	docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
… on the merged tree; carry this branch's rows 11b/23b and the census prose figures over main's

Claude-Session: https://claude.ai/code/session_01YLbobfnaoKFrxZcQZT8rYP
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 11, 2026 12:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 11, 2026 12:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 990341a Oct 11, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-15204-s6a-position-seeders branch October 11, 2026 12:43
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants