Skip to content

refactor(plugin-security): move what start() registers after the authorization middleware out of security-plugin.ts (#25223 cut 13) - #25599

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-3fzya4
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-3fzya4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25223
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6353073966

Cut 13 of the security-plugin.ts split is the third and last part of the start() row: R4, what start() 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 for claimTargetAdminUserId in the #25508 form. This PR moves one run of start()'s statements into one new module, unedited:

  • packages/plugins/plugin-security/src/security-plugin-start-hooks.ts: registerStartHooks, the 16 statements that close start().

Not Tier H: check-governed-merges --test on 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_sets resolves 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 from origin/main at e073455929 (which contains cut 12's merge df893f6d76). 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, where security-plugin.ts is 6,164 lines and start() is :1744-:2619 (48 statements).

  • The run is 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 ends start(). In order, it registers:
    • the sys_user_position catalog refusal middleware and the grant-holder membership refusal;
    • the permission-set name hooks, followed by the ADR-0131 D3 note;
    • the bootstrap once-guards and runBootstrap, with its kernel:ready registration;
    • the boot reports on kernel:bootstrapped;
    • the middleware that replays the bootstrap after a qualifying write;
    • the sys_member middleware that reconciles organization_admin grants;
    • runOrgAdminBackfill, with its kernel:ready registration.
  • It closes over two of start()'s locals and nothing else: ctx and ql. It uses no arguments, super, new.target or bare this, and no this inside a non-arrow function. It has no return, await, break or continue at its top level that would act on start(); the closures' own returns and awaits move with them. All of that was measured on the AST.
  • The run's eight own locals (bootstrapRanOnce, readSeedSettlement, everyoneMutationWired, catalogEchoWired, catalogEchoBooted, managedDeniesApplied, runBootstrap, runOrgAdminBackfill) move with it. Nothing after the run read them, because the run ended start().
  • So the function takes the two locals as parameters, typed as the run saw them there: PluginContext and IObjectQLEngine. Its body is the run, re-indented one level and otherwise unedited, with every comment above and between its statements. It returns void.
  • In the run's place, start() has one BLOCK: the read and write locals, one host literal annotated with the Pick of StartHooksHost, and registerStartHooks.call(host, ctx, ql);. It sits right after the middleware's log line (:2190), as the run did, and is now start()'s last statement. The comment that closes start() (the per-organization seed DATA replay note) stays after it. Every registration is still synchronous and in the same order.

What moved

code base lines host members
the registrations after the authorization middleware (16 statements of start()) :2192-:2612 3 methods: declareEveryone, deploymentAcceptance, deploymentOrgScopingEntitlement; by value: bootstrapPermissionSets; getters: metadata, tenancyPosture; get/set pair: claimTargetAdminUserId
  • No module-level declaration moves. The run reads none: everything it names at module level comes from an import.
  • Import names that only the run reads leave security-plugin.ts, 26 in all. The module imports each from the specifier security-plugin.ts imports it from, and type imports stay type imports (the proof checks every pair). Thirteen imports leave whole, and five lose names:
    • POSITION_ASSIGNMENT_OBJECT and createPositionCatalogRefusal, registerGrantHolderMembershipRefusal, registerGrantPermissionSetNameHooks, reportCatalogReferences, reportAccessUpgrade and the type AccessUpgradeNotifier, reportUpgradeImpact, SETTINGS_SERVICE and the type SettingsManifestReader, applyManagedWriteDenies, reportPackagedPermissionSetOverlays, registerObjectPostureGate, registerPackagedPermissionSetLockGate, registerSecurityCatalogEcho and echoStoredSecurityCatalogOverlays, reconcileAudienceBindingSuggestions, cleanupPackagePermissions, readDeclared, readSeedSettlementSnapshot, bootstrapPlatformAdmin and shouldReplayBootstrapFor, and backfillOrgAdminGrants, extractMemberPairs and reconcileOrgAdminGrant, each from its ./ module;
    • the type SeedSettlementSnapshot (@objectstack/spec/contracts).
  • Six names stay in security-plugin.ts, because code that stays reads them too. The module imports them from the same specifiers: PluginContext and createSecurityCatalogReader (@objectstack/core), postureEnforcesWall (@objectstack/spec/security), SHARING_RULES_SERVICE and the type DeclaredSharingRuleReader (./access-upgrade-report.js), and IObjectQLEngine.
  • One name stays as a type-only import, and one comment above it is rewritten. Both are disclosed:
    • readSeededRecordIds (cut 11's ./security-plugin-seed-claim.js) is called only by the run, but the staying docblock of claimSeedOwnershipOnSettle links 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.
    • Cut 11's 4-line comment above that import said this file imports "the two seed-settlement readers it shares with the promotion-time pass in start()". That pass now runs in the new module. So the comment becomes 6 lines that say where the readers are imported now and why readSeededRecordIds stays type-only. No other comment in security-plugin.ts changes, and every comment in the run moves verbatim.
    • A scan of every {@link X} in security-plugin.ts whose head names nothing the file declares or imports gives the same list at base and at head.
  • The module header says what moved, why it is shaped this way, and how each field is passed. It cites no ADR.

The shape (#23050 shape B, as cuts 1 to 12 applied it, plus #25508's get/set pair)

  • StartHooksHost 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 the run names, 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 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.
  • Fields follow the rule cut 1 set, judged on each field's own declaration:
  • Nothing re-exports the module, so the package entry does not move.

No reader keys on the moved lines

Each was measured on this head, not recalled:

No new anchor entry, and one disclosure about the existing one

scripts/adr-anchors/ is not touched. Paragraph by paragraph, the security-plugin.ts entry:

  • ADR-0055 §1 is the relation reader (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.
  • ADR-0138 D1 says the seed-ownership claim "this file runs on every app:seeded (claimSeedOwnershipOnSettle) and at the first promotion" re-owns a NULL-owner row only when the seed reported landing on it (readSeededRecordIds).
    • The first-promotion arm, runBootstrap's seededRecordIds: readSeededRecordIds(ctx), moves into the new module with the run.
    • The run cites no ADR-0138, so an entry for the new module would need a citation the moved code does not carry, and this cut plants none. Cut 12's ADR-0123 D2 disclosure is the precedent.
    • The paragraph still holds for security-plugin.ts: the claim method and the readSeededRecordIds import stay there, and start() still registers the first-promotion pass, through the stub.
    • Cut 11's claim (6373426297) gave three reasons why that entry stays true: the method, the promotion-time pass and the import. The second now lives in the new module. The seed-claim module's own entry is unaffected. A follow-up can add an entry if the seat wants one.

The published .d.ts does not change

Measured under the series bar (ruling 6348891160 item 1), on base e073455929 and on this head. Each was a full turbo run build in its own worktree (both exit 0, 77 tasks; plugin-security was a cache miss at both ends, so both were really built).

  • 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:
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 gives raw 96fda037… 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 gives 7e6a99f7… again.
  • security-plugin.ts gains no export. registerStartHooks and StartHooksHost 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 df56a808 for all four.
  • The bundle JS bytes differ, because code moved within them: index.js goes from 77bed321 to 72283d9d, and index.mjs from 29e9cece to af35c900. The external specifiers each bundle loads are the same set at both ends: 17 in index.js and 20 in index.mjs.
  • Bundle order, disclosed. Thirteen module chunks are emitted later in index.mjs, because security-plugin.ts no longer imports them first.
    • Ten are modules whose import leaves security-plugin.ts whole: 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-echo and cleanup-package-permissions. The other three are modules those ten import: federated-phantom-anchors, packaged-permission-set-lock and objects/sys-user-position.object.
    • None of the thirteen imports security-plugin.ts.
    • Their top level only declares: constants, functions, classes, interfaces and types, a Set, a WeakMap, and objects/sys-user-position.object.ts's ObjectSchema.create(…). That is a validating constructor from @objectstack/spec, outside the bundle, and it registers nothing.
    • 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 this head against e073455929, with a scratch script that parses both files with the TypeScript compiler API.

  • The run, by AST token and by text. 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 of start() 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.
  • The stub's host literal. It has 7 members, exactly the Pick, in the same order. Each is in the form its own declaration calls for: 3 methods as this.m(...a) closures, bootstrapPermissionSets by value (declared readonly), two getters, each over a read… local that reads this.m, and claimTargetAdminUserId as a get/set pair over readClaimTargetAdminUserId and writeClaimTargetAdminUserId. The check also derives the members the base run writes, from the AST, and requires exactly those to be the get/set pairs.
  • The host interface. 7 members, each typed SecurityPlugin['m']. The fields the run only reads are readonly; the one it writes is not. Its set equals the Pick, and the Pick equals the set of this.m the base run names.
  • 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 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 changes readSeededRecordIds to type 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 type StartHooksHost, the type readSeededRecordIds). It also asserts that no dropped name is referenced in code, and that readSeededRecordIds appears exactly three times: the type import, the import comment and the one {@link}.
  • Comments above imports. security-plugin.ts loses 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.
  • The module's imports. 33 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. The module's top level is its 21 imports, StartHooksHost and registerStartHooks, and nothing else.
  • Negative controls. Fourteen mutations of one copy each, and every one fails the check it targets:
    • in the moved run, { object: POSITION_ASSIGNMENT_OBJECT } changed to a string literal;
    • in the moved run, one word added to a comment between two statements ([#22801 → A]);
    • in start(), the log line before the stub changed;
    • in the stub, the set claimTargetAdminUserId line removed;
    • in the stub, the write local changed to assign v ?? undefined;
    • in the host interface, readonly added to claimTargetAdminUserId;
    • in the host interface, readonly dropped from metadata;
    • in the function's Pick, bootstrapPermissionSets left out;
    • in security-plugin.ts, bootstrapPlatformAdmin imported again;
    • in security-plugin.ts, readSeededRecordIds imported as a value again;
    • in security-plugin.ts, "the claim's docblock" changed to "its docblock" in the rewritten cut 11 comment;
    • in security-plugin.ts, one space added to init()'s signature;
    • in the module, readDeclared imported from another specifier;
    • in the module, a top-level const added.
  • git's own view. This is git diff --color-moved=plain --color-moved-ws=allow-indentation-change e073455929 over the two source files.
    • Of the 448 deleted lines in 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.
    • Of the 523 lines of the new module, 446 show as moved. The other 77 are the module's own lines: its header, the regrouped imports, the host interface, and the function's docline, signature and closing brace.
  • Size. 999 changed lines (+551 / −448) over 2 files: 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.mjs exits 0 at base e073455929, at this head, at today's main (a57643d3de) and on this head merged into it: 2,384 anchors over 142 records, 466 symbol anchors, at each.
  • pnpm check:adr-anchors exits 0 at both. Anchored files stay at 84.
  • Decision letters on the anchored surface. On the merge-base e073455929: 1888. On this head: 1877. On today's main, 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's main (a clean git merge-tree): 1872. So the drop is −11 on either base. That gate counts ADR-NNNN Dk citations in each anchored file and in each entry's own invariant text. All −11 are R4's letters leaving security-plugin.ts, which holds 185 at base and 174 at head:
    • ADR-0090 D5 ×3, ADR-0131 D3 ×3;
    • ×1 each: ADR-0057 D4, ADR-0086 D1, ADR-0090 D11, ADR-0131 D10 and ADR-0131 D4.
    • Five letters leave security-plugin.ts entirely: ADR-0057 D4, ADR-0086 D1, ADR-0090 D11, ADR-0131 D10 and ADR-0131 D4.
    • The stub, the new import comment, the rewritten cut 11 comment and the module header 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 stay at 52,315, across 6,700 files at base and 6,701 at head (the new file is the module).
  • The anchor map covers every ADR symbol anchor that names security-plugin.ts or the new module, at both ends: 26 citing lines over 17 symbols, measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs (measurement, not a new gate). No anchor changes file or class, and none resolves inside R4.
ADR line anchor base head
ADR-0049:49 #SecurityPlugin security-plugin.ts · declaration · :1206 (class) security-plugin.ts · declaration · :1189 (class)
ADR-0055:123 #computeControlledByParentFilter security-plugin.ts · declaration · :5067 (computeControlledByParentFilter) security-plugin.ts · declaration · :4647 (computeControlledByParentFilter)
ADR-0055:125 #assertControlledByParentWrite security-plugin.ts · declaration · :5421 (assertControlledByParentWrite) security-plugin.ts · declaration · :5001 (assertControlledByParentWrite)
ADR-0055:125 #assertMasterRowEditable security-plugin.ts · declaration · :5547 (assertMasterRowEditable) security-plugin.ts · declaration · :5127 (assertMasterRowEditable)
ADR-0055:135 #CBP_MAX_CHAIN_DEPTH security-plugin.ts · declaration · :700 (top-level) security-plugin.ts · declaration · :683 (top-level)
ADR-0055:24 #computeRlsFilter security-plugin.ts · declaration · :4441 (computeRlsFilter) security-plugin.ts · declaration · :4021 (computeRlsFilter)
ADR-0055:5 #computeControlledByParentFilter security-plugin.ts · declaration · :5067 (computeControlledByParentFilter) security-plugin.ts · declaration · :4647 (computeControlledByParentFilter)
ADR-0055:58 #assertControlledByParentWrite security-plugin.ts · declaration · :5421 (assertControlledByParentWrite) security-plugin.ts · declaration · :5001 (assertControlledByParentWrite)
ADR-0066:112 #writeCheckPolicies security-plugin.ts · declaration · :1142 (writeCheckPolicies) security-plugin.ts · declaration · :1125 (writeCheckPolicies)
ADR-0096:181 #getReadFilter security-plugin.ts · declaration · :3176 (getReadFilter) security-plugin.ts · declaration · :2756 (getReadFilter)
ADR-0096:182 #getReadFilter security-plugin.ts · declaration · :3176 (getReadFilter) security-plugin.ts · declaration · :2756 (getReadFilter)
ADR-0096:183 #getReadFilter security-plugin.ts · declaration · :3176 (getReadFilter) security-plugin.ts · declaration · :2756 (getReadFilter)
ADR-0096:199 #getReadFilter security-plugin.ts · declaration · :3176 (getReadFilter) security-plugin.ts · declaration · :2756 (getReadFilter)
ADR-0099:341 #hasPlatformAdminPosture security-plugin.ts · declaration · :4468 (hasPlatformAdminPosture) security-plugin.ts · declaration · :4048 (hasPlatformAdminPosture)
ADR-0099:68 #manage_metadata security-plugin.ts · literal · :363 (top-level) security-plugin.ts · literal · :346 (top-level)
ADR-0105:93 #collectRLSPolicies security-plugin.ts · declaration · :5601 (collectRLSPolicies) security-plugin.ts · declaration · :5181 (collectRLSPolicies)
ADR-0123:4 #composeWriteTenantWall security-plugin.ts · declaration · :4759 (composeWriteTenantWall) security-plugin.ts · declaration · :4339 (composeWriteTenantWall)
ADR-0126:406 #nav_permission_sets security-plugin.ts · literal · :1634 (init) security-plugin.ts · literal · :1617 (init)
ADR-0138:152 #isPrincipalLessContext security-plugin.ts · declaration · :573 (isPrincipalLessContext) security-plugin.ts · declaration · :556 (isPrincipalLessContext)
ADR-0138:153 #SecurityPlugin.getMetadataReadableFields security-plugin.ts · declaration · :3311 (getMetadataReadableFields) security-plugin.ts · declaration · :2891 (getMetadataReadableFields)
ADR-0138:248 #SecurityPlugin security-plugin.ts · declaration · :1206 (class) security-plugin.ts · declaration · :1189 (class)
ADR-0138:265 #SecurityPlugin security-plugin.ts · declaration · :1206 (class) security-plugin.ts · declaration · :1189 (class)
ADR-0138:427 #SecurityPlugin.resolvePermissionSetsForContextUnmemoized security-plugin.ts · declaration · :3905 (resolvePermissionSetsForContextUnmemoized) security-plugin.ts · declaration · :3485 (resolvePermissionSetsForContextUnmemoized)
ADR-0139:222 #getReadFilter security-plugin.ts · declaration · :3176 (getReadFilter) security-plugin.ts · declaration · :2756 (getReadFilter)
ADR-0139:223 #computeDelegatedReadFilter security-plugin.ts · declaration · :3230 (computeDelegatedReadFilter) security-plugin.ts · declaration · :2810 (computeDelegatedReadFilter)
ADR-0139:246 #computeControlledByParentFilter security-plugin.ts · declaration · :5067 (computeControlledByParentFilter) security-plugin.ts · declaration · :4647 (computeControlledByParentFilter)

Censuses

Each census was run at base e073455929 and at this head, and compared site by site, with the module path mapped back to security-plugin.ts and registerStartHooks mapped back to start.

  • isystem-census: 149 sites at both ends, 0 differences, 0 sites in the new module. Scanned files: 408 at both ends.
  • check-system-context-census exits 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 e073455929 and at this head, each in its own worktree after a full turbo run build. They are the 54 families scripts/pm/dispatch-gates derives for this file list, the 18-command artifact roster, the hand-picked readers of security-plugin.ts (ADR and scripts symbol anchors, check:adr-anchors, the platform checklist, the three censuses, query-options erasure), and the package tests.

  • Head: 85 of 85 exit 0. dispatch-gates --ran: 54 derived, 54 run, 0 NOT-MEASURED.
  • Base: 84 exit 0. The eslint line names the new module, which does not exist there; eslint on security-plugin.ts alone exits 0 at base.
  • Tests: @objectstack/plugin-security 222 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.
  • Output compared with paths, timings and timestamps masked: 58 identical and 27 different, each read.
    • 22 differ only by a file count that grows by one, the new module (for example undeclared-dep-imports 3,347 → 3,348 files, isystem-census corpus 9,692 → 9,693, tenant-audit-census sources 755 → 756).
    • check-issue-citations reads 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) and sourcemap-no-sources-content (602 → 598 maps): the base worktree's packages/metadata-protocol/dist still held six orphan files from an earlier build there (build-probes-UIM275UL.cjs, chunk-PHCN2WJI.cjs and four maps, referenced by nothing). Otherwise the two trees' dist file lists are identical; the head's count is the clean one.
    • The plugin-security test log differs in interleaving only.
  • Today's main: origin/main moved to a57643d3de (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 touches plugin-security, scripts/adr-anchors or docs/adr). This head merges into it cleanly. On today's main and on the merged tree, check:adr-anchors, check-adr-symbol-anchors, check-scripts-symbol-anchors, check-system-context-census, check-tenant-audit-census and check-platform-checklist all exit 0, with the same counts at both apart from the −11 letters.
  • check-governed-merges --test on the final file list at today's main: 0 of 2 paths hit the register; 999 changed lines (+551 / −448). Not governed.

Acceptance notes


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa

…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
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 24, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 24, 2026 04:26
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 19 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/environment-variables.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/deployment/tenancy-modes.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/permissions/authentication.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/permissions/delegated-administration.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/permissions/permission-sets.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/permissions/positions.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/permissions/system-context.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v16.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v17/17-0.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v17/17-1.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v17/17-3.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v17/17-6.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v18/18-0.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))
  • content/docs/releases/v18/18-1.mdx (via sys_member (literal, a string literal in registerStartHooks; a string literal in start))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 17 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a57643d3de7067cb7d292b4802773bdf938fd2cb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8ce0058d41b05c72411aa3c08af0c5dcac58803e — the merge of head 2d8c53ca970f8c05cf98002d88d0fa4b82b3ed5d into base a57643d3de7067cb7d292b4802773bdf938fd2cb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a57643d3de7067cb7d292b4802773bdf938fd2cb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T04:34Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 cut 13 / R4, the series' last cut (os-dev-report 6379853213, which doubles as the series final report) · reviewed head 2d8c53ca97 · merge base e073455929

  • Shape: start()'s last 16 statements (R4) move unedited into security-plugin-start-hooks.ts as registerStartHooks. claimTargetAdminUserId becomes a get/set host pair in the refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508 form, as the seat set it. The change touches 2 files, +551 / −448, 999 changed lines. security-plugin.ts goes from 6,164 to 5,744 lines.
  • ADR-0138 D1, a seat call, disclosed and accepted: the first-promotion arm moves, but its only ADR-0138 citation stays on claimSeedOwnershipOnSettle's docblock in security-plugin.ts. A new-module anchor entry fails check-adr-anchors ("no longer references ADR-0138"), and the card thread measured that before pushing. Adding a header citation to make it pass would plant a token, which ruling 6353073966 item 4 forbids. So no entry is added, and the point stays a disclosure, as cut 12's ADR-0123 note does.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree (turbo run build --filter=@objectstack/plugin-security at both ends):
    • Raw 7e6a99f7d59debba… on index.d.ts and index.d.mts at both ends, with a raw diff of 0 lines.
    • Normalised e22a714da70e9efc… at both ends.
    • Positive control: the head private permissionEvaluator (line 94) made public. That moves the hashes to raw c2d8413e71c1b931… and normalised aed83afe2a517670….
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors (2,384 anchors)
    • check-platform-checklist
    • check-tenant-audit-census (238)
    • check-system-context-census (149)
    • check-adr-anchors
  • Decision letters, read by the seat: 1888 at the merge base and 1877 at head. The 11 citations in the moved text leave the anchored surface unchanged. The report gives the same −11 on today's main.
  • Tests and gates (report): plugin-security passes 4,628 tests with 45 skipped at both ends. All 85 gates exit 0 at head.
  • CI at the time of this verdict: 27 success, 12 expected skips, 6 still in progress (45 runs). The seat lands only on a fully green head.
  • Mergeable: git merge-tree onto today's main is clean.
  • Body: the attribution header and the os-sales reviewer are in place, and the footers are present.
  • Series: R4 was the last movable row of start(). The final report gives security-plugin.ts going from 11,054 to 5,744 lines, lists all 13 cut PRs, and gives every body that stays with its reason. The seat closes [maintainer] refactor(plugin-security): split src/security-plugin.ts (10,746 lines, +2,803 in 30 days) along its SecurityPlugin seams, one move-only slice per PR #25223 after this PR lands.
  • Landing: HELD. Sales paused merges for a release at 2026-10-24T03:57Z. The PR is not governed (check-governed-merges --pr 25599: 999 lines). The seat readies it and arms auto-merge through the relay only after merges resume and CI is fully green, and it re-runs git merge-tree against main at that point.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 24, 2026 09:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 24, 2026 09:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit dad27ef Oct 24, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3fzya4 branch October 24, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants