Skip to content

refactor(plugin-security): move the read filters, field readers and object admissions out of security-plugin.ts (#25223 cut 9) - #25456

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

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3fzya4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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

Cut 9 of the security-plugin.ts split: 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:

To packages/plugins/plugin-security/src/security-plugin-field-readers.ts:

To packages/plugins/plugin-security/src/security-plugin-object-admission.ts:

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 from origin/main at 25f88e899a, 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 in start(), and the four public projections. Four members of the row stay whole: getReadableFields, getMetadataReadableFields, getWritableFields and getQueryableFields. 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 by resolveCallerPosture's body. It moves with it, unedited, to the field-readers module, as cuts 1 to 4 moved the declarations only their bodies read.
    • Its type, NormalizedRequiredPermissions, stays unexported in security-plugin.ts, where the posture interface and its normaliser read it. The module names it through the class's own signature: ObjectSecurityMeta as getObjectSecurityMeta's resolved return type, as cut 1's module does, and the capability-contract type as that posture's requiredPermissions field. So the moved declaration's text is byte-identical.
  • The import name intersectFieldMasks (./explain-engine.js) is read only by resolveProjectionFieldMask's body. It leaves security-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.
  • The modules import two values that are already exported: isPrincipalLessContext (cut 6) and requiredCapsForOperation. security-plugin.ts gains no export in this cut, so the cut 6 .d.ts trap (an export whose signature names another file's type) does not arise. The .d.ts was measured anyway, in a local trial build before the claim: byte-identical to base (below).
  • Each value import is read only inside a function body, at call time, never while any of the four modules evaluates. index.ts still exports only SecurityPlugin, 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-census keys each elevation read by file and enclosing symbol. The five context?.isSystem reads of this row (in getReadFilter, resolveProjectionFieldMask, canExport, canReadObject and canWriteObject) 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, from security-plugin.ts#X to 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, canWriteObject and canExport. The expected list gains the three modules, and the title says six files instead of three.
    • The rule it pins, asks equal resolutions in every file, is unchanged. It holds at head: the read filter counts 1 and 1, the field readers 1 and 1, the object admission 3 and 3, and security-plugin.ts 2 and 2 (one in start(), one in explainAccessForCaller).

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.ts entry, verbatim, because that paragraph's metadata-plane half is a branch of the moved derivation. See the ADR anchors section below.

The published .d.ts does not change

Under the series bar (ruling 6348891160 item 1), measured on base 25f88e899a and on the head. The head build of 0feb79ca4f and the full turbo run build at b4ced7f87e produced the same four dist files byte for byte:

  • Raw sha256 of dist/index.d.ts and dist/index.d.mts, base and head, all four the same: 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da. The raw diff is 0 lines, so no union order moved either.
  • Normalised sha256, all four the same: b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4.
  • Base and head are both built against 25f88e899a's dependencies, rebuilt on that commit first. The hashes differ from those cuts 6 to 8 reported because main moved 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-action confirmText to a template slot, and the .d.ts inlines that type in three places. The diff between cut 8's head .d.ts and this base is exactly those three hunks.
  • security-plugin.ts gains no export in this cut, and the modules import only values that were already exported.
  • The normaliser, run from packages/plugins/plugin-security (it 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 controls on a copy of the head dist/index.d.ts, each a one-line edit:
    • Line 839 (private computeDelegatedReadFilter;) made public (computeDelegatedReadFilter;): raw b46fd6c3c1ffaf2ede883a72a5d6fee1c692d38ab4d504639667880a0236ac09, normalised cadb75e96b0419b7bddb047925e7e3e8fab28abbed6e5ab161bd8f2a0d8a2bf6.
    • Line 839 renamed (private computeDelegatedReadFilterRenamed;): raw 1a5427bc1e748676c425f150b4e402dc29823002fe9e10389c7720c1106367dc, normalised 365336182c2d65b2021c3844251cc211787143f0fdbcb60af1418ed1e8daf73a.
    • Line 1246, the public canExport signature, with context?: any changed to context?: unknown: raw f0088fc10e9713f29fc84a6ec7fde84f3b5005556ca001ceb7bae565f20d6ab0, normalised f8bfba0031d62ff891175079526ff10f4017ffd0cf7148c61e220d1092777115.
    • All three differ from the real hashes, so the comparison does see a visibility change, a rename and a public signature change.
  • The four public methods carry their full signatures and docblocks in the .d.ts: getReadFilter (line 801), canReadObject (1045), canWriteObject (1227) and canExport (1246). The five private ones carry their name and docblock only: computeDelegatedReadFilter (839), computeReadableFields (896), resolveProjectionFieldMask (950), resolveCallerPosture (995) and objectReadAdmission (1080). Those lines are the same in both files.
  • Export tables: the ESM and CJS bundles each export the same 66 names at base and head. The lists were loaded at runtime (require and import() of the built bundles), not parsed. The sorted name list hashes 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 on all four, as at cuts 6 to 8.
  • The bundle JS bytes differ (index.js 3a109ef0 to dfa83a54, index.mjs 1c38ee91 to 18c4d919), 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 distinct require specifiers in index.js, 17 distinct from specifiers in index.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.

method base lines module host members
getReadFilter (public, async) :5871-:5954 read-filter computeControlledByParentFilter, computeDelegatedReadFilter, computeRlsFilter, logger, resolvePermissionSetsForContext, resolveSharingReadFilter
computeDelegatedReadFilter (private, async) :5993-:6055 read-filter computeControlledByParentFilter, computeRlsFilter, getObjectSecurityMeta, logger, permissionEvaluator, ql, resolvePermissionSetsForContext, resolveSharingReadFilter, tenancyPosture
computeReadableFields (private, async) :6119-:6140 field-readers resolveProjectionFieldMask
resolveProjectionFieldMask (private, async) :6207-:6300 field-readers computeQueryGuardFieldPerms, computeReadPartialMaskRules, foldFieldRequiredPermissions, getObjectFieldNames, metadata, permissionEvaluator, ql, resolveCallerPosture, resolveFallbackPermissionSets, resolvePermissionSetsForContext, tenancyPosture
resolveCallerPosture (private, async) :6346-:6365 field-readers getObjectSecurityMeta
canReadObject (public, async) :6416-:6457 object-admission logger, objectReadAdmission, ql, resolvePermissionSetsForContext, tenancyPosture
objectReadAdmission (private, async) :6493-:6532 object-admission getObjectSecurityMeta, permissionEvaluator
canWriteObject (public, async) :6680-:6846 object-admission computeForbiddenFieldWrites, delegatedAdminGate, getObjectSecurityMeta, logger, organizationWallRefusal, permissionEvaluator, ql, resolvePermissionSetsForContext, tenancyPosture
canExport (public, async) :6866-:6913 object-admission getObjectSecurityMeta, permissionEvaluator, ql, resolvePermissionSetsForContext, tenancyPosture

Each stub drops async and 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.ts carries 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)

  • The class keeps each method with its name, signature, docblock and position, because tsup emits members with their JSDoc into the .d.ts. Each body is now one host literal plus fn.call(host, …), marked [#25223 cut 9].
  • ReadFilterHost, FieldReadersHost and ObjectAdmissionHost 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 body 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. A spy or test double set on the plugin is still the one the body reaches:
    • the read scope still asks the plugin's RLS filter, controlled-by-parent leaf, sharing filter and set resolution;
    • the projection derivation still asks the plugin's field universe, fallback sets, posture and field folds, and the two closures it returns (readPartialMaskRules, readQueryGuardPerms) still call the plugin when they are called;
    • the admissions still ask the plugin's arms, organization wall and forbidden-field check.
      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, permissionEvaluator and ql are objects whose own methods the bodies call.
  • Fields follow the rule cut 1 set, on each field's own declaration. None of the six fields the bodies read is declared 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 but permissionEvaluator after construction, and tests replace permissionEvaluator on a constructed plugin.
    • The hosts declare every field readonly, so a moved body cannot assign a member through them. The typecheck passes, so none does.
  • Nothing re-exports any of the modules, so the package entry does not move.

Proof of move-only

  • Bodies, by AST token: each moved function's body has the same TypeScript token sequence as the base method body, and each of the 526 body lines is the base line with two leading spaces removed. Parameters, return types and async match. 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:
    • in getReadFilter, if (context?.isSystem) return undefined; changed to if (!context?.isSystem) …;
    • in resolveProjectionFieldMask, the system answer fields: allFields changed to fields: [];
    • in canExport, checkObjectPermission('export', …) changed to checkObjectPermission('read', …).
  • Class side: each moved method keeps its docblock and signature, less 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.
  • Top level of security-plugin.ts: 102 of the 104 base top-level statements other than the class are byte-identical to base. The other two:
    • the ./explain-engine.js import loses intersectFieldMasks;
    • EMPTY_REQUIRED_PERMISSIONS is 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.
  • The two re-pointed readers: the page diff is five lines, each one anchor's path. The pin diff is the title's count and the expected list.
  • git's own view (git diff --color-moved=plain --color-moved-ws=allow-indentation-change 25f88e899a HEAD over the four source files): of the 538 deleted lines, git sees 528 as moved. The other 10 are the nine signatures that lose async and the import line intersectFieldMasks,. 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 of EMPTY_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 }; of resolveProjectionFieldMask's and resolveCallerPosture'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.
  • Size: 1,610 changed lines (+1,065 / -545) over 7 files, under the 3,000-line manual-merge line. security-plugin.ts goes from 9,248 to 8,852 lines; security-plugin-read-filter.ts has 241, security-plugin-field-readers.ts 252 and security-plugin-object-admission.ts 409. 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 base 25f88e899a and exit 0 at head, both with 2384 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), but dispatch-gates --commands does 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.
  • One new entry, in the claim amendment 6366124111. It is 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 the security-plugin.ts entry, verbatim, as the entries of cuts 2 to 5 carry theirs.
    • That paragraph names getMetadataReadableFields and getReadableFields, which stay. Its metadata-plane half, "the same caller resolves the configured fallback permission set", is the fallbackOnEmptySets branch of resolveProjectionFieldMask, 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.
    • The security-plugin.ts entry is unchanged, because the two methods, their docblocks and the flag each passes stay in that file.
    • No other paragraph describes a moved body. ADR-0055 §1 is the controlled-by-parent relation (cuts 2 to 4). ADR-0123 D2 is the write wall's no-organization refusal (cut 5); canWriteObject only 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.
  • The decision-letter count on the anchored surface goes from 2001 to 1978. That gate counts ADR-NNNN Dk citations in each anchored file and in each entry's own invariant text.
    • 24 citations leave the anchored surface with the read-filter and object-admission bodies:
      • getReadFilter 4: ADR-0056 D2, ADR-0090 D10, ADR-0096 D5, ADR-0139 D5;
      • computeDelegatedReadFilter 4: ADR-0090 D10 three times, ADR-0123 D2;
      • canReadObject 3 and canExport 3: each ADR-0056 D2, ADR-0090 D10, ADR-0096 D5;
      • objectReadAdmission 2: ADR-0066 D3, ADR-0090 D10;
      • canWriteObject 8: 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) leave security-plugin.ts but stay on the anchored surface, in the field-readers module.
    • One arrives: the new entry's invariant is the ADR-0106 D7 paragraph, and it names its own decision letter once. The three new import comments and the module headers cite none.
    • The amendment 6366124111 gave 1977. It left out that one citation in the entry's own text, which the gate counts. The measured head count is 1978.
    • Every moved citation is still in the tree, in the module its body moved to. The tree-wide citations that resolve go from 52175 across 6636 files at base to 52179 across 6640 at head. The three modules add files and no citations, and the new entry adds one file and four citations.
  • docs/adr/** is not touched, and no anchor token is planted.
  • The anchor map covers every ADR symbol anchor naming security-plugin.ts: 27 anchors, 18 symbols. It was measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs, which is measurement, not a new gate. It was read on 25f88e899a BEFORE the claim, with the whole cut applied as a local trial: no anchor changed class. Every anchor resolves at head in security-plugin.ts with 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.

anchor cited at base class · line (member) head class · line (member)
#CBP_MAX_CHAIN_DEPTH ADR-0055:135 declaration · :688 (top-level) declaration · :712 (top-level)
#SecurityPlugin ADR-0049:49, ADR-0138:248, ADR-0138:265 declaration · :1320 (class) declaration · :1344 (class)
#SecurityPlugin.getMetadataReadableFields ADR-0138:153 declaration · :6115 (method) declaration · :6030 (method)
#SecurityPlugin.resolvePermissionSetsForContextUnmemoized ADR-0138:427 declaration · :7020 (method) declaration · :6624 (method)
#assertControlledByParentWrite ADR-0055:58, ADR-0055:125 declaration · :8505 (method) declaration · :8109 (method)
#assertMasterRowEditable ADR-0055:125 declaration · :8631 (method) declaration · :8235 (method)
#collectRLSPolicies ADR-0105:93 declaration · :8685 (method) declaration · :8289 (method)
#composeWriteTenantWall ADR-0123:4 declaration · :7843 (method) declaration · :7447 (method)
#computeControlledByParentFilter ADR-0055:5, ADR-0055:123, ADR-0139:246 declaration · :8151 (method) declaration · :7755 (method)
#computeDelegatedReadFilter ADR-0139:223 declaration · :5993 (method) declaration · :5949 (method)
#computeRlsFilter ADR-0055:24 declaration · :7551 (method) declaration · :7155 (method)
#getReadFilter ADR-0096:181, ADR-0096:182, ADR-0096:183, ADR-0096:199, ADR-0139:222 declaration · :5871 (method) declaration · :5895 (method)
#hasPlatformAdminPosture ADR-0099:317 declaration · :7578 (method) declaration · :7182 (method)
#isPrincipalLessContext ADR-0138:152 declaration · :561 (top-level) declaration · :585 (top-level)
#manage_metadata ADR-0099:68 literal · :310 (top-level) literal · :338 (top-level)
#nav_permission_sets ADR-0126:406 literal · :1748 (init) literal · :1772 (init)
#rlsFilter ADR-0055:24 declaration · :4322 (start) declaration · :4346 (start)
#writeCheckPolicies ADR-0066:112 declaration · :1215 (top-level) declaration · :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 --json gives the same 148 sites at base and head, compared without line numbers and with the three modules mapped back to security-plugin.ts: 0 differences. Five of the head sites are in the modules, the five context?.isSystem reads the page's re-pointed anchors name: getReadFilter (read-filter), resolveProjectionFieldMask (field-readers), and canReadObject, canWriteObject and canExport (object-admission). The role counts are the same (read 153, declaration 38, key 438, other 9), 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-census exits 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-test exits 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 ql to the delegator resolution and the field universe, and canWriteObject asks it for getSchema. node scripts/tenant-audit-census.mjs --json gives 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. Only scannedSources moves, 724 to 727: the three modules. check-tenant-audit-census and its --self-test exit 0 at base and head with the same output, and tenant-audit-census.mdx is 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.ts classifies every non-test caller of the read admission, .canReadObject(. No moved body calls it: the canReadObject body is the admission's definition, not a call. security-plugin.ts still calls it, from describePositionGrants and from the service binding in start(), so its row stays true and its "still calls it" check still fires. operation-private-keys.pin.test.ts in the same directory finds every declaration of withoutOperationPrivateKeys and 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.json cites security-plugin.ts#canExport as evidence for allowExport. The anchor resolves on the method, which stays with its docblock, and allowExport is still named in security-plugin.ts (in that docblock), so the key-mention reading does not change. pnpm --filter @objectstack/spec check:liveness gives byte-identical output at base and head. The entry's prose, canExport asking checkObjectPermission('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.ts by path is: the check-system-context-census.mjs row for the file (keyed on start, which this cut does not touch), the spec liveness ledger's file-level evidence in field.json, the file's scripts/adr-anchors/ entry, and the citation string in measure-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-checklist exits 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 outside docs/adr/ was resolved in its own file at base and at head: 32 at both, 0 unresolved. At head, 23 point into security-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, spec conversions/registry.ts) hold 27 anchors over 17 symbols naming security-plugin.ts; with symbolSegmentResolution, 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:

    • the behaviour tests of the read scope, the projections, the admissions and the export axis in this package. Each drives the plugin; none calls or replaces a moved method directly;
    • stand-ins in other packages that offer their own getReadFilter or canReadObject (engine-readable-ids-composition.test.ts, parent-record-read-admission.integration.test.ts and the REST and storage tests). They replace a method on their own fake service object, not on the plugin;
    • ADR-0139's prose naming computeDelegatedReadFilter, and the system-context page's row text naming getReadableFields, getWritableFields, canExport, canReadObject and canWriteObject, which stay true by method.
  • Line citations: no security-plugin.ts:N citation 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' NormalizedRequiredPermissions alias;
    • permissionSets (10), a local of the moved bodies;
    • SecurityPlugin (6), each module's import type;
    • the moved methods' own names: getReadFilter (4), canReadObject (3), canExport (2), canWriteObject (2) and resolveProjectionFieldMask (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 25f88e899a and head and compared:

    • All 104 commands (the 98 derived and the 6 hand-run below) exit 0 at head. 63 give identical output at base and head, once the worktree path and timings are masked. That includes 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-erasure and the spec liveness ledger.
    • The other 41, each read line by line:
      • check:adr-anchors, check-system-context-census and the two census --json reads move as described above.
      • Scanned counts that move by the three modules: check-plugin-teardown-shape 9384 to 9387 sources, check-undeclared-dep-imports 3310 to 3313 files and 2859 to 2862 specifiers (the modules import only relative files and @objectstack/spec, a declared dependency), check:doc-formula-expressions 477 to 480 files, the spec package's check:duration-unit-keys 3315 to 3318, check:closed-ceremony-text 4553 to 4556, check:engine-double-contract 3619 to 3622 production files, check:error-status-conformance 3407 to 3410, check:org-identifier 3652 to 3655, check:refd-timer-probe 9983 to 9986, check:type-check-coverage and check:type-check-debt 3536 to 3539 walked sources, and check:skill-identifier-liveness 139729 to 139733 implementation word tokens.
      • Counts that move by four, the modules and the docs build's apps/docs/next-env.d.ts (gitignored, in the head worktree only): check-comment-mask-corpus 9989 to 9993, check:logger-receiver-detach 3626 to 3630, check:page-declaration-shape 3659 to 3663.
      • Tracked-file counts that move by four, the modules and the new anchor entry: check-closing-keyword-parity and check:gitlink-declared 11997 to 12001, check:nul-bytes 11988 to 11992. check:pnpm-flag-spellings reads 449 files under scripts/ instead of 448 and leaves 123 data files unread instead of 122: the entry is JSON. check:agent-test-spelling reads 608 files instead of 607 (the entry, under scripts/) and 87 manifests instead of 86 (a package.json the docs build wrote under apps/docs/.next).
      • Docs counts that move because the head worktree holds three gitignored generated pages, 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-name 418 to 421 files, check:corpus-claim-drift 259 to 262, check:doc-anchors and check:doc-authoring 424 to 427, check:react-page-adapter-contract 399 to 402, check-doc-route-spelling --advisory 745 to 885 literals (0 flagged at both), and check:vendor-version-stamps 10378 to 10385 files with 7 more better-auth stamps, all 7 in the generated 18.md. check:doc-authoring also parses 1432 sources instead of 1429, the modules.
      • check-position-name-fold-loaders walks the working tree: 12350 to 12357 files, with the same 8 references (6 test, 2 declared instrument, 0 loader).
      • check:dts-closure and check:sourcemap-no-sources-content sweep 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-citations judges 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-driver differs only in the names of its temporary directories.
      • Five answer non-zero at base only, each a missing or stale build in the base worktree rather than a verdict: the spec package's check:docs (exit 1, its generated schema is absent until gen:schema), check:skill-examples, check:dual-build-cjs-loads and check:i18n (exit 3, prerequisite not met), and check:docs-transcript-drift (exit 1). The last reads the built @objectstack/lint registry, 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's dist in the base worktree was rebuilt later in the same run, and a rerun at base then exits 0 with the head's output.
    • Six more gates keep a roster in a directory this change touches (scripts/ or packages/), so dispatch-gates marks 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-parity and check:published-readme-exports. All exit 0 at both with identical output, except that check:error-code-casing scans 9370 files instead of 9367, the modules.

Acceptance notes

  • Nothing outside this cut is touched: no other section of the class, no behaviour, no docs/adr/**, no existing scripts/adr-anchors/ entry, no census ledger, and no packages/spec/**. Outside the package, only two files change: the five anchor paths on the system-context page, and the one new anchor entry.
  • Serial check, also in the claim: of the 24 open PRs at claim time, none touched 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.
  • Since the local runs, origin/main gained 13 commits, to 2581efbcbd. None touches this cut's files. The closest, tenant-audit-census.mdx and 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): split src/auth-manager.ts (10,338 lines, +1,867 in 30 days) along its AuthManager seams, 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 onto 2581efbcbd, check-adr-symbol-anchors, check:adr-anchors, check-platform-checklist, check-system-context-census, check-tenant-audit-census and the isystem-census read all exit 0. There, check:adr-anchors counts 81 anchored files and 1978 decision-letter citations, the same as this head, and the system-context census counts 148 sites across 74 files.
  • What is left of the plan's row 7 in security-plugin.ts after this cut: the four projections that stay whole and the nine method shells. security-plugin.ts has 8,852 lines at head, 9,248 at base. Next is row 8 (explain, sharing verdicts, authored row write, baselines), then start(), 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 of 0feb79ca4f, the first commit. The second commit, b4ced7f87e, adds only the anchor entry JSON under scripts/adr-anchors/, which none of those runs reads. Every other gate ran on b4ced7f87e.

  • 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). Base 25f88e899a gives the same counts. VERDICT command-exit 0 on both.
  • pnpm --filter @objectstack/plugin-security typecheck: exit 0. It runs tsc --noEmit, the scripts tsconfig, and check:test-typecheck.
  • eslint --no-inline-config over the five touched source and test files: exit 0.
  • pnpm --filter @objectstack/plugin-security build: exit 0. It produced the .d.ts and bundles measured above. The package's dependencies were rebuilt on 25f88e899a first.
  • row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts (@objectstack/core): 8 of 8 pass.
  • Six commands are not derived for this change set, and the reconciliation names them: node scripts/check-adr-symbol-anchors.mjs, check-platform-checklist (the script and pnpm check:platform-checklist), pnpm --filter @objectstack/spec check:liveness and the two census --json reads. Each was run by hand. pnpm check:adr-anchors, check-scripts-symbol-anchors (and --self-test), pnpm check:query-options-erasure and the spec liveness check (spelled run check:liveness) are derived for it this time.
  • dispatch-gates --commands: 98 commands. The --ran reconciliation 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.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test exit 0
  • node scripts/check-comment-mask-adoption.mjs exit 0
  • node scripts/check-comment-mask-adoption.mjs --self-test exit 0
  • node scripts/check-comment-mask-corpus.mjs exit 0
  • node scripts/check-doc-frontmatter.mjs exit 0
  • node scripts/check-doc-frontmatter.mjs --self-test exit 0
  • node scripts/check-doc-route-spelling.mjs --advisory exit 0
  • node scripts/check-doc-route-spelling.mjs --self-test exit 0
  • node scripts/check-docs-section-name.mjs exit 0
  • node scripts/check-docs-section-name.mjs --self-test exit 0
  • node scripts/check-dts-emitted.mjs --self-test exit 0
  • node scripts/check-issue-citations.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs --self-test exit 0
  • node scripts/check-platform-object-tenancy-census.mjs exit 0
  • node scripts/check-platform-object-tenancy-census.mjs --self-test exit 0
  • node scripts/check-plugin-teardown-shape.mjs exit 0
  • node scripts/check-plugin-teardown-shape.mjs --self-test exit 0
  • node scripts/check-position-name-fold-loaders.mjs exit 0
  • node scripts/check-position-name-fold-loaders.mjs --self-test exit 0
  • node scripts/check-registry-log-declared.mjs exit 0
  • node scripts/check-registry-log-declared.mjs --self-test exit 0
  • node scripts/check-rest-log-spy-declared.mjs exit 0
  • node scripts/check-rest-log-spy-declared.mjs --self-test exit 0
  • node scripts/check-scripts-symbol-anchors.mjs exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test exit 0
  • node scripts/check-section-landing-index.mjs exit 0
  • node scripts/check-section-landing-index.mjs --self-test exit 0
  • node scripts/check-system-context-census.mjs exit 0
  • node scripts/check-system-context-census.mjs --self-test exit 0
  • node scripts/check-tenant-audit-census.mjs exit 0
  • node scripts/check-tenant-audit-census.mjs --self-test exit 0
  • node scripts/check-undeclared-dep-imports.mjs exit 0
  • node scripts/check-undeclared-dep-imports.mjs --self-test exit 0
  • node scripts/docs-audit/check-affected-docs.mjs exit 0
  • node scripts/docs-audit/check-drift-comment.mjs exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0
  • pnpm --filter @objectstack/lint run check:doc-security-posture exit 0
  • pnpm --filter @objectstack/spec run check:docs exit 0
  • pnpm --filter @objectstack/spec run check:duration-unit-keys exit 0
  • pnpm --filter @objectstack/spec run check:empty-state exit 0
  • pnpm --filter @objectstack/spec run check:liveness exit 0
  • pnpm --filter @objectstack/spec run check:skill-examples exit 0
  • pnpm --filter @objectstack/spec run check:strictness-ledger exit 0
  • pnpm --filter @objectstack/spec run check:variant-docs exit 0
  • pnpm --filter @objectstack/spec run check:yaml-examples exit 0
  • pnpm check:adr-anchors exit 0
  • pnpm check:agent-test-spelling exit 0
  • pnpm check:bash32-floor exit 0
  • pnpm check:cli-command-ids exit 0
  • pnpm check:closed-ceremony-text exit 0
  • pnpm check:corpus-claim-drift exit 0
  • pnpm check:cross-package-test-inputs exit 0
  • pnpm check:doc-anchors exit 0
  • pnpm check:doc-authoring exit 0
  • pnpm check:docs-audit-scope exit 0
  • pnpm check:docs-redirects exit 0
  • pnpm check:docs-single-h1 exit 0
  • pnpm check:docs-spec-enumerations exit 0
  • pnpm check:docs-transcript-drift exit 0
  • pnpm check:driver-memory-census exit 0
  • pnpm check:dts-closure exit 0
  • pnpm check:dual-build-cjs-loads exit 0
  • pnpm check:engine-double-contract exit 0
  • pnpm check:entry-guard exit 0
  • pnpm check:error-status-conformance exit 0
  • pnpm check:gitlink-declared exit 0
  • pnpm check:i18n exit 0
  • pnpm check:i18n-stale-fill exit 0
  • pnpm check:issue-citations exit 0
  • pnpm check:lean-entry-closure exit 0
  • pnpm check:logger-receiver-detach exit 0
  • pnpm check:merge-driver exit 0
  • pnpm check:nul-bytes exit 0
  • pnpm check:objectql-double-limit exit 0
  • pnpm check:org-identifier exit 0
  • pnpm check:page-declaration-shape exit 0
  • pnpm check:parse-guard exit 0
  • pnpm check:pnpm-filter-targets exit 0
  • pnpm check:pnpm-flag-spellings exit 0
  • pnpm check:published-files exit 0
  • pnpm check:published-readme-links exit 0
  • pnpm check:query-options-erasure exit 0
  • pnpm check:react-page-adapter-contract exit 0
  • pnpm check:refd-timer-probe exit 0
  • pnpm check:role-word exit 0
  • pnpm check:skill-identifier-liveness exit 0
  • pnpm check:slot-lookup exit 0
  • pnpm check:sourcemap-no-sources-content exit 0
  • pnpm check:test-source-alias exit 0
  • pnpm check:tier-file-adoption exit 0
  • pnpm check:type-check-coverage exit 0
  • pnpm check:type-check-debt exit 0
  • pnpm check:vendor-version-stamps exit 0
  • pnpm check:watch-hint-literal exit 0
  • pnpm check:where-matcher exit 0
  • node scripts/check-adr-symbol-anchors.mjs exit 0
  • node scripts/check-platform-checklist.mjs exit 0
  • pnpm check:platform-checklist exit 0
  • node scripts/isystem-census.mjs --json exit 0
  • node scripts/tenant-audit-census.mjs --json exit 0
  • pnpm --filter @objectstack/spec check:liveness exit 0

The six roster gates run by hand (above):

  • node scripts/check-published-list-mirrors.mjs exit 0
  • pnpm check:authz-resolver exit 0
  • pnpm check:console-injection exit 0
  • pnpm check:error-code-casing exit 0
  • pnpm check:filter-alias-parity exit 0
  • pnpm check:published-readme-exports exit 0

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa

…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
@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 14:46
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc.

⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: objectName (symbol, 40 pages)
  • 5 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 — 16 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 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 69a135f2e9fd02edaad67fe15314aadac097b39a — the merge of head b4ced7f87e2954d1041841b25405346d813c67a5 into base 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc, 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 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

⚠️ 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 6c2699b3f44bc4b21bf9323dbb220a86b91f40dc → 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-23T15:27Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 cut 9, plan row 7 (claim 6365993271, amendment 6366124111; os-dev-report 6368253392) · reviewed head b4ced7f87e · merge base 25f88e899a

  • Shape: 9 bodies move unedited, in shape B as in cuts 1–8, into three new modules: security-plugin-read-filter.ts, security-plugin-field-readers.ts and security-plugin-object-admission.ts. 7 files, +1065 / −545. Four row members stay whole, for the reasons given in the report.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree. turbo run build --filter=@objectstack/plugin-security ran at 25f88e899a and at this head. dist/index.d.ts and dist/index.d.mts are raw 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da and normalised b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4 at both. The raw diff is 0 lines.
    • The seat's own positive control uses head line 94, private permissionEvaluator, made public: raw 215b11dd4d1b35c5… and normalised a107b92fe3d20d75… both moved.
    • Export lists: 66 names, base = head (report).
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: 2384 … 466 symbol at both.
    • check-platform-checklist.
    • check-tenant-audit-census: 238 sites.
    • check-system-context-census: 148 sites; files 71 → 74, which are the three new modules.
    • check-adr-anchors.
  • Decision-letter count, read by the seat: 2001 at base and 1978 at head. Anchored files go 80 → 81.
    • 24 citations leave the anchored surface with the read-filter and object-admission bodies. That is accepted and disclosed, as for cuts 6–8.
    • The three ADR-0106 citations in resolveProjectionFieldMask stay on it, because the new security-plugin-field-readers.ts anchor entry carries ADR-0106 and the D7 paragraph verbatim. The fallback branch that paragraph describes moved with that body.
    • The entry's own invariant adds 1. That is why the amendment's 1977 reads 1978, and the PR body discloses the correction.
  • Path readers re-pointed in the same PR: five system-context.mdx anchors (path only) and the plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 pin's expected file list in delegated-organization-membership.test.ts.
  • Tests and gates (report): plugin-security 4465 at both ends. 98 derived gates, all exit 0 at head. The page touch widened the derived set.
  • CI on this head: 47 check-runs, 36 success and 11 expected skips, none failed.
  • Mergeable: git merge-tree onto today's main (0b8dbea00d) is clean. The dev's scratch merge onto 2581efbcbd ran the anchor and census gates green.
  • Body: the attribution header, both footers and the os-sales reviewer are present.
  • Declarations: refactor(plugin-security), Clause-②: no, Metadata-protocol: none. No contract-review record or dogfood battery is owed.
  • Landing: not governed (check-governed-merges --pr 25456: 1610 lines). Ready + auto-merge through the relay. The card stays open and dispatched. Next is plan row 8, claimed afresh on the merged main.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 15:28
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 15:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit dd124e9 Oct 23, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3fzya4 branch October 23, 2026 15:55
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