Repository navigation
Commit df893f6
<!-- 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
⛔ **Tier H on both counts, so this is a human-governed landing.** (1)
Size: 4,369 changed lines (+2,284 / −2,085), over the 3,000-line line.
(2) Path: one path in one anchor of
`docs/adr/0055-master-detail-controlled-by-parent.md` (ruling 6353073966
item 5, the `start()`-local re-anchor), in its own commit. A governed
approver (os-zhuang) approved this PR and enabled auto-merge at 00:20Z
on its first head. That head no longer merged once #25541 landed, so the
cut was re-derived (below). The seat re-measures this head before it
lands.
Cut 12 of the `security-plugin.ts` split is the second part of the
`start()` row. The claim is 6375757728. The seat agreed the order on the
plan (R2 in cut 11, then this cut claimed afresh on the merged `main`)
and cleared it after cut 11 merged as `c15fd9a72b`. This PR moves one
statement of `start()`, the authorization middleware's registration,
into one new module, unedited:
-
`packages/plugins/plugin-security/src/security-plugin-authz-middleware.ts`:
`registerAuthzMiddleware`, the `ql.registerMiddleware(…)` call whose
handler every engine operation passes through, with the two module-level
declarations only the handler reads.
The change is move-only, and the card stays open for R4. The branch is
`claude/project-thread-3fzya4`, the harness-pinned branch for this
session. The branch name names no issue, so the claim comment is the
identity record.
## Re-derived after #25541, and what happened to its hunk
The first head (`1b2378c299`, on `0fab7baf51`) yielded to #25541, as its
claim said, because #25541 inserted lines into the moved statement.
#25541 merged at 23:59Z as `45f996231e`. The cut was then re-derived on
that `main` by the same scripted move, and every measurement below is of
the re-derivation.
- **#25541's hunk now lives in the module.** Its 16 lines are the
EXTERNAL rung's grant stamp in step 2.6 (`if
(isExternalAudienceContext(sc)) { … stampExternalGrant(…) … }`). They
sit inside the moved statement, so they moved with it, unedited, into
`registerAuthzMiddleware`, where they follow the access-depth stash
exactly as they did in `start()`.
- They read `this.collectRLSPolicies`, so the host gains
`collectRLSPolicies`: a late-bound closure, like every other method.
- They import three names that nothing else in `security-plugin.ts`
reads: `isExternalAudienceContext` (`@objectstack/core`), and
`grantRowPolicySpeaks` and `stampExternalGrant`
(`./external-owd-grant-stamp.js`). Those move to the module from the
same specifiers. The second import moves whole.
- #25541's other edits to `security-plugin.ts`, the `humanBaselineFor`
import and the edits inside other methods, are outside the statement and
stay where #25541 put them.
- Two negative controls are on these lines: a token changed inside the
stamp, and `collectRLSPolicies` left out of the stub. Each fails the
proof.
- **Why the history has a merge, not a rewrite.** AGENTS §3 forbids a
force-push here: the open PR carries reviewers and an approval (④), and
the branch is not `claude/issue-*` (①). So the branch keeps its two
first commits, and one merge of `origin/main` (`9c43e19f65`) resolves
the conflict by taking the re-derivation's tree.
- The merge's tree is byte-identical to the scripted re-derivation
merged onto that `main`: tree `8704c395d0`.
- The re-derivation itself is `6feaeb7274` (the move and readers) and
`7e3f354475` (the ADR re-anchor alone) on `45f996231e`. Both are kept as
local refs and were measured there.
- The diff of this head against `origin/main` is the same as that
re-derivation's diff (sha256 of both diffs: `78b9e39a…`).
- The seven commits `main` gained after `45f996231e` touch no file this
cut touches.
Commits:
1. `b7b408e502` moves the statement and the two declarations on the
first base, and re-points the system-context page rows and the census
ledger row.
2. `1b2378c299` is the ADR-0055 re-anchor alone: one path in one anchor,
`docs/adr/0055-master-detail-controlled-by-parent.md:24`.
3. `fa9e98b036` merges `origin/main` and carries #25541's hunk with the
statement, as above.
## How the statement moves
The same form cut 11 used for one statement of `start()`. Base line
numbers here and below are at `45f996231e`.
- The statement is `:2251`-`:4207` (1,957 lines). It closes over three
of `start()`'s locals and nothing else: `ctx`, `ql` and `metadata`. It
uses no `arguments`, `super`, `new.target` or bare `this`. It has no
`return`, `await`, `break` or `continue` at its top level that would act
on `start()`; the handler's own `return`s and `await`s are inside the
arrow and move with it. All of that was measured on the AST.
- So the function takes the three locals as parameters, typed as the
statement saw them there: `PluginContext`, `IObjectQLEngine` and
`IMetadataService`. Its body is the statement, re-indented one level and
otherwise unedited. It returns `void`.
- `start()` keeps the statement's position, the comment above it (`//
Register security middleware`, `:2250`) and the log line after it
(`:4209`). In its place is one BLOCK: the getter reads, one host literal
annotated with the `Pick` of `AuthzMiddlewareHost`, and
`registerAuthzMiddleware.call(host, ctx, ql, metadata);`.
- Registration is still synchronous and in the same place in `start()`'s
order: after the `security` service registration (cut 11's stub), before
the log line and the position-catalog middleware. `start()`'s three
other middlewares (`:4219`, `:4538`, `:4561`) stay.
## What moved
| code | base lines | host members |
|---|---|---|
| the authorization middleware registration (one statement of `start()`)
| `:2251`-`:4207` | 25 methods: `addressedByIdWriteId`,
`addressedPredicateWrite`, `assertControlledByParentWrite`,
`callerCannotReadAddressedRow`, `collectRLSPolicies`,
`composeWriteTenantWall`, `computeControlledByParentFilter`,
`computeForbiddenFieldWrites`, `computeLayeredRlsFilter`,
`computePartialMaskRules`, `computeQueryGuardFieldPerms`,
`computeRlsFilter`, `computeWriteCheckFilter`,
`delegatorOwnWriteFloorOptions`, `extractSingleId`, `getCallerPreImage`,
`getObjectFieldNames`, `maskOperationResult`, `raiseWriteWallRefusal`,
`readableRowsScopeForPredicateWrite`, `resolveCallerPosture`,
`resolveOperationPrincipals`, `resolvePermissionSetsForContext`,
`resolvePreImageFloorDrop`, `writeCheckFieldOptions`; by value:
`engineOperationScope`; getters: `delegatedAdminGate`, `epoch`,
`fieldMasker`, `logger`, `permissionEvaluator`, `ql` |
| `WriteImageCheckSeam` (module interface, with its docblock) |
`:407`-`:442` | none |
| `userFacingDenialMessage` (module function, with its docblock) |
`:1144`-`:1197` | none |
- `WriteImageCheckSeam` gains `export` and nothing else.
`security-plugin.ts` imports it back type-only, because the staying
`WriteWallRowVerdict` docblock links to it (`{@link
WriteImageCheckSeam}`, `:491`). Cut 6's type-only `principalLessDenial`
import is the precedent. `userFacingDenialMessage` moves unchanged.
- Import names that only the moved code reads leave
`security-plugin.ts`, 23 in all. The module imports each from the
specifier `security-plugin.ts` imports it from (the proof checks every
pair):
- `recordNotFoundError` and `isExternalAudienceContext`
(`@objectstack/core`), `PUBLIC_FORM_SERVER_MANAGED_FIELDS`
(`@objectstack/spec/security`), `markFilterSubtreeProvenance`
(`@objectstack/spec/data`), `renderOperationMessage`
(`@objectstack/spec/system`), `resolveEngineUpdateDispatch` and the type
`EngineUpdateDispatchData` (`@objectstack/metadata-core`),
`crossFieldClassRefusalCarriedBy` and `findCrossFieldClassRefusal`
(`@objectstack/formula`);
- `assertFlowSystemIdentityWriteAllowed`, `intersectDelegatedScope`,
`jsonColumnCheckRefusalCarriedBy`, `storedFormCheckJudge`,
`compiledPolicyNameOf`, `intersectTenantLayer0Verdicts`,
`rlsOperationForVerb`, `unresolvedPostureDenialMessage`,
`unresolvedPostureLogLine`, `assertReadableQueryFields`,
`MaskedValueWriteError`, `assertEngineOwnedWriteAllowed`,
`grantRowPolicySpeaks` and `stampExternalGrant`, each from its `./`
module.
- Seven comment lines describe three of those imports, and they move
with them, verbatim: `[#7414]` above `renderOperationMessage`,
`[#19989]` above the `@objectstack/metadata-core` import, and the
`[#8220]` paragraph above the `@objectstack/spec/data` import, which
names `markFilterSubtreeProvenance` alone. The `[#21829]` paragraph
under it describes `MAX_BULK_PER_ROW_HOOK_ROWS`, which stays, and it
stays.
- Two exported functions stay, and the module imports them by value:
`isGuestEnvelope`, which the principal resolution reads too, and
`requiredCapsForOperation`, which `packages/spec/liveness/object.json`
anchors by symbol in that file. Each is read only inside the handler, at
call time, never while either module evaluates.
- `RlsFilterOptions` keeps its one declaration in `security-plugin.ts`.
The module names it through the class's own signature,
`computeLayeredRlsFilter`'s fifth parameter, as cuts 9 and 10 did.
- Staying prose that names a moved import is in backticks
(`assertEngineOwnedWriteAllowed`, `intersectDelegatedScope`,
`resolveEngineUpdateDispatch`, each in a staying docblock), not a link,
so nothing dangles.
- The module header lists the handler's steps. Its step 2.6 entry names
the grant stamp too ("the access depth and an external principal's grant
stamp (2.6)").
## The shape (#23050 shape B, as cuts 1 to 11 applied it)
- `AuthzMiddlewareHost` is new. It types every member as the class
member itself (`SecurityPlugin['m']`). The function types its `this` as
the `Pick` of the host over exactly the members its code 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. So every step still reaches the plugin's method on
the plugin, and a spy or test double set on the plugin is still the one
the handler reaches.
- Fields follow the rule cut 1 set, judged on each field's own
declaration:
- `engineOperationScope` is declared `readonly`, so it goes by value:
the same `AsyncLocalStorage`.
- `delegatedAdminGate`, `epoch`, `fieldMasker`, `logger`,
`permissionEvaluator` and `ql` are not, so they go as getters. Each read
happens when the handler makes it, on every operation, as before: a
gate, epoch source or logger assigned after `start()` is the one the
next operation sees.
- None is function-valued: each is an object whose own methods the
handler calls, or a value it reads. No receiver changes.
- The host declares every field `readonly`, so moved code cannot assign
a member through it. The typecheck passes, so none does.
- Nothing re-exports the module, so the package entry does not move.
## Three readers key on the moved lines, and all three are re-pointed in
this PR
Each was run on the re-derivation with its edit reverted, and each fails
without it.
- `content/docs/permissions/system-context.mdx`, rows 1, 2 and 30. R3
holds `start()`'s last `isSystem` read, the row-1 short-circuit (`:2307`
at base). `check-system-context-census` keys each read by file and
enclosing symbol, so each row's `security-plugin.ts#start` becomes
`security-plugin-authz-middleware.ts#registerAuthzMiddleware`. The row
text and every other anchor stay.
- Reverted: `[file-without-a-row]` for the new module and
`[anchor-is-not-a-read-site]` for `#start`.
- `scripts/check-system-context-census.mjs`, the `NON_READ_ANCHORS` row
that declares `collapsesOntoRead` for row 2's `owner_id` guard. Its
`file`, its `symbol` and the symbol name in its `why` change;
`collapsesOntoRead: true` and its `rowSeams` stay. The ledger's docblock
names the same pair as its worked example of `collapsesOntoRead`, so
that one path changes too.
- Reverted: `[ledger-stale-collapse]`, because the census no longer
finds a read in `security-plugin.ts#start`.
- `docs/adr/0055-master-detail-controlled-by-parent.md:24`, in its own
commit (`1b2378c299`; `7e3f354475` in the re-derivation). The
`rlsFilter` local is R3's (`:4064` at base), so the anchor
`security-plugin.ts#rlsFilter` becomes
`security-plugin-authz-middleware.ts#rlsFilter`. One path changes; the
sentence, the symbol and every other anchor in the record stay.
- Reverted: `check-adr-symbol-anchors` reports `[unresolved-symbol]` for
that anchor.
- Not readers here, measured: the #25107 pin (R3 resolves no delegator:
0 asks, 0 resolutions in the module), the core `canReadObject` caller
pin (R3 has no call), and the position-name fold census (its method
stays). #25541's lines add none of those.
## No new anchor entry, and why
`scripts/adr-anchors/` is not touched. The `security-plugin.ts` entry is
unchanged and stays true. Paragraph by paragraph:
- ADR-0055 §1 is the relation reader
(`security-plugin-cbp-relation.ts`).
- ADR-0106 D7 is the readable-field asymmetry, in cut 9's field-readers
module.
- ADR-0138 D1 names the seed claim, in cut 11's module. R3's guest-owner
branch cites ADR-0138 D1, but the paragraph is about the claim's
re-owning arm.
- ADR-0123 D2 names "step 3.7's no-active-organization refusal" and its
order ahead of the forge guard. The refusal, its message and its
pre-test live in `security-plugin-write-tenant-wall.ts`, which carries
that paragraph in its own entry since cut 5.
- Disclosed: R3's step 3.7 asks that module, takes `wall.preTest` before
`wall.judgePayload(…)`, and runs only for `insert` and `update`. That
call-site order now sits in a module with no entry. An ADR-0123 entry
for it would need an ADR-0123 citation the moved code does not carry,
and this cut plants none. A follow-up can add one if the seat wants it.
## The published `.d.ts` does not change
Measured under the series bar (ruling 6348891160 item 1), on base
`45f996231e` and on the re-derivation. Each was a full `turbo run build`
in its own worktree (both exit 0, 77 tasks).
- Raw sha256 of `dist/index.d.ts` and `dist/index.d.mts` is the same for
all four files, base and head:
`7e6a99f7d59debbac137ff8509e7d92fd5d000c655b412ab0f042a27ad35b380`
(14,722 lines). The raw diff is 0 lines, so no union order moved either.
- Normalised sha256 is the same for all four:
`e22a714da70e9efc2849122d87f64bc2f364aa9a5a3fe73965118b5fd272f5a3`.
- The normaliser, run from `packages/plugins/plugin-security`, 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 control, a real package build rather than an edit of the
emitted file. With `private` removed from the `epoch` field in the head
source, `pnpm build` in the package emits `epoch` as a public member,
and the `.d.ts` pulls in `AuthzInvalidationReason`. That gives raw
`96fda037ff639ae8acc5dd136334fe517dbace0789a4a0abae075cc73b014b94` and
normalised
`89c71232ae367d47666acc8f443ac22528b32a5d272ea91574eb2f4677e294c9`, both
different from the real hashes. So the comparison does see a visibility
change. With the source restored, a rebuild gives `7e6a99f7…` again.
- `security-plugin.ts` gains no export. `registerAuthzMiddleware`,
`AuthzMiddlewareHost` and `WriteImageCheckSeam` are in no published
file, because nothing re-exports the module.
- Export tables: the ESM and CJS bundles each export the same 66 names
at base and at head. The lists were loaded at runtime, with `require`
and `import()` of the built bundles, not parsed. The sorted name list
hashes to
`83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116` for
all four, as at cuts 6 to 11.
- The bundle JS bytes differ, because code moved within them: `index.js`
goes from `e29c0e06` to `77bed321`, and `index.mjs` from `d9401b7c` to
`29e9cece`. The set of external specifiers each bundle loads is the same
at both ends: 17 distinct `require(…)` specifiers in `index.js` and 24
in `index.mjs`.
- Bundle order, disclosed. `security-plugin.ts` no longer imports twelve
modules ahead of its first chunk, so they are now emitted later in
`index.mjs`: `flow-system-identity-write-refusal.ts`,
`explain-engine.ts`, `lifecycle-verb-rls-operation.ts`,
`rls-compiler.ts`, `object-default-row-scope.ts`,
`object-membership-keys.ts`, `tenant-layer.ts`,
`declared-comparison-columns.ts`, `rls-check-stored-form.ts`,
`external-baseline.ts`, `objects/default-permission-sets.ts` and
`predicate-guard.ts`.
- None of the twelve imports `security-plugin.ts`.
- Their top level only declares: constants, sets, `Map`s, `WeakMap`s, a
`WeakSet`, symbols, and `objects/default-permission-sets.ts`'s default
sets, which it builds from its own constants. The imported values their
top level reads all come from `@objectstack/spec` and
`@objectstack/platform-objects`, outside the bundle.
- Both built bundles load at head (`import()` and `require`), so no
top-level read meets an uninitialised binding.
No changeset: `skip-changeset`, per ruling 6348891160 item 4.
## Proof of move-only
Measured on the re-derivation against `45f996231e`. The head tree
differs from it only by `main`'s later commits, none of which touches
these files.
- **The statement, by AST token.** `registerAuthzMiddleware`'s body is
one statement, with the same TypeScript token sequence as the base
statement: 5,471 tokens. Each of its 1,957 lines is the base line with
two leading spaces removed. Multi-line JSDoc text tokens inside it are
re-indented one level, like every other line, so the comparison dedents
those tokens one level, and only those.
- The base statement is the only one in `start()` whose handler names
`chainNext`; the other three `registerMiddleware` calls stay, and none
is left at head that matches it.
- **`start()` outside the statement.** The head text of `start()` equals
the base text exactly, outside the one statement, which is now a 44-line
stub block ending in the `.call`. Its signature, docblock and every
other statement are byte-identical: R1, cut 11's stub, the comment
above, the log line after, and R4. `start()` goes from 2,789 lines to
876.
- **The stub's host literal.** It has 32 members, exactly the `Pick`, in
the same order. Each is in the form its own declaration calls for: 25
methods as `this.m(...a)` closures, `engineOperationScope` by value
(declared `readonly`), and six getters, each over a `read…` local that
reads `this.m`.
- **The host interface.** 32 members, each typed `SecurityPlugin['m']`,
fields `readonly`, and its set equals the `Pick`.
- **The two declarations.** `WriteImageCheckSeam`'s text in the module,
docblock included, is the base text with `export` added, and nothing
else. `userFacingDenialMessage`'s is the base text. Neither is left in
`security-plugin.ts`.
- **Class side.** Every other class member is byte-identical to base:
125 named members plus the constructor. The class has 127 members at
base and at head.
- **Top level of `security-plugin.ts`.** Of the 112 top-level statements
at base other than the class, 93 are byte-identical at head. The other
19 are:
- eight imports that each lose the names listed above (one of them also
loses the `[#8220]` comment paragraph above it);
- nine imports that are gone, two of them with the comment lines above
them (`[#7414]`, `[#19989]`);
- the two declarations, which are gone from the file.
The one new statement is the module's import, with its comment. The
check asserts the exact list of 23 dropped names and 3 added names
(`registerAuthzMiddleware`, the type `AuthzMiddlewareHost`, the type
`WriteImageCheckSeam`), and that no dropped name is still referenced in
the file.
- **The module's imports.** 31 names, each imported from the specifier
`security-plugin.ts` imports it from, with no type import turned into a
value import. `./security-plugin.js` gives only the type
`SecurityPlugin` and the two staying exports. The seven comment lines
that left `security-plugin.ts` above imports are exactly the module's
import comments, in order.
- **Negative controls.** Eleven mutations of one copy each, and every
one fails the check it targets:
- in the moved statement, one denial code changed
(`record_change_not_allowed` to `record_access_denied`);
- in the moved statement, inside #25541's grant stamp, the read stamp's
`'read'` changed to `'write'`;
- in `start()`, the log line after the stub changed;
- in the stub, the `epoch` getter turned into a by-value member;
- in the stub, `collectRLSPolicies` left out;
- in the host interface, `readonly` dropped from `epoch`;
- in `userFacingDenialMessage`, one space added to its signature;
- in `security-plugin.ts`, `recordNotFoundError` left in the core
import;
- in the module, `assertReadableQueryFields` imported from
`./field-masker.js`;
- in the module, the `[#19989]` import comment edited;
- in `security-plugin.ts`, one space added to `init()`'s signature.
- **git's own view.** This is `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change 45f9962` over the two
source files.
- Of the 2,077 deleted lines in `security-plugin.ts`, git sees 2,068 as
moved. The other 9 are the four one-line imports that lose names (core,
`spec/security`, `spec/data`, `rls-compiler`), four specifier lines of
multi-line imports, and the `interface WriteImageCheckSeam {` line,
which gains `export`.
- Of the 2,218 lines of the new module, 2,092 show as moved. The other
126 are among the module's own lines: its header, the regrouped imports,
the `RlsFilterOptions` alias, the host interface, the function's docline
and signature, and the `export interface WriteImageCheckSeam {` line.
- **Size.** 4,369 changed lines (+2,284 / −2,085) over 5 files:
`security-plugin.ts` (+58 / −2,077, from 8,183 to 6,164 lines), the new
module (+2,218), the page (+3 / −3), the census script (+4 / −4) and
ADR-0055 (+1 / −1).
## ADR anchors (ruling 6353073966 item 2)
- `node scripts/check-adr-symbol-anchors.mjs` exits 0 at base
`45f996231e`, on the re-derivation, on today's `main` `9c43e19f65` and
on this head: 2,384 anchors over 142 records, 466 symbol anchors, at
each.
- `pnpm check:adr-anchors` exits 0 at each. Anchored files stay at 84.
- **Decision letters on the anchored surface.** On the merge-base of the
measured re-derivation, `45f996231e`: 1946. On the re-derivation: 1895.
On today's `main` `9c43e19f65`: 1947. On this head `fa9e98b036`: 1896.
The same 51 leave either way. That gate counts `ADR-NNNN Dk` citations
in each anchored file and in each entry's own invariant text. All −51
are R3's letters leaving `security-plugin.ts`, which holds 236 at base
and 185 at head:
- ADR-0090 D10 ×20, ADR-0138 D1 ×6, ADR-0096 D5 ×4, ADR-0066 D2 ×3,
ADR-0058 D4 ×2, ADR-0066 D3 ×2, ADR-0090 D12 ×2, ADR-0105 D5 ×2;
- ×1 each: ADR-0029 D8, ADR-0056 D2, ADR-0057 D1, ADR-0058 D6, ADR-0095
D1, ADR-0111 D3, ADR-0131 D8, ADR-0139 D1, ADR-0139 D5.1, ADR-0139 D6.
- Three letters leave `security-plugin.ts` entirely: ADR-0058 D6,
ADR-0105 D5 and ADR-0131 D8.
- #25541's lines cite a ruling of record, not a decision letter, so they
move no letter. The stub and the new import comment cite none. No letter
is planted and none is invented.
- Every moved citation is still in the tree, in the module its code
moved to. Citations that resolve tree-wide go from 52,280 across 6,688
files to 52,282 across 6,689 (on `45f996231e`), and from 52,285 across
6,691 to 52,287 across 6,692 (on `9c43e19f65`). The new file is the
module. The two added citations are in its header, which names ADR-0056
and ADR-0090 D12 once each, describing two of the handler's steps.
- The anchor map covers every ADR symbol anchor that names
`security-plugin.ts` or the new module, at both ends: 27 citing lines
over 18 symbols, measured with `extractAnchors` and
`symbolSegmentResolution` from `scripts/symbol-anchors.mjs`
(measurement, not a new gate). One anchor changes file: `#rlsFilter`, by
the re-anchor above. No anchor changes class.
- Several anchored members are named inside the stub's host literal and
its `Pick` (`#computeControlledByParentFilter`,
`#assertControlledByParentWrite`, `#computeRlsFilter`,
`#composeWriteTenantWall`, and now `#collectRLSPolicies`). Each still
resolves as `declaration` on its method.
| ADR line | anchor | base | head |
|---|---|---|---|
| ADR-0049:49 | `#SecurityPlugin` | security-plugin.ts · declaration ·
`:1312` (class) | security-plugin.ts · declaration · `:1206` (class) |
| ADR-0055:123 | `#computeControlledByParentFilter` | security-plugin.ts
· declaration · `:7086` (`computeControlledByParentFilter`) |
security-plugin.ts · declaration · `:5067`
(`computeControlledByParentFilter`) |
| ADR-0055:125 | `#assertControlledByParentWrite` | security-plugin.ts ·
declaration · `:7440` (`assertControlledByParentWrite`) |
security-plugin.ts · declaration · `:5421`
(`assertControlledByParentWrite`) |
| ADR-0055:125 | `#assertMasterRowEditable` | security-plugin.ts ·
declaration · `:7566` (`assertMasterRowEditable`) | security-plugin.ts ·
declaration · `:5547` (`assertMasterRowEditable`) |
| ADR-0055:135 | `#CBP_MAX_CHAIN_DEPTH` | security-plugin.ts ·
declaration · `:751` (top-level) | security-plugin.ts · declaration ·
`:700` (top-level) |
| ADR-0055:24 | `#computeRlsFilter` | security-plugin.ts · declaration ·
`:6460` (`computeRlsFilter`) | security-plugin.ts · declaration ·
`:4441` (`computeRlsFilter`) |
| ADR-0055:24 | `#rlsFilter` | security-plugin.ts · declaration ·
`:4064` (`start`) | authz-middleware.ts · declaration · `:2074`
(`registerAuthzMiddleware`) |
| ADR-0055:5 | `#computeControlledByParentFilter` | security-plugin.ts ·
declaration · `:7086` (`computeControlledByParentFilter`) |
security-plugin.ts · declaration · `:5067`
(`computeControlledByParentFilter`) |
| ADR-0055:58 | `#assertControlledByParentWrite` | security-plugin.ts ·
declaration · `:7440` (`assertControlledByParentWrite`) |
security-plugin.ts · declaration · `:5421`
(`assertControlledByParentWrite`) |
| ADR-0066:112 | `#writeCheckPolicies` | security-plugin.ts ·
declaration · `:1248` (`writeCheckPolicies`) | security-plugin.ts ·
declaration · `:1142` (`writeCheckPolicies`) |
| ADR-0096:181 | `#getReadFilter` | security-plugin.ts · declaration ·
`:5195` (`getReadFilter`) | security-plugin.ts · declaration · `:3176`
(`getReadFilter`) |
| ADR-0096:182 | `#getReadFilter` | security-plugin.ts · declaration ·
`:5195` (`getReadFilter`) | security-plugin.ts · declaration · `:3176`
(`getReadFilter`) |
| ADR-0096:183 | `#getReadFilter` | security-plugin.ts · declaration ·
`:5195` (`getReadFilter`) | security-plugin.ts · declaration · `:3176`
(`getReadFilter`) |
| ADR-0096:199 | `#getReadFilter` | security-plugin.ts · declaration ·
`:5195` (`getReadFilter`) | security-plugin.ts · declaration · `:3176`
(`getReadFilter`) |
| ADR-0099:317 | `#hasPlatformAdminPosture` | security-plugin.ts ·
declaration · `:6487` (`hasPlatformAdminPosture`) | security-plugin.ts ·
declaration · `:4468` (`hasPlatformAdminPosture`) |
| ADR-0099:68 | `#manage_metadata` | security-plugin.ts · literal ·
`:377` (top-level) | security-plugin.ts · literal · `:363` (top-level) |
| ADR-0105:93 | `#collectRLSPolicies` | security-plugin.ts · declaration
· `:7620` (`collectRLSPolicies`) | security-plugin.ts · declaration ·
`:5601` (`collectRLSPolicies`) |
| ADR-0123:4 | `#composeWriteTenantWall` | security-plugin.ts ·
declaration · `:6778` (`composeWriteTenantWall`) | security-plugin.ts ·
declaration · `:4759` (`composeWriteTenantWall`) |
| ADR-0126:406 | `#nav_permission_sets` | security-plugin.ts · literal ·
`:1740` (`init`) | security-plugin.ts · literal · `:1634` (`init`) |
| ADR-0138:152 | `#isPrincipalLessContext` | security-plugin.ts ·
declaration · `:624` (`isPrincipalLessContext`) | security-plugin.ts ·
declaration · `:573` (`isPrincipalLessContext`) |
| ADR-0138:153 | `#SecurityPlugin.getMetadataReadableFields` |
security-plugin.ts · declaration · `:5330` (`getMetadataReadableFields`)
| security-plugin.ts · declaration · `:3311`
(`getMetadataReadableFields`) |
| ADR-0138:248 | `#SecurityPlugin` | security-plugin.ts · declaration ·
`:1312` (class) | security-plugin.ts · declaration · `:1206` (class) |
| ADR-0138:265 | `#SecurityPlugin` | security-plugin.ts · declaration ·
`:1312` (class) | security-plugin.ts · declaration · `:1206` (class) |
| ADR-0138:427 |
`#SecurityPlugin.resolvePermissionSetsForContextUnmemoized` |
security-plugin.ts · declaration · `:5924`
(`resolvePermissionSetsForContextUnmemoized`) | security-plugin.ts ·
declaration · `:3905` (`resolvePermissionSetsForContextUnmemoized`) |
| ADR-0139:222 | `#getReadFilter` | security-plugin.ts · declaration ·
`:5195` (`getReadFilter`) | security-plugin.ts · declaration · `:3176`
(`getReadFilter`) |
| ADR-0139:223 | `#computeDelegatedReadFilter` | security-plugin.ts ·
declaration · `:5249` (`computeDelegatedReadFilter`) |
security-plugin.ts · declaration · `:3230`
(`computeDelegatedReadFilter`) |
| ADR-0139:246 | `#computeControlledByParentFilter` | security-plugin.ts
· declaration · `:7086` (`computeControlledByParentFilter`) |
security-plugin.ts · declaration · `:5067`
(`computeControlledByParentFilter`) |
## Censuses, and the `collapsesOntoRead` reading
Each census was run at base `45f996231e` and on the re-derivation, and
compared site by site, with the module path mapped back to
`security-plugin.ts` and `registerAuthzMiddleware` mapped back to
`start`.
- `isystem-census`: 149 sites at both ends, 0 differences. The one moved
read is the row-1 short-circuit, `if (opCtx.context?.isSystem)`:
`security-plugin.ts:2307` (`start`) at base,
`security-plugin-authz-middleware.ts:317` (`registerAuthzMiddleware`) at
head. Scanned files go from 406 to 407, the module.
- `check-system-context-census` exits 0 at both ends with the same
summary: 149 sites in 22 packages across 77 files, 129 symbols, 142
cited against 142 required, 164 anchors, 21 declared non-read, 20 row
references. It exits 0 on today's `main` and on this head too.
- `collapsesOntoRead`, at both ends. The gate holds the ledger row's
declaration to the census in both directions, and both ends agree:
- base: the row names `security-plugin.ts#start` and declares the
overlap; the census finds exactly 1 elevation read in
`security-plugin.ts#start`;
- head: the row names
`security-plugin-authz-middleware.ts#registerAuthzMiddleware` and
declares the overlap; the census finds exactly 1 read there, and 0 in
`security-plugin.ts#start`.
- Without the ledger edit, the head reports `[ledger-stale-collapse]`
(above).
- `tenant-audit-census`: 238 sites at both ends, 0 differences, 176
non-engine calls, 87 engine types, 117 declared objects. The moved
handler holds no site, #25541's lines included.
- `pnpm check:query-options-erasure` exits 0 at both ends; its baseline
key set is unchanged ("no files added").
## Gates
All gates ran against base `45f996231e` and on the re-derivation
(`7e3f354475`), each in its own worktree after a full `turbo run build`
there (both exit 0, 77 tasks).
- `pnpm --filter @objectstack/plugin-security test` (`vitest run`): Test
Files 222 passed (222); Tests 4628 passed, 45 skipped (4673). Base
`45f996231e` gives the same counts.
- `pnpm --filter @objectstack/plugin-security typecheck`: exit 0 at head
and at base. It runs `tsc --noEmit`, the scripts tsconfig, and
`check:test-typecheck`.
- `pnpm --filter @objectstack/plugin-security build`: exit 0. It
produced the `.d.ts` and the bundles measured above.
- `row-serving-door-exposure.pin.test.ts` and
`operation-private-keys.pin.test.ts` (`@objectstack/core`): 8 of 8 pass
at head and at base.
- `dispatch-gates --commands`: 110 commands, the same set as on the
first head. The `--ran` reconciliation shows 110 derived, 110 run and 0
NOT-MEASURED. The zero is derived: all 110 recorded an exit code, and
none is 3.
- Hand-run, because the series reads them every cut:
`check-adr-symbol-anchors`, `pnpm check:adr-anchors`,
`check-platform-checklist` (script and `pnpm check:platform-checklist`),
`check-scripts-symbol-anchors` and its `--self-test`, `pnpm
check:query-options-erasure`, the two census `--json` reads,
`check-system-context-census` and its `--self-test`, and `pnpm --filter
@objectstack/spec check:liveness`.
- Artifact rosters: ten families keep their roster in a directory this
change touches, so `dispatch-gates` marks their silence as no evidence.
All ten were run: `check-published-list-mirrors` and its `--self-test`,
`check:authz-resolver`, `check:console-injection`,
`check:engine-double-contract`, `check:error-code-casing`,
`check:filter-alias-parity`, `check:i18n-stale-fill`,
`check:published-readme-exports` and `check-dts-references --self-test`.
The package typecheck was run with them.
- **Base against head, all 128 commands.** All 128 exit 0 at head and at
base. 102 give identical output at both, once the worktree path and
timings are masked. The other 26 are the same 26 families as on the
first head, each read line by line:
- These move as described above: `check:adr-anchors` (1946 to 1895
letters) and the two census `--json` reads (the module path and symbol
of the one moved read).
- The plugin-security suite's console output is not stable between runs:
log lines from parallel workers, generated ids and timestamps. Its
counts are the same at both ends.
- `check-issue-citations` judges a change set, so it has nothing to read
at base. At head it judged 134 citations across 2 files: 129 resolve, 4
resolve as pull requests and 1 is cross-repo.
- `check:merge-driver` differs only by the name of the temporary
repository it builds.
- The other 20 move by exactly the one new file, and by nothing else:
`check-closing-keyword-parity`, `check:gitlink-declared` and
`check:nul-bytes` (tracked files), `check-comment-mask-corpus`,
`check-plugin-teardown-shape`, `check-position-name-fold-loaders` (the
same 8 references), `check:duration-unit-keys`,
`check:closed-ceremony-text`, `check:error-status-conformance` (the same
511 producer sites), `check:logger-receiver-detach`,
`check:org-identifier`, `check:page-declaration-shape`,
`check:pm-dispatch-gates`, `check:refd-timer-probe`,
`check:vendor-version-stamps`, `check:engine-double-contract` and
`check:error-code-casing`, plus three that also count what the files
hold:
- `check-undeclared-dep-imports`: 3,341 to 3,342 files, and 2,914 to
2,919 `@objectstack/*` specifiers (the module imports from seven package
specifiers; `security-plugin.ts` drops two);
- `check:doc-authoring`: 1,456 to 1,457 parsed sources, and 105,684 to
105,793 strings read. The 109 new strings are the module's import
specifiers net of the imports `security-plugin.ts` drops (+11), the new
import's specifier (+1), the host's indexed-access keys (+32), the
alias's (+1), and the `Pick` key lists in the function signature and the
stub (+32 each);
- `check:skill-identifier-liveness`: 140,292 to 140,294 implementation
word tokens (`registerAuthzMiddleware`, `AuthzMiddlewareHost`), with the
same 457 citations checked.
- On this head's tree, which adds `main`'s seven later commits to the
re-derivation: `check-adr-symbol-anchors`, `check:adr-anchors`,
`check-system-context-census` exit 0 (above). CI runs the rest on this
head.
## Acceptance notes
- Nothing outside this cut is touched: no other section of the class or
of `start()`, no behaviour, no anchor entry and no `packages/spec/**`.
Outside the package, three files change: three anchor paths on the
system-context page, the census ledger row and its docblock example, and
the one ADR-0055 anchor path.
- Serial check: #25541 has landed and its hunk moved with the statement
(above). #25528 landed before it and touches nothing this cut moves.
- After this cut, `start()` holds R1, cut 11's stub, this cut's stub and
R4. What is left of the row is R4, the hooks `start()` registers after
the middleware. R4 assigns `claimTargetAdminUserId`, so it moves as cut
13 with that field as a get/set host (the #25508 precedent), or it is
recorded in the final report.
---
_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 e1e9022 commit df893f6
5 files changed
Lines changed: 2284 additions & 2085 deletions
File tree
- content/docs/permissions
- docs/adr
- packages/plugins/plugin-security/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
| 144 | + | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
0 commit comments