Skip to content

Commit 0d3ea29

Browse files
refactor(plugin-security): remove the retired catalog objects' write gates and the walled data door; report positions naming an unresolved set (C3 stage 8-post) (#23428)
Fixes #22975 Clause-②: no The last slice of #22975 (C3 stages 7a ∥ 7b → 8-post, under #15204). Stage 8 retired the four security-catalog row objects (`sys_position`, `sys_permission_set`, `sys_position_permission_set`, `sys_capability`), and 7a and 7b deleted the modules that wrote them. This PR removes what only guarded those writes, adds the report class ruled in #23229 → B, re-tightens the test spans the stage-8 test half loosened, and corrects the texts that still named the deleted objects and modules. ## What changes **`@objectstack/plugin-security`** - Three security-middleware gates on the retired objects are removed, with their call sites and cases: the package-provenance gate on `sys_permission_set`, the platform/package row gate on `sys_position`/`sys_capability` (with `SYSTEM_ROW_PROVENANCE`), and the curated capability-name gate on `sys_capability`. The delegated-administration gate drops its `sys_position_permission_set` and `sys_permission_set` cases. It still governs `sys_user_position`, `sys_user_permission_set` and `sys_member`, and `delegated-admin-gate.test.ts` keeps its direct-grants describe (`sys_user_permission_set`, nine cases) unchanged. - `SecurityPlugin.assertAudienceAnchorBindingGate`, the fourth gate on the retired objects, is not in this diff: #23412 (`be50633be1`, closing #23403) deleted it on `main` together with the ADR-0138 text that named it. The merge of `main` took that deletion; the `security-plugin.ts` conflict was resolved by dropping both sides. - `walled-catalog-data-door.ts` is deleted, with its ADR-anchor JSON and row 55b's data-door clause in `system-context.mdx`. Measured on 7a's head: no non-test object definition registers any of the three objects it judged; only `__tests__/legacy-catalog-objects.testkit.ts` does. Nothing imported the module since stage 8. - `PermissionSetNameConflictError` is retired, with its error-code-ledger row. - The packaged-permission-set lock loses its insert-only text (a "choose a different name" variant only the retired data door's create raised). Both published signatures keep their `operation` parameter, so no export changes. - **#23229 → B (director ruling 6145194655):** `catalog-reference-report.ts` gains a sixth class. It lists, per position, a position definition whose `permissionSets` names a set the catalog cannot resolve, with the owner package and the unresolved names. It reads `catalog.list('position')`. An unreadable answer, or one that is not a list, stops the report as `catalog-unreadable`. One `error` line per boot. The class clears by itself on the next boot after the package is reinstalled. **Re-tightened spans (S8-T follow-ups, 6140398039)**, each now exact on post-stage-8 `main`: - `cleanup-package-permissions`: `toEqual` on the whole outcome; `refused` is `toBe(4)`; the control asserts that no grant is revoked. - The two `kernel:bootstrapped` handler floors: `toHaveLength(2)`. - The clone-route prescription pinned in full, with the `{ROUTE}` matcher removed. - `wall-catalog-refusal`: `404 OBJECT_NOT_FOUND`, with the objects' absence asserted. - The clone-door dogfood asserts that no row object exists. - `tenancy-by-object-classification`: no `global` entry; `sys_permission_set` reads `unclassified` and out of scope, and `sys_file` reads in scope. - `uninstall-cleanups-runner`: `toEqual([[request]])`. **Residue and prose** - 7b's review (6150966333): `persistPositionBackfillRecord` is removed from both durability scripts, and the `position-row-definition.ts` JSDoc is corrected. - 7a's review (6160232724, item 5) and the seat's later list: the comments, descriptions and evidence that named the deleted modules are corrected or put in the past tense. That covers the spec docblocks, the durability scripts, `access-security.json` (`:2602`, `:2633`, `:2649`), the core row-definition doc comments, plugin-security and plugin-sharing comments, metadata-protocol and plugin-auth test comments, and `tsconfig.test.json`. No ADR text is edited. - `@objectstack/metadata-protocol` (comments only): the comments in `protocol.ts` that gave `permission` → `sys_permission_set` as a live example now give the examples registered today (`flow` for the publish materializer, `datasource` for the mutation projector, `sys_user_permission_set` for the uninstall cleanup), and the three delete-reset comments describe `sys_permission_set` as the case it was until stage 8. The changeset adds `@objectstack/metadata-protocol` as patch. - **Q4 (ADR-0131 D3):** the `name` describes of `PositionSchema` and `PermissionSetSchema` say a name is unique within the security catalog the environment serves, replacing "unique per organization", which read the retired rows' index. Spec patch. - Regenerated with the repo's tools: the spec references (`protocol.mdx`, `eval-user.mdx`, `position.mdx`, `permission.mdx`) and the `en` translation bundles. ## Carried, not in this PR - **#23403, closed by #23412 (`be50633be1`, on `main`):** the removal of `assertAudienceAnchorBindingGate`, and the ADR text (ADR-0094, ADR-0103, ADR-0126, ADR-0131, ADR-0138) that named it or the deleted modules. - **#23404:** `packages/spec/liveness/permission.json:20`. The `permission.description` evidence cites the retired row object and projection. No runtime reader of `PermissionSet.description` was found after 7a and S8, so it needs a liveness re-verification, not a prose fix. ## For the contract review - **Q4:** the two "unique per organization" describes are restated to ADR-0131 D3: "unique within the security catalog the environment serves" (`PositionSchema.name` in `position.zod.ts`, `PermissionSetSchema.name` in `permission.zod.ts`). - `packages/spec` is touched in describe text, doc comments and one ledger row: the exported `ERROR_CODE_LEDGER` drops plugin-security's `UNIQUE_VIOLATION` row (the code stays registered by its other emitters). No key, type or schema shape moves. Changeset: `.changeset/15204-8post-catalog-gates-retired.md`, all patch (plugin-security, platform-objects, spec, core, metadata-protocol), `Clause-②: no`. ## Verification Head `a338b13ce3`: this branch with `origin/main` `7b3d5585ca` merged (that merge brought #23412's deletion of the anchor gate; the `security-plugin.ts` conflict was resolved by dropping both sides), plus the post-merge prose commit, plus the lint fix `a338b13ce3`. That fix removes the orphaned block comment the merge resolution left in `security-plugin.ts`: a stray opener that swallowed two leftover closing braces of the retired anchor gate. The earlier merge (`5656c5638a`, after 7a's `dac99c2277`) stays in history. Delta against `main`: 63 files, +514 / −2,322 = 2,836 changed lines, generated files included. No governed path. - On `a338b13ce3`: ESLint as CI runs it (`pnpm lint` flags) over the 53 changed source files exits 0; on `a33c341826`'s copy of the file the same command exits 1 at the orphan. `plugin-security` typecheck 0, and its suite 186/186 files, 3,701 passed (45 skipped). - The rest below was measured on `a33c341826`, which differs from `a338b13ce3` only by those 4 deleted lines: - `turbo build`: 76/76. No generated artifact was stale after the merge (`check:generated` clean). - `plugin-security`: 186/186 files, 3,701 passed (45 skipped); typecheck 0. - `metadata-protocol`: 235 files (3 skipped), 28,389 passed. - `turbo typecheck` over the 9 touched packages (including `example-showcase`): 0. - CLI integration tier: 127/127 files, 1,099 passed (2 skipped). - `dispatch-gates --ran` with recorded exits: 147 derived, 147 run, 0 NOT-MEASURED. Non-zero: - `check:platform-checklist` exit 1: 8 reds, identical to the same gate on `main`; - `check-engine-split-ratio` exit 2: it refuses on this shallow clone. - `check:adr-symbol-anchors` 0, and the other anchor sweeps 0. - Dogfood was run on `c4e9c4f919`, before this merge: 257 files passed (1 skipped). The merge resolution touched no runtime code beyond `security-plugin.ts`, and this push touched only comments, so it was not re-run. - Ablation of the new report class (`scripts/ablation-replace.mjs`, the per-position push replaced by `void`): 3 of 14 report tests red, then restored to the HEAD blob. ## Acceptance notes - The retired `access-security.packaged-permission-set-lifecycle` item's title, `automated.ref`, `source` and the lines naming the deleted module are corrected here. Its other clauses still describe the retired data-door PATCH flow as they were written. Carrier: none. - ADR-0090 D9 (`guest`) and D12 have no metadata-door counterpart pinned (NOT MEASURED). D5/D7 are pinned in `suggested-binding-acceptance.dogfood.test.ts`: the risky accept is refused, and `PUT /meta/position/everyone` answers 403. - The objectui pinned sibling is NOT MEASURED: no sibling checkout exists in this container, and Console Pin Gate runs only in `main`'s full battery. No entry export of any package changes in this PR, so nothing objectui could import moves. - `AdminScope.manageBindings` and `authorEnvironmentSets` are honoured by no write path once the delegated-admin catalog cases leave. The seat files this at landing (seat review 6124054616). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KppP2MoD91F3pXiXXrAmcF --- _Generated by [Claude Code](https://claude.ai/code/session_01KppP2MoD91F3pXiXXrAmcF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a1285f2 commit 0d3ea29

63 files changed

Lines changed: 514 additions & 2322 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
'@objectstack/platform-objects': patch
4+
'@objectstack/spec': patch
5+
'@objectstack/core': patch
6+
'@objectstack/metadata-protocol': patch
7+
---
8+
9+
fix(plugin-security): the data-door write gates of the retired catalog row objects are removed, the boot report names positions that bind a permission set nobody serves, and the texts that still named the retired objects and modules are corrected (ADR-0131 D3/D4, C3 stage 8-post)
10+
11+
Clause-②: no
12+
13+
Stage 8 retired the `sys_position`, `sys_permission_set`, `sys_position_permission_set` and `sys_capability` row objects, so no write to them reaches the engine. This change removes what only guarded those writes. No export of any package's entry changes, and no request that reaches the server is answered differently.
14+
15+
- **`@objectstack/plugin-security`.**
16+
- Three of the security middleware's gates on those objects are gone: the package-provenance gate on `sys_permission_set`, the platform/package row gate on `sys_position` and `sys_capability`, and the curated capability-name gate on `sys_capability`. The delegated-administration gate no longer judges `sys_position_permission_set` bindings or `sys_permission_set` authoring; it still governs `sys_user_position` assignments, `sys_user_permission_set` grants and `sys_member`. The rules those gates carried still hold where the catalog now lives: `everyone` and `guest` are code-declared positions an environment cannot overlay, and a capability cannot be created or overlaid through the metadata door at all.
17+
- The walled data-door refusal for those objects is deleted. Stage 8 already left it unwired, and no composition registers the objects it judged; the metadata door's walled refusal is unchanged.
18+
- The internal duplicate-name refusal class that only the retired permission-set data door raised is removed.
19+
- The packaged-permission-set lock's refusal no longer has a "choose a different name" variant: only the retired data door's create raised it. The metadata door's refusal text is unchanged, and the published signatures keep their `operation` parameter.
20+
- The boot's catalog reference report lists one more finding, per position: a position definition whose `permissionSets` names a set the security catalog does not resolve, usually because the package that declared the set is uninstalled or disabled. A holder of such a position is granted nothing through that name. The line clears by itself at the next boot after the package is installed again. It is logged at `error` once per boot, like the report's other lines.
21+
- **`@objectstack/platform-objects`** and **`@objectstack/plugin-security`** object metadata: the descriptions of `sys_invitation.positions`, `sys_user_position` (object and `position`), and `sys_audience_binding_suggestion`'s permission-set name no longer name the retired objects. The translated texts follow.
22+
- **`@objectstack/spec`**: the `projectionApplied` and `materializeApplied` descriptions on the metadata save, publish and batch-publish responses give a current example (`datasource`, `flow`) instead of `permission` → `sys_permission_set`. `EvalUser.positions` describes the positions assigned through `sys_user_position`. `PermissionSet.description`'s doc comment no longer names the deleted projection. The `name` describes of `PositionSchema` and `PermissionSetSchema` say a name is unique within the security catalog the environment serves (ADR-0131 D3: there is no organization-level catalog), replacing "unique per organization", which read the retired rows' index. The error-code ledger drops `@objectstack/plugin-security`'s `UNIQUE_VIOLATION` row, which named the removed refusal; `UNIQUE_VIOLATION` stays registered by its other emitters.
23+
- **`@objectstack/core`**: the doc comments of `positionDefinitionFromRow` and `permissionSetDefinitionFromRow` no longer describe the deleted boot backfills as current.
24+
- **`@objectstack/metadata-protocol`**: the doc comments of the publish materializer, mutation projector and uninstall cleanup registries give current examples (`flow`, `datasource`, the grant revocation) instead of `permission` → `sys_permission_set`.

‎content/docs/permissions/system-context.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ One of the largest consumers of the flag;
184184
| 54 | Package REST route capability gate bypassed | rest | Get: a marketplace publish over REST (`POST /packages/publish`, the one route the REST registrar mounts since #14503) without `manage_metadata`; the package read cohort (`studio.access` / `setup.access`) is enforced by the dispatcher `/packages` domain's own read gate, where the reads are served | `packages/rest/src/package-routes.ts#refusePackageRequest` |
185185
| 55 | Package domain capability gates bypassed | runtime | Get: package management and package-inventory reads without the capability | `packages/runtime/src/domains/packages.ts#requireManageMetadata`, `#requireReadCapability` |
186186
| 56 | Activation write / authoring refusals do not fire | runtime | Get: activation artifacts writable and authorable without the activation-authoring capability | `packages/runtime/src/domains/activation-gate.ts#refuseUngrantedActivationWrite`, `#refuseUngrantedActivationAuthoring` |
187-
| 55b | Walled environment-metadata refusal does not fire | metadata-core, rest, plugin-security, runtime | Get: under `isolated` / `group`, a write of the environment's metadata, of any type, at the `/meta` write doors (save, reset, publish, rollback) and `POST /meta/_migrate-stored` on both transports, at every `/packages` write door (create, the manifest `PATCH`, enable, disable, publish, `publish-drafts`, `discard-drafts`, `revert`, `rollback`, `commits/:commitId/revert`, `duplicate`, `adopt-orphans`, delete), at the `/automation` definition writes (create, update, delete, clone), at the permission-set clone door, and at the data door on `sys_position`, `sys_permission_set` and `sys_capability` while those objects are registered. Every other caller below the platform-operator rung is refused `403` there (ADR-0131 D3, D12 items 7–8, D11), because one organization's write would change what every organization resolves. The seeders, the package door and migrations write the environment's metadata this way. Under `single` the refusal is inert for every caller | `packages/metadata-core/src/meta-write-capability.ts#walledCatalogWriteVerdict`, `packages/rest/src/rest-server.ts#walledCatalogRefusal`, `packages/plugins/plugin-security/src/walled-catalog-data-door.ts#createWalledCatalogDataDoorRefusal`, `packages/runtime/src/domains/packages.ts#refuseWalledCatalogPackageWrite`, `packages/runtime/src/domains/automation.ts#refuseWalledFlowDefinitionWrite` |
187+
| 55b | Walled environment-metadata refusal does not fire | metadata-core, rest, runtime | Get: under `isolated` / `group`, a write of the environment's metadata, of any type, at the `/meta` write doors (save, reset, publish, rollback) and `POST /meta/_migrate-stored` on both transports, at every `/packages` write door (create, the manifest `PATCH`, enable, disable, publish, `publish-drafts`, `discard-drafts`, `revert`, `rollback`, `commits/:commitId/revert`, `duplicate`, `adopt-orphans`, delete), at the `/automation` definition writes (create, update, delete, clone), and at the permission-set clone door. Every other caller below the platform-operator rung is refused `403` there (ADR-0131 D3, D12 items 7–8, D11), because one organization's write would change what every organization resolves. The seeders, the package door and migrations write the environment's metadata this way. Under `single` the refusal is inert for every caller | `packages/metadata-core/src/meta-write-capability.ts#walledCatalogWriteVerdict`, `packages/rest/src/rest-server.ts#walledCatalogRefusal`, `packages/runtime/src/domains/packages.ts#refuseWalledCatalogPackageWrite`, `packages/runtime/src/domains/automation.ts#refuseWalledFlowDefinitionWrite` |
188188
| 57 | Automation run-state read, flow-authoring write, the paused-run caller gate (screen read and resume), the two operator run-lifecycle writes and the elevated self-triggered start at every door that starts a flow by name all pass | runtime | Get: run state, flow writes, a paused run's screen and its resume with no grant and without being the run's starter — and cancelling or restoring a suspension without the platform-operator rung. The screen read and the resume ask one predicate, so this is one bypass for both. The lifecycle bypass is the in-process owner's door: plugin-approvals' revise-window recall (ADR-0044) cancels on behalf of a decision it already authorized and recorded. And starting a flow declared `runAs: 'system'` whose type is self-triggered (`autolaunched`, `record_change`, `schedule`) through any door that starts a flow by name — the trigger route, an action of `type: 'flow'` (REST `/actions` and the MCP `run_action` bridge) and a declared endpoint of `type: 'flow'` — which every other caller, the guest principal included, is refused `403 PERMISSION_DENIED` (the maintainer's rulings, letters B and A). The three doors ask one predicate, so this is one bypass for all of them, and it is the in-process caller's, such as a job: inbound HTTP never carries the flag | `packages/runtime/src/domains/automation.ts#mayReadRunState`, `#refuseUngrantedFlowWrite`, `#isRunStarterOrRunStateReader`, `#refuseUngrantedRunLifecycleWrite`, `packages/runtime/src/flow-start-admission.ts#refusesElevatedSelfTriggeredStart` |
189189
| 58 | Audience-binding suggestion authority gate passes | plugin-security | Get: a system caller lists, accepts and dismisses suggestions without `manage_metadata` and, under a wall, without the platform-operator rung | `packages/plugins/plugin-security/src/suggested-audience-bindings.ts#assertDeploymentAuthority` |
190190
| 59 | Email-template organization door passes; webhook provenance stamp skipped | plugin-email, plugin-webhooks | Get (`sys_email_template`): a create or update of a template row. The door refuses every other write that names a caller, `403 PERMISSION_DENIED` (ADR-0131 D6); the plugin itself no longer writes the table (templates resolve from the registry), so the v18 migration ceremony's promotion is the one remaining writer. Lose (`sys_webhook`): the row is not marked as an admin customization | `packages/plugins/plugin-email/src/email-template-door.ts#isOrganizationWrite`, `packages/plugins/plugin-webhooks/src/webhook-provenance.ts#bindWebhookProvenanceStamp` |

0 commit comments

Comments
 (0)