Repository navigation
refactor(plugin-security): move the read filters, field readers and object admissions out of security-plugin.ts (#25223 cut 9) - #25456
Conversation
…bject admissions out of security-plugin.ts The bodies of getReadFilter and computeDelegatedReadFilter (the row-level read scope and its on-behalf-of intersection) move unedited to security-plugin-read-filter.ts. The bodies of resolveProjectionFieldMask, computeReadableFields and resolveCallerPosture (the field projections' shared derivation, the readable projection and the caller's posture) move unedited to security-plugin-field-readers.ts, with the one declaration and the one import name only they read. The bodies of canReadObject, objectReadAdmission, canWriteObject and canExport move unedited to security-plugin-object-admission.ts. Each method keeps its name, signature, docblock and position and hands its body one host literal of exactly the members it reads. The four thin field projections stay whole. The two readers that read the moved lines by path are re-pointed: the system-context page's five elevation-read anchors name the new modules, and the delegator enumeration pin lists the three new files. Move-only: no verdict changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
The metadata-plane half of the ADR-0106 D7 paragraph, the configured fallback set a caller with no set resolves, is the fallbackOnEmptySets branch of resolveProjectionFieldMask, whose body moved to security-plugin-field-readers.ts. The new entry carries ADR-0106 and that paragraph of the security-plugin.ts entry verbatim, as the cbp and wall module entries carry theirs. The security-plugin.ts entry is unchanged: getReadableFields and getMetadataReadableFields stay there. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
📓 Docs Drift CheckThis PR changes 1 package(s): 31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 69a135f2e9fd02edaad67fe15314aadac097b39a && git checkout 69a135f2e9fd02edaad67fe15314aadac097b39a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc b4ced7f87e2954d1041841b25405346d813c67a5 && git checkout -B drift-repro 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc && git merge --no-ff b4ced7f87e2954d1041841b25405346d813c67a5
node scripts/docs-audit/affected-docs.mjs --json 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc
|
席位核对 · ACCEPT · 2026-10-23T15:27Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25223
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6353073966
Cut 9 of the
security-plugin.tssplit: row 7 of the series plan, the read filters, the field readers and the object admissions. The claim is 6365993271. The row has three seams, so the bodies of nine methods move unedited out of the class to three new modules in this one PR, as cuts 6 and 8 split theirs over two.To
packages/plugins/plugin-security/src/security-plugin-read-filter.ts:getReadFilter(public), the row-level read scope thesecurityservice serves to the doors that compile their own statement: the same three layers the engine middleware ANDs into every find, the deny sentinel for a caller who resolves no set or carries no principal, fail-closed on any resolution failure.computeDelegatedReadFilter, the same scope for an on-behalf-of caller: the principal's leg and the delegator's, intersected layer for layer (ADR-0090 D10, plugin-security: the raw-SQL read-scope provider (getReadFilter) does not apply the delegated organization-membership rule PR #25101 enforces at the operation prologue; measure reach first (detail withheld) #25106).To
packages/plugins/plugin-security/src/security-plugin-field-readers.ts:resolveProjectionFieldMask, the derivation every published field projection shares: the field universe, the caller's sets (the fallback set on the metadata plane), the posture, the field map with the capability fold, and the delegator's intersection (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).computeReadableFields, the readable projection both planes answer from (spec/security: field masking is all-or-nothing — no partial masking (phone last-4, ID middle-8), andmaskingRulewas pruned as dead in 2026-06 #8993).resolveCallerPosture, the one posture a caller's gates read, with the stand-in for a caller who resolved no set (security(plugin-security): for a caller who resolves no permission set, the field-projection answers say no masking rule reaches it, whilemaskingRule's describe and the result masker mask it — which one a public door serves is not measured #20995, security(spec): a capability-gated field (requiredPermissions, no maskingRule) is served stored to a caller who resolves no permission set, while explain reports it hidden; narrowing it moves getReadableFields' contract answer #21063).To
packages/plugins/plugin-security/src/security-plugin-object-admission.ts:canReadObject(public), the object-level read admission the read doors that bypass the middleware ask.objectReadAdmission, that admission's arms over permission sets already resolved.canWriteObject(public), the write admission the write preview asks (formula/objectql: relationship traversal in predicates — a validation rule or visibility predicate reads one hop through a lookup (record.crm_account.type); replaces the removedos.lookupdeclaration (#18318, batch #148) #18682).canExport(public), the user-level export axis (用户级 export 权限轴(接入 P1 预留的 userExportAllowed 槽) #3544).It is move-only, and the card stays open for the later cuts. Branch:
claude/project-thread-3fzya4, the harness-pinned branch for this session, restarted fromorigin/mainat25f88e899a, cut 8's merge. It names no issue, so the claim comment is the identity record.What stays in the class. Each moved method keeps its name, signature, docblock and position, less
async. Every caller still calls the methods: the service binding, the middleware and the write preview's seam instart(), and the four public projections. Four members of the row stay whole:getReadableFields,getMetadataReadableFields,getWritableFieldsandgetQueryableFields. Each body is no longer than its stub would be.Module-level declarations.
EMPTY_REQUIRED_PERMISSIONS, the stand-in's empty capability contract, is read only byresolveCallerPosture's body. It moves with it, unedited, to the field-readers module, as cuts 1 to 4 moved the declarations only their bodies read.NormalizedRequiredPermissions, stays unexported insecurity-plugin.ts, where the posture interface and its normaliser read it. The module names it through the class's own signature:ObjectSecurityMetaasgetObjectSecurityMeta's resolved return type, as cut 1's module does, and the capability-contract type as that posture'srequiredPermissionsfield. So the moved declaration's text is byte-identical.intersectFieldMasks(./explain-engine.js) is read only byresolveProjectionFieldMask's body. It leavessecurity-plugin.ts's import, and the field readers import it from the same specifier. No staying docblock links to it;canWriteObject's docblock names it in backticks only.isPrincipalLessContext(cut 6) andrequiredCapsForOperation.security-plugin.tsgains no export in this cut, so the cut 6.d.tstrap (an export whose signature names another file's type) does not arise. The.d.tswas measured anyway, in a local trial build before the claim: byte-identical to base (below).index.tsstill exports onlySecurityPlugin, so the published surface does not move (measured below).Two readers outside the moved code read the moved lines by path, and both are re-pointed here, as cut 6 re-pointed the same two.
content/docs/permissions/system-context.mdx, rows 3, 4, 5, 6 and 6b.check-system-context-censuskeys each elevation read by file and enclosing symbol. The fivecontext?.isSystemreads of this row (ingetReadFilter,resolveProjectionFieldMask,canExport,canReadObjectandcanWriteObject) now sit in the new modules. A trial cut with the page untouched measured it: the census reports 8 problems, the three new files with no row and five anchors that are no longer read sites. Each of the five anchors keeps its symbol and changes only its path, fromsecurity-plugin.ts#Xto the module's#X. No row's text changes, and no other anchor changes. With that, the census exits 0 (below).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. It counts, per source file, the delegator resolutions and the organization-refusal asks, and lists the files that resolve one. Five moved bodies resolve a delegator, each beside its ask:computeDelegatedReadFilter,resolveProjectionFieldMask,canReadObject,canWriteObjectandcanExport. The expected list gains the three modules, and the title says six files instead of three.security-plugin.ts2 and 2 (one instart(), one inexplainAccessForCaller).One ADR anchor entry is added, in the claim amendment 6366124111: the field-readers module gets an entry carrying ADR-0106 and the ADR-0106 D7 paragraph of the
security-plugin.tsentry, verbatim, because that paragraph's metadata-plane half is a branch of the moved derivation. See the ADR anchors section below.The published
.d.tsdoes not changeUnder the series bar (ruling 6348891160 item 1), measured on base
25f88e899aand on the head. The head build of0feb79ca4fand the fullturbo run buildatb4ced7f87eproduced the same fourdistfiles byte for byte:dist/index.d.tsanddist/index.d.mts, base and head, all four the same:7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da. The raw diff is 0 lines, so no union order moved either.b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4.25f88e899a's dependencies, rebuilt on that commit first. The hashes differ from those cuts 6 to 8 reported becausemainmoved under the series: feat(spec,lint): a bulk action's confirmText is a template slot over count and its params, judged at validate, compile and the publish gate #24947 widened the spec's bulk-actionconfirmTextto a template slot, and the.d.tsinlines that type in three places. The diff between cut 8's head.d.tsand this base is exactly those three hunks.security-plugin.tsgains no export in this cut, and the modules import only values that were already exported.packages/plugins/plugin-security(it sorts the members of each string-literal union and changes nothing else):dist/index.d.ts, each a one-line edit:private computeDelegatedReadFilter;) made public (computeDelegatedReadFilter;): rawb46fd6c3c1ffaf2ede883a72a5d6fee1c692d38ab4d504639667880a0236ac09, normalisedcadb75e96b0419b7bddb047925e7e3e8fab28abbed6e5ab161bd8f2a0d8a2bf6.private computeDelegatedReadFilterRenamed;): raw1a5427bc1e748676c425f150b4e402dc29823002fe9e10389c7720c1106367dc, normalised365336182c2d65b2021c3844251cc211787143f0fdbcb60af1418ed1e8daf73a.canExportsignature, withcontext?: anychanged tocontext?: unknown: rawf0088fc10e9713f29fc84a6ec7fde84f3b5005556ca001ceb7bae565f20d6ab0, normalisedf8bfba0031d62ff891175079526ff10f4017ffd0cf7148c61e220d1092777115..d.ts:getReadFilter(line 801),canReadObject(1045),canWriteObject(1227) andcanExport(1246). The five private ones carry their name and docblock only:computeDelegatedReadFilter(839),computeReadableFields(896),resolveProjectionFieldMask(950),resolveCallerPosture(995) andobjectReadAdmission(1080). Those lines are the same in both files.requireandimport()of the built bundles), not parsed. The sorted name list hashes83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116on all four, as at cuts 6 to 8.index.js3a109ef0todfa83a54,index.mjs1c38ee91to18c4d919), because code moved within them. Every value the new modules import comes from a file inside this package, so the set of external specifiers each bundle loads is the same at base and head (16 distinctrequirespecifiers inindex.js, 17 distinctfromspecifiers inindex.mjs).No changeset:
skip-changeset, per ruling 6348891160 item 4.What moved
The base line numbers are each method's signature and closing brace at
25f88e899a. The last column lists the members the body reads, which is exactly what its host carries.getReadFilter(public, async):5871-:5954computeControlledByParentFilter,computeDelegatedReadFilter,computeRlsFilter,logger,resolvePermissionSetsForContext,resolveSharingReadFiltercomputeDelegatedReadFilter(private, async):5993-:6055computeControlledByParentFilter,computeRlsFilter,getObjectSecurityMeta,logger,permissionEvaluator,ql,resolvePermissionSetsForContext,resolveSharingReadFilter,tenancyPosturecomputeReadableFields(private, async):6119-:6140resolveProjectionFieldMaskresolveProjectionFieldMask(private, async):6207-:6300computeQueryGuardFieldPerms,computeReadPartialMaskRules,foldFieldRequiredPermissions,getObjectFieldNames,metadata,permissionEvaluator,ql,resolveCallerPosture,resolveFallbackPermissionSets,resolvePermissionSetsForContext,tenancyPostureresolveCallerPosture(private, async):6346-:6365getObjectSecurityMetacanReadObject(public, async):6416-:6457logger,objectReadAdmission,ql,resolvePermissionSetsForContext,tenancyPostureobjectReadAdmission(private, async):6493-:6532getObjectSecurityMeta,permissionEvaluatorcanWriteObject(public, async):6680-:6846computeForbiddenFieldWrites,delegatedAdminGate,getObjectSecurityMeta,logger,organizationWallRefusal,permissionEvaluator,ql,resolvePermissionSetsForContext,tenancyPosturecanExport(public, async):6866-:6913getObjectSecurityMeta,permissionEvaluator,ql,resolvePermissionSetsForContext,tenancyPostureEach stub drops
asyncand returns the moved function's promise, as cuts 1 and 3 to 8 did for their async methods. Four of the nine methods are public, so the.d.tscarries their full signatures. A body move does not touch a signature, and those lines do not change (below).The shape (#23050 shape B, as cuts 1 to 8 applied it)
.d.ts. Each body is now one host literal plusfn.call(host, …), marked[#25223 cut 9].ReadFilterHost,FieldReadersHostandObjectAdmissionHostare new. Each types every member as the class member itself (SecurityPlugin['m']). Each moved function types itsthisas thePickof its host over exactly the members its body reads, and the stub annotates its literal with the samePick. There is no cast, and no member becomes public or protected.this.m(...a)on the plugin at call time. A spy or test double set on the plugin is still the one the body reaches:readPartialMaskRules,readQueryGuardPerms) still call the plugin when they are called;No test replaces a moved method on the plugin instance today; one that did would still replace it for every caller. No moved body calls a function-valued field, so no receiver changes.
delegatedAdminGate,logger,metadata,permissionEvaluatorandqlare objects whose own methods the bodies call.readonly(delegatedAdminGate,logger,metadata,permissionEvaluator,ql,tenancyPosture), so all go as getters, and each read happens when the body makes it.start()assigns all of them butpermissionEvaluatorafter construction, and tests replacepermissionEvaluatoron a constructed plugin.readonly, so a moved body cannot assign a member through them. The typecheck passes, so none does.Proof of move-only
asyncmatch.resolveProjectionFieldMask's multi-line return type, with the docblock of its member inside it, is repeated on the function one indentation level in, as cut 7 did; its 82 tokens are equal. Three negative controls fail the check:getReadFilter,if (context?.isSystem) return undefined;changed toif (!context?.isSystem) …;resolveProjectionFieldMask, the system answerfields: allFieldschanged tofields: [];canExport,checkObjectPermission('export', …)changed tocheckObjectPermission('read', …).async. Every other class member, 117 named members plus the constructor, is byte-identical to base. That includes the four staying members above. The class has 127 members at base and at head.security-plugin.ts: 102 of the 104 base top-level statements other than the class are byte-identical to base. The other two:./explain-engine.jsimport losesintersectFieldMasks;EMPTY_REQUIRED_PERMISSIONSis gone from the file. The field-readers module holds the same statement, byte for byte.The three new statements are the imports of the new modules.
git diff --color-moved=plain --color-moved-ws=allow-indentation-change 25f88e899a HEADover the four source files): of the 538 deleted lines, git sees 528 as moved. The other 10 are the nine signatures that loseasyncand the import lineintersectFieldMasks,. Of the 1,044 added lines, 561 show as moved, all in the three new modules: 524 of the 526 body lines, the 3 lines ofEMPTY_REQUIRED_PERMISSIONS, and 34 lines whose text git also finds among the deleted lines (20 blank lines, 12 closing braces, 2 lines of the new function signatures). The 2 body lines git leaves uncoloured are the closing};ofresolveProjectionFieldMask's andresolveCallerPosture's return literals, which git pairs with a stub's};of the same text rather than with a deletion. The other 483 added lines are the modules' headers, imports, host interfaces, type aliases and function signatures, the nine stubs, and the three new imports with their comments.security-plugin.tsgoes from 9,248 to 8,852 lines;security-plugin-read-filter.tshas 241,security-plugin-field-readers.ts252 andsecurity-plugin-object-admission.ts409. The other three are the page (+5 / -5), the pin (+9 / -2) and the new anchor entry (+7).ADR anchors (ruling 6353073966 item 2)
node scripts/check-adr-symbol-anchors.mjs: exit 0 at base25f88e899aand exit 0 at head, both with2384 anchors across 142 records resolve — 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation. CI runs this gate (lint.yml), butdispatch-gates --commandsdoes not derive it for this change set ([finding] dispatch-gates derives the symbol-anchor sweeps only from their corpus paths, so a change that removes an anchored symbol from a TARGET file passes every local gate and reds CI #21697), so it is run by hand here.pnpm check:adr-anchors: exit 0 at base and at head. It counts 80 anchored files at base and 81 at head, the new entry below.scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-field-readers.ts.json, and it carries ADR-0106 and the ADR-0106 D7 invariant paragraph of thesecurity-plugin.tsentry, verbatim, as the entries of cuts 2 to 5 carry theirs.getMetadataReadableFieldsandgetReadableFields, which stay. Its metadata-plane half, "the same caller resolves the configured fallback permission set", is thefallbackOnEmptySetsbranch ofresolveProjectionFieldMask, whose body moved here with its[ADR-0106 D7]comment. Cut 4's entry followed the same rule: its module got one because the "reads answer RLS_DENY_FILTER" half of the ADR-0055 §1 paragraph was the leaf it moved.security-plugin.tsentry is unchanged, because the two methods, their docblocks and the flag each passes stay in that file.canWriteObjectonly asks that wall's scope pre-test, as the middleware does. ADR-0138 D1 is the seed-ownership claim. So the read-filter and object-admission modules get no entry, as the modules of cuts 1 and 6 to 8 have none.ADR-NNNN Dkcitations in each anchored file and in each entry's own invariant text.getReadFilter4: ADR-0056 D2, ADR-0090 D10, ADR-0096 D5, ADR-0139 D5;computeDelegatedReadFilter4: ADR-0090 D10 three times, ADR-0123 D2;canReadObject3 andcanExport3: each ADR-0056 D2, ADR-0090 D10, ADR-0096 D5;objectReadAdmission2: ADR-0066 D3, ADR-0090 D10;canWriteObject8: ADR-0056 D2, ADR-0066 D3, ADR-0090 D10 twice, ADR-0090 D12, ADR-0096 D5, ADR-0123 D2 twice.resolveProjectionFieldMask's 3 (ADR-0090 D10, ADR-0096 D5, ADR-0106 D7) leavesecurity-plugin.tsbut stay on the anchored surface, in the field-readers module.docs/adr/**is not touched, and no anchor token is planted.security-plugin.ts: 27 anchors, 18 symbols. It was measured withextractAnchorsandsymbolSegmentResolutionfromscripts/symbol-anchors.mjs, which is measurement, not a new gate. It was read on25f88e899aBEFORE the claim, with the whole cut applied as a local trial: no anchor changed class. Every anchor resolves at head insecurity-plugin.tswith its base class.The table gives each anchor's class at base and head and the line its own declaration resolves on. Every anchor stays in
security-plugin.ts.#CBP_MAX_CHAIN_DEPTH:688(top-level):712(top-level)#SecurityPlugin:1320(class):1344(class)#SecurityPlugin.getMetadataReadableFields:6115(method):6030(method)#SecurityPlugin.resolvePermissionSetsForContextUnmemoized:7020(method):6624(method)#assertControlledByParentWrite:8505(method):8109(method)#assertMasterRowEditable:8631(method):8235(method)#collectRLSPolicies:8685(method):8289(method)#composeWriteTenantWall:7843(method):7447(method)#computeControlledByParentFilter:8151(method):7755(method)#computeDelegatedReadFilter:5993(method):5949(method)#computeRlsFilter:7551(method):7155(method)#getReadFilter:5871(method):5895(method)#hasPlatformAdminPosture:7578(method):7182(method)#isPrincipalLessContext:561(top-level):585(top-level)#manage_metadata:310(top-level):338(top-level)#nav_permission_sets:1748(init):1772(init)#rlsFilter:4322(start):4346(start)#writeCheckPolicies:1215(top-level):1239(top-level)Readers
Two readers outside the moved code read the moved lines by path, the system-context page and the #25107 enumeration pin, and both are re-pointed in this PR (above). No other gate, baseline, docs anchor or test reads the moved lines by path. Each kind was read:
The system-context census.
node scripts/isystem-census.mjs --jsongives the same 148 sites at base and head, compared without line numbers and with the three modules mapped back tosecurity-plugin.ts: 0 differences. Five of the head sites are in the modules, the fivecontext?.isSystemreads the page's re-pointed anchors name:getReadFilter(read-filter),resolveProjectionFieldMask(field-readers), andcanReadObject,canWriteObjectandcanExport(object-admission). The role counts are the same (read153,declaration38,key438,other9), and so are the 5 non-elevation reads and the 22 packages. The files holding a site go from 71 to 74 and the scanned files from 393 to 396, the three modules.check-system-context-censusexits 0 at base and head with the same totals (148 sites, 127 symbols, 140 cited against 140 required, 163 anchors) and files 71 to 74, and its--self-testexits 0 at both. With the page left untouched it would report 8 problems (claim).The tenant-audit census. No moved body holds an engine write: the bodies hand
qlto the delegator resolution and the field universe, andcanWriteObjectasks it forgetSchema.node scripts/tenant-audit-census.mjs --jsongives the same 238 write call sites at base and head, compared the same way: 0 differences, none in the modules, the same totals, the same 174 non-engine subtractions, 0 unresolved and 0 unledgered. OnlyscannedSourcesmoves, 724 to 727: the three modules.check-tenant-audit-censusand its--self-testexit 0 at base and head with the same output, andtenant-audit-census.mdxis not touched.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 is described above. It is part of the plugin-security suite, which passes at head with the same counts as base.
packages/core/src/security/row-serving-door-exposure.pin.test.tsclassifies every non-test caller of the read admission,.canReadObject(. No moved body calls it: thecanReadObjectbody is the admission's definition, not a call.security-plugin.tsstill calls it, fromdescribePositionGrantsand from the service binding instart(), so its row stays true and its "still calls it" check still fires.operation-private-keys.pin.test.tsin the same directory finds every declaration ofwithoutOperationPrivateKeysand the key prefix; no moved body names either. Run on their own at head: 8 of 8 pass. Open PRs test: scope the tree-wide pin scans to the extensions they judge #25389 and test: scope the tree-wide pin scans to the extensions they judge, one shared scan per file #25410 change the first pin's scan scope (an extension-scoped prefilter), not what it discriminates on, and touch none of this cut's files.The spec liveness ledger.
packages/spec/liveness/permission.jsoncitessecurity-plugin.ts#canExportas evidence forallowExport. The anchor resolves on the method, which stays with its docblock, andallowExportis still named insecurity-plugin.ts(in that docblock), so the key-mention reading does not change.pnpm --filter @objectstack/spec check:livenessgives byte-identical output at base and head. The entry's prose,canExportaskingcheckObjectPermission('export'), stays true by method: the body is one call away.packages/spec/**is not touched.Other readers that name the file in code. Besides the two pins above, the code that names
security-plugin.tsby path is: thecheck-system-context-census.mjsrow for the file (keyed onstart, which this cut does not touch), the spec liveness ledger's file-level evidence infield.json, the file'sscripts/adr-anchors/entry, and the citation string inmeasure-position-name-fold-census.mjs. None reads text inside the moved bodies. The tree-wide scanners that read every source file are the gates and censuses below, each run at base and head.The platform checklist.
check-platform-checklistexits 0 at base and head with the same output. No checklist citation names a moved symbol or a local inside a moved body.Symbol anchors outside the ADRs. Every path-qualified
plugin-security/src/security-plugin*.ts#…anchor outsidedocs/adr/was resolved in its own file at base and at head: 32 at both, 0 unresolved. At head, 23 point intosecurity-plugin.ts, the five re-pointed ones into the new modules (1, 1 and 3), and 4 into the cut 5 and cut 3 modules, as at base. The non-ADR corpora that cuts 2 to 8 read (spec liveness, platform checklist,system-context.mdx, specconversions/registry.ts) hold 27 anchors over 17 symbols namingsecurity-plugin.ts; withsymbolSegmentResolution, each resolves at head with its base class: 0 changed.Readers by name, not by path. These were read and none needs a change, because each method still exists in the class under the same name and signature:
getReadFilterorcanReadObject(engine-readable-ids-composition.test.ts,parent-record-read-admission.integration.test.tsand the REST and storage tests). They replace a method on their own fake service object, not on the plugin;computeDelegatedReadFilter, and the system-context page's row text naminggetReadableFields,getWritableFields,canExport,canReadObjectandcanWriteObject, which stay true by method.Line citations: no
security-plugin.ts:Ncitation in the repo points into the moved ranges (base:5871-:6913). The 13 citations found each already pointed at an unrelated line before cut 2, so this cut does not make any of them wrong.Docs Drift Check, re-derived with
node scripts/docs-audit/affected-docs.mjs --json 25f88e899a(advisory): 41 pages. The derivation reads every name in the three added files as touched, so it reaches pages through names the move only relocated (a page can be reached by more than one):requiredPermissions(29 pages), the indexed-access key in the field readers'NormalizedRequiredPermissionsalias;permissionSets(10), a local of the moved bodies;SecurityPlugin(6), each module'simport type;getReadFilter(4),canReadObject(3),canExport(2),canWriteObject(2) andresolveProjectionFieldMask(1).Each of those members keeps its name, signature, docblock and behaviour, so no page goes stale. The one page among them that reads the moved lines by path,
system-context.mdx, is re-pointed in this PR (above), and every path-qualified anchor and line citation into the file was checked separately (above).Census gates, run on base
25f88e899aand head and compared:check-adr-symbol-anchors,check-platform-checklist,check-scripts-symbol-anchors,check-platform-object-tenancy-census,check-tenant-audit-census,check-registry-log-declared,check-rest-log-spy-declared,check:cross-package-test-inputs,check:driver-memory-census,check:query-options-erasureand the spec liveness ledger.check:adr-anchors,check-system-context-censusand the two census--jsonreads move as described above.check-plugin-teardown-shape9384 to 9387 sources,check-undeclared-dep-imports3310 to 3313 files and 2859 to 2862 specifiers (the modules import only relative files and@objectstack/spec, a declared dependency),check:doc-formula-expressions477 to 480 files, the spec package'scheck:duration-unit-keys3315 to 3318,check:closed-ceremony-text4553 to 4556,check:engine-double-contract3619 to 3622 production files,check:error-status-conformance3407 to 3410,check:org-identifier3652 to 3655,check:refd-timer-probe9983 to 9986,check:type-check-coverageandcheck:type-check-debt3536 to 3539 walked sources, andcheck:skill-identifier-liveness139729 to 139733 implementation word tokens.apps/docs/next-env.d.ts(gitignored, in the head worktree only):check-comment-mask-corpus9989 to 9993,check:logger-receiver-detach3626 to 3630,check:page-declaration-shape3659 to 3663.check-closing-keyword-parityandcheck:gitlink-declared11997 to 12001,check:nul-bytes11988 to 11992.check:pnpm-flag-spellingsreads 449 files underscripts/instead of 448 and leaves 123 data files unread instead of 122: the entry is JSON.check:agent-test-spellingreads 608 files instead of 607 (the entry, underscripts/) and 87 manifests instead of 86 (apackage.jsonthe docs build wrote underapps/docs/.next).content/docs/protocol-upgrade/{17,18,index}.md, that the base worktree does not, as at cuts 6 to 8 (CI's checkout does not have them):check-docs-section-name418 to 421 files,check:corpus-claim-drift259 to 262,check:doc-anchorsandcheck:doc-authoring424 to 427,check:react-page-adapter-contract399 to 402,check-doc-route-spelling --advisory745 to 885 literals (0 flagged at both), andcheck:vendor-version-stamps10378 to 10385 files with 7 morebetter-authstamps, all 7 in the generated18.md.check:doc-authoringalso parses 1432 sources instead of 1429, the modules.check-position-name-fold-loaderswalks the working tree: 12350 to 12357 files, with the same 8 references (6 test, 2 declared instrument, 0 loader).check:dts-closureandcheck:sourcemap-no-sources-contentsweep built packages, and the head worktree has more of them built (76 and 72) than the base worktree (19). Both pass at both.check-issue-citationsjudges a change set, so it has nothing to read at base. At head it judged 56 citations across 4 files, and all resolve.check:merge-driverdiffers only in the names of its temporary directories.check:docs(exit 1, its generated schema is absent untilgen:schema),check:skill-examples,check:dual-build-cjs-loadsandcheck:i18n(exit 3, prerequisite not met), andcheck:docs-transcript-drift(exit 1). The last reads the built@objectstack/lintregistry, and the base worktree's build predated feat(spec,lint): a bulk action's confirmText is a template slot over count and its params, judged at validate, compile and the publish gate #24947's added rule, so it read 50 rules against the pages' 51. That package'sdistin the base worktree was rebuilt later in the same run, and a rerun at base then exits 0 with the head's output.scripts/orpackages/), sodispatch-gatesmarks their silence as no evidence. Each was run at base and head:check-published-list-mirrors,check:authz-resolver,check:console-injection,check:error-code-casing,check:filter-alias-parityandcheck:published-readme-exports. All exit 0 at both with identical output, except thatcheck:error-code-casingscans 9370 files instead of 9367, the modules.Acceptance notes
docs/adr/**, no existingscripts/adr-anchors/entry, no census ledger, and nopackages/spec/**. Outside the package, only two files change: the five anchor paths on the system-context page, and the one new anchor entry.security-plugin.ts, the new module names,system-context.mdx,delegated-organization-membership.test.ts, the census scripts, the liveness ledger or the plugin-security anchor entry.origin/maingained 13 commits, to2581efbcbd. None touches this cut's files. The closest,tenant-audit-census.mdxand its counts file from refactor(plugin-auth): move AuthManager's audience gate and membership admission into auth-manager-membership-admission.ts (#25224 cut 8) #25423 (cut 8 of [maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224), are files this cut reads through its gates and does not edit. On a scratch merge of this head onto2581efbcbd,check-adr-symbol-anchors,check:adr-anchors,check-platform-checklist,check-system-context-census,check-tenant-audit-censusand theisystem-censusread all exit 0. There,check:adr-anchorscounts 81 anchored files and 1978 decision-letter citations, the same as this head, and the system-context census counts 148 sites across 74 files.security-plugin.tsafter this cut: the four projections that stay whole and the nine method shells.security-plugin.tshas 8,852 lines at head, 9,248 at base. Next is row 8 (explain, sharing verdicts, authored row write, baselines), thenstart(), which is Tier H and goes to the seat before any PR.Gates
All gates ran against base
25f88e899a. The package runs (its test, typecheck, eslint and build, and the two core pins) ran on the tree of0feb79ca4f, the first commit. The second commit,b4ced7f87e, adds only the anchor entry JSON underscripts/adr-anchors/, which none of those runs reads. Every other gate ran onb4ced7f87e.pnpm --filter @objectstack/plugin-security test(vitest run --maxWorkers=3, under the verify lock): Test Files 214 passed (214), Tests 4465 passed, 45 skipped (4510). Base25f88e899agives the same counts. VERDICT command-exit 0 on both.pnpm --filter @objectstack/plugin-security typecheck: exit 0. It runstsc --noEmit, the scripts tsconfig, andcheck:test-typecheck.eslint --no-inline-configover the five touched source and test files: exit 0.pnpm --filter @objectstack/plugin-security build: exit 0. It produced the.d.tsand bundles measured above. The package's dependencies were rebuilt on25f88e899afirst.row-serving-door-exposure.pin.test.tsandoperation-private-keys.pin.test.ts(@objectstack/core): 8 of 8 pass.node scripts/check-adr-symbol-anchors.mjs,check-platform-checklist(the script andpnpm check:platform-checklist),pnpm --filter @objectstack/spec check:livenessand the two census--jsonreads. Each was run by hand.pnpm check:adr-anchors,check-scripts-symbol-anchors(and--self-test),pnpm check:query-options-erasureand the spec liveness check (spelledrun check:liveness) are derived for it this time.dispatch-gates --commands: 98 commands. The--ranreconciliation shows 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN. The zero is derived: all 98 recorded an exit code and none is 3. The six hand-run commands outside the derivation are recorded beside them.Per command (exit code), the derived first and the hand-run after:
node scripts/check-ci-filter-parity.mjsexit 0node scripts/check-closing-keyword-parity.mjsexit 0node scripts/check-closing-keyword-parity.mjs --self-testexit 0node scripts/check-comment-mask-adoption.mjsexit 0node scripts/check-comment-mask-adoption.mjs --self-testexit 0node scripts/check-comment-mask-corpus.mjsexit 0node scripts/check-doc-frontmatter.mjsexit 0node scripts/check-doc-frontmatter.mjs --self-testexit 0node scripts/check-doc-route-spelling.mjs --advisoryexit 0node scripts/check-doc-route-spelling.mjs --self-testexit 0node scripts/check-docs-section-name.mjsexit 0node scripts/check-docs-section-name.mjs --self-testexit 0node scripts/check-dts-emitted.mjs --self-testexit 0node scripts/check-issue-citations.mjsexit 0node scripts/check-keyed-text-bounds.mjsexit 0node scripts/check-keyed-text-bounds.mjs --self-testexit 0node scripts/check-platform-object-tenancy-census.mjsexit 0node scripts/check-platform-object-tenancy-census.mjs --self-testexit 0node scripts/check-plugin-teardown-shape.mjsexit 0node scripts/check-plugin-teardown-shape.mjs --self-testexit 0node scripts/check-position-name-fold-loaders.mjsexit 0node scripts/check-position-name-fold-loaders.mjs --self-testexit 0node scripts/check-registry-log-declared.mjsexit 0node scripts/check-registry-log-declared.mjs --self-testexit 0node scripts/check-rest-log-spy-declared.mjsexit 0node scripts/check-rest-log-spy-declared.mjs --self-testexit 0node scripts/check-scripts-symbol-anchors.mjsexit 0node scripts/check-scripts-symbol-anchors.mjs --self-testexit 0node scripts/check-section-landing-index.mjsexit 0node scripts/check-section-landing-index.mjs --self-testexit 0node scripts/check-system-context-census.mjsexit 0node scripts/check-system-context-census.mjs --self-testexit 0node scripts/check-tenant-audit-census.mjsexit 0node scripts/check-tenant-audit-census.mjs --self-testexit 0node scripts/check-undeclared-dep-imports.mjsexit 0node scripts/check-undeclared-dep-imports.mjs --self-testexit 0node scripts/docs-audit/check-affected-docs.mjsexit 0node scripts/docs-audit/check-drift-comment.mjsexit 0pnpm --filter @objectstack/lint run check:doc-formula-expressionsexit 0pnpm --filter @objectstack/lint run check:doc-security-postureexit 0pnpm --filter @objectstack/spec run check:docsexit 0pnpm --filter @objectstack/spec run check:duration-unit-keysexit 0pnpm --filter @objectstack/spec run check:empty-stateexit 0pnpm --filter @objectstack/spec run check:livenessexit 0pnpm --filter @objectstack/spec run check:skill-examplesexit 0pnpm --filter @objectstack/spec run check:strictness-ledgerexit 0pnpm --filter @objectstack/spec run check:variant-docsexit 0pnpm --filter @objectstack/spec run check:yaml-examplesexit 0pnpm check:adr-anchorsexit 0pnpm check:agent-test-spellingexit 0pnpm check:bash32-floorexit 0pnpm check:cli-command-idsexit 0pnpm check:closed-ceremony-textexit 0pnpm check:corpus-claim-driftexit 0pnpm check:cross-package-test-inputsexit 0pnpm check:doc-anchorsexit 0pnpm check:doc-authoringexit 0pnpm check:docs-audit-scopeexit 0pnpm check:docs-redirectsexit 0pnpm check:docs-single-h1exit 0pnpm check:docs-spec-enumerationsexit 0pnpm check:docs-transcript-driftexit 0pnpm check:driver-memory-censusexit 0pnpm check:dts-closureexit 0pnpm check:dual-build-cjs-loadsexit 0pnpm check:engine-double-contractexit 0pnpm check:entry-guardexit 0pnpm check:error-status-conformanceexit 0pnpm check:gitlink-declaredexit 0pnpm check:i18nexit 0pnpm check:i18n-stale-fillexit 0pnpm check:issue-citationsexit 0pnpm check:lean-entry-closureexit 0pnpm check:logger-receiver-detachexit 0pnpm check:merge-driverexit 0pnpm check:nul-bytesexit 0pnpm check:objectql-double-limitexit 0pnpm check:org-identifierexit 0pnpm check:page-declaration-shapeexit 0pnpm check:parse-guardexit 0pnpm check:pnpm-filter-targetsexit 0pnpm check:pnpm-flag-spellingsexit 0pnpm check:published-filesexit 0pnpm check:published-readme-linksexit 0pnpm check:query-options-erasureexit 0pnpm check:react-page-adapter-contractexit 0pnpm check:refd-timer-probeexit 0pnpm check:role-wordexit 0pnpm check:skill-identifier-livenessexit 0pnpm check:slot-lookupexit 0pnpm check:sourcemap-no-sources-contentexit 0pnpm check:test-source-aliasexit 0pnpm check:tier-file-adoptionexit 0pnpm check:type-check-coverageexit 0pnpm check:type-check-debtexit 0pnpm check:vendor-version-stampsexit 0pnpm check:watch-hint-literalexit 0pnpm check:where-matcherexit 0node scripts/check-adr-symbol-anchors.mjsexit 0node scripts/check-platform-checklist.mjsexit 0pnpm check:platform-checklistexit 0node scripts/isystem-census.mjs --jsonexit 0node scripts/tenant-audit-census.mjs --jsonexit 0pnpm --filter @objectstack/spec check:livenessexit 0The six roster gates run by hand (above):
node scripts/check-published-list-mirrors.mjsexit 0pnpm check:authz-resolverexit 0pnpm check:console-injectionexit 0pnpm check:error-code-casingexit 0pnpm check:filter-alias-parityexit 0pnpm check:published-readme-exportsexit 0Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa