Skip to content

Commit 2dfeb1a

Browse files
refactor(plugin-security): move the RLS filter layers out of security-plugin.ts (#25223 cut 7) (#25401)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuMYdF2uchcinMphEKW4sYxd)_ Part of #25223 Clause-②: no Metadata-protocol: none Ruling-ref: 6353073966 Cut 7 of the `security-plugin.ts` split: the RLS filter layers, row 5 of the series plan. The claim is 6361091603. The bodies of four private async methods move unedited out of the class, to the new module `packages/plugins/plugin-security/src/security-plugin-rls-filter.ts`: - `computeLayeredRlsFilter`, the ADR-0095 D1 split every row filter is composed from: Layer 0 (the tenant wall, its one platform-admin exemption and the verified platform owner's read crossing, #12974) and Layer 1 (business RLS, the ownership floor's replacements, the #7665 derived write scope and the field-existence net), before the AND-compose. `computeRlsFilter`, `checkAuthoredRowWrite`, `explainAccessForCaller`, the write wall's adapter `computeWriteTenantWall` and the middleware in `start()` call it, and the explain engine reads it through the plugin. - `isVerifiedPlatformOwnerSession`, the fail-closed probe the owner crossing asks (#12974, #13147). - `computeWriteCheckFilter`, the ADR-0058 D4 post-image `check` compile. - `stageRlsMembership`, the ADR-0105 D11 membership staging both compiles ask. It is move-only, and the card stays open for the later cuts. Branch: `claude/project-thread-3fzya4`, the harness-pinned branch for this session, restarted from `origin/main` at `77a77e42f8`, cut 6's merge. It names no issue, so the claim comment is the identity record. **What stays in the class.** Each moved method keeps its name, signature, docblock and position, less `async`. Three thin members of the plan row stay whole: `computeRlsFilter`, `hasPlatformAdminPosture` and `platformFloorYieldsToObjectWriteModel`. Each body is shorter than its stub would be. Every caller still calls the methods. **Module-level declarations.** - Two stay and gain `export`. The module imports both by value, as cuts 3, 5 and 6 import theirs. - `writeCheckPolicies`: ADR-0066:112 anchors it in `security-plugin.ts`, and so do two evidence anchors of the spec liveness ledger (`object.json`, `permission.json`), a file this project does not own. - `isCarriedPosture`: the `PLATFORM_ADMIN_ONLY_CAPABILITIES` docblock names it in place, with a `{@link}`. - The cut 6 `.d.ts` trap was checked before the claim: an export whose signature names another file's type moved a declaration in the published `.d.ts` then. `writeCheckPolicies` names `RowLevelSecurityPolicy`, so a local trial build ran first. With both exports in place, the head `dist/index.d.ts` is byte-identical to base (below). - `RlsFilterOptions` keeps its one declaration, unexported. The middleware, the explain path and the floor-drop resolution read it. The module names it through the class's own signature, as the type of `computeLayeredRlsFilter`'s fifth parameter less `undefined`, as cuts 2 to 6 name theirs. - Import names only the moved bodies read leave `security-plugin.ts`, and the module imports them from the same specifiers: - `computeTenantLayer0Verdict`, `tenantLayer0FilterOf` and the type `TenantLayer0Input` from `./tenant-layer.js`; - `PLATFORM_OWNER_WALL_BYPASS_EVENT` from `./platform-owner-wall-bypass.js`; - `resolvePlatformAdminEmails` from `@objectstack/core`; - `RESERVED_RLS_MEMBERSHIP_KEYS` from `@objectstack/spec/contracts`. - `isConfiguredPlatformAdminEmail` and `isVerifiedPlatformOwnerRow` stay imported in `security-plugin.ts` too, unchanged, because the probe's docblock links to both with `{@link}`, and that docblock stays. TypeScript counts a `{@link}` as a use, so typecheck and lint pass with them. No code there reads them any more, so the bundler drops that file's import of `./platform-owner-wall-bypass.js`, and the bundle now places that module (one string constant and two functions, no top-level effect) inside the new module's segment instead of before it. - Each value import is read only inside a function body, at call time, never while either module evaluates. `index.ts` still exports only `SecurityPlugin`, so the published surface does not move (measured below). **Readers.** No reader outside the moved code reads the moved lines by path, so this PR changes only the two source files (see Readers below). ## The published `.d.ts` does not change Under the series bar (ruling 6348891160 item 1), measured on base `77a77e42f8` and on the head (the tree of this PR's commit): - Raw sha256 of `dist/index.d.ts` and `dist/index.d.mts`, base and head, all four the same: `af5dee1bebafa0376a876d1e7b247c878ea42ac35c7336a6d3c12a7953143156`. The raw diff is 0 lines, so no union order moved either. - Normalised sha256, all four the same: `431e7f5097ac1c220bc840188aef02105bc0b16e845e976dd562d02cc79be8bd`. - These are the same hashes as cut 6's report. Nothing that landed on `origin/main` between cut 6's base `bdf762c9bd` and this base changed the published `.d.ts`. Base and head here are both built against `77a77e42f8`'s dependencies, rebuilt on that commit first. - `writeCheckPolicies` and `isCarriedPosture` gain `export` in `security-plugin.ts`. A local trial build measured this before the claim, because at cut 6 an export whose signature names another file's type moved a declaration. Here the `.d.ts` did not change by a byte. - The normaliser, run from `packages/plugins/plugin-security` (it sorts the members of each string-literal union and changes nothing else): ```sh node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum ``` - Positive controls on a copy of the head `dist/index.d.ts`, line 1707 (`private computeLayeredRlsFilter;`): - Made public (`computeLayeredRlsFilter;`): raw `e247cabdd554c6601280095ccd71226e90fb217fd05c87901921812fa2a33fd0`, normalised `591d99b9859452aa1906d0303d4e17dac91bea54e192a7654be58c60d18364b6`. - Renamed (`private computeLayeredRlsFilterRenamed;`): raw `5a474968b2a42ea1a06cfa814dd95074ae24cf4bc27a7b8ec08a1e617f3dedef`, normalised `ad123c5a9397c5684e008a4f7924e7d8e0b70de181f7a0c79cadc2330f805e38`. - Both differ from the real hashes, so the comparison does see a visibility change and a rename. - The four moved methods are private, so the `.d.ts` carries each one's name and docblock only (lines 1707, 1751, 1761 and 1798 of both). - Export tables: the ESM and CJS bundles each export the same 66 names at base and head. The lists were loaded at runtime (`require` and `import()` of the built bundles), not parsed. The sorted name list hashes `83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116` on all four, as at cut 6. The new exports of `security-plugin.ts` do not reach the entry. - The bundle JS bytes differ (`index.js` `77ef1113` to `cf7a65c1`, `index.mjs` `f604cc07` to `d4178511`), because code moved within them. That includes `platform-owner-wall-bypass.ts`, which the bundle now places inside the new module's segment (above). No changeset: `skip-changeset`, per ruling 6348891160 item 4. ## What moved The base line numbers are each method's signature and closing brace at `77a77e42f8`. The last column lists the members the body reads, which is exactly what its host carries. | method | base lines | host members | |---|---|---| | `computeLayeredRlsFilter` (private, async) | `:7721`-`:8133` | `collectRLSPolicies`, `datetimeFieldNamesCache`, `extractTargetField`, `getObjectFieldNames`, `getObjectSecurityMeta`, `hasPlatformAdminPosture`, `isVerifiedPlatformOwnerSession`, `logger`, `metadata`, `numberComparandFieldsCache`, `permissionEvaluator`, `platformFloorYieldsToObjectWriteModel`, `ql`, `rlsCompiler`, `stageRlsMembership`, `tenancyDisabledCache`, `tenancyPosture` | | `isVerifiedPlatformOwnerSession` (private, async) | `:8178`-`:8205` | `ql` | | `computeWriteCheckFilter` (private, async) | `:8216`-`:8305` | `collectRLSPolicies`, `datetimeFieldNamesCache`, `getObjectFieldNames`, `getObjectSecurityMeta`, `metadata`, `numberComparandFieldsCache`, `permissionEvaluator`, `platformFloorYieldsToObjectWriteModel`, `ql`, `rlsCompiler`, `stageRlsMembership` | | `stageRlsMembership` (private, async) | `:8343`-`:8383` | `logger`, `rlsMembershipResolver` | Each stub drops `async` and returns the moved function's promise, as cuts 1, 3, 5 and 6 did for their async methods. All four methods are private, so the `.d.ts` carries each one's name and docblock only. `computeLayeredRlsFilter` declares a multi-line return type, an object type whose two last members carry docblocks. The moved function repeats it as the method declares it, one indentation level in. The method has no docblock of its own: its `[C2 / ADR-0095 D1]` docblock sits above `hasPlatformAdminPosture`'s at base, and stays there. The moved function's pointer comment says so. ## The shape (#23050 shape B, as cuts 1 to 6 applied it) - The class keeps each method with its name, signature, docblock and position, because tsup emits members with their JSDoc into the `.d.ts`. Each body is now one host literal plus `fn.call(host, …)`, marked `[#25223 cut 7]`. - `RlsFilterHost` is new. It types every member as the class member itself (`SecurityPlugin['m']`). Each moved function types its `this` as the `Pick` of the host over exactly the members its body reads, and the stub annotates its literal with the same `Pick`. There is no cast, and no member becomes public or protected. - Methods go in as late-bound closures that call `this.m(...a)` on the plugin at call time. A spy or test double set on the plugin is still the one the body reaches: - the layered computation still stages membership, asks the owner probe and the platform-admin probe, and reads the floor predicate through the plugin; - the write check still stages membership and reads the floor predicate through the plugin; - a test that replaces `plugin.computeLayeredRlsFilter` on the instance (`explain-dependency-fault.test.ts`) still replaces it for `computeRlsFilter` and every other caller. No moved body calls a function-valued field, so no receiver changes: `permissionEvaluator`, `rlsCompiler`, `rlsMembershipResolver`, `ql` and the caches are objects whose own methods the bodies call. - Fields follow the rule cut 1 set, on each field's own declaration. - `datetimeFieldNamesCache`, `numberComparandFieldsCache` and `tenancyDisabledCache` are declared `readonly` and go by value, so the bodies read the plugin's own maps. - `logger`, `metadata`, `permissionEvaluator`, `ql`, `rlsCompiler`, `rlsMembershipResolver` and `tenancyPosture` are not declared `readonly`, so they go as getters, and each read happens when the body makes it. - The host declares every field `readonly`, so a moved body cannot assign a member through it; the typecheck passes, so none does. - Nothing re-exports the module, so the package entry does not move. ## Proof of move-only - **Bodies, by AST token**: each moved function's body has the same TypeScript token sequence as the base method body, and each of its 536 lines is the base line with two leading spaces removed. Parameters and `async` match. The return types match by token too; the two docblock tokens inside `computeLayeredRlsFilter`'s are compared one indentation level in. Three negative controls fail the check: - in `computeLayeredRlsFilter`, `superuserBypass && platformPosture` changed to `superuserBypass || platformPosture`; - in `computeWriteCheckFilter`, `withCheck.length === 0` changed to `withCheck.length !== 0`; - in `computeLayeredRlsFilter`'s return type, `crossedByPlatformAdmin: boolean` changed to `boolean | undefined`. - **Class side**: each moved method keeps its docblock and signature, less `async`. Every other class member, 122 named members plus the constructor, is byte-identical to base. That includes `computeRlsFilter`, `hasPlatformAdminPosture`, `platformFloorYieldsToObjectWriteModel`, `getReadFilter`, the wall adapters and `start()`. The class has 127 members at base and at head. - **Top level of `security-plugin.ts`**: 96 of the 102 base top-level statements other than the class are byte-identical to base, `RlsFilterOptions` included. The six that differ: - two are the base text with `export ` before `function`: `isCarriedPosture`, `writeCheckPolicies`; - four imports lose the names listed above: `./tenant-layer.js`, `./platform-owner-wall-bypass.js`, `@objectstack/core` and `@objectstack/spec/contracts`. The one new statement is the import of the new module. - **git's own view** (`git diff --color-moved=plain --color-moved-ws=allow-indentation-change 77a77e4 HEAD` over the two source files): of the 547 deleted lines, git sees 539 as moved. The other 8 are the four signatures that lose `async`, the two `function` lines that gain `export`, the `@objectstack/core` import line that loses `resolvePlatformAdminEmails`, and the `RESERVED_RLS_MEMBERSHIP_KEYS` line. Of the 799 added lines, 552 show as moved. The other 247 are the new module's header, imports, alias and host interface, the four stubs, the new import with its comment, and the edited lines above. - **Size**: 1,346 changed lines (+799 / -547) over 2 files, under the 3,000-line manual-merge line. `security-plugin.ts` goes from 9,863 to 9,394 lines, and the new module has 721. ## ADR anchors (ruling 6353073966 item 2) - `node scripts/check-adr-symbol-anchors.mjs`: exit 0 at base `77a77e42f8` and exit 0 at head, both with `2384 anchors across 142 records resolve — 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation`. CI runs this gate (`lint.yml`), but `dispatch-gates --commands` does not derive it for this change set (#21697), so it is run by hand here. - `pnpm check:adr-anchors`: exit 0 at base and at head, both with 80 anchored files. The decision-letter count on the anchored surface goes from 2014 to 2004. That gate counts `ADR-NNNN Dk` citations in anchored files only, and the new module has no entry: - 13 citations leave `security-plugin.ts` with the moved bodies: 12 in `computeLayeredRlsFilter` (ADR-0095 D1, D2 and D3, ADR-0099 D1, ADR-0105 D2, D3 twice and D11, ADR-0058 D4, ADR-0139 D2, ADR-0123 D2 twice) and 1 in `computeWriteCheckFilter` (ADR-0105 D11); - 3 arrive in the new import's comment (ADR-0095 D1, ADR-0058 D4, ADR-0105 D11). - `grep -oE 'ADR-[0-9]{4} D[0-9]+'` counts 294 in `security-plugin.ts` at base and 284 at head. Each moved citation is still in the tree, in the new module, which counts 18: the 13, the return type's ADR-0095 D3, 3 in its header and 1 in `computeLayeredRlsFilter`'s pointer comment. - `scripts/adr-anchors/` is unchanged. No paragraph of the `security-plugin.ts` entry (ADR-0055 §1, ADR-0106 D7, ADR-0123 D2, ADR-0138 D1) describes the RLS layers, so the new module gets no entry, as the modules of cuts 1 and 6 have none, and none is invented to hold the count. The `security-plugin.ts` entry is unchanged. `docs/adr/**` is not touched. - The anchor map covers every ADR symbol anchor naming `security-plugin.ts`: 27 anchors, 18 symbols. It was measured with `extractAnchors` and `symbolSegmentResolution` from `scripts/symbol-anchors.mjs`, which is measurement, not a new gate. It was read on `77a77e42f8` BEFORE the cut, with the four bodies blanked: no anchor changed class. Every anchor resolves at head in `security-plugin.ts` with its base class. The table gives each anchor's class at base and head and the line its own declaration resolves on. Every anchor stays in `security-plugin.ts`. | anchor | cited at | base class · line (member) | head class · line (member) | |---|---|---|---| | `#CBP_MAX_CHAIN_DEPTH` | ADR-0055:135 | declaration · `:658` (top-level) | declaration · `:665` (top-level) | | `#SecurityPlugin` | ADR-0049:49, ADR-0138:248, ADR-0138:265 | declaration · `:1290` (class) | declaration · `:1297` (class) | | `#SecurityPlugin.getMetadataReadableFields` | ADR-0138:153 | declaration · `:6085` (method) | declaration · `:6092` (method) | | `#SecurityPlugin.resolvePermissionSetsForContextUnmemoized` | ADR-0138:427 | declaration · `:6996` (method) | declaration · `:7003` (method) | | `#assertControlledByParentWrite` | ADR-0055:58, ADR-0055:125 | declaration · `:9120` (method) | declaration · `:8651` (method) | | `#assertMasterRowEditable` | ADR-0055:125 | declaration · `:9246` (method) | declaration · `:8777` (method) | | `#collectRLSPolicies` | ADR-0105:93 | declaration · `:9300` (method) | declaration · `:8831` (method) | | `#composeWriteTenantWall` | ADR-0123:4 | declaration · `:8458` (method) | declaration · `:7989` (method) | | `#computeControlledByParentFilter` | ADR-0055:5, ADR-0055:123, ADR-0139:246 | declaration · `:8766` (method) | declaration · `:8297` (method) | | `#computeDelegatedReadFilter` | ADR-0139:223 | declaration · `:5963` (method) | declaration · `:5970` (method) | | `#computeRlsFilter` | ADR-0055:24 | declaration · `:7690` (method) | declaration · `:7697` (method) | | `#getReadFilter` | ADR-0096:181, ADR-0096:182, ADR-0096:183, ADR-0096:199, ADR-0139:222 | declaration · `:5841` (method) | declaration · `:5848` (method) | | `#hasPlatformAdminPosture` | ADR-0099:317 | declaration · `:7717` (method) | declaration · `:7724` (method) | | `#isPrincipalLessContext` | ADR-0138:152 | declaration · `:531` (top-level) | declaration · `:538` (top-level) | | `#manage_metadata` | ADR-0099:68 | literal · `:280` (top-level) | literal · `:287` (top-level) | | `#nav_permission_sets` | ADR-0126:406 | literal · `:1718` (`init`) | literal · `:1725` (`init`) | | `#rlsFilter` | ADR-0055:24 | declaration · `:4292` (`start`) | declaration · `:4299` (`start`) | | `#writeCheckPolicies` | ADR-0066:112 | declaration · `:1185` (top-level) | declaration · `:1192` (top-level) | No resolving site moves out of the file. Two anchors gain sites, and neither changes class: - ADR-0099:317 `#hasPlatformAdminPosture` still resolves on the method, which stays whole. At head the name also starts a line in `computeLayeredRlsFilter`'s stub, as its host-literal key (`:7764`), and appears quoted in that stub's `Pick` (`:7758`). - ADR-0105:93 `#collectRLSPolicies` still resolves on the method. At head it also keys the host literals of `computeLayeredRlsFilter`'s and `computeWriteCheckFilter`'s stubs (`:7759`, `:7854`), as it already keyed `assertMasterRowEditable`'s (cut 3, `:8790`). Cuts 3 and 6 added the same kind of site for `#assertControlledByParentWrite` and `#composeWriteTenantWall`. An anchor is judged by its class, which is `declaration` at base and at head for both. ADR-0066:112 `#writeCheckPolicies` resolves on the declaration, which stays and gains `export`. The spec liveness ledger's two `#writeCheckPolicies` evidence anchors resolve on the same line. ## Readers No reader outside the moved code reads the moved lines by path, so no gate, baseline, docs anchor or test moves with this cut. Each kind was read: - **The system-context census.** The moved bodies hold no elevation read: `isVerifiedPlatformOwnerSession`'s `isSystem: true` is a key of its own system read of `sys_user`. `check-system-context-census` exits 0 at base and at head with the same output. `node scripts/isystem-census.mjs --json` gives the same 148 sites at base and head, compared without line numbers and with the new module mapped back to `security-plugin.ts`: 0 differences, and no site in the new module. The role counts are the same (`read` 153, `declaration` 38, `key` 438, `other` 9). Only the scanned file count moves, 392 to 393: the new module. - **The tenant-audit census.** The moved bodies hold no engine write: the probe's `ql.findOne` and `ql.find` are reads. `node scripts/tenant-audit-census.mjs --json` gives the same 238 write call sites at base and head, compared the same way: 0 differences, the same totals, 0 unresolved, 0 unledgered. Only `scannedSources` moves, 718 to 719, plus the measured-at ref. `check-tenant-audit-census` prints the same output at both. - **The #25107 enumeration pin** (`delegated-organization-membership.test.ts`) counts `resolveDelegatorContext(` and `delegatedOrganizationRefusal(` per source file. The moved bodies call neither, so its list of three files is unchanged, and it passes at head. - **`packages/core/src/security/row-serving-door-exposure.pin.test.ts`** classifies every caller of the read admission. The moved bodies call no admission, so the new module needs no row. Run on its own: 3 of 3 pass. - **Other source readers by path.** The tests and scripts that read `security-plugin.ts` as text were read: `engine-middleware-operation-vocabulary.test.ts` (objectql), `rls-predicate.test.ts` (formula), `seed-write-refusal.test.ts`, `open-only-wall-acceptance.test.ts` (organizations), `check-runtime-services-index.mjs`, `check-position-name-fold-loaders.mjs` and `check-system-context-census.mjs`. None reads text inside the moved bodies. - **The platform checklist.** `check-platform-checklist` exits 0 at base and head with the same output. No checklist citation names a moved symbol or a local inside a moved body. - **Symbol anchors outside the ADRs.** The spec liveness ledger (`object.json`, `permission.json`), the platform checklist areas, `system-context.mdx` and spec `conversions/registry.ts` hold 27 anchors into `security-plugin.ts`, over 17 symbols. None names a moved method; `#writeCheckPolicies` (four of them) and `#collectRLSPolicies` name declarations that stay. With `symbolSegmentResolution`, each resolves at head with its base class: 0 changed. - **Readers by name, not by path.** These were read and none needs a change, because each method still exists in the class under the same name and signature: - tests that call `(plugin as any).computeWriteCheckFilter(…)` (`rls-check-membership-staging.test.ts`, `rls-shared-lowering-seam.test.ts`) or replace `stack.plugin.computeLayeredRlsFilter` (`explain-dependency-fault.test.ts`), and the explain tests that pass a `computeLayeredRlsFilter` dependency; - comments and prose naming the four methods in `explain-engine.ts`, `rls-compiler.ts`, `platform-ownership-policies.ts`, `lifecycle-verb-rls-operation.ts`, `platform-owner-wall-bypass.ts`, the lint package, the dogfood matrix, and two docs pages (`permissions-matrix.mdx` and `sharing-rules.mdx`, which say `security-plugin.ts` (`computeLayeredRlsFilter`): the method is still there, and its body is one call away). - **Line citations**: no `security-plugin.ts:N` citation in the repo points into the moved ranges (base `:7721`-`:8133`, `:8178`-`:8205`, `:8216`-`:8305`, `:8343`-`:8383`). The citations found are the list cuts 2 to 6 read (`docs/launch-readiness.md` `:309`, `packages/objectql/src/engine.ts` `:1614`, `:1690`, `:1736`, spec liveness `:3281` and the rest). Each already pointed at an unrelated line before cut 2, so this cut does not make any of them wrong. - **Docs Drift Check**, re-derived with `node scripts/docs-audit/affected-docs.mjs --json 77a77e4` (advisory): 25 pages. Each is reached through a literal inside the moved bodies (`PLATFORM_ADMIN`, and `public_read_write` and `org_member` in comments), and `permissions-matrix.mdx` and `sharing-rules.mdx` also through the `computeLayeredRlsFilter` name. Every literal is unchanged and still in the tree, and the method still exists under its name, so no page's claim changes. This PR edits no page. - **Census gates, run on base `77a77e42f8` and head and compared:** - Identical output: `check-adr-symbol-anchors`, `check-platform-checklist`, `check-platform-object-tenancy-census`, `check-registry-log-declared`, `check-rest-log-spy-declared`, `check-system-context-census`, `check-tenant-audit-census`, `check:cross-package-test-inputs`, `check:driver-memory-census`, `check:issue-citations` and `check:query-options-erasure`. The last reports the same counts at both, and `security-plugin.ts` is not a baseline key. - Only scanned counts move: - `check-undeclared-dep-imports`: 3304 to 3305 files, and 2847 to 2850 specifiers. The new module imports `@objectstack/spec/security`, `@objectstack/spec/contracts` and `@objectstack/core`, all declared dependencies, and `security-plugin.ts` still imports from all three. - `check:org-identifier`: 3646 to 3647 files. - `check:doc-authoring`: 1423 to 1424 parsed sources. Its clean-file count goes from 424 to 427 because the head worktree holds three gitignored generated pages that the base worktree does not (`content/docs/protocol-upgrade/17.md`, `18.md` and `index.md`), as at cut 6. CI's checkout does not have them. - `check:logger-receiver-detach`: 3620 to 3622 files: the new module, and a local build output under `apps/docs/.next` that exists only in the head worktree. - `check-position-name-fold-loaders`: 12337 to 12341 files, with the same 8 references (6 test, 2 declared instrument, 0 loader). It walks the working tree: the new module plus local build outputs that exist only in the head worktree. - `check-issue-citations` judges a change set, so it has nothing to read at base. At head it judged 45 citations across 2 files, and all resolve (42 issues, 3 pull requests). ## Acceptance notes - Nothing outside this cut is touched: no other section of the class, no behaviour, no `docs/adr/**`, no `scripts/adr-anchors/` entry, and no file outside the package. - Serial check, also in the claim: of the 13 open PRs at claim time, none touched `security-plugin.ts`, `explain-engine.ts`, the `rls-*` or `platform-owner-*` sources, the permissions pages or the plugin-security anchor entries. - Since the local runs, `origin/main` gained 3 commits (to `c6bdcaccb7`). None touches this cut's files. On a scratch merge of this head onto that main, `check-adr-symbol-anchors`, `check:adr-anchors`, `check-platform-checklist`, `check-system-context-census` and `check-tenant-audit-census` all exit 0. `check:adr-anchors` there counts 80 anchored files and 2005 decision-letter citations; that main alone counts 2015, so the shift this cut makes is the same 10. - What is left of the plan's row 5 in `security-plugin.ts` after this cut: the three thin members (`computeRlsFilter`, `hasPlatformAdminPosture`, `platformFloorYieldsToObjectWriteModel`) and the four method shells. `security-plugin.ts` has 9,394 lines at head, 9,863 at base. ## Gates All gates ran at the head, the tree of `41c661896c`, on base `77a77e42f8`. - `pnpm --filter @objectstack/plugin-security test` (`vitest run --maxWorkers=3`, under the verify lock): Test Files 214 passed (214), Tests 4465 passed, 45 skipped (4510). Base `77a77e42f8` gives the same counts. VERDICT command-exit 0 on both. - `pnpm --filter @objectstack/plugin-security typecheck`: exit 0. It runs `tsc --noEmit`, the scripts tsconfig, and `check:test-typecheck`. - `eslint --no-inline-config` over the two touched source files: exit 0. - `pnpm --filter @objectstack/plugin-security build`: exit 0. It produced the `.d.ts` and bundles measured above. The package's dependencies were rebuilt on `77a77e42f8` first. - `packages/core/src/security/row-serving-door-exposure.pin.test.ts`: 3 of 3 pass. - `node scripts/check-adr-symbol-anchors.mjs`, `pnpm check:adr-anchors`, `pnpm check:platform-checklist` (and its script), `check-scripts-symbol-anchors` (and `--self-test`), and the two census `--json` reads. None of these is derived for this change set; each was run by hand, and each exit is listed below. - `dispatch-gates --commands`: 52 commands. The `--ran` reconciliation shows 52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN. The zero is derived: all 52 recorded an exit code and none is 3. No docs family is derived, because this cut touches no page. Per command (exit code), the derived first and the hand-run after: - `node scripts/check-ci-filter-parity.mjs` exit 0 - `node scripts/check-closing-keyword-parity.mjs` exit 0 - `node scripts/check-closing-keyword-parity.mjs --self-test` exit 0 - `node scripts/check-comment-mask-adoption.mjs` exit 0 - `node scripts/check-comment-mask-adoption.mjs --self-test` exit 0 - `node scripts/check-comment-mask-corpus.mjs` exit 0 - `node scripts/check-dts-emitted.mjs --self-test` exit 0 - `node scripts/check-issue-citations.mjs` exit 0 - `node scripts/check-keyed-text-bounds.mjs` exit 0 - `node scripts/check-keyed-text-bounds.mjs --self-test` exit 0 - `node scripts/check-platform-object-tenancy-census.mjs` exit 0 - `node scripts/check-platform-object-tenancy-census.mjs --self-test` exit 0 - `node scripts/check-plugin-teardown-shape.mjs` exit 0 - `node scripts/check-plugin-teardown-shape.mjs --self-test` exit 0 - `node scripts/check-position-name-fold-loaders.mjs` exit 0 - `node scripts/check-position-name-fold-loaders.mjs --self-test` exit 0 - `node scripts/check-registry-log-declared.mjs` exit 0 - `node scripts/check-registry-log-declared.mjs --self-test` exit 0 - `node scripts/check-rest-log-spy-declared.mjs` exit 0 - `node scripts/check-rest-log-spy-declared.mjs --self-test` exit 0 - `node scripts/check-system-context-census.mjs` exit 0 - `node scripts/check-system-context-census.mjs --self-test` exit 0 - `node scripts/check-tenant-audit-census.mjs` exit 0 - `node scripts/check-tenant-audit-census.mjs --self-test` exit 0 - `node scripts/check-undeclared-dep-imports.mjs` exit 0 - `node scripts/check-undeclared-dep-imports.mjs --self-test` exit 0 - `node scripts/docs-audit/check-affected-docs.mjs` exit 0 - `node scripts/docs-audit/check-drift-comment.mjs` exit 0 - `pnpm --filter @objectstack/spec run check:duration-unit-keys` exit 0 - `pnpm check:closed-ceremony-text` exit 0 - `pnpm check:cross-package-test-inputs` exit 0 - `pnpm check:doc-authoring` exit 0 - `pnpm check:driver-memory-census` exit 0 - `pnpm check:dts-closure` exit 0 - `pnpm check:dual-build-cjs-loads` exit 0 - `pnpm check:error-status-conformance` exit 0 - `pnpm check:gitlink-declared` exit 0 - `pnpm check:i18n` exit 0 - `pnpm check:i18n-stale-fill` exit 0 - `pnpm check:issue-citations` exit 0 - `pnpm check:lean-entry-closure` exit 0 - `pnpm check:logger-receiver-detach` exit 0 - `pnpm check:nul-bytes` exit 0 - `pnpm check:org-identifier` exit 0 - `pnpm check:page-declaration-shape` exit 0 - `pnpm check:published-files` exit 0 - `pnpm check:refd-timer-probe` exit 0 - `pnpm check:slot-lookup` exit 0 - `pnpm check:sourcemap-no-sources-content` exit 0 - `pnpm check:test-source-alias` exit 0 - `pnpm check:tier-file-adoption` exit 0 - `pnpm check:watch-hint-literal` exit 0 - `node scripts/check-adr-symbol-anchors.mjs` exit 0 - `node scripts/check-platform-checklist.mjs` exit 0 - `node scripts/check-scripts-symbol-anchors.mjs` exit 0 - `node scripts/check-scripts-symbol-anchors.mjs --self-test` exit 0 - `node scripts/isystem-census.mjs --json` exit 0 - `node scripts/tenant-audit-census.mjs --json` exit 0 - `pnpm check:adr-anchors` exit 0 - `pnpm check:platform-checklist` exit 0 - `pnpm check:query-options-erasure` exit 0 --- _Generated by [Claude Code](https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa Co-authored-by: Claude <noreply@anthropic.com>
1 parent 860f002 commit 2dfeb1a

2 files changed

Lines changed: 799 additions & 547 deletions

File tree

0 commit comments

Comments
 (0)