Repository navigation
Commit dd124e9
<!-- 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 9 of the `security-plugin.ts` split: row 7 of the series plan, the
read filters, the field readers and the object admissions. The claim is
6365993271. The row has three seams, so the bodies of nine methods move
unedited out of the class to three new modules in this one PR, as cuts 6
and 8 split theirs over two.
To
`packages/plugins/plugin-security/src/security-plugin-read-filter.ts`:
- `getReadFilter` (public), the row-level read scope the `security`
service serves to the doors that compile their own statement: the same
three layers the engine middleware ANDs into every find, the deny
sentinel for a caller who resolves no set or carries no principal,
fail-closed on any resolution failure.
- `computeDelegatedReadFilter`, the same scope for an on-behalf-of
caller: the principal's leg and the delegator's, intersected layer for
layer (ADR-0090 D10, #25106).
To
`packages/plugins/plugin-security/src/security-plugin-field-readers.ts`:
- `resolveProjectionFieldMask`, the derivation every published field
projection shares: the field universe, the caller's sets (the fallback
set on the metadata plane), the posture, the field map with the
capability fold, and the delegator's intersection (#25107).
- `computeReadableFields`, the readable projection both planes answer
from (#8993).
- `resolveCallerPosture`, the one posture a caller's gates read, with
the stand-in for a caller who resolved no set (#20995, #21063).
To
`packages/plugins/plugin-security/src/security-plugin-object-admission.ts`:
- `canReadObject` (public), the object-level read admission the read
doors that bypass the middleware ask.
- `objectReadAdmission`, that admission's arms over permission sets
already resolved.
- `canWriteObject` (public), the write admission the write preview asks
(#18682).
- `canExport` (public), the user-level export axis (#3544).
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 `25f88e899a`, cut 8'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`. Every caller still calls
the methods: the service binding, the middleware and the write preview's
seam in `start()`, and the four public projections. Four members of the
row stay whole: `getReadableFields`, `getMetadataReadableFields`,
`getWritableFields` and `getQueryableFields`. Each body is no longer
than its stub would be.
**Module-level declarations.**
- `EMPTY_REQUIRED_PERMISSIONS`, the stand-in's empty capability
contract, is read only by `resolveCallerPosture`'s body. It moves with
it, unedited, to the field-readers module, as cuts 1 to 4 moved the
declarations only their bodies read.
- Its type, `NormalizedRequiredPermissions`, stays unexported in
`security-plugin.ts`, where the posture interface and its normaliser
read it. The module names it through the class's own signature:
`ObjectSecurityMeta` as `getObjectSecurityMeta`'s resolved return type,
as cut 1's module does, and the capability-contract type as that
posture's `requiredPermissions` field. So the moved declaration's text
is byte-identical.
- The import name `intersectFieldMasks` (`./explain-engine.js`) is read
only by `resolveProjectionFieldMask`'s body. It leaves
`security-plugin.ts`'s import, and the field readers import it from the
same specifier. No staying docblock links to it; `canWriteObject`'s
docblock names it in backticks only.
- The modules import two values that are already exported:
`isPrincipalLessContext` (cut 6) and `requiredCapsForOperation`.
`security-plugin.ts` gains no export in this cut, so the cut 6 `.d.ts`
trap (an export whose signature names another file's type) does not
arise. The `.d.ts` was measured anyway, in a local trial build before
the claim: byte-identical to base (below).
- Each value import is read only inside a function body, at call time,
never while any of the four modules evaluates. `index.ts` still exports
only `SecurityPlugin`, so the published surface does not move (measured
below).
**Two readers outside the moved code read the moved lines by path, and
both are re-pointed here, as cut 6 re-pointed the same two.**
- `content/docs/permissions/system-context.mdx`, rows 3, 4, 5, 6 and 6b.
`check-system-context-census` keys each elevation read by file and
enclosing symbol. The five `context?.isSystem` reads of this row (in
`getReadFilter`, `resolveProjectionFieldMask`, `canExport`,
`canReadObject` and `canWriteObject`) now sit in the new modules. A
trial cut with the page untouched measured it: the census reports 8
problems, the three new files with no row and five anchors that are no
longer read sites. Each of the five anchors keeps its symbol and changes
only its path, from `security-plugin.ts#X` to the module's `#X`. No
row's text changes, and no other anchor changes. With that, the census
exits 0 (below).
-
`packages/plugins/plugin-security/src/delegated-organization-membership.test.ts`,
the #25107 enumeration pin. It counts, per source file, the delegator
resolutions and the organization-refusal asks, and lists the files that
resolve one. Five moved bodies resolve a delegator, each beside its ask:
`computeDelegatedReadFilter`, `resolveProjectionFieldMask`,
`canReadObject`, `canWriteObject` and `canExport`. The expected list
gains the three modules, and the title says six files instead of three.
- The rule it pins, asks equal resolutions in every file, is unchanged.
It holds at head: the read filter counts 1 and 1, the field readers 1
and 1, the object admission 3 and 3, and `security-plugin.ts` 2 and 2
(one in `start()`, one in `explainAccessForCaller`).
**One ADR anchor entry is added**, in the claim amendment 6366124111:
the field-readers module gets an entry carrying ADR-0106 and the
ADR-0106 D7 paragraph of the `security-plugin.ts` entry, verbatim,
because that paragraph's metadata-plane half is a branch of the moved
derivation. See the ADR anchors section below.
## The published `.d.ts` does not change
Under the series bar (ruling 6348891160 item 1), measured on base
`25f88e899a` and on the head. The head build of `0feb79ca4f` and the
full `turbo run build` at `b4ced7f87e` produced the same four `dist`
files byte for byte:
- Raw sha256 of `dist/index.d.ts` and `dist/index.d.mts`, base and head,
all four the same:
`7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da`. The
raw diff is 0 lines, so no union order moved either.
- Normalised sha256, all four the same:
`b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4`.
- Base and head are both built against `25f88e899a`'s dependencies,
rebuilt on that commit first. The hashes differ from those cuts 6 to 8
reported because `main` moved under the series: #24947 widened the
spec's bulk-action `confirmText` to a template slot, and the `.d.ts`
inlines that type in three places. The diff between cut 8's head `.d.ts`
and this base is exactly those three hunks.
- `security-plugin.ts` gains no export in this cut, and the modules
import only values that were already exported.
- 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`, each a
one-line edit:
- Line 839 (`private computeDelegatedReadFilter;`) made public
(`computeDelegatedReadFilter;`): raw
`b46fd6c3c1ffaf2ede883a72a5d6fee1c692d38ab4d504639667880a0236ac09`,
normalised
`cadb75e96b0419b7bddb047925e7e3e8fab28abbed6e5ab161bd8f2a0d8a2bf6`.
- Line 839 renamed (`private computeDelegatedReadFilterRenamed;`): raw
`1a5427bc1e748676c425f150b4e402dc29823002fe9e10389c7720c1106367dc`,
normalised
`365336182c2d65b2021c3844251cc211787143f0fdbcb60af1418ed1e8daf73a`.
- Line 1246, the public `canExport` signature, with `context?: any`
changed to `context?: unknown`: raw
`f0088fc10e9713f29fc84a6ec7fde84f3b5005556ca001ceb7bae565f20d6ab0`,
normalised
`f8bfba0031d62ff891175079526ff10f4017ffd0cf7148c61e220d1092777115`.
- All three differ from the real hashes, so the comparison does see a
visibility change, a rename and a public signature change.
- The four public methods carry their full signatures and docblocks in
the `.d.ts`: `getReadFilter` (line 801), `canReadObject` (1045),
`canWriteObject` (1227) and `canExport` (1246). The five private ones
carry their name and docblock only: `computeDelegatedReadFilter` (839),
`computeReadableFields` (896), `resolveProjectionFieldMask` (950),
`resolveCallerPosture` (995) and `objectReadAdmission` (1080). Those
lines are the same in both files.
- 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 cuts 6 to 8.
- The bundle JS bytes differ (`index.js` `3a109ef0` to `dfa83a54`,
`index.mjs` `1c38ee91` to `18c4d919`), because code moved within them.
Every value the new modules import comes from a file inside this
package, so the set of external specifiers each bundle loads is the same
at base and head (16 distinct `require` specifiers in `index.js`, 17
distinct `from` specifiers in `index.mjs`).
No changeset: `skip-changeset`, per ruling 6348891160 item 4.
## What moved
The base line numbers are each method's signature and closing brace at
`25f88e899a`. The last column lists the members the body reads, which is
exactly what its host carries.
| method | base lines | module | host members |
|---|---|---|---|
| `getReadFilter` (public, async) | `:5871`-`:5954` | read-filter |
`computeControlledByParentFilter`, `computeDelegatedReadFilter`,
`computeRlsFilter`, `logger`, `resolvePermissionSetsForContext`,
`resolveSharingReadFilter` |
| `computeDelegatedReadFilter` (private, async) | `:5993`-`:6055` |
read-filter | `computeControlledByParentFilter`, `computeRlsFilter`,
`getObjectSecurityMeta`, `logger`, `permissionEvaluator`, `ql`,
`resolvePermissionSetsForContext`, `resolveSharingReadFilter`,
`tenancyPosture` |
| `computeReadableFields` (private, async) | `:6119`-`:6140` |
field-readers | `resolveProjectionFieldMask` |
| `resolveProjectionFieldMask` (private, async) | `:6207`-`:6300` |
field-readers | `computeQueryGuardFieldPerms`,
`computeReadPartialMaskRules`, `foldFieldRequiredPermissions`,
`getObjectFieldNames`, `metadata`, `permissionEvaluator`, `ql`,
`resolveCallerPosture`, `resolveFallbackPermissionSets`,
`resolvePermissionSetsForContext`, `tenancyPosture` |
| `resolveCallerPosture` (private, async) | `:6346`-`:6365` |
field-readers | `getObjectSecurityMeta` |
| `canReadObject` (public, async) | `:6416`-`:6457` | object-admission |
`logger`, `objectReadAdmission`, `ql`,
`resolvePermissionSetsForContext`, `tenancyPosture` |
| `objectReadAdmission` (private, async) | `:6493`-`:6532` |
object-admission | `getObjectSecurityMeta`, `permissionEvaluator` |
| `canWriteObject` (public, async) | `:6680`-`:6846` | object-admission
| `computeForbiddenFieldWrites`, `delegatedAdminGate`,
`getObjectSecurityMeta`, `logger`, `organizationWallRefusal`,
`permissionEvaluator`, `ql`, `resolvePermissionSetsForContext`,
`tenancyPosture` |
| `canExport` (public, async) | `:6866`-`:6913` | object-admission |
`getObjectSecurityMeta`, `permissionEvaluator`, `ql`,
`resolvePermissionSetsForContext`, `tenancyPosture` |
Each stub drops `async` and returns the moved function's promise, as
cuts 1 and 3 to 8 did for their async methods. Four of the nine methods
are public, so the `.d.ts` carries their full signatures. A body move
does not touch a signature, and those lines do not change (below).
## The shape (#23050 shape B, as cuts 1 to 8 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 9]`.
- `ReadFilterHost`, `FieldReadersHost` and `ObjectAdmissionHost` are
new. Each types every member as the class member itself
(`SecurityPlugin['m']`). Each moved function types its `this` as the
`Pick` of its 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 read scope still asks the plugin's RLS filter,
controlled-by-parent leaf, sharing filter and set resolution;
- the projection derivation still asks the plugin's field universe,
fallback sets, posture and field folds, and the two closures it returns
(`readPartialMaskRules`, `readQueryGuardPerms`) still call the plugin
when they are called;
- the admissions still ask the plugin's arms, organization wall and
forbidden-field check.
No test replaces a moved method on the plugin instance today; one that
did would still replace it for every caller. No moved body calls a
function-valued field, so no receiver changes. `delegatedAdminGate`,
`logger`, `metadata`, `permissionEvaluator` and `ql` are objects whose
own methods the bodies call.
- Fields follow the rule cut 1 set, on each field's own declaration.
None of the six fields the bodies read is declared `readonly`
(`delegatedAdminGate`, `logger`, `metadata`, `permissionEvaluator`,
`ql`, `tenancyPosture`), so all go as getters, and each read happens
when the body makes it. `start()` assigns all of them but
`permissionEvaluator` after construction, and tests replace
`permissionEvaluator` on a constructed plugin.
- The hosts declare every field `readonly`, so a moved body cannot
assign a member through them. The typecheck passes, so none does.
- Nothing re-exports any of the modules, 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 the 526
body lines is the base line with two leading spaces removed. Parameters,
return types and `async` match. `resolveProjectionFieldMask`'s
multi-line return type, with the docblock of its member inside it, is
repeated on the function one indentation level in, as cut 7 did; its 82
tokens are equal. Three negative controls fail the check:
- in `getReadFilter`, `if (context?.isSystem) return undefined;` changed
to `if (!context?.isSystem) …`;
- in `resolveProjectionFieldMask`, the system answer `fields: allFields`
changed to `fields: []`;
- in `canExport`, `checkObjectPermission('export', …)` changed to
`checkObjectPermission('read', …)`.
- **Class side**: each moved method keeps its docblock and signature,
less `async`. Every other class member, 117 named members plus the
constructor, is byte-identical to base. That includes the four staying
members above. The class has 127 members at base and at head.
- **Top level of `security-plugin.ts`**: 102 of the 104 base top-level
statements other than the class are byte-identical to base. The other
two:
- the `./explain-engine.js` import loses `intersectFieldMasks`;
- `EMPTY_REQUIRED_PERMISSIONS` is gone from the file. The field-readers
module holds the same statement, byte for byte.
The three new statements are the imports of the new modules.
- **The two re-pointed readers**: the page diff is five lines, each one
anchor's path. The pin diff is the title's count and the expected list.
- **git's own view** (`git diff --color-moved=plain
--color-moved-ws=allow-indentation-change 25f88e8 HEAD` over the four
source files): of the 538 deleted lines, git sees 528 as moved. The
other 10 are the nine signatures that lose `async` and the import line
`intersectFieldMasks,`. Of the 1,044 added lines, 561 show as moved, all
in the three new modules: 524 of the 526 body lines, the 3 lines of
`EMPTY_REQUIRED_PERMISSIONS`, and 34 lines whose text git also finds
among the deleted lines (20 blank lines, 12 closing braces, 2 lines of
the new function signatures). The 2 body lines git leaves uncoloured are
the closing `};` of `resolveProjectionFieldMask`'s and
`resolveCallerPosture`'s return literals, which git pairs with a stub's
`};` of the same text rather than with a deletion. The other 483 added
lines are the modules' headers, imports, host interfaces, type aliases
and function signatures, the nine stubs, and the three new imports with
their comments.
- **Size**: 1,610 changed lines (+1,065 / -545) over 7 files, under the
3,000-line manual-merge line. `security-plugin.ts` goes from 9,248 to
8,852 lines; `security-plugin-read-filter.ts` has 241,
`security-plugin-field-readers.ts` 252 and
`security-plugin-object-admission.ts` 409. The other three are the page
(+5 / -5), the pin (+9 / -2) and the new anchor entry (+7).
## ADR anchors (ruling 6353073966 item 2)
- `node scripts/check-adr-symbol-anchors.mjs`: exit 0 at base
`25f88e899a` 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. It counts 80
anchored files at base and 81 at head, the new entry below.
- **One new entry, in the claim amendment 6366124111.** It is
`scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-field-readers.ts.json`,
and it carries ADR-0106 and the ADR-0106 D7 invariant paragraph of the
`security-plugin.ts` entry, verbatim, as the entries of cuts 2 to 5
carry theirs.
- That paragraph names `getMetadataReadableFields` and
`getReadableFields`, which stay. Its metadata-plane half, "the same
caller resolves the configured fallback permission set", is the
`fallbackOnEmptySets` branch of `resolveProjectionFieldMask`, whose body
moved here with its `[ADR-0106 D7]` comment. Cut 4's entry followed the
same rule: its module got one because the "reads answer RLS_DENY_FILTER"
half of the ADR-0055 §1 paragraph was the leaf it moved.
- The `security-plugin.ts` entry is unchanged, because the two methods,
their docblocks and the flag each passes stay in that file.
- No other paragraph describes a moved body. ADR-0055 §1 is the
controlled-by-parent relation (cuts 2 to 4). ADR-0123 D2 is the write
wall's no-organization refusal (cut 5); `canWriteObject` only asks that
wall's scope pre-test, as the middleware does. ADR-0138 D1 is the
seed-ownership claim. So the read-filter and object-admission modules
get no entry, as the modules of cuts 1 and 6 to 8 have none.
- **The decision-letter count on the anchored surface goes from 2001 to
1978.** That gate counts `ADR-NNNN Dk` citations in each anchored file
and in each entry's own invariant text.
- 24 citations leave the anchored surface with the read-filter and
object-admission bodies:
- `getReadFilter` 4: ADR-0056 D2, ADR-0090 D10, ADR-0096 D5, ADR-0139
D5;
- `computeDelegatedReadFilter` 4: ADR-0090 D10 three times, ADR-0123 D2;
- `canReadObject` 3 and `canExport` 3: each ADR-0056 D2, ADR-0090 D10,
ADR-0096 D5;
- `objectReadAdmission` 2: ADR-0066 D3, ADR-0090 D10;
- `canWriteObject` 8: ADR-0056 D2, ADR-0066 D3, ADR-0090 D10 twice,
ADR-0090 D12, ADR-0096 D5, ADR-0123 D2 twice.
- `resolveProjectionFieldMask`'s 3 (ADR-0090 D10, ADR-0096 D5, ADR-0106
D7) leave `security-plugin.ts` but stay on the anchored surface, in the
field-readers module.
- One arrives: the new entry's invariant is the ADR-0106 D7 paragraph,
and it names its own decision letter once. The three new import comments
and the module headers cite none.
- The amendment 6366124111 gave 1977. It left out that one citation in
the entry's own text, which the gate counts. The measured head count is
1978.
- Every moved citation is still in the tree, in the module its body
moved to. The tree-wide citations that resolve go from 52175 across 6636
files at base to 52179 across 6640 at head. The three modules add files
and no citations, and the new entry adds one file and four citations.
- `docs/adr/**` is not touched, and no anchor token is planted.
- 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 `25f88e899a` BEFORE the claim, with the whole cut applied as
a local trial: 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 · `:688`
(top-level) | declaration · `:712` (top-level) |
| `#SecurityPlugin` | ADR-0049:49, ADR-0138:248, ADR-0138:265 |
declaration · `:1320` (class) | declaration · `:1344` (class) |
| `#SecurityPlugin.getMetadataReadableFields` | ADR-0138:153 |
declaration · `:6115` (method) | declaration · `:6030` (method) |
| `#SecurityPlugin.resolvePermissionSetsForContextUnmemoized` |
ADR-0138:427 | declaration · `:7020` (method) | declaration · `:6624`
(method) |
| `#assertControlledByParentWrite` | ADR-0055:58, ADR-0055:125 |
declaration · `:8505` (method) | declaration · `:8109` (method) |
| `#assertMasterRowEditable` | ADR-0055:125 | declaration · `:8631`
(method) | declaration · `:8235` (method) |
| `#collectRLSPolicies` | ADR-0105:93 | declaration · `:8685` (method) |
declaration · `:8289` (method) |
| `#composeWriteTenantWall` | ADR-0123:4 | declaration · `:7843`
(method) | declaration · `:7447` (method) |
| `#computeControlledByParentFilter` | ADR-0055:5, ADR-0055:123,
ADR-0139:246 | declaration · `:8151` (method) | declaration · `:7755`
(method) |
| `#computeDelegatedReadFilter` | ADR-0139:223 | declaration · `:5993`
(method) | declaration · `:5949` (method) |
| `#computeRlsFilter` | ADR-0055:24 | declaration · `:7551` (method) |
declaration · `:7155` (method) |
| `#getReadFilter` | ADR-0096:181, ADR-0096:182, ADR-0096:183,
ADR-0096:199, ADR-0139:222 | declaration · `:5871` (method) |
declaration · `:5895` (method) |
| `#hasPlatformAdminPosture` | ADR-0099:317 | declaration · `:7578`
(method) | declaration · `:7182` (method) |
| `#isPrincipalLessContext` | ADR-0138:152 | declaration · `:561`
(top-level) | declaration · `:585` (top-level) |
| `#manage_metadata` | ADR-0099:68 | literal · `:310` (top-level) |
literal · `:338` (top-level) |
| `#nav_permission_sets` | ADR-0126:406 | literal · `:1748` (`init`) |
literal · `:1772` (`init`) |
| `#rlsFilter` | ADR-0055:24 | declaration · `:4322` (`start`) |
declaration · `:4346` (`start`) |
| `#writeCheckPolicies` | ADR-0066:112 | declaration · `:1215`
(top-level) | declaration · `:1239` (top-level) |
## Readers
Two readers outside the moved code read the moved lines by path, the
system-context page and the #25107 enumeration pin, and both are
re-pointed in this PR (above). No other gate, baseline, docs anchor or
test reads the moved lines by path. Each kind was read:
- **The system-context census.** `node scripts/isystem-census.mjs
--json` gives the same 148 sites at base and head, compared without line
numbers and with the three modules mapped back to `security-plugin.ts`:
0 differences. Five of the head sites are in the modules, the five
`context?.isSystem` reads the page's re-pointed anchors name:
`getReadFilter` (read-filter), `resolveProjectionFieldMask`
(field-readers), and `canReadObject`, `canWriteObject` and `canExport`
(object-admission). The role counts are the same (`read` 153,
`declaration` 38, `key` 438, `other` 9), and so are the 5 non-elevation
reads and the 22 packages. The files holding a site go from 71 to 74 and
the scanned files from 393 to 396, the three modules.
`check-system-context-census` exits 0 at base and head with the same
totals (148 sites, 127 symbols, 140 cited against 140 required, 163
anchors) and files 71 to 74, and its `--self-test` exits 0 at both. With
the page left untouched it would report 8 problems (claim).
- **The tenant-audit census.** No moved body holds an engine write: the
bodies hand `ql` to the delegator resolution and the field universe, and
`canWriteObject` asks it for `getSchema`. `node
scripts/tenant-audit-census.mjs --json` gives the same 238 write call
sites at base and head, compared the same way: 0 differences, none in
the modules, the same totals, the same 174 non-engine subtractions, 0
unresolved and 0 unledgered. Only `scannedSources` moves, 724 to 727:
the three modules. `check-tenant-audit-census` and its `--self-test`
exit 0 at base and head with the same output, and
`tenant-audit-census.mdx` is not touched.
- **The #25107 enumeration pin** is described above. It is part of the
plugin-security suite, which passes at head with the same counts as
base.
- **`packages/core/src/security/row-serving-door-exposure.pin.test.ts`**
classifies every non-test caller of the read admission,
`.canReadObject(`. No moved body calls it: the `canReadObject` body is
the admission's definition, not a call. `security-plugin.ts` still calls
it, from `describePositionGrants` and from the service binding in
`start()`, so its row stays true and its "still calls it" check still
fires. **`operation-private-keys.pin.test.ts`** in the same directory
finds every declaration of `withoutOperationPrivateKeys` and the key
prefix; no moved body names either. Run on their own at head: 8 of 8
pass. Open PRs #25389 and #25410 change the first pin's scan scope (an
extension-scoped prefilter), not what it discriminates on, and touch
none of this cut's files.
- **The spec liveness ledger.** `packages/spec/liveness/permission.json`
cites `security-plugin.ts#canExport` as evidence for `allowExport`. The
anchor resolves on the method, which stays with its docblock, and
`allowExport` is still named in `security-plugin.ts` (in that docblock),
so the key-mention reading does not change. `pnpm --filter
@objectstack/spec check:liveness` gives byte-identical output at base
and head. The entry's prose, `canExport` asking
`checkObjectPermission('export')`, stays true by method: the body is one
call away. `packages/spec/**` is not touched.
- **Other readers that name the file in code.** Besides the two pins
above, the code that names `security-plugin.ts` by path is: the
`check-system-context-census.mjs` row for the file (keyed on `start`,
which this cut does not touch), the spec liveness ledger's file-level
evidence in `field.json`, the file's `scripts/adr-anchors/` entry, and
the citation string in `measure-position-name-fold-census.mjs`. None
reads text inside the moved bodies. The tree-wide scanners that read
every source file are the gates and censuses below, each run at base and
head.
- **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.** Every path-qualified
`plugin-security/src/security-plugin*.ts#…` anchor outside `docs/adr/`
was resolved in its own file at base and at head: 32 at both, 0
unresolved. At head, 23 point into `security-plugin.ts`, the five
re-pointed ones into the new modules (1, 1 and 3), and 4 into the cut 5
and cut 3 modules, as at base. The non-ADR corpora that cuts 2 to 8 read
(spec liveness, platform checklist, `system-context.mdx`, spec
`conversions/registry.ts`) hold 27 anchors over 17 symbols naming
`security-plugin.ts`; 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:
- the behaviour tests of the read scope, the projections, the admissions
and the export axis in this package. Each drives the plugin; none calls
or replaces a moved method directly;
- stand-ins in other packages that offer their own `getReadFilter` or
`canReadObject` (`engine-readable-ids-composition.test.ts`,
`parent-record-read-admission.integration.test.ts` and the REST and
storage tests). They replace a method on their own fake service object,
not on the plugin;
- ADR-0139's prose naming `computeDelegatedReadFilter`, and the
system-context page's row text naming `getReadableFields`,
`getWritableFields`, `canExport`, `canReadObject` and `canWriteObject`,
which stay true by method.
- **Line citations**: no `security-plugin.ts:N` citation in the repo
points into the moved ranges (base `:5871`-`:6913`). The 13 citations
found 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 25f88e8` (advisory): 41
pages. The derivation reads every name in the three added files as
touched, so it reaches pages through names the move only relocated (a
page can be reached by more than one):
- `requiredPermissions` (29 pages), the indexed-access key in the field
readers' `NormalizedRequiredPermissions` alias;
- `permissionSets` (10), a local of the moved bodies;
- `SecurityPlugin` (6), each module's `import type`;
- the moved methods' own names: `getReadFilter` (4), `canReadObject`
(3), `canExport` (2), `canWriteObject` (2) and
`resolveProjectionFieldMask` (1).
Each of those members keeps its name, signature, docblock and behaviour,
so no page goes stale. The one page among them that reads the moved
lines by path, `system-context.mdx`, is re-pointed in this PR (above),
and every path-qualified anchor and line citation into the file was
checked separately (above).
- **Census gates, run on base `25f88e899a` and head and compared:**
- All 104 commands (the 98 derived and the 6 hand-run below) exit 0 at
head. 63 give identical output at base and head, once the worktree path
and timings are masked. That includes `check-adr-symbol-anchors`,
`check-platform-checklist`, `check-scripts-symbol-anchors`,
`check-platform-object-tenancy-census`, `check-tenant-audit-census`,
`check-registry-log-declared`, `check-rest-log-spy-declared`,
`check:cross-package-test-inputs`, `check:driver-memory-census`,
`check:query-options-erasure` and the spec liveness ledger.
- The other 41, each read line by line:
- `check:adr-anchors`, `check-system-context-census` and the two census
`--json` reads move as described above.
- Scanned counts that move by the three modules:
`check-plugin-teardown-shape` 9384 to 9387 sources,
`check-undeclared-dep-imports` 3310 to 3313 files and 2859 to 2862
specifiers (the modules import only relative files and
`@objectstack/spec`, a declared dependency),
`check:doc-formula-expressions` 477 to 480 files, the spec package's
`check:duration-unit-keys` 3315 to 3318, `check:closed-ceremony-text`
4553 to 4556, `check:engine-double-contract` 3619 to 3622 production
files, `check:error-status-conformance` 3407 to 3410,
`check:org-identifier` 3652 to 3655, `check:refd-timer-probe` 9983 to
9986, `check:type-check-coverage` and `check:type-check-debt` 3536 to
3539 walked sources, and `check:skill-identifier-liveness` 139729 to
139733 implementation word tokens.
- Counts that move by four, the modules and the docs build's
`apps/docs/next-env.d.ts` (gitignored, in the head worktree only):
`check-comment-mask-corpus` 9989 to 9993, `check:logger-receiver-detach`
3626 to 3630, `check:page-declaration-shape` 3659 to 3663.
- Tracked-file counts that move by four, the modules and the new anchor
entry: `check-closing-keyword-parity` and `check:gitlink-declared` 11997
to 12001, `check:nul-bytes` 11988 to 11992. `check:pnpm-flag-spellings`
reads 449 files under `scripts/` instead of 448 and leaves 123 data
files unread instead of 122: the entry is JSON.
`check:agent-test-spelling` reads 608 files instead of 607 (the entry,
under `scripts/`) and 87 manifests instead of 86 (a `package.json` the
docs build wrote under `apps/docs/.next`).
- Docs counts that move because the head worktree holds three gitignored
generated pages, `content/docs/protocol-upgrade/{17,18,index}.md`, that
the base worktree does not, as at cuts 6 to 8 (CI's checkout does not
have them): `check-docs-section-name` 418 to 421 files,
`check:corpus-claim-drift` 259 to 262, `check:doc-anchors` and
`check:doc-authoring` 424 to 427, `check:react-page-adapter-contract`
399 to 402, `check-doc-route-spelling --advisory` 745 to 885 literals (0
flagged at both), and `check:vendor-version-stamps` 10378 to 10385 files
with 7 more `better-auth` stamps, all 7 in the generated `18.md`.
`check:doc-authoring` also parses 1432 sources instead of 1429, the
modules.
- `check-position-name-fold-loaders` walks the working tree: 12350 to
12357 files, with the same 8 references (6 test, 2 declared instrument,
0 loader).
- `check:dts-closure` and `check:sourcemap-no-sources-content` sweep
built packages, and the head worktree has more of them built (76 and 72)
than the base worktree (19). Both pass at both.
- `check-issue-citations` judges a change set, so it has nothing to read
at base. At head it judged 56 citations across 4 files, and all resolve.
- `check:merge-driver` differs only in the names of its temporary
directories.
- Five answer non-zero at base only, each a missing or stale build in
the base worktree rather than a verdict: the spec package's `check:docs`
(exit 1, its generated schema is absent until `gen:schema`),
`check:skill-examples`, `check:dual-build-cjs-loads` and `check:i18n`
(exit 3, prerequisite not met), and `check:docs-transcript-drift` (exit
1). The last reads the built `@objectstack/lint` registry, and the base
worktree's build predated #24947's added rule, so it read 50 rules
against the pages' 51. That package's `dist` in the base worktree was
rebuilt later in the same run, and a rerun at base then exits 0 with the
head's output.
- Six more gates keep a roster in a directory this change touches
(`scripts/` or `packages/`), so `dispatch-gates` marks their silence as
no evidence. Each was run at base and head:
`check-published-list-mirrors`, `check:authz-resolver`,
`check:console-injection`, `check:error-code-casing`,
`check:filter-alias-parity` and `check:published-readme-exports`. All
exit 0 at both with identical output, except that
`check:error-code-casing` scans 9370 files instead of 9367, the modules.
## Acceptance notes
- Nothing outside this cut is touched: no other section of the class, no
behaviour, no `docs/adr/**`, no existing `scripts/adr-anchors/` entry,
no census ledger, and no `packages/spec/**`. Outside the package, only
two files change: the five anchor paths on the system-context page, and
the one new anchor entry.
- Serial check, also in the claim: of the 24 open PRs at claim time,
none touched `security-plugin.ts`, the new module names,
`system-context.mdx`, `delegated-organization-membership.test.ts`, the
census scripts, the liveness ledger or the plugin-security anchor entry.
- Since the local runs, `origin/main` gained 13 commits, to
`2581efbcbd`. None touches this cut's files. The closest,
`tenant-audit-census.mdx` and its counts file from #25423 (cut 8 of
#25224), are files this cut reads through its gates and does not edit.
On a scratch merge of this head onto `2581efbcbd`,
`check-adr-symbol-anchors`, `check:adr-anchors`,
`check-platform-checklist`, `check-system-context-census`,
`check-tenant-audit-census` and the `isystem-census` read all exit 0.
There, `check:adr-anchors` counts 81 anchored files and 1978
decision-letter citations, the same as this head, and the system-context
census counts 148 sites across 74 files.
- What is left of the plan's row 7 in `security-plugin.ts` after this
cut: the four projections that stay whole and the nine method shells.
`security-plugin.ts` has 8,852 lines at head, 9,248 at base. Next is row
8 (explain, sharing verdicts, authored row write, baselines), then
`start()`, which is Tier H and goes to the seat before any PR.
## Gates
All gates ran against base `25f88e899a`. The package runs (its test,
typecheck, eslint and build, and the two core pins) ran on the tree of
`0feb79ca4f`, the first commit. The second commit, `b4ced7f87e`, adds
only the anchor entry JSON under `scripts/adr-anchors/`, which none of
those runs reads. Every other gate ran on `b4ced7f87e`.
- `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 `25f88e899a` 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 five touched source and test
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 `25f88e899a` first.
- `row-serving-door-exposure.pin.test.ts` and
`operation-private-keys.pin.test.ts` (`@objectstack/core`): 8 of 8 pass.
- Six commands are not derived for this change set, and the
reconciliation names them: `node scripts/check-adr-symbol-anchors.mjs`,
`check-platform-checklist` (the script and `pnpm
check:platform-checklist`), `pnpm --filter @objectstack/spec
check:liveness` and the two census `--json` reads. Each was run by hand.
`pnpm check:adr-anchors`, `check-scripts-symbol-anchors` (and
`--self-test`), `pnpm check:query-options-erasure` and the spec liveness
check (spelled `run check:liveness`) are derived for it this time.
- `dispatch-gates --commands`: 98 commands. The `--ran` reconciliation
shows 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN. The zero is derived:
all 98 recorded an exit code and none is 3. The six hand-run commands
outside the derivation are recorded beside them.
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-doc-frontmatter.mjs` exit 0
- `node scripts/check-doc-frontmatter.mjs --self-test` exit 0
- `node scripts/check-doc-route-spelling.mjs --advisory` exit 0
- `node scripts/check-doc-route-spelling.mjs --self-test` exit 0
- `node scripts/check-docs-section-name.mjs` exit 0
- `node scripts/check-docs-section-name.mjs --self-test` 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-scripts-symbol-anchors.mjs` exit 0
- `node scripts/check-scripts-symbol-anchors.mjs --self-test` exit 0
- `node scripts/check-section-landing-index.mjs` exit 0
- `node scripts/check-section-landing-index.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/lint run check:doc-formula-expressions`
exit 0
- `pnpm --filter @objectstack/lint run check:doc-security-posture` exit
0
- `pnpm --filter @objectstack/spec run check:docs` exit 0
- `pnpm --filter @objectstack/spec run check:duration-unit-keys` exit 0
- `pnpm --filter @objectstack/spec run check:empty-state` exit 0
- `pnpm --filter @objectstack/spec run check:liveness` exit 0
- `pnpm --filter @objectstack/spec run check:skill-examples` exit 0
- `pnpm --filter @objectstack/spec run check:strictness-ledger` exit 0
- `pnpm --filter @objectstack/spec run check:variant-docs` exit 0
- `pnpm --filter @objectstack/spec run check:yaml-examples` exit 0
- `pnpm check:adr-anchors` exit 0
- `pnpm check:agent-test-spelling` exit 0
- `pnpm check:bash32-floor` exit 0
- `pnpm check:cli-command-ids` exit 0
- `pnpm check:closed-ceremony-text` exit 0
- `pnpm check:corpus-claim-drift` exit 0
- `pnpm check:cross-package-test-inputs` exit 0
- `pnpm check:doc-anchors` exit 0
- `pnpm check:doc-authoring` exit 0
- `pnpm check:docs-audit-scope` exit 0
- `pnpm check:docs-redirects` exit 0
- `pnpm check:docs-single-h1` exit 0
- `pnpm check:docs-spec-enumerations` exit 0
- `pnpm check:docs-transcript-drift` 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:engine-double-contract` exit 0
- `pnpm check:entry-guard` 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:merge-driver` exit 0
- `pnpm check:nul-bytes` exit 0
- `pnpm check:objectql-double-limit` exit 0
- `pnpm check:org-identifier` exit 0
- `pnpm check:page-declaration-shape` exit 0
- `pnpm check:parse-guard` exit 0
- `pnpm check:pnpm-filter-targets` exit 0
- `pnpm check:pnpm-flag-spellings` exit 0
- `pnpm check:published-files` exit 0
- `pnpm check:published-readme-links` exit 0
- `pnpm check:query-options-erasure` exit 0
- `pnpm check:react-page-adapter-contract` exit 0
- `pnpm check:refd-timer-probe` exit 0
- `pnpm check:role-word` exit 0
- `pnpm check:skill-identifier-liveness` 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:type-check-coverage` exit 0
- `pnpm check:type-check-debt` exit 0
- `pnpm check:vendor-version-stamps` exit 0
- `pnpm check:watch-hint-literal` exit 0
- `pnpm check:where-matcher` exit 0
- `node scripts/check-adr-symbol-anchors.mjs` exit 0
- `node scripts/check-platform-checklist.mjs` exit 0
- `pnpm check:platform-checklist` exit 0
- `node scripts/isystem-census.mjs --json` exit 0
- `node scripts/tenant-audit-census.mjs --json` exit 0
- `pnpm --filter @objectstack/spec check:liveness` exit 0
The six roster gates run by hand (above):
- `node scripts/check-published-list-mirrors.mjs` exit 0
- `pnpm check:authz-resolver` exit 0
- `pnpm check:console-injection` exit 0
- `pnpm check:error-code-casing` exit 0
- `pnpm check:filter-alias-parity` exit 0
- `pnpm check:published-readme-exports` 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 ea33919 commit dd124e9
7 files changed
Lines changed: 1065 additions & 545 deletions
File tree
- content/docs/permissions
- packages/plugins/plugin-security/src
- scripts/adr-anchors
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
685 | 685 | | |
686 | 686 | | |
687 | 687 | | |
688 | | - | |
| 688 | + | |
689 | 689 | | |
690 | 690 | | |
691 | 691 | | |
| |||
694 | 694 | | |
695 | 695 | | |
696 | 696 | | |
697 | | - | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
698 | 705 | | |
699 | 706 | | |
700 | 707 | | |
0 commit comments