Repository navigation
refactor(plugin-security): move the authorization middleware out of security-plugin.ts (#25223 cut 12) - #25545
Conversation
…ecurity-plugin.ts The one statement of start() that registers the authorization middleware on the ObjectQL engine moves unedited to security-plugin-authz-middleware.ts as registerAuthzMiddleware. It closed over start()'s ctx, ql and metadata and nothing else, so it takes them as parameters; start() keeps the statement's position, the comment above it and the log line after it, and calls it from one block that hands it a host literal of exactly the members it reads. The two module-level declarations only the handler reads move with it: WriteImageCheckSeam, which gains export so that security-plugin.ts can import it back type-only for a docblock link, and userFacingDenialMessage. Import names only the moved code reads leave security-plugin.ts, each whole import with the comment lines that describe it. The two readers that key on the moved read are re-pointed: the system-context page's rows 1, 2 and 30 anchor the new function, and the census ledger row that declares collapsesOntoRead for the owner_id guard names it too. Move-only: no verdict changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
…middleware ADR-0055's context item 1 anchors the read-path RLS injection by the rlsFilter local that start()'s middleware declared. That statement now lives in security-plugin-authz-middleware.ts as registerAuthzMiddleware, so the anchor names that file. One path changes; the sentence, the symbol and every other anchor stay. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
📓 Docs Drift CheckThis PR changes 1 package(s): 51 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 17 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0cb74c35b904ba0a381ac3dafbc230b80db9a883 && git checkout 0cb74c35b904ba0a381ac3dafbc230b80db9a883
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9c43e19f659c40bb065601bda50c7e05250169f2 fa9e98b036db151769c7ffbc022fade3556860b4 && git checkout -B drift-repro 9c43e19f659c40bb065601bda50c7e05250169f2 && git merge --no-ff fa9e98b036db151769c7ffbc022fade3556860b4
node scripts/docs-audit/affected-docs.mjs --json 9c43e19f659c40bb065601bda50c7e05250169f2
|
main gained the EXTERNAL rung's grant stamp inside start()'s authorization middleware (step 2.6), the statement this branch moves, and it edits the @objectstack/core import line this branch also edits. The conflict in security-plugin.ts is resolved by carrying those lines with the statement into security-plugin-authz-middleware.ts: the host gains collectRLSPolicies, which they read, and the three names only they import (isExternalAudienceContext, grantRowPolicySpeaks, stampExternalGrant) move with them. The resulting tree is the one the scripted move gives when it is re-run on the new main. Move-only: no verdict changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
席位核对 · ACCEPT · 2026-10-24T01:33Z
Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 40052496958 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
Queue triage · Queue build 40052496958 went red on one test: Seat re-run of the file, on head
The test's timing is unchanged by the move (about 0.7 s against a 5 s budget), and it passes at both ends. That is a load timeout, not a regression (triage case 2). Generated by Claude Code |
Requested by Sales · project thread
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, thestart()-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.tssplit is the second part of thestart()row. The claim is 6375757728. The seat agreed the order on the plan (R2 in cut 11, then this cut claimed afresh on the mergedmain) and cleared it after cut 11 merged asc15fd9a72b. This PR moves one statement ofstart(), the authorization middleware's registration, into one new module, unedited:packages/plugins/plugin-security/src/security-plugin-authz-middleware.ts:registerAuthzMiddleware, theql.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, on0fab7baf51) yielded to #25541, as its claim said, because #25541 inserted lines into the moved statement. #25541 merged at 23:59Z as45f996231e. The cut was then re-derived on thatmainby the same scripted move, and every measurement below is of the re-derivation.if (isExternalAudienceContext(sc)) { … stampExternalGrant(…) … }). They sit inside the moved statement, so they moved with it, unedited, intoregisterAuthzMiddleware, where they follow the access-depth stash exactly as they did instart().this.collectRLSPolicies, so the host gainscollectRLSPolicies: a late-bound closure, like every other method.security-plugin.tsreads:isExternalAudienceContext(@objectstack/core), andgrantRowPolicySpeaksandstampExternalGrant(./external-owd-grant-stamp.js). Those move to the module from the same specifiers. The second import moves whole.security-plugin.ts, thehumanBaselineForimport and the edits inside other methods, are outside the statement and stay where feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #25541 put them.collectRLSPoliciesleft out of the stub. Each fails the proof.claude/issue-*(①). So the branch keeps its two first commits, and one merge oforigin/main(9c43e19f65) resolves the conflict by taking the re-derivation's tree.main: tree8704c395d0.6feaeb7274(the move and readers) and7e3f354475(the ADR re-anchor alone) on45f996231e. Both are kept as local refs and were measured there.origin/mainis the same as that re-derivation's diff (sha256 of both diffs:78b9e39a…).maingained after45f996231etouch no file this cut touches.Commits:
b7b408e502moves the statement and the two declarations on the first base, and re-points the system-context page rows and the census ledger row.1b2378c299is the ADR-0055 re-anchor alone: one path in one anchor,docs/adr/0055-master-detail-controlled-by-parent.md:24.fa9e98b036mergesorigin/mainand carries feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #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 at45f996231e.:2251-:4207(1,957 lines). It closes over three ofstart()'s locals and nothing else:ctx,qlandmetadata. It uses noarguments,super,new.targetor barethis. It has noreturn,await,breakorcontinueat its top level that would act onstart(); the handler's ownreturns andawaits are inside the arrow and move with it. All of that was measured on the AST.PluginContext,IObjectQLEngineandIMetadataService. Its body is the statement, re-indented one level and otherwise unedited. It returnsvoid.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 thePickofAuthzMiddlewareHost, andregisterAuthzMiddleware.call(host, ctx, ql, metadata);.start()'s order: after thesecurityservice 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
start()):2251-:4207addressedByIdWriteId,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,qlWriteImageCheckSeam(module interface, with its docblock):407-:442userFacingDenialMessage(module function, with its docblock):1144-:1197WriteImageCheckSeamgainsexportand nothing else.security-plugin.tsimports it back type-only, because the stayingWriteWallRowVerdictdocblock links to it ({@link WriteImageCheckSeam},:491). Cut 6's type-onlyprincipalLessDenialimport is the precedent.userFacingDenialMessagemoves unchanged.security-plugin.ts, 23 in all. The module imports each from the specifiersecurity-plugin.tsimports it from (the proof checks every pair):recordNotFoundErrorandisExternalAudienceContext(@objectstack/core),PUBLIC_FORM_SERVER_MANAGED_FIELDS(@objectstack/spec/security),markFilterSubtreeProvenance(@objectstack/spec/data),renderOperationMessage(@objectstack/spec/system),resolveEngineUpdateDispatchand the typeEngineUpdateDispatchData(@objectstack/metadata-core),crossFieldClassRefusalCarriedByandfindCrossFieldClassRefusal(@objectstack/formula);assertFlowSystemIdentityWriteAllowed,intersectDelegatedScope,jsonColumnCheckRefusalCarriedBy,storedFormCheckJudge,compiledPolicyNameOf,intersectTenantLayer0Verdicts,rlsOperationForVerb,unresolvedPostureDenialMessage,unresolvedPostureLogLine,assertReadableQueryFields,MaskedValueWriteError,assertEngineOwnedWriteAllowed,grantRowPolicySpeaksandstampExternalGrant, each from its./module.[#7414]aboverenderOperationMessage,[#19989]above the@objectstack/metadata-coreimport, and the[#8220]paragraph above the@objectstack/spec/dataimport, which namesmarkFilterSubtreeProvenancealone. The[#21829]paragraph under it describesMAX_BULK_PER_ROW_HOOK_ROWS, which stays, and it stays.isGuestEnvelope, which the principal resolution reads too, andrequiredCapsForOperation, whichpackages/spec/liveness/object.jsonanchors by symbol in that file. Each is read only inside the handler, at call time, never while either module evaluates.RlsFilterOptionskeeps its one declaration insecurity-plugin.ts. The module names it through the class's own signature,computeLayeredRlsFilter's fifth parameter, as cuts 9 and 10 did.assertEngineOwnedWriteAllowed,intersectDelegatedScope,resolveEngineUpdateDispatch, each in a staying docblock), not a link, so nothing dangles.The shape (#23050 shape B, as cuts 1 to 11 applied it)
AuthzMiddlewareHostis new. It types every member as the class member itself (SecurityPlugin['m']). The function types itsthisas thePickof the host over exactly the members its code reads, and the stub annotates its literal with the samePick. There is no cast, and no member becomes public or protected.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.engineOperationScopeis declaredreadonly, so it goes by value: the sameAsyncLocalStorage.delegatedAdminGate,epoch,fieldMasker,logger,permissionEvaluatorandqlare 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 afterstart()is the one the next operation sees.readonly, so moved code cannot assign a member through it. The typecheck passes, so none does.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 holdsstart()'s lastisSystemread, the row-1 short-circuit (:2307at base).check-system-context-censuskeys each read by file and enclosing symbol, so each row'ssecurity-plugin.ts#startbecomessecurity-plugin-authz-middleware.ts#registerAuthzMiddleware. The row text and every other anchor stay.[file-without-a-row]for the new module and[anchor-is-not-a-read-site]for#start.scripts/check-system-context-census.mjs, theNON_READ_ANCHORSrow that declarescollapsesOntoReadfor row 2'sowner_idguard. Itsfile, itssymboland the symbol name in itswhychange;collapsesOntoRead: trueand itsrowSeamsstay. The ledger's docblock names the same pair as its worked example ofcollapsesOntoRead, so that one path changes too.[ledger-stale-collapse], because the census no longer finds a read insecurity-plugin.ts#start.docs/adr/0055-master-detail-controlled-by-parent.md:24, in its own commit (1b2378c299;7e3f354475in the re-derivation). TherlsFilterlocal is R3's (:4064at base), so the anchorsecurity-plugin.ts#rlsFilterbecomessecurity-plugin-authz-middleware.ts#rlsFilter. One path changes; the sentence, the symbol and every other anchor in the record stay.check-adr-symbol-anchorsreports[unresolved-symbol]for that anchor.canReadObjectcaller pin (R3 has no call), and the position-name fold census (its method stays). feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #25541's lines add none of those.No new anchor entry, and why
scripts/adr-anchors/is not touched. Thesecurity-plugin.tsentry is unchanged and stays true. Paragraph by paragraph:security-plugin-cbp-relation.ts).security-plugin-write-tenant-wall.ts, which carries that paragraph in its own entry since cut 5.wall.preTestbeforewall.judgePayload(…), and runs only forinsertandupdate. 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.tsdoes not changeMeasured under the series bar (ruling 6348891160 item 1), on base
45f996231eand on the re-derivation. Each was a fullturbo run buildin its own worktree (both exit 0, 77 tasks).dist/index.d.tsanddist/index.d.mtsis 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.e22a714da70e9efc2849122d87f64bc2f364aa9a5a3fe73965118b5fd272f5a3.packages/plugins/plugin-security, sorts the members of each string-literal union and changes nothing else:privateremoved from theepochfield in the head source,pnpm buildin the package emitsepochas a public member, and the.d.tspulls inAuthzInvalidationReason. That gives raw96fda037ff639ae8acc5dd136334fe517dbace0789a4a0abae075cc73b014b94and normalised89c71232ae367d47666acc8f443ac22528b32a5d272ea91574eb2f4677e294c9, both different from the real hashes. So the comparison does see a visibility change. With the source restored, a rebuild gives7e6a99f7…again.security-plugin.tsgains no export.registerAuthzMiddleware,AuthzMiddlewareHostandWriteImageCheckSeamare in no published file, because nothing re-exports the module.requireandimport()of the built bundles, not parsed. The sorted name list hashes to83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116for all four, as at cuts 6 to 11.index.jsgoes frome29c0e06to77bed321, andindex.mjsfromd9401b7cto29e9cece. The set of external specifiers each bundle loads is the same at both ends: 17 distinctrequire(…)specifiers inindex.jsand 24 inindex.mjs.security-plugin.tsno longer imports twelve modules ahead of its first chunk, so they are now emitted later inindex.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.tsandpredicate-guard.ts.security-plugin.ts.Maps,WeakMaps, aWeakSet, symbols, andobjects/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/specand@objectstack/platform-objects, outside the bundle.import()andrequire), 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 bymain'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.start()whose handler nameschainNext; the other threeregisterMiddlewarecalls stay, and none is left at head that matches it.start()outside the statement. The head text ofstart()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 asthis.m(...a)closures,engineOperationScopeby value (declaredreadonly), and six getters, each over aread…local that readsthis.m.The host interface. 32 members, each typed
SecurityPlugin['m'], fieldsreadonly, and its set equals thePick.The two declarations.
WriteImageCheckSeam's text in the module, docblock included, is the base text withexportadded, and nothing else.userFacingDenialMessage's is the base text. Neither is left insecurity-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:[#8220]comment paragraph above it);[#7414],[#19989]);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 typeAuthzMiddlewareHost, the typeWriteImageCheckSeam), and that no dropped name is still referenced in the file.The module's imports. 31 names, each imported from the specifier
security-plugin.tsimports it from, with no type import turned into a value import../security-plugin.jsgives only the typeSecurityPluginand the two staying exports. The seven comment lines that leftsecurity-plugin.tsabove 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:
record_change_not_allowedtorecord_access_denied);'read'changed to'write';start(), the log line after the stub changed;epochgetter turned into a by-value member;collectRLSPoliciesleft out;readonlydropped fromepoch;userFacingDenialMessage, one space added to its signature;security-plugin.ts,recordNotFoundErrorleft in the core import;assertReadableQueryFieldsimported from./field-masker.js;[#19989]import comment edited;security-plugin.ts, one space added toinit()'s signature.git's own view. This is
git diff --color-moved=plain --color-moved-ws=allow-indentation-change 45f996231eover the two source files.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 theinterface WriteImageCheckSeam {line, which gainsexport.RlsFilterOptionsalias, the host interface, the function's docline and signature, and theexport 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.mjsexits 0 at base45f996231e, on the re-derivation, on today'smain9c43e19f65and on this head: 2,384 anchors over 142 records, 466 symbol anchors, at each.pnpm check:adr-anchorsexits 0 at each. Anchored files stay at 84.45f996231e: 1946. On the re-derivation: 1895. On today'smain9c43e19f65: 1947. On this headfa9e98b036: 1896. The same 51 leave either way. That gate countsADR-NNNN Dkcitations in each anchored file and in each entry's own invariant text. All −51 are R3's letters leavingsecurity-plugin.ts, which holds 236 at base and 185 at head:security-plugin.tsentirely: ADR-0058 D6, ADR-0105 D5 and ADR-0131 D8.45f996231e), and from 52,285 across 6,691 to 52,287 across 6,692 (on9c43e19f65). 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.security-plugin.tsor the new module, at both ends: 27 citing lines over 18 symbols, measured withextractAnchorsandsymbolSegmentResolutionfromscripts/symbol-anchors.mjs(measurement, not a new gate). One anchor changes file:#rlsFilter, by the re-anchor above. No anchor changes class.Pick(#computeControlledByParentFilter,#assertControlledByParentWrite,#computeRlsFilter,#composeWriteTenantWall, and now#collectRLSPolicies). Each still resolves asdeclarationon its method.#SecurityPlugin:1312(class):1206(class)#computeControlledByParentFilter:7086(computeControlledByParentFilter):5067(computeControlledByParentFilter)#assertControlledByParentWrite:7440(assertControlledByParentWrite):5421(assertControlledByParentWrite)#assertMasterRowEditable:7566(assertMasterRowEditable):5547(assertMasterRowEditable)#CBP_MAX_CHAIN_DEPTH:751(top-level):700(top-level)#computeRlsFilter:6460(computeRlsFilter):4441(computeRlsFilter)#rlsFilter:4064(start):2074(registerAuthzMiddleware)#computeControlledByParentFilter:7086(computeControlledByParentFilter):5067(computeControlledByParentFilter)#assertControlledByParentWrite:7440(assertControlledByParentWrite):5421(assertControlledByParentWrite)#writeCheckPolicies:1248(writeCheckPolicies):1142(writeCheckPolicies)#getReadFilter:5195(getReadFilter):3176(getReadFilter)#getReadFilter:5195(getReadFilter):3176(getReadFilter)#getReadFilter:5195(getReadFilter):3176(getReadFilter)#getReadFilter:5195(getReadFilter):3176(getReadFilter)#hasPlatformAdminPosture:6487(hasPlatformAdminPosture):4468(hasPlatformAdminPosture)#manage_metadata:377(top-level):363(top-level)#collectRLSPolicies:7620(collectRLSPolicies):5601(collectRLSPolicies)#composeWriteTenantWall:6778(composeWriteTenantWall):4759(composeWriteTenantWall)#nav_permission_sets:1740(init):1634(init)#isPrincipalLessContext:624(isPrincipalLessContext):573(isPrincipalLessContext)#SecurityPlugin.getMetadataReadableFields:5330(getMetadataReadableFields):3311(getMetadataReadableFields)#SecurityPlugin:1312(class):1206(class)#SecurityPlugin:1312(class):1206(class)#SecurityPlugin.resolvePermissionSetsForContextUnmemoized:5924(resolvePermissionSetsForContextUnmemoized):3905(resolvePermissionSetsForContextUnmemoized)#getReadFilter:5195(getReadFilter):3176(getReadFilter)#computeDelegatedReadFilter:5249(computeDelegatedReadFilter):3230(computeDelegatedReadFilter)#computeControlledByParentFilter:7086(computeControlledByParentFilter):5067(computeControlledByParentFilter)Censuses, and the
collapsesOntoReadreadingEach census was run at base
45f996231eand on the re-derivation, and compared site by site, with the module path mapped back tosecurity-plugin.tsandregisterAuthzMiddlewaremapped back tostart.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-censusexits 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'smainand 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:security-plugin.ts#startand declares the overlap; the census finds exactly 1 elevation read insecurity-plugin.ts#start;security-plugin-authz-middleware.ts#registerAuthzMiddlewareand declares the overlap; the census finds exactly 1 read there, and 0 insecurity-plugin.ts#start.[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, feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #25541's lines included.pnpm check:query-options-erasureexits 0 at both ends; its baseline key set is unchanged ("no files added").Gates
All gates ran against base
45f996231eand on the re-derivation (7e3f354475), each in its own worktree after a fullturbo run buildthere (both exit 0, 77 tasks).pnpm --filter @objectstack/plugin-security test(vitest run): Test Files 222 passed (222); Tests 4628 passed, 45 skipped (4673). Base45f996231egives the same counts.pnpm --filter @objectstack/plugin-security typecheck: exit 0 at head and at base. It runstsc --noEmit, the scripts tsconfig, andcheck:test-typecheck.pnpm --filter @objectstack/plugin-security build: exit 0. It produced the.d.tsand the bundles measured above.row-serving-door-exposure.pin.test.tsandoperation-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--ranreconciliation shows 110 derived, 110 run and 0 NOT-MEASURED. The zero is derived: all 110 recorded an exit code, and none is 3.check-adr-symbol-anchors,pnpm check:adr-anchors,check-platform-checklist(script andpnpm check:platform-checklist),check-scripts-symbol-anchorsand its--self-test,pnpm check:query-options-erasure, the two census--jsonreads,check-system-context-censusand its--self-test, andpnpm --filter @objectstack/spec check:liveness.dispatch-gatesmarks their silence as no evidence. All ten were run:check-published-list-mirrorsand 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-exportsandcheck-dts-references --self-test. The package typecheck was run with them.check:adr-anchors(1946 to 1895 letters) and the two census--jsonreads (the module path and symbol of the one moved read).check-issue-citationsjudges 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-driverdiffers only by the name of the temporary repository it builds.check-closing-keyword-parity,check:gitlink-declaredandcheck: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-contractandcheck: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.tsdrops 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 importssecurity-plugin.tsdrops (+11), the new import's specifier (+1), the host's indexed-access keys (+32), the alias's (+1), and thePickkey 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.main's seven later commits to the re-derivation:check-adr-symbol-anchors,check:adr-anchors,check-system-context-censusexit 0 (above). CI runs the rest on this head.Acceptance notes
start(), no behaviour, no anchor entry and nopackages/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.start()holds R1, cut 11's stub, this cut's stub and R4. What is left of the row is R4, the hooksstart()registers after the middleware. R4 assignsclaimTargetAdminUserId, so it moves as cut 13 with that field as a get/set host (the refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508 precedent), or it is recorded in the final report.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa