Repository navigation
refactor(plugin-security): move what start() registers after the authorization middleware out of security-plugin.ts (#25223 cut 13) - #25599
Conversation
…orization middleware out of security-plugin.ts The run of statements that closes start() moves unedited to security-plugin-start-hooks.ts as registerStartHooks: the position catalog and grant-holder membership refusals, the permission-set name hooks, the platform bootstrap on kernel:ready, the boot reports on kernel:bootstrapped, the bootstrap replay and organization_admin reconcile middlewares, and the organization_admin backfill. It closed over start()'s ctx and ql and nothing else, so it takes them as parameters; start() calls it from one block at the same position, the last statement, which hands it a host literal of exactly the members it reads. claimTargetAdminUserId, the one member the run writes, goes as a get/set pair, so the bootstrap's write still lands on the plugin, where the seed-ownership claim reads it. Import names only the run reads leave security-plugin.ts. readSeededRecordIds stays there type-only, because the seed claim's docblock links to it, and the seed-claim import comment no longer says this file imports the two seed-settlement readers. Move-only: no verdict changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
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 8ce0058d41b05c72411aa3c08af0c5dcac58803e && git checkout 8ce0058d41b05c72411aa3c08af0c5dcac58803e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a57643d3de7067cb7d292b4802773bdf938fd2cb 2d8c53ca970f8c05cf98002d88d0fa4b82b3ed5d && git checkout -B drift-repro a57643d3de7067cb7d292b4802773bdf938fd2cb && git merge --no-ff 2d8c53ca970f8c05cf98002d88d0fa4b82b3ed5d
node scripts/docs-audit/affected-docs.mjs --json a57643d3de7067cb7d292b4802773bdf938fd2cb
|
席位核对 · ACCEPT · 2026-10-24T04:34Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25223
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6353073966
Cut 13 of the
security-plugin.tssplit is the third and last part of thestart()row: R4, whatstart()registers after the authorization middleware. The claim is 6379358210. The seat asked for it in cut 12's landing note (6379109594), as a get/set host pair forclaimTargetAdminUserIdin the #25508 form. This PR moves one run ofstart()'s statements into one new module, unedited:packages/plugins/plugin-security/src/security-plugin-start-hooks.ts:registerStartHooks, the 16 statements that closestart().Not Tier H:
check-governed-merges --teston the file list finds 0 of 2 paths in the register, and the size is 999 changed lines (+551 / −448). The landing is the ordinary queue landing after the seat's ACCEPT.This is the last movable row of
start(). After it,start()holds R1 (the setup, which assigns 10 of the plugin's members and stays) and the three stub blocks of cuts 11, 12 and 13. (The plan and the claim said R1 writes 11. Measured on the AST,start()assigned 11 members, and the 11th,claimTargetAdminUserId, was R4's, which this cut moves.)init()stays, because ADR-0126:406#nav_permission_setsresolves only on a literal there. So this cut's report on #25223 is also the series' final report.The change is move-only. The branch is
claude/project-thread-3fzya4, the harness-pinned branch for this session, restarted fromorigin/mainate073455929(which contains cut 12's mergedf893f6d76). The branch name names no issue, so the claim comment is the identity record.How the run moves
Base line numbers here and below are at
e073455929, wheresecurity-plugin.tsis 6,164 lines andstart()is:1744-:2619(48 statements).start()'s statements 33 to 48,:2192-:2612: 421 lines, counted from the first statement's leading comment (// [ADR-0057 D4 / ADR-0112] …,:2192). It endsstart(). In order, it registers:sys_user_positioncatalog refusal middleware and the grant-holder membership refusal;runBootstrap, with itskernel:readyregistration;kernel:bootstrapped;sys_membermiddleware that reconcilesorganization_admingrants;runOrgAdminBackfill, with itskernel:readyregistration.start()'s locals and nothing else:ctxandql. It uses noarguments,super,new.targetor barethis, and nothisinside a non-arrow function. It has noreturn,await,breakorcontinueat its top level that would act onstart(); the closures' ownreturns andawaits move with them. All of that was measured on the AST.bootstrapRanOnce,readSeedSettlement,everyoneMutationWired,catalogEchoWired,catalogEchoBooted,managedDeniesApplied,runBootstrap,runOrgAdminBackfill) move with it. Nothing after the run read them, because the run endedstart().PluginContextandIObjectQLEngine. Its body is the run, re-indented one level and otherwise unedited, with every comment above and between its statements. It returnsvoid.start()has one BLOCK: the read and write locals, one host literal annotated with thePickofStartHooksHost, andregisterStartHooks.call(host, ctx, ql);. It sits right after the middleware's log line (:2190), as the run did, and is nowstart()'s last statement. The comment that closesstart()(the per-organization seed DATA replay note) stays after it. Every registration is still synchronous and in the same order.What moved
start()):2192-:2612declareEveryone,deploymentAcceptance,deploymentOrgScopingEntitlement; by value:bootstrapPermissionSets; getters:metadata,tenancyPosture; get/set pair:claimTargetAdminUserIdsecurity-plugin.ts, 26 in all. The module imports each from the specifiersecurity-plugin.tsimports it from, and type imports stay type imports (the proof checks every pair). Thirteen imports leave whole, and five lose names:POSITION_ASSIGNMENT_OBJECTandcreatePositionCatalogRefusal,registerGrantHolderMembershipRefusal,registerGrantPermissionSetNameHooks,reportCatalogReferences,reportAccessUpgradeand the typeAccessUpgradeNotifier,reportUpgradeImpact,SETTINGS_SERVICEand the typeSettingsManifestReader,applyManagedWriteDenies,reportPackagedPermissionSetOverlays,registerObjectPostureGate,registerPackagedPermissionSetLockGate,registerSecurityCatalogEchoandechoStoredSecurityCatalogOverlays,reconcileAudienceBindingSuggestions,cleanupPackagePermissions,readDeclared,readSeedSettlementSnapshot,bootstrapPlatformAdminandshouldReplayBootstrapFor, andbackfillOrgAdminGrants,extractMemberPairsandreconcileOrgAdminGrant, each from its./module;SeedSettlementSnapshot(@objectstack/spec/contracts).security-plugin.ts, because code that stays reads them too. The module imports them from the same specifiers:PluginContextandcreateSecurityCatalogReader(@objectstack/core),postureEnforcesWall(@objectstack/spec/security),SHARING_RULES_SERVICEand the typeDeclaredSharingRuleReader(./access-upgrade-report.js), andIObjectQLEngine.readSeededRecordIds(cut 11's./security-plugin-seed-claim.js) is called only by the run, but the staying docblock ofclaimSeedOwnershipOnSettlelinks to it ({@link readSeededRecordIds},:1728). It stays in that import, type-only, so the link still resolves. A type-only import emits nothing. Cuts 6 (principalLessDenial) and 12 (WriteImageCheckSeam) are the precedent.seed-settlementreaders it shares with the promotion-time pass instart()". That pass now runs in the new module. So the comment becomes 6 lines that say where the readers are imported now and whyreadSeededRecordIdsstays type-only. No other comment insecurity-plugin.tschanges, and every comment in the run moves verbatim.{@link X}insecurity-plugin.tswhose head names nothing the file declares or imports gives the same list at base and at head.The shape (#23050 shape B, as cuts 1 to 12 applied it, plus #25508's get/set pair)
StartHooksHostis new. It types every member as the class member itself (SecurityPlugin['m']). The function types itsthisas thePickof the host over exactly the members the run names, 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 hook still reaches the plugin's method on the plugin, and a spy or test double set on the plugin is still the one the hooks reach.bootstrapPermissionSetsis declaredreadonly, so it goes by value. It is the same array, whose setsapplyManagedWriteDeniesextends in place, as before.metadataandtenancyPostureare not, so they go as getters, read when the hooks run.claimTargetAdminUserIdis the one member the run WRITES:runBootstraprecords the platform admin it found or promoted (if (report?.adminUserId) this.claimTargetAdminUserId = report.adminUserId;,:2281). It goes as a get/set pair over two locals in the stub,readClaimTargetAdminUserIdandwriteClaimTargetAdminUserId, which read and assignthis.claimTargetAdminUserIdon the plugin. This is the form [maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 cut 10 (PR refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508,7d3c0139fb) used forrouteOwnershipinauth-manager-request-routing.ts. The write lands on the plugin, where cut 11's seed claim reads it through its own getter, so the claim still sees the admin the bootstrap recorded.readonly, andclaimTargetAdminUserIdwithout it. The typecheck passes, so the run assigns no other member through the host.No reader keys on the moved lines
Each was measured on this head, not recalled:
isSystemread and no tenant-audit site.isystem-censusgives 149 sites andtenant-audit-census238 at both ends, with 0 differences and 0 sites in the new module.check-system-context-censusprints the same summary at both ends, socontent/docs/permissions/system-context.mdxand the census ledger are untouched.canReadObjectcall, so the core row-serving pin is untouched. It resolves no delegator, so the plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 pin is untouched.security-plugin.ts#declareEveryone(access-security.json:2032) resolves on the method, which stays.security-plugin.tsby path names R4's content.No new anchor entry, and one disclosure about the existing one
scripts/adr-anchors/is not touched. Paragraph by paragraph, thesecurity-plugin.tsentry:security-plugin-cbp-relation.ts). ADR-0106 D7 is the readable-field asymmetry (cut 9's module). ADR-0123 D2 is the write tenant wall's refusal (cut 5's module). R4 touches none of them.app:seeded(claimSeedOwnershipOnSettle) and at the first promotion" re-owns a NULL-owner row only when the seed reported landing on it (readSeededRecordIds).runBootstrap'sseededRecordIds: readSeededRecordIds(ctx), moves into the new module with the run.security-plugin.ts: the claim method and thereadSeededRecordIdsimport stay there, andstart()still registers the first-promotion pass, through the stub.The published
.d.tsdoes not changeMeasured under the series bar (ruling 6348891160 item 1), on base
e073455929and on this head. Each was a fullturbo run buildin its own worktree (both exit 0, 77 tasks;plugin-securitywas a cache miss at both ends, so both were really built).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 gives raw96fda037…for both files, different from the real hash. So the comparison does see a visibility change. With the source restored (byte-compared to the committed file), a rebuild gives7e6a99f7…again.security-plugin.tsgains no export.registerStartHooksandStartHooksHostare in no published file, because nothing re-exports the module.requireandimport()of the built bundles, not parsed. The sorted name list hashes todf56a808for all four.index.jsgoes from77bed321to72283d9d, andindex.mjsfrom29e9cecetoaf35c900. The external specifiers each bundle loads are the same set at both ends: 17 inindex.jsand 20 inindex.mjs.index.mjs, becausesecurity-plugin.tsno longer imports them first.security-plugin.tswhole:position-catalog-refusal,grant-holder-membership-refusal,catalog-reference-report,upgrade-impact-report,managed-object-write-denies,packaged-permission-set-overlay-detection,object-posture-gate,packaged-permission-set-lock-gate,security-catalog-echoandcleanup-package-permissions. The other three are modules those ten import:federated-phantom-anchors,packaged-permission-set-lockandobjects/sys-user-position.object.security-plugin.ts.Set, aWeakMap, andobjects/sys-user-position.object.ts'sObjectSchema.create(…). That is a validating constructor from@objectstack/spec, outside the bundle, and it registers nothing.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 this head against
e073455929, with a scratch script that parses both files with the TypeScript compiler API.registerStartHooks's body holds 16 statements, with the same TypeScript token sequence as the base run: 1,610 tokens. The body between its braces is the base text from the run's first comment line to its last statement's end, 421 lines, each with two leading spaces removed. That text check is what covers the comments between the statements.start()outside the run. The head text ofstart()equals the base text exactly, outside the run, which is now an 18-line stub block ending in the.call. Its signature, docblock, the 32 statements before the run and the comment after it are byte-identical.start()goes from 876 lines to 473.Pick, in the same order. Each is in the form its own declaration calls for: 3 methods asthis.m(...a)closures,bootstrapPermissionSetsby value (declaredreadonly), two getters, each over aread…local that readsthis.m, andclaimTargetAdminUserIdas aget/setpair overreadClaimTargetAdminUserIdandwriteClaimTargetAdminUserId. The check also derives the members the base run writes, from the AST, and requires exactly those to be the get/set pairs.SecurityPlugin['m']. The fields the run only reads arereadonly; the one it writes is not. Its set equals thePick, and thePickequals the set ofthis.mthe base run names.security-plugin.ts. Of the 102 top-level statements at base other than the class, 84 are byte-identical at head. The other 18 are imports: 13 are gone and 5 lose names (the seed-claim one also changesreadSeededRecordIdstotype readSeededRecordIds). The one new statement is the module's import. The check asserts the exact list of 27 dropped names and 3 added names (registerStartHooks, the typeStartHooksHost, the typereadSeededRecordIds). It also asserts that no dropped name is referenced in code, and thatreadSeededRecordIdsappears exactly three times: the type import, the import comment and the one{@link}.security-plugin.tsloses cut 11's 4-line seed-claim comment and gains its 6-line rewrite plus the 3-line comment over the new import, and no other. The module's imports carry no comment.security-plugin.tsimports it from, with no type import turned into a value import../security-plugin.jsgives only the typeSecurityPlugin. The module's top level is its 21 imports,StartHooksHostandregisterStartHooks, and nothing else.{ object: POSITION_ASSIGNMENT_OBJECT }changed to a string literal;[#22801 → A]);start(), the log line before the stub changed;set claimTargetAdminUserIdline removed;v ?? undefined;readonlyadded toclaimTargetAdminUserId;readonlydropped frommetadata;Pick,bootstrapPermissionSetsleft out;security-plugin.ts,bootstrapPlatformAdminimported again;security-plugin.ts,readSeededRecordIdsimported as a value again;security-plugin.ts, "the claim's docblock" changed to "its docblock" in the rewritten cut 11 comment;security-plugin.ts, one space added toinit()'s signature;readDeclaredimported from another specifier;constadded.git diff --color-moved=plain --color-moved-ws=allow-indentation-change e073455929over the two source files.security-plugin.ts, git sees 439 as moved. The other 9 are cut 11's 4-line comment (rewritten, above) and five specifier lines of multi-line imports that lose a name.security-plugin.ts(+28 / −448, from 6,164 to 5,744 lines) and the new module (+523).ADR anchors (ruling 6353073966 item 2)
node scripts/check-adr-symbol-anchors.mjsexits 0 at basee073455929, at this head, at today'smain(a57643d3de) and on this head merged into it: 2,384 anchors over 142 records, 466 symbol anchors, at each.pnpm check:adr-anchorsexits 0 at both. Anchored files stay at 84.e073455929: 1888. On this head: 1877. On today'smain,a57643d3de: 1883, because refactor(metadata-protocol): move the record reads out of protocol.ts into protocol-record-reads.ts (#25467 cut 4) #25589, feat(sharing): a share or sharing rule on a public object reaches an external member of the record's organization #25591 and fix(plugin-approvals)!: refuse a reassign target that resolves to no one #25592 landed after the merge-base (none touches this package). On this head merged into today'smain(a cleangit merge-tree): 1872. So the drop is −11 on either base. That gate countsADR-NNNN Dkcitations in each anchored file and in each entry's own invariant text. All −11 are R4's letters leavingsecurity-plugin.ts, which holds 185 at base and 174 at head:security-plugin.tsentirely: ADR-0057 D4, ADR-0086 D1, ADR-0090 D11, ADR-0131 D10 and ADR-0131 D4.security-plugin.tsor the new module, at both ends: 26 citing lines over 17 symbols, measured withextractAnchorsandsymbolSegmentResolutionfromscripts/symbol-anchors.mjs(measurement, not a new gate). No anchor changes file or class, and none resolves inside R4.#SecurityPlugin:1206(class):1189(class)#computeControlledByParentFilter:5067(computeControlledByParentFilter):4647(computeControlledByParentFilter)#assertControlledByParentWrite:5421(assertControlledByParentWrite):5001(assertControlledByParentWrite)#assertMasterRowEditable:5547(assertMasterRowEditable):5127(assertMasterRowEditable)#CBP_MAX_CHAIN_DEPTH:700(top-level):683(top-level)#computeRlsFilter:4441(computeRlsFilter):4021(computeRlsFilter)#computeControlledByParentFilter:5067(computeControlledByParentFilter):4647(computeControlledByParentFilter)#assertControlledByParentWrite:5421(assertControlledByParentWrite):5001(assertControlledByParentWrite)#writeCheckPolicies:1142(writeCheckPolicies):1125(writeCheckPolicies)#getReadFilter:3176(getReadFilter):2756(getReadFilter)#getReadFilter:3176(getReadFilter):2756(getReadFilter)#getReadFilter:3176(getReadFilter):2756(getReadFilter)#getReadFilter:3176(getReadFilter):2756(getReadFilter)#hasPlatformAdminPosture:4468(hasPlatformAdminPosture):4048(hasPlatformAdminPosture)#manage_metadata:363(top-level):346(top-level)#collectRLSPolicies:5601(collectRLSPolicies):5181(collectRLSPolicies)#composeWriteTenantWall:4759(composeWriteTenantWall):4339(composeWriteTenantWall)#nav_permission_sets:1634(init):1617(init)#isPrincipalLessContext:573(isPrincipalLessContext):556(isPrincipalLessContext)#SecurityPlugin.getMetadataReadableFields:3311(getMetadataReadableFields):2891(getMetadataReadableFields)#SecurityPlugin:1206(class):1189(class)#SecurityPlugin:1206(class):1189(class)#SecurityPlugin.resolvePermissionSetsForContextUnmemoized:3905(resolvePermissionSetsForContextUnmemoized):3485(resolvePermissionSetsForContextUnmemoized)#getReadFilter:3176(getReadFilter):2756(getReadFilter)#computeDelegatedReadFilter:3230(computeDelegatedReadFilter):2810(computeDelegatedReadFilter)#computeControlledByParentFilter:5067(computeControlledByParentFilter):4647(computeControlledByParentFilter)Censuses
Each census was run at base
e073455929and at this head, and compared site by site, with the module path mapped back tosecurity-plugin.tsandregisterStartHooksmapped back tostart.isystem-census: 149 sites at both ends, 0 differences, 0 sites in the new module. Scanned files: 408 at both ends.check-system-context-censusexits 0 at both ends with byte-identical output: 149 sites in 22 packages across 77 files, 129 symbols, 142 cited against 142 required, 164 anchors, 21 declared non-read, 20 row references.tenant-audit-census: 238 sites at both ends, 0 differences, 0 sites in the new module, 176 non-engine calls, 87 engine types, 117 declared objects.Gates
85 commands, run at base
e073455929and at this head, each in its own worktree after a fullturbo run build. They are the 54 familiesscripts/pm/dispatch-gatesderives for this file list, the 18-command artifact roster, the hand-picked readers ofsecurity-plugin.ts(ADR and scripts symbol anchors,check:adr-anchors, the platform checklist, the three censuses, query-options erasure), and the package tests.dispatch-gates --ran: 54 derived, 54 run, 0 NOT-MEASURED.security-plugin.tsalone exits 0 at base.@objectstack/plugin-security222 files, 4,628 passed and 45 skipped at both ends; its typecheck exits 0 at both; the two core pins that read the plugin (row-serving-door-exposure,operation-private-keys) pass 8 of 8 at both. No test is added, edited or skipped.undeclared-dep-imports3,347 → 3,348 files,isystem-censuscorpus 9,692 → 9,693,tenant-audit-censussources 755 → 756).check-issue-citationsreads no added lines at base; at head it judges the 19 citations the added lines carry, all resolve.check:adr-anchors: the letters above.dual-build-cjs-loads(730 → 728 CommonJS files) andsourcemap-no-sources-content(602 → 598 maps): the base worktree'spackages/metadata-protocol/diststill held six orphan files from an earlier build there (build-probes-UIM275UL.cjs,chunk-PHCN2WJI.cjsand four maps, referenced by nothing). Otherwise the two trees'distfile lists are identical; the head's count is the clean one.main:origin/mainmoved toa57643d3de(refactor(metadata-protocol): move the record reads out of protocol.ts into protocol-record-reads.ts (#25467 cut 4) #25589, feat(sharing): a share or sharing rule on a public object reaches an external member of the record's organization #25591, fix(plugin-approvals)!: refuse a reassign target that resolves to no one #25592; none touchesplugin-security,scripts/adr-anchorsordocs/adr). This head merges into it cleanly. On today'smainand on the merged tree,check:adr-anchors,check-adr-symbol-anchors,check-scripts-symbol-anchors,check-system-context-census,check-tenant-audit-censusandcheck-platform-checklistall exit 0, with the same counts at both apart from the −11 letters.check-governed-merges --teston the final file list at today'smain: 0 of 2 paths hit the register; 999 changed lines (+551 / −448). Not governed.Acceptance notes
start(), no behaviour, no anchor entry, nodocs/**and nopackages/spec/**. The PR changes two files, both inpackages/plugins/plugin-security/src/.security-plugin.tsor anysecurity-plugin-*.tsmodule (re-listed right before this PR was opened).security-plugin.ts. The report on [maintainer] refactor(plugin-security): splitsrc/security-plugin.ts(10,746 lines, +2,803 in 30 days) along itsSecurityPluginseams, one move-only slice per PR #25223 lists every body that stays, with its reason, every cut PR, and the file's line count at the start of the series (11,054) and at the end (5,744).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa