Skip to content

refactor(plugin-security): move the security service registration and the seed-ownership claim out of security-plugin.ts (#25223 cut 11) - #25530

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

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

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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

Cut 11 of the security-plugin.ts split is the first part of the start() row. The claim is 6373426297. Before any claim, the card thread mapped start() into four regions and took the plan to the seat, as cut 10's landing note asked. The seat agreed the order: this cut first, through the normal path, then cut 12 (the middleware, Tier H) claimed afresh on the merged main. This PR moves two seams into two new modules, unedited.

To packages/plugins/plugin-security/src/security-plugin-security-service.ts:

  • registerSecurityService, which is ONE STATEMENT of start(). It is the try that builds the ISecurityService literal and registers it as the security kernel service. That literal is what every door bypassing the engine middleware asks: the analytics raw-SQL path, the REST export route, the sharing service's management probes, security/explain and the metadata plane (ADR-0021 D-C, ADR-0090 D5/D6/D10/D12, ADR-0105 D3/D8, ADR-0106 D7, ADR-0111 D1/D2, ADR-0131 D3).

To packages/plugins/plugin-security/src/security-plugin-seed-claim.ts:

  • the body of claimSeedOwnershipOnSettle, the seed-ownership claim that init() subscribes to app:seeded (ADR-0138 D1);
  • the two readers it asks the published seed-settlement contract through, readSeedSettlementSnapshot and readSeededRecordIds.

The change is move-only, and the card stays open for cut 12. The branch is claude/project-thread-3fzya4, the harness-pinned branch for this session. It was restarted from origin/main at 82f5a111b4, which contains cut 10's merge. The branch name names no issue, so the claim comment is the identity record.

How one statement of start() moves

This is the first cut to move a statement out of a method rather than a whole method body. The seat agreed the plan's form for it before the claim.

  • The statement is :2266-:2560 at base (295 lines). It closes over three of start()'s locals and nothing else: ctx, ql and metadata. It uses no arguments, super, new.target or bare this. It has no return, await, break or continue at its top level that would act on start(). All of that was measured on the AST.
  • So the function takes the three locals as parameters, typed as the statement saw them there: PluginContext, IObjectQLEngine and IMetadataService. Both handles are already known to be present at that point. Its body is the statement, re-indented one level and otherwise unedited. It returns void, as the statement produced no value.
  • start() keeps the statement's position and the comment above it (:2259-:2265). In its place is one BLOCK: the getter reads, one host literal annotated with the Pick of SecurityServiceHost, and registerSecurityService.call(host, ctx, ql, metadata);. Because it is a block, its consts stay inside it and cannot collide with anything later in start().
  • The statement ran synchronously and still does. The call is in the same place in start()'s order: after the invitation placement service is registered and before the middleware is registered.

What moved

The base line numbers are at 82f5a111b4. The last column lists the members the moved code reads, which is exactly what its host carries.

code base lines module host members
the security service registration (one statement of start()) :2266-:2560 security-service methods: canExport, canReadObject, checkAuthoredRowWrite, checkControlledByParentWrite, declareEveryone, describePositionGrants, explainAccessForCaller, getEffectiveObjectPermissions, getMetadataReadableFields, getObjectSecurityMeta, getQueryableFields, getReadFilter, getReadableFields, getWritableFields, resolvePermissionSetsForContext; by value: acceptedDefaultPermissionSets, ownershipFloorAlternates; getters: delegatedAdminGate, logger, permissionEvaluator, ql, tenancyPosture
claimSeedOwnershipOnSettle (private, async) :1873-:1905 seed-claim by value: bootstrapPermissionSets; getter: claimTargetAdminUserId
readSeedSettlementSnapshot (module function, with its docblock) :1254-:1273 seed-claim none
readSeededRecordIds (module function, with its docblock) :1275-:1293 seed-claim none
  • claimSeedOwnershipOnSettle keeps its name, signature, docblock and position, less async. It returns the moved function's promise, as cuts 1 and 3 to 10 did for their async methods. init()'s app:seeded subscription still calls it on the plugin.
  • The two readers move with their docblocks, unedited, and gain export and nothing else. security-plugin.ts imports both back, because R4's promotion-time pass on kernel:ready reads both (:4550, :4590).
    • Why they move rather than gain export where they stood: each signature names a contract type. Cut 6 measured that an export from security-plugin.ts whose signature names another module's type reorders the .d.ts. So they moved, and the .d.ts was measured anyway (below).
  • Import names that only the moved code reads leave security-plugin.ts, 16 in all. The modules import them from the same specifiers.
    • From the registration:
      • resolveDelegatorContext, delegatedOrganizationRefusal and d10NarrowingStatement (./explain-engine.js);
      • listAudienceBindingSuggestions, confirmAudienceBindingSuggestion, dismissAudienceBindingSuggestion and the types SuggestionDeps and SuggestionListFilter (./suggested-audience-bindings.js);
      • the type OwnershipFloorAlternate (./platform-ownership-policies.js);
      • the type ObjectAccessScope (@objectstack/spec/security);
      • the types ISecurityService and DelegationNarrowing (@objectstack/spec/contracts).
    • From the seed claim:
      • SEED_SETTLEMENT_SERVICE and the type ISeedSettlementService (@objectstack/spec/contracts);
      • findExistingPlatformAdmin (./bootstrap-platform-admin.js);
      • the whole ./claim-seed-ownership.js import (claimSeedOwnership).
    • Disclosed: one staying docblock, computeDelegatedReadFilter's, names resolveDelegatorContext in a {@link}. The name is no longer imported, so that link is now plain text, like the bare-member {@link computeRlsFilter} beside it. No docblock is edited. Every other staying mention of a dropped name is in backticks or prose.
  • Each value import is read only inside a function body, at call time, never while a module evaluates. index.ts exports only what it did, so the published surface does not move (measured below).

The shape (#23050 shape B, as cuts 1 to 10 applied it)

  • SecurityServiceHost and SeedClaimHost are new. Each types every member as the class member itself (SecurityPlugin['m']). Each moved function types its this as the Pick of its host over exactly the members its code reads, and the stub annotates its literal with the same Pick. There is no cast, and no member becomes public or protected.
  • Methods go in as late-bound closures that call this.m(...a) on the plugin at call time. Every member of the registered service that binds a plugin method was already an arrow calling this.m(…). this.m is now the host's closure, which calls the plugin's method on the plugin. So every call reaches the same method on the same receiver, and a spy or test double set on the plugin is still the one the service reaches.
  • Fields follow the rule cut 1 set, judged on each field's own declaration:
    • acceptedDefaultPermissionSets, ownershipFloorAlternates and bootstrapPermissionSets are declared readonly, so they go by value: the same object references.
    • delegatedAdminGate, logger, permissionEvaluator, ql, tenancyPosture and claimTargetAdminUserId are not. So they go as getters, and each read happens when the code makes it. For example, the posture the suggestion deps hand out is still read per call, and the claim still reads the admin R4 names when it runs.
    • None of them is function-valued: each is an object whose own methods the code calls, or a value it reads. No receiver changes.
    • The hosts declare every field readonly, so moved code cannot assign a member through them. The typecheck passes, so none does. Neither moved region writes a member.
  • Nothing re-exports either module, so the package entry does not move.

Three readers read the moved lines by path, and all three are re-pointed in this PR

These are the three the seat named. Each was run with its edit reverted, and each fails without it.

  • content/docs/permissions/system-context.mdx, row 7. The registration holds 3 of start()'s 4 isSystem reads: hasWriteBypass's, resolveWriteScope's and describeDelegationNarrowing's system short-circuits. check-system-context-census keys each read by file and enclosing symbol, so row 7's anchor changes from security-plugin.ts#start to security-plugin-security-service.ts#registerSecurityService.
    • The symbol changes because the reads now sit in the function. The row's text and every other anchor stay. Rows 1, 2 and 30 keep #start, which still holds the middleware's read.
    • With the page untouched, the census reports 1 problem: [file-without-a-row] for the new module. With the re-point it exits 0.
  • packages/plugins/plugin-security/src/delegated-organization-membership.test.ts, 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 enumeration pin. The registration holds the only delegator resolution in security-plugin.ts, and the organization ask beside it. So in the expected list, security-plugin-security-service.ts replaces security-plugin.ts, and the count stays at seven files.
    • The rule it pins is unchanged: asks equal resolutions in every file. It holds: the new module has 1 and 1, and security-plugin.ts has 0 and 0.
    • Unedited, it fails on the list.
  • packages/core/src/security/row-serving-door-exposure.pin.test.ts. Every file that calls canReadObject must be classified, and the service literal's binding this.canReadObject(…) now sits in the new module.
    • The new module gains a not-a-door entry: "the provider's service registration: it binds the admission into the security service the plugin registers; it serves no rows".
    • security-plugin.ts stays a caller, through the stub's closure and describePositionGrants' own ask. Its reason no longer says it binds the admission into the service. It now says "the provider: it implements the admission, hands it to its service registration, and asks it itself only to decide whether describePositionGrants may count a position's holders; it serves no rows".
    • Unedited, it fails with the new module unclassified.

One new anchor entry

scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-seed-claim.ts.json (adrs: ["ADR-0138"]).

  • Its invariant is the ADR-0138 D1 paragraph of the security-plugin.ts entry, verbatim. That paragraph names claimSeedOwnershipOnSettle and readSeededRecordIds, whose bodies now sit in the module. Cut 9 carried ADR-0106 D7 into security-plugin-field-readers.ts the same way.
  • The security-plugin.ts entry is unchanged and stays true: the method, the promotion-time pass and the import of readSeededRecordIds stay in that file.
  • The security-service module gets no entry, because no paragraph describes the registration:
    • ADR-0055 §1 is the relation reader;
    • ADR-0106 D7 is the readable-field asymmetry, in the field-readers module since cut 9;
    • ADR-0123 D2 is step 3.7's refusal, in the middleware.

docs/adr/** is not touched, and no other governed path is, so this is a normal landing.

The published .d.ts does not change

Measured under the series bar (ruling 6348891160 item 1), on base 82f5a111b4 and on head 5a4747b9c7. Each was a full turbo run build in its own worktree (both exit 0).

  • Raw sha256 of dist/index.d.ts and dist/index.d.mts is the same for all four files, base and head: 5e7e8df69375e6606767eee938111aaaf0b9bac4dd9c728689c343be9e5f7702 (14,696 lines). The raw diff is 0 lines, so no union order moved either.
  • Normalised sha256 is the same for all four: 1e2e8299c2b3aa8502ae3684a5a8a4f7148b2211876a0a581f20d27677531ed9.
  • security-plugin.ts gains no export. The two readers gained export in the new module, which nothing re-exports, so the cut 6 trap does not arise.
  • 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 claimSeedOwnershipOnSettle in the head source, pnpm --filter @objectstack/plugin-security build emits line 431 as the full public signature instead of private claimSeedOwnershipOnSettle;. That gives raw 12beec21603e70025c7a1b70d2c49fd0dd110d66a61b57e82098a6eb903fe08c and normalised 0509461ad52776d160e6875c3a99cee28dda954089413b05db158aabda6d52ff, both different from the real hashes. So the comparison does see a visibility change. With the source restored, a rebuild gives 5e7e8df6… again for both files.
  • claimSeedOwnershipOnSettle is private, so the .d.ts carries only its name and docblock (line 431). registerSecurityService and the two readers are in no published file.
  • Export tables: the ESM and CJS bundles each export the same 66 names at base and at head. The lists were loaded at runtime, with require and import() of the built bundles, not parsed. The sorted name list hashes to 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 for all four, as at cuts 6 to 10.
  • The bundle JS bytes differ, because code moved within them: index.js goes from a0d05e78 to b734b9f0, and index.mjs from 1080b070 to 51498bc7. Every value the new modules import comes from a file inside this package or from a specifier security-plugin.ts already imports. So the set of external specifiers each bundle loads is the same at base and head: 17 distinct require(…) specifiers in index.js and 24 distinct from specifiers in index.mjs, counted the same way at both ends.
  • Bundle order, disclosed. The seed-claim module imports bootstrap-platform-admin.ts and claim-seed-ownership.ts. So those two, and platform-admin-standing-audit.ts, are now emitted after the two new modules and ahead of ownership-floor-alternates.ts and security-plugin.ts's first chunk, instead of after predicate-guard.ts. None of the three imports security-plugin.ts. Their top level only declares (one constant is computed from an imported constant, CLAIM_PAGE_ROWS).

No changeset: skip-changeset, per ruling 6348891160 item 4.

Proof of move-only

  • The statement, by AST token. registerSecurityService's body is one statement, with the same TypeScript token sequence as the base try statement: 1,296 tokens. Each of its 295 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() outside the statement. The head text of start() equals the base text exactly, outside the one statement, which is now a 33-line stub block ending in the .call. Its signature, docblock and every other statement are byte-identical. That covers R1, the comment above the statement, the middleware and R4.

  • The method, by AST token. claimSeedOwnershipOnSettle's function body has the same token sequence as the base method body (136 tokens), and each of its 28 lines is the base line less two spaces. The parameters, the return type and async match. The class keeps the method's docblock and signature, less async.

  • The two readers. Each one's text in the module, docblock included, is the base text with export added, and nothing else. Neither is left in security-plugin.ts.

  • Class side. Every other class member is byte-identical to base: 124 named members plus the constructor. The class has 127 members at base and at head.

  • Top level of security-plugin.ts. Of the 110 top-level statements at base other than the class, 101 are byte-identical at head. The other nine are:

    • six imports that each lose the names listed above;
    • the ./claim-seed-ownership.js import, which is gone;
    • the two readers, which are gone from the file.

    The two new statements are the imports of the new modules, with their comments. The check asserts the exact list of 16 dropped names and 6 added names, and that no dropped name is still referenced in the file.

  • Negative controls. Seven fail the check:

    • in the registration, ctx.logger.info( changed to ctx.logger.debug(;
    • in the registration, one line indented by one more space, with the tokens unchanged;
    • in start(), one edit in R1 (:1914), ahead of the stub;
    • in start(), one edit in the middleware, after the stub;
    • in readSeedSettlementSnapshot, return undefined changed to return null;
    • in claimSeedOwnershipOnSettle, ctx.logger.warn( changed to ctx.logger.error(;
    • in a top-level docblock of security-plugin.ts, one trailing space added.
  • git's own view. This is git diff --color-moved=plain --color-moved-ws=allow-indentation-change 82f5a111b4 HEAD over the three source files.

    • Of the 378 deleted lines, git sees 370 as moved. The other 8 are four type-import lines and the ./bootstrap-platform-admin.js import line, which the modules group differently; the two reader signatures that gain export; and the method signature that loses async.
    • Of the 613 added lines, 391 show as moved: 311 in the security-service module, 79 in the seed-claim module, and 1 in security-plugin.ts (a stub's };). The rest are the modules' headers, imports, host interfaces and function signatures, the two stubs, and the two new imports with their comments.
  • Size. 1,010 changed lines (+629 / -381) over 7 files, under the 3,000-line manual-merge line.

    • security-plugin.ts goes from 8,442 to 8,120 lines.
    • security-plugin-security-service.ts has 418 lines, and security-plugin-seed-claim.ts 139.
    • The other four files are the page (+1 / -1), the two pins (+1 / -1 and +7 / -1) and the anchor entry (+7).

ADR anchors (ruling 6353073966 item 2)

  • node scripts/check-adr-symbol-anchors.mjs exits 0 and gives the same output at base 82f5a111b4 and at head.

  • pnpm check:adr-anchors exits 0 at base and at head. Anchored files go from 82 to 83, the new entry.

  • The decision-letter count on the anchored surface goes from 1970 to 1958. That gate counts ADR-NNNN Dk citations in each anchored file and in each entry's own invariant text. The −12 is:

    • −13: the registration's letters leave the anchored surface with it (ADR-0090 D5 three times, D6, D10 and D12; ADR-0105 D3 and D8; ADR-0106 D7 twice; ADR-0111 D1 and D2; ADR-0131 D3);
    • 0: readSeededRecordIds' docblock carries ADR-0138 D1, and it stays on the anchored surface, now in an anchored module;
    • +1: the new entry's invariant text.

    The method body, the module headers, the stubs and the new import comments cite none. Measured per file: security-plugin.ts holds 250 such citations at base and 236 at head. The security-service module holds the 13, and the seed-claim module the 1.

  • Every moved citation is still in the tree, in the module its code moved to. Citations that resolve tree-wide go from 52262 across 6671 files at base to 52264 across 6674 at head. The two added citations are the new entry's adrs list and its invariant. The three added files are the two modules and the entry.

  • docs/adr/** is not touched, and no anchor token is planted.

  • The anchor map covers every ADR symbol anchor that names security-plugin.ts: 18 symbols.

    • It was measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs. That is measurement, not a new gate.
    • It was read on 82f5a111b4 BEFORE the claim, with the whole cut applied as a local trial, and no anchor changed class.
    • No ADR anchors the moved method, the two readers or the new function. Two anchored members are named inside the service literal: #getReadFilter and #SecurityPlugin.getMetadataReadableFields. Their names now also appear in the stub's host literal and its Pick. Both still resolve as declaration, on the methods.

The table gives each anchor's class at base and at head, and the line its own declaration resolves on. Every anchor stays in security-plugin.ts.

anchor cited at base class · line (member) head class · line (member)
#CBP_MAX_CHAIN_DEPTH ADR-0055:135 declaration · :746 (top-level) declaration · :748 (top-level)
#SecurityPlugin ADR-0049:49, ADR-0138:248, ADR-0138:265 declaration · :1348 (class) declaration · :1309 (class)
#SecurityPlugin.getMetadataReadableFields ADR-0138:153 declaration · :5620 (method) declaration · :5298 (method)
#SecurityPlugin.resolvePermissionSetsForContextUnmemoized ADR-0138:427 declaration · :6214 (method) declaration · :5892 (method)
#assertControlledByParentWrite ADR-0055:58, ADR-0055:125 declaration · :7699 (method) declaration · :7377 (method)
#assertMasterRowEditable ADR-0055:125 declaration · :7825 (method) declaration · :7503 (method)
#collectRLSPolicies ADR-0105:93 declaration · :7879 (method) declaration · :7557 (method)
#composeWriteTenantWall ADR-0123:4 declaration · :7037 (method) declaration · :6715 (method)
#computeControlledByParentFilter ADR-0055:5, ADR-0055:123, ADR-0139:246 declaration · :7345 (method) declaration · :7023 (method)
#computeDelegatedReadFilter ADR-0139:223 declaration · :5539 (method) declaration · :5217 (method)
#computeRlsFilter ADR-0055:24 declaration · :6745 (method) declaration · :6423 (method)
#getReadFilter ADR-0096:181, ADR-0096:182, ADR-0096:183, ADR-0096:199, ADR-0139:222 declaration · :5485 (method) declaration · :5163 (method)
#hasPlatformAdminPosture ADR-0099:317 declaration · :6772 (method) declaration · :6450 (method)
#isPrincipalLessContext ADR-0138:152 declaration · :619 (top-level) declaration · :621 (top-level)
#manage_metadata ADR-0099:68 literal · :372 (top-level) literal · :374 (top-level)
#nav_permission_sets ADR-0126:406 literal · :1776 (init) literal · :1737 (init)
#rlsFilter ADR-0055:24 declaration · :4360 (start) declaration · :4038 (start)
#writeCheckPolicies ADR-0066:112 declaration · :1243 (top-level) declaration · :1245 (top-level)

Readers

The three readers above are the only ones that read the moved lines by path. No other gate, baseline, docs anchor or test does. Each kind was read:

  • The system-context census.
    • node scripts/isystem-census.mjs --json gives the same 149 sites at base and at head, with 0 differences. The comparison ignores line numbers, maps the two modules back to security-plugin.ts, and maps the symbol registerSecurityService back to start.
    • Three head sites are in the security-service module: the three short-circuits, which the re-pointed row 7 names.
    • The role counts are the same (read 154, declaration 39, key 440, other 9), and so are the 5 non-elevation reads. Files holding a site go from 76 to 77, and scanned files from 402 to 403.
    • check-system-context-census and its --self-test exit 0 at base and at head. The totals: 149 sites at both; symbols 128 to 129; cited 141 to 142 against as many required; files 76 to 77. With the page untouched, it reports 1 problem (above).
  • The tenant-audit census. No moved code holds an engine write. The seed claim writes through claimSeedOwnership, which stays in its own file, and the registration only reads.
    • node scripts/tenant-audit-census.mjs --json gives the same 238 write call sites at base and at head, compared the same way: 0 differences, none in the modules, and the same 176 non-engine calls. Only scannedSources moves, from 739 to 741: the two modules.
    • check-tenant-audit-census exits 0 at base and at head, and tenant-audit-census.mdx is not touched.
  • The two pins are described above. operation-private-keys.pin.test.ts (@objectstack/core) names no moved code, and it passes at head.
  • Symbol anchors outside the ADRs. Across the ADR, spec liveness, platform checklist, system-context page and spec conversions/registry.ts corpora, security-plugin.ts#X is cited 48 times over 26 symbols at base and 47 at head. With symbolSegmentResolution, each resolves at head with its base class: 0 changed. The one ref that leaves is row 7's.
    • check-platform-checklist (the script and pnpm check:platform-checklist), check-scripts-symbol-anchors and its --self-test, and pnpm --filter @objectstack/spec check:liveness were run at base and at head (Gates).
  • Readers by name, not by path. These were read, and none needs a change:
    • the behaviour tests of the security service and the seed claim in this package. Each drives the plugin or the registered service, and none calls the moved code directly. No test replaces claimSeedOwnershipOnSettle on the plugin instance;
    • the page's row text and the docblocks that stay in the class, which name these members and stay true by name.
  • Line citations. No security-plugin.ts:N citation in the repo points into the moved ranges (base :1254 to :1293, :1873 to :1905 and :2266 to :2560).

Gates

All gates ran against base 82f5a111b4 and on head 5a4747b9c7, each in its own worktree after a full turbo run build there (both exit 0). The CI run on this head is green: 36 checks passed and 11 were skipped by their path filters.

  • pnpm --filter @objectstack/plugin-security test (vitest run): Test Files 218 passed (218); Tests 4573 passed, 45 skipped (4618). Base 82f5a111b4 gives the same counts.

  • pnpm --filter @objectstack/plugin-security typecheck: exit 0 at head and at base. It runs tsc --noEmit, the scripts tsconfig, and check:test-typecheck.

  • eslint over the five touched source and test files: exit 0.

  • pnpm --filter @objectstack/plugin-security build: exit 0. It produced the .d.ts and the bundles measured above.

  • row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts (@objectstack/core): 8 of 8 pass at head and at base.

  • dispatch-gates --commands: 101 commands. The --ran reconciliation shows 101 derived, 101 run, 0 NOT-MEASURED and 0 UNRUN. The zero is derived: all 101 recorded an exit code, and none is 3.

  • Six commands are not derived for this change set. Each was run by hand:

    • node scripts/check-adr-symbol-anchors.mjs;
    • check-platform-checklist, both the script and pnpm check:platform-checklist;
    • the two census --json reads;
    • pnpm --filter @objectstack/spec check:liveness.

    pnpm check:adr-anchors, check-scripts-symbol-anchors and its --self-test, and pnpm check:query-options-erasure are derived for this change set this time.

  • Nine more were run at base and at head:

    • Ten artifact-roster families keep their roster in a directory this change touches, so dispatch-gates marks their silence as no evidence. Two of them, check:engine-double-contract and check:i18n-stale-fill, are derived anyway. The other eight were run: check-published-list-mirrors and its --self-test, check:authz-resolver, check:console-injection, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports and check-dts-references --self-test.
    • The ninth is the package typecheck.

    All of them exit 0 at both ends.

  • Base against head, all 118 commands. All 118 exit 0 at head and at base. 88 give identical output at both, once the worktree path and timings are masked. The other 30, each read line by line:

    • These move as described above: check:adr-anchors, check-system-context-census and the two census --json reads. The tenant read moves only by scannedSources and its own ref.
    • The plugin-security suite's console output is not stable between runs: log lines from parallel workers, generated ids and timestamps. Its stack frames into security-plugin.ts carry the file's new line numbers. Its counts are the same at both ends (above).
    • check-issue-citations judges a change set, so it has nothing to read at base. At head it judged 22 citations across 3 files, and all resolve.
    • These counts move by the two modules (and the anchor entry, for the whole-index readers):
      • check-closing-keyword-parity 12065 to 12068 tracked files;
      • check:gitlink-declared 12065 to 12068 index entries;
      • check:nul-bytes 12056 to 12059;
      • check-comment-mask-corpus 10030 to 10032;
      • check-plugin-teardown-shape 9421 to 9423 sources;
      • check-position-name-fold-loaders 12399 to 12402 files, with the same 8 references;
      • check-undeclared-dep-imports 3326 to 3328 files;
      • the spec package's check:duration-unit-keys 3331 to 3333;
      • check:closed-ceremony-text 4594 to 4596;
      • check:doc-authoring 1445 to 1447 parsed sources;
      • check:engine-double-contract 3636 to 3638 production files;
      • check:error-status-conformance 3424 to 3426, with the same 510 producer sites;
      • check:error-code-casing 9404 to 9406;
      • check:logger-receiver-detach 3644 to 3646;
      • check:org-identifier 3669 to 3671;
      • check:page-declaration-shape 3677 to 3679;
      • check:refd-timer-probe 10023 to 10025;
      • check:type-check-coverage and check:type-check-debt 3553 to 3555 walked sources;
      • check:vendor-version-stamps 10422 to 10424;
      • check:agent-test-spelling 613 to 614 files, with the same tokens and runs;
      • check:pnpm-flag-spellings 454 to 455 files under scripts/ (the anchor entry), with the same scripts read.
    • check-undeclared-dep-imports also reads 2885 to 2890 @objectstack/* specifiers. Those are the ones the two modules import, all declared dependencies. security-plugin.ts keeps every specifier it still uses.
    • check:doc-authoring also reads 105072 to 105157 strings, the strings of the new headers and stubs.
    • check:skill-identifier-liveness reads 140073 to 140077 implementation word tokens. Those are the four words new to the tree: SecurityServiceHost, SeedClaimHost, registerSecurityService and the stub's getter local readClaimTargetAdminUserId. No citation changes.
    • check:merge-driver differs only in its temporary directory names.
  • The Docs Drift Check comment lists pages that name identifiers in the moved text. The change is move-only, so none of them describes behaviour that changed, and no page is edited.

Per command (exit code at head, then at base), the derived first, then the hand-run, the roster and the two suites:

  • node scripts/check-ci-filter-parity.mjs exit 0 (base 0)
  • node scripts/check-closing-keyword-parity.mjs exit 0 (base 0)
  • node scripts/check-closing-keyword-parity.mjs --self-test exit 0 (base 0)
  • node scripts/check-comment-mask-adoption.mjs exit 0 (base 0)
  • node scripts/check-comment-mask-adoption.mjs --self-test exit 0 (base 0)
  • node scripts/check-comment-mask-corpus.mjs exit 0 (base 0)
  • node scripts/check-dev-prereqs.mjs --self-test exit 0 (base 0)
  • node scripts/check-doc-frontmatter.mjs exit 0 (base 0)
  • node scripts/check-doc-frontmatter.mjs --self-test exit 0 (base 0)
  • node scripts/check-doc-route-spelling.mjs --advisory exit 0 (base 0)
  • node scripts/check-doc-route-spelling.mjs --self-test exit 0 (base 0)
  • node scripts/check-docs-section-name.mjs exit 0 (base 0)
  • node scripts/check-docs-section-name.mjs --self-test exit 0 (base 0)
  • node scripts/check-dts-emitted.mjs --self-test exit 0 (base 0)
  • node scripts/check-issue-citations.mjs exit 0 (base 0)
  • node scripts/check-keyed-text-bounds.mjs exit 0 (base 0)
  • node scripts/check-keyed-text-bounds.mjs --self-test exit 0 (base 0)
  • node scripts/check-platform-object-tenancy-census.mjs exit 0 (base 0)
  • node scripts/check-platform-object-tenancy-census.mjs --self-test exit 0 (base 0)
  • node scripts/check-plugin-teardown-shape.mjs exit 0 (base 0)
  • node scripts/check-plugin-teardown-shape.mjs --self-test exit 0 (base 0)
  • node scripts/check-position-name-fold-loaders.mjs exit 0 (base 0)
  • node scripts/check-position-name-fold-loaders.mjs --self-test exit 0 (base 0)
  • node scripts/check-registry-log-declared.mjs exit 0 (base 0)
  • node scripts/check-registry-log-declared.mjs --self-test exit 0 (base 0)
  • node scripts/check-rest-log-spy-declared.mjs exit 0 (base 0)
  • node scripts/check-rest-log-spy-declared.mjs --self-test exit 0 (base 0)
  • node scripts/check-scripts-symbol-anchors.mjs exit 0 (base 0)
  • node scripts/check-scripts-symbol-anchors.mjs --self-test exit 0 (base 0)
  • node scripts/check-section-landing-index.mjs exit 0 (base 0)
  • node scripts/check-section-landing-index.mjs --self-test exit 0 (base 0)
  • node scripts/check-system-context-census.mjs exit 0 (base 0)
  • node scripts/check-system-context-census.mjs --self-test exit 0 (base 0)
  • node scripts/check-tenant-audit-census.mjs exit 0 (base 0)
  • node scripts/check-tenant-audit-census.mjs --self-test exit 0 (base 0)
  • node scripts/check-undeclared-dep-imports.mjs exit 0 (base 0)
  • node scripts/check-undeclared-dep-imports.mjs --self-test exit 0 (base 0)
  • node scripts/ci/assert-turbo-remote-cache.mjs --self-test exit 0 (base 0)
  • node scripts/docs-audit/check-affected-docs.mjs exit 0 (base 0)
  • node scripts/docs-audit/check-drift-comment.mjs exit 0 (base 0)
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0 (base 0)
  • pnpm --filter @objectstack/lint run check:doc-security-posture exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:docs exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:duration-unit-keys exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:empty-state exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:liveness exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:skill-examples exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:strictness-ledger exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:variant-docs exit 0 (base 0)
  • pnpm --filter @objectstack/spec run check:yaml-examples exit 0 (base 0)
  • pnpm check:adr-anchors exit 0 (base 0)
  • pnpm check:agent-test-spelling exit 0 (base 0)
  • pnpm check:bash32-floor exit 0 (base 0)
  • pnpm check:cli-command-ids exit 0 (base 0)
  • pnpm check:closed-ceremony-text exit 0 (base 0)
  • pnpm check:corpus-claim-drift exit 0 (base 0)
  • pnpm check:cross-package-test-inputs exit 0 (base 0)
  • pnpm check:doc-anchors exit 0 (base 0)
  • pnpm check:doc-authoring exit 0 (base 0)
  • pnpm check:docs-audit-scope exit 0 (base 0)
  • pnpm check:docs-redirects exit 0 (base 0)
  • pnpm check:docs-single-h1 exit 0 (base 0)
  • pnpm check:docs-spec-enumerations exit 0 (base 0)
  • pnpm check:docs-transcript-drift exit 0 (base 0)
  • pnpm check:driver-memory-census exit 0 (base 0)
  • pnpm check:dts-closure exit 0 (base 0)
  • pnpm check:dual-build-cjs-loads exit 0 (base 0)
  • pnpm check:engine-double-contract exit 0 (base 0)
  • pnpm check:entry-guard exit 0 (base 0)
  • pnpm check:error-status-conformance exit 0 (base 0)
  • pnpm check:gitlink-declared exit 0 (base 0)
  • pnpm check:i18n exit 0 (base 0)
  • pnpm check:i18n-stale-fill exit 0 (base 0)
  • pnpm check:issue-citations exit 0 (base 0)
  • pnpm check:kernel-hook-pairs exit 0 (base 0)
  • pnpm check:lean-entry-closure exit 0 (base 0)
  • pnpm check:logger-receiver-detach exit 0 (base 0)
  • pnpm check:merge-driver exit 0 (base 0)
  • pnpm check:nul-bytes exit 0 (base 0)
  • pnpm check:objectql-double-limit exit 0 (base 0)
  • pnpm check:org-identifier exit 0 (base 0)
  • pnpm check:page-declaration-shape exit 0 (base 0)
  • pnpm check:parse-guard exit 0 (base 0)
  • pnpm check:pnpm-filter-targets exit 0 (base 0)
  • pnpm check:pnpm-flag-spellings exit 0 (base 0)
  • pnpm check:published-files exit 0 (base 0)
  • pnpm check:published-readme-links exit 0 (base 0)
  • pnpm check:query-options-erasure exit 0 (base 0)
  • pnpm check:react-page-adapter-contract exit 0 (base 0)
  • pnpm check:refd-timer-probe exit 0 (base 0)
  • pnpm check:role-word exit 0 (base 0)
  • pnpm check:skill-identifier-liveness exit 0 (base 0)
  • pnpm check:slot-lookup exit 0 (base 0)
  • pnpm check:sourcemap-no-sources-content exit 0 (base 0)
  • pnpm check:test-source-alias exit 0 (base 0)
  • pnpm check:tier-file-adoption exit 0 (base 0)
  • pnpm check:type-check-coverage exit 0 (base 0)
  • pnpm check:type-check-debt exit 0 (base 0)
  • pnpm check:vendor-version-stamps exit 0 (base 0)
  • pnpm check:watch-hint-literal exit 0 (base 0)
  • pnpm check:where-matcher exit 0 (base 0)
  • node scripts/check-adr-symbol-anchors.mjs exit 0 (base 0)
  • node scripts/check-platform-checklist.mjs exit 0 (base 0)
  • pnpm check:platform-checklist exit 0 (base 0)
  • node scripts/isystem-census.mjs --json exit 0 (base 0)
  • node scripts/tenant-audit-census.mjs --json exit 0 (base 0)
  • pnpm --filter @objectstack/spec check:liveness exit 0 (base 0)
  • node scripts/check-published-list-mirrors.mjs exit 0 (base 0)
  • pnpm check:authz-resolver exit 0 (base 0)
  • pnpm check:console-injection exit 0 (base 0)
  • pnpm check:error-code-casing exit 0 (base 0)
  • pnpm check:filter-alias-parity exit 0 (base 0)
  • pnpm check:published-readme-exports exit 0 (base 0)
  • pnpm --filter @objectstack/plugin-security typecheck exit 0 (base 0)
  • pnpm --filter @objectstack/plugin-security test exit 0 (base 0)
  • pnpm --filter @objectstack/core exec vitest run src/security/row-serving-door-exposure.pin.test.ts src/security/operation-private-keys.pin.test.ts exit 0 (base 0)
  • node scripts/check-dts-references.mjs --self-test exit 0 (base 0)
  • node scripts/check-published-list-mirrors.mjs --self-test exit 0 (base 0)

Acceptance notes


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa

… the seed-ownership claim out of security-plugin.ts

The one statement of start() that builds the ISecurityService literal
and registers it as the security kernel service moves unedited to
security-plugin-security-service.ts as registerSecurityService. It
closed over start()'s ctx, ql and metadata and nothing else, so it takes
them as parameters; start() keeps the statement's position and its
comment, and calls it from one block that hands it a host literal of
exactly the members it reads. The body of claimSeedOwnershipOnSettle
moves unedited to security-plugin-seed-claim.ts, with the two
seed-settlement readers it shares with the promotion-time pass; they gain
export and nothing else, and security-plugin.ts imports them back. The
method keeps its name, signature, docblock and position. Import names
only the moved code reads leave security-plugin.ts.

The three readers that read the moved lines by path are re-pointed: the
system-context page's write-bypass row names the new module, the
delegator enumeration pin lists it instead of security-plugin.ts, and the
core canReadObject caller pin classifies it. The seed-claim module gets
the anchor entry carrying the ADR-0138 D1 paragraph that names its
bodies. 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 23, 2026
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests labels Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 19:56
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/kernel/contracts/index.mdx (via ISecurityService (symbol, a top-level type))
  • content/docs/kernel/runtime-services/index.mdx (via ISecurityService (symbol, a top-level type))
  • content/docs/kernel/runtime-services/sharing-service.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/access-recipes.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/administrator-guide.mdx (via unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/attachments-access.mdx (via ISecurityService (symbol, a top-level type), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/capabilities.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/index.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/permission-sets.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/permissions-matrix.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/positions.mdx (via own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/sharing-rules.mdx (via getReadFilter (symbol, a field of interface SecurityServiceHost), getReadFilter (literal, a string literal in getReadFilter; a string literal in registerSecurityService; a string literal in start), own_and_reports (literal, a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/permissions/system-context.mdx (via canExport (symbol, a field of interface SecurityServiceHost), canReadObject (symbol, a field of interface SecurityServiceHost), checkControlledByParentWrite (symbol, a field of interface SecurityServiceHost), explainAccessForCaller (symbol, a field of interface SecurityServiceHost), getReadFilter (symbol, a field of interface SecurityServiceHost), getReadableFields (symbol, a field of interface SecurityServiceHost), getWritableFields (symbol, a field of interface SecurityServiceHost), registerSecurityService (symbol, a top-level function), canExport (literal, a string literal in canExport; a string literal in registerSecurityService; a string literal in start), canReadObject (literal, a string literal in canReadObject; a string literal in registerSecurityService; a string literal in start), checkControlledByParentWrite (literal, a string literal in checkControlledByParentWrite; a string literal in registerSecurityService; a string literal in start), explainAccessForCaller (literal, a string literal in explainAccessForCaller; a string literal in registerSecurityService; a string literal in start), getReadFilter (literal, a string literal in getReadFilter; a string literal in registerSecurityService; a string literal in start), getReadableFields (literal, a string literal in getReadableFields; a string literal in registerSecurityService; a string literal in start), getWritableFields (literal, a string literal in getWritableFields; a string literal in registerSecurityService; a string literal in start))

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

  • content/docs/releases/v15.mdx (via getReadFilter (symbol, a field of interface SecurityServiceHost), getReadFilter (literal, a string literal in getReadFilter; a string literal in registerSecurityService; a string literal in start), unit_and_below (literal, a string literal in registerSecurityService; a string literal in start))
  • content/docs/releases/v17/17-0.mdx (via ISecurityService (symbol, a top-level type))
  • content/docs/releases/v17/17-5.mdx (via IObjectQLEngine (symbol, a top-level type), ISecurityService (symbol, a top-level type), canReadObject (symbol, a field of interface SecurityServiceHost), getEffectiveObjectPermissions (symbol, a field of interface SecurityServiceHost), canReadObject (literal, a string literal in canReadObject; a string literal in registerSecurityService; a string literal in start), getEffectiveObjectPermissions (literal, a string literal in getEffectiveObjectPermissions; a string literal in registerSecurityService; a string literal in start))
  • content/docs/releases/v17/17-6.mdx (via ISecurityService (symbol, a top-level type), canExport (symbol, a field of interface SecurityServiceHost), canReadObject (symbol, a field of interface SecurityServiceHost), getMetadataReadableFields (symbol, a field of interface SecurityServiceHost), getQueryableFields (symbol, a field of interface SecurityServiceHost), getReadFilter (symbol, a field of interface SecurityServiceHost), getReadableFields (symbol, a field of interface SecurityServiceHost), getWritableFields (symbol, a field of interface SecurityServiceHost), canExport (literal, a string literal in canExport; a string literal in registerSecurityService; a string literal in start), canReadObject (literal, a string literal in canReadObject; a string literal in registerSecurityService; a string literal in start), getMetadataReadableFields (literal, a string literal in getMetadataReadableFields; a string literal in registerSecurityService; a string literal in start), getQueryableFields (literal, a string literal in getQueryableFields; a string literal in registerSecurityService; a string literal in start), getReadFilter (literal, a string literal in getReadFilter; a string literal in registerSecurityService; a string literal in start), getReadableFields (literal, a string literal in getReadableFields; a string literal in registerSecurityService; a string literal in start), getWritableFields (literal, a string literal in getWritableFields; a string literal in registerSecurityService; a string literal in start))
  • content/docs/releases/v17/17-7.mdx (via ISecurityService (symbol, a top-level type))
  • content/docs/releases/v18/18-0.mdx (via ISecurityService (symbol, a top-level type))

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 1ff43b67ea739739ef0870fe5dede5afe51f15f3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d6f7597e1250dcf37a1d90babb7dc79ad2d811f9 — the merge of head 5a4747b9c7ca0060ff927391485247b6f0e5855b into base 1ff43b67ea739739ef0870fe5dede5afe51f15f3, 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 d6f7597e1250dcf37a1d90babb7dc79ad2d811f9 && git checkout d6f7597e1250dcf37a1d90babb7dc79ad2d811f9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1ff43b67ea739739ef0870fe5dede5afe51f15f3 5a4747b9c7ca0060ff927391485247b6f0e5855b && git checkout -B drift-repro 1ff43b67ea739739ef0870fe5dede5afe51f15f3 && git merge --no-ff 5a4747b9c7ca0060ff927391485247b6f0e5855b

node scripts/docs-audit/affected-docs.mjs --json 1ff43b67ea739739ef0870fe5dede5afe51f15f3

⚠️ 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 1ff43b67ea739739ef0870fe5dede5afe51f15f3 → 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-23T20:58Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 cut 11, start() R2 and the seed claim (claim 6373426297; os-dev-report 6374531569) · reviewed head 5a4747b9c7 · merge base 82f5a111b4

  • Shape: this is the first start() cut under the seat's plan reply.
    • start()'s security-service try statement becomes registerSecurityService(ctx, ql, metadata) in security-plugin-security-service.ts, called as .call(host, …) in place.
    • claimSeedOwnershipOnSettle's body and the two seed-settlement readers move to security-plugin-seed-claim.ts. The readers move rather than gaining export, which avoids the cut-6 trap.
    • 7 files, +629 / −381.
  • Seat check, host.
    • The seat read the host literal at security-plugin.ts:2213. Every method is a late-bound closure ((...a) => this.m(...a)). The five mutable fields (delegatedAdminGate, logger, permissionEvaluator, ql, tenancyPosture) are getters.
    • The only by-value members are acceptedDefaultPermissionSets and ownershipFloorAlternates, and both are declared private readonly (:1326, :1625).
    • The host outlives the call, because the service closures keep it. That is safe for the same reason: each read goes through a getter or closure at call time. The cut 1 rule holds.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree:
    • turbo run build --filter=@objectstack/plugin-security at 82f5a111b4 and at this head.
    • At both, dist/index.d.ts and dist/index.d.mts give raw 5e7e8df69375e660… / normalised 1e2e8299c2b3aa85…. The raw diff is 0 lines.
    • The seat's positive control makes head line 94, private permissionEvaluator;, public. That moves both hashes, to raw 6a1a7f630f039ae2… and normalised 90bf6646eb64659b….
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: byte-identical output.
    • check-tenant-audit-census: byte-identical output, 238 sites.
    • check-system-context-census: 149 sites at both. Files go 76 → 77, symbols 128 → 129 and page citations 141 → 142, because row 7's read now sits in registerSecurityService.
    • check-platform-checklist
    • check-adr-anchors
  • Decision-letter count, read by the seat: 1970 at base and 1958 at head, with anchored files 82 → 83.
    • R2's own 13 letters leave the anchored surface.
    • The new security-plugin-seed-claim.ts entry carries ADR-0138 D1 verbatim, which adds 1.
    • The card thread's scratch merge with 74496c64b0 reads 1959, against 1971 on that main alone. Accepted as measured.
  • Readers re-pointed in this PR, each shown to fail when reverted (report):
  • Tests and gates (report): 4573 passed / 45 skipped at both ends. 118 commands exit 0, and --ran reconciles 101 / 101.
  • CI on this head: 40 success and 14 expected skips, none failed or pending (54 runs).
  • Mergeable: git merge-tree onto today's main (74496c64b0) is clean.
  • Body: the attribution header and the os-sales reviewer are in place. The footers are present.
  • Declarations: refactor(plugin-security), Part of #25223, Clause-②: no, Metadata-protocol: none, Ruling-ref: 6353073966. No contract-review record or dogfood battery is owed.
  • Landing: not governed (check-governed-merges --pr 25530: 1,010 lines). Ready + auto-merge through the relay. The card stays open and dispatched.
  • Next: cut 12 (R3, the authz middleware) is Tier H. It is claimed afresh on the merged main. The seat will re-measure and ACCEPT it, but will not ready, queue or arm it.

Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants