Skip to content

refactor(plugin-security): move SecurityPlugin's Layer 0 tenant write wall into security-plugin-write-tenant-wall.ts (#25223 cut 5) - #25378

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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

Cut 5 of the security-plugin.ts split: step 3.7's Layer 0 tenant write wall. The claim is 6357083478. The body of one async method, composeWriteTenantWall, moves unedited to the new module packages/plugins/plugin-security/src/security-plugin-write-tenant-wall.ts. It composes the wall once for one write:

Nothing in it logs or throws. The middleware's step 3.7 logs and throws each refusal through raiseWriteWallRefusal, and the write preview writeWallVerdict answers it as a row's verdict. Both stay in the class and still call the method.

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 4d7e2b1f38 (cut 4's merge). It names no issue, so the claim comment is the identity record.

Why now. Ruling 6353073966 item 4 held this body with the two read bodies until the Tier H docs PR for #25314 landed. That PR, #25316, merged as 97967a9a06, and cut 4 moved the two read bodies. On this base, ADR-0123:4 anchors security-plugin.ts#composeWriteTenantWall, a method name. The method stays as a shell, so that anchor still resolves on its declaration.

What stays in security-plugin.ts.

  • No module-level declaration moves. The WriteTenantWall and WriteWallRefusal interfaces keep their one declaration, because the method's signature and raiseWriteWallRefusal read them. The module names both through the class's own signature, as cuts 2 to 4 name CbpRelation.
  • carriesActiveOrganization stays, because callerHasOrganizationScope reads it too. It gains export, and the module imports it by value, as cut 3's module imports its five values. It is read only inside the function body, at call time, never while either module evaluates. index.ts still exports only SecurityPlugin, so the published surface does not move (measured below).
  • The matchesFilterCondition name leaves the @objectstack/formula import of security-plugin.ts, because no code left there reads it. The module imports it from the same package. The module also imports postureUsesUnionScope from @objectstack/spec/security and PermissionDeniedError from errors.js, as security-plugin.ts does.

One reader outside the package moves with the body. docs/qa/platform-checklist/areas/access-security.json cites packages/plugins/plugin-security/src/security-plugin.ts#denyTenantPlacement twice, in the fixtures.requires prose of items access-security.activation-write-operator-gate and access-security.walled-environment-metadata-operator-only. denyTenantPlacement is a local const inside the moved body. With the body moved and that citation unchanged, check-platform-checklist exits 1 with ABSENT SYMBOL, measured on a trial before the commit. Both citations now name security-plugin-write-tenant-wall.ts#denyTenantPlacement. Only the path changed: the symbol, the quoted 403 text and the rest of both strings are as they were. The gate exits 0 at head.

The published .d.ts does not change

Under the series bar (ruling 6348891160 item 1), measured on base 4d7e2b1f38 and on the head (the tree of this PR's commit):

  • Raw sha256 of dist/index.d.ts and dist/index.d.mts, base and head, all four the same: ea54b6c9538b135fee36e1309fddc763113443c0dddd174950cbd96c290536a2. The raw diff is 0 lines, so no union order moved either.
  • Normalised sha256, all four the same: 652f26b24f41bf031d428f708685abb7517d4247a765246d153d2bbeac13e444.
  • The hashes differ from cut 4's report because origin/main moved between cut 4's head and its merge. The only lines that differ from cut 4's head .d.ts are six inlined requiresFeature unions from @objectstack/spec, which gained "cloudManaged". Base and head here are both built against 4d7e2b1f38's dependencies.
  • 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, line 1794 (private composeWriteTenantWall;):
    • Made public (composeWriteTenantWall;): raw 5c1fa0e3230960f533466bb151a1e1769cdcc855d3227f776a9832d6f0fb7195, normalised 4defa680f3cf4b2ed2b0c60696d8bea7031a82edd43bcabc4ad4b2e346727c80.
    • Renamed (private composeWriteTenantWallRenamed;): raw 82704c4f85cb5d28cb672046ef7e2b05da8348c6cb4efccd4e0e5fe7e58eb0dc, normalised 614cf491f999aa7a4fde4a3317cec1b1ee9e95306e29dab7c5f1c452b9982870.
    • Both differ from the real hashes, so the comparison does see a visibility change and a rename.
  • Export tables: the ESM and CJS bundles each export the same 63 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 f607fd765d6b277e08b3b46ccb16d1e2d133ec55658ffe40155aef25e1bf54f0 on all four, the same as at cuts 2 to 4. The new carriesActiveOrganization export of security-plugin.ts does not reach the entry.
  • The bundle JS bytes differ (index.js 53af1376 to a0e03516, index.mjs 7beda6f7 to a75436eb), because code moved within them.

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

What moved

The base line numbers are the method's signature and closing brace at 4d7e2b1f38. The last column lists the members the body reads, which is exactly what its host carries.

method base lines host members
composeWriteTenantWall (async) :8408-:8746 computeWriteTenantCheckFilter, computeWriteTenantWall, orgScopingEnabled, organizationWallRefusal, tenancyPosture

The stub drops async and returns the moved function's promise, as cuts 1 and 3 did for their async methods (6 and 5 of them). The .d.ts emits a private member without modifiers, so this does not reach it.

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

  • The class keeps the private method with its name, signature, docblock and position, because tsup emits private members with their JSDoc into the .d.ts. The body is now one host literal plus composeWriteTenantWall.call(host, …), marked [#25223 cut 5].
  • WriteTenantWallHost types every member as the class member itself (SecurityPlugin['m']). The moved function types its this as the Pick of the 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.
  • The module imports SecurityPlugin type-only. It names WriteTenantWall as the awaited return type of SecurityPlugin['composeWriteTenantWall'], and WriteWallRefusal as the non-null type of that wall's preTest.
  • 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 pre-test is still organizationWallRefusal, the one verdict canWriteObject asks too. The caller's and the delegator's Layer 0 still come from the read side's computeLayeredRlsFilter, through computeWriteTenantWall and computeWriteTenantCheckFilter. No moved body calls a function-valued field, so no receiver changes.
  • Fields go as getters. tenancyPosture is not declared readonly, because start() resolves it after construction. orgScopingEnabled is the class's own getter over it, handed over as cut 1 handed it. The host declares both readonly, so the moved body cannot assign a member through it; the typecheck passes, so it does not.
  • Some reads of this.tenancyPosture happen when a returned judgement is called, not when the wall is composed: the developer message of denyOrganizationRemoval, which judgePayload and judgeRow reach, and the developer messages of judgeLanding's two refusals. this inside those arrow functions is the host, and the host's getter reads the plugin's field at that call, which is the read the method made before.
  • Nothing re-exports the module, so the package entry does not move.

Proof of move-only

  • Body, by AST token: the moved function's body has the same TypeScript token sequence as the base method body, and each of its 329 lines is the base line with two leading spaces removed. Parameters, the return type and async match. A negative control, rows.some(emptiesOrganization) changed to !rows.some(emptiesOrganization) in the payload half, fails the check.
  • Class side: the method keeps its docblock and signature, less async. Every other class member, 124 named members plus the constructor, is byte-identical to base, organizationWallRefusal, computeWriteTenantWall, computeWriteTenantCheckFilter, raiseWriteWallRefusal, writeWallVerdict and start() included. The class has 126 members at base and at head.
  • Top level: 100 of the 102 top-level statements other than the class are byte-identical to base, WriteTenantWall and WriteWallRefusal included. The two that differ are the @objectstack/formula import, which loses matchesFilterCondition, and carriesActiveOrganization, which is the base text with export before function. The one new statement is the import of the new module.
  • git's own view (git diff --color-moved=plain --color-moved-ws=allow-indentation-change 4d7e2b1f38 HEAD over the two source files): 328 of 331 deleted lines and 337 of 453 added lines are detected as moved.
    • Deleted, not moved (3): the matchesFilterCondition, import line, the function carriesActiveOrganization( line, which comes back with export, and the method's signature line, which comes back without async.
    • Added, not moved: those two lines' replacements, the stub body, and the import of the new module with its comment. In the module: the license header, the module docblock, the imports, the two type aliases, WriteTenantWallHost, the pointer docblock and the rewritten signature head. The rest are lines git does not pair with a moved block, such as blanks and closing braces.
  • Size: 795 changed lines (+462 / -333, 4 files), under the 3000-line human-merge threshold. security-plugin.ts goes from 10,205 to 9,890 lines.

ADR anchors (ruling 6353073966 item 2)

  • node scripts/check-adr-symbol-anchors.mjs: exit 0 at base 4d7e2b1f38 and exit 0 at head, both with 2383 anchors across 142 records resolve — 466 symbol (440 declaration, 26 literal), 1889 file-level, 28 cross-repo, 6 exempt, 18 continuation, the same counts as cut 4. 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, 79 anchored files (78 at base, plus the new entry). The new entry is scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-write-tenant-wall.ts.json. It carries ADR-0123 and the ADR-0123 D2 paragraph of the security-plugin.ts entry, verbatim: the no-active-organization refusal is this body's pre-test, and the paragraph's rule that it stays ordered ahead of the forge guard is about code that now lives in this module. The moved comments also cite ADR-0090, ADR-0095, ADR-0105, ADR-0112 and ADR-0131. Like the security-plugin.ts entry, the new entry does not list every ADR its file cites, and the gate passes. The security-plugin.ts entry is unchanged: the middleware's step 3.7, which throws that refusal, stays there. docs/adr/** is not touched.
  • The anchor map covers every ADR symbol anchor naming security-plugin.ts: 27 anchors, 18 symbols, 10 ADRs on this base. It was measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs, which is measurement, not a new gate. It was read on 4d7e2b1f38 BEFORE the cut, with the body blanked: 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, the line it resolves on, and the member that line sits in. Every anchor stays in security-plugin.ts.

anchor cited at base class · line (member) head class · line
#CBP_MAX_CHAIN_DEPTH ADR-0055:135 declaration · :657 (top-level) declaration · :660
#SecurityPlugin ADR-0049:49, ADR-0138:248, ADR-0138:265 declaration · :1334 (class) declaration · :1337
#SecurityPlugin.getMetadataReadableFields ADR-0138:153 declaration · :2483 (start), :6102 (method) declaration · :2486, :6105
#SecurityPlugin.resolvePermissionSetsForContextUnmemoized ADR-0138:427 declaration · :7013 (method) declaration · :7016
#assertControlledByParentWrite ADR-0055:58, ADR-0055:125 declaration · :9462 (method shell) declaration · :9147
#assertMasterRowEditable ADR-0055:125 declaration · :9588 (method shell) declaration · :9273
#collectRLSPolicies ADR-0105:93 declaration · :9642 (method) declaration · :9327
#composeWriteTenantWall ADR-0123:4 declaration · :8408 (method) declaration · :8411 (method shell)
#computeControlledByParentFilter ADR-0055:5, ADR-0055:123, ADR-0139:246 declaration · :9054 (method shell) declaration · :8739
#computeDelegatedReadFilter ADR-0139:223 declaration · :5980 (method) declaration · :5983
#computeRlsFilter ADR-0055:24 declaration · :5232 (explainAccessForCaller), :7635 (method) declaration · :5235, :7638
#getReadFilter ADR-0096:181, :182, :183, :199, ADR-0139:222 declaration · :2246 (start), :5858 (method) declaration · :2249, :5861
#hasPlatformAdminPosture ADR-0099:317 declaration · :7662 (method) declaration · :7665
#isPrincipalLessContext ADR-0138:152 declaration · :511 (top-level) declaration · :514
#manage_metadata ADR-0099:68 literal · :260 (PLATFORM_ADMIN_ONLY_CAPABILITIES) literal · :263
#nav_permission_sets ADR-0126:363 literal · :1739 (init) literal · :1742
#rlsFilter ADR-0055:24 declaration · :4309 (local in start) declaration · :4312
#writeCheckPolicies ADR-0066:112 declaration · :1229 (top-level) declaration · :1232

No resolving site moves. ADR-0123:4 resolves on the composeWriteTenantWall declaration, which stays as a shell. The new module also declares a function of that name, but the anchor names security-plugin.ts, so it is judged there. This cut adds no host-literal key line for an anchored name.

Readers

One reader of the moved lines exists outside this package, the platform checklist above, and it is re-pointed in this PR. No test, script, gate, baseline or other docs anchor reads the moved lines by path.

  • The platform checklist. check-platform-checklist exits 0 at base and at head. Both print symbol anchors: 986/996 resolved … 10 on the named #16898 residual, 18 file floors held. The cited-source count goes from 435 to 436, because the new module is now a cited source. The scoped run --changed-from origin/main judged 7 anchors in 3 anchored files of the change set, and lost 0. Before the re-point, on a trial of the same move, the full gate exited 1 with ABSENT SYMBOL for both citations.
  • The census pages. The body holds no isSystem read. One comment names isSystem, which the census does not count. So check-system-context-census has no row to move, and content/docs/permissions/system-context.mdx is not touched. node scripts/isystem-census.mjs --json gives the same 148 sites at base and head (12 in security-plugin*.ts), compared without line numbers. Only the corpus and scanned file counts move, by one. The body holds no engine write, so node scripts/tenant-audit-census.mjs --json gives the same 237 write call sites at base and head, compared the same way. Only scannedSources moves, 712 to 713, plus the measured-at ref.
  • The other symbol-anchor gates all exit 0: check-scripts-symbol-anchors, check-spec-docblock-symbol-anchors and pnpm --filter @objectstack/spec check:liveness.
  • Source scans by path. Two tests read package sources by path. packages/core/src/security/row-serving-door-exposure.pin.test.ts classifies every caller of the read admission, and the body calls no admission, so the new file needs no row. Run on its own: 3 of 3 pass. delegated-organization-membership.test.ts counts resolveDelegatorContext( per source file, and the body has none, so the two counted files are still explain-engine.ts and security-plugin.ts. It is part of the plugin-security suite, which passes.
  • Readers by name, not by path. These were read and none needs a change:
    • Docblocks in security-plugin.ts itself name {@link SecurityPlugin.composeWriteTenantWall} (on WriteWallRefusal and WriteTenantWall, and in step 3.7 and writeWallVerdict). The method still exists under that name.
    • Test files name the wall's refusal sentences and its [#24784], [#24821] and [#24822] arms, and assert behaviour, not location. The suite passes at head with the same counts as base.
  • Line citations: no security-plugin.ts:N citation in the repo points into the moved range, base :8417-:8745.
    • Head line numbers shift by +4 for base :146-:186 (the new import), by +3 for base :188-:8416, and by -315 after :8745. Lines :1-:145 do not move. The moved range becomes the 11-line stub at head :8420-:8430.
    • The citations :132, :303, :309, :1614, :1690, :1736, :3215, :3281, :3519 and :4344 are the same list cuts 2 to 4 read. Each lands in an unmoved or shifted range, and 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 4d7e2b1f38 (advisory): 0 pages. Its 17 anchors are the moved names and literals, and the one broad literal, organization_id (37 pages), is dropped as over-broad.
  • Census gates, run on base 4d7e2b1f38 and head and compared:
    • Identical output: check-platform-object-tenancy-census, check-registry-log-declared, check-rest-log-spy-declared, check-system-context-census, check-tenant-audit-census, check-adr-symbol-anchors, check:cross-package-test-inputs, check:driver-memory-census and check:query-options-erasure. The last reports the same counts at both, and security-plugin.ts is not a baseline key.
    • Only scanned counts move:
      • check-undeclared-dep-imports: 3298 to 3299 files, and 2837 to 2840 specifiers. The three are the new module's @objectstack/spec/security (type and value) and @objectstack/formula imports, both declared dependencies.
      • check:doc-authoring: 1417 to 1418 sources.
      • check:org-identifier: 3640 to 3641 files.
      • check:logger-receiver-detach: 3614 to 3616 files.
      • check-position-name-fold-loaders: 12316 to 12321 files, with the same 8 references (6 test, 2 declared instrument, 0 loader). It walks the working tree. Two of the five are this PR's new files. The other three are gitignored local build outputs that exist only in the head worktree (packages/spec/json-schema/data/QueryPageWindow.json, QueryPageWindowParam.json and apps/docs/next-env.d.ts), which CI's checkout does not have. The second extra file of check:logger-receiver-detach is the same kind of local output, as at cuts 2 to 4.
    • check-issue-citations judges a change set, so it has nothing to read at base. At head it judged 19 citations across 2 files, and all resolve.

Acceptance notes

Gates

All gates ran at the head, the tree of 765326ddf5, on base 4d7e2b1f38.

  • pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under the verify lock): Test Files 212 passed (212), Tests 4442 passed, 45 skipped (4487). Base 4d7e2b1f38 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 two touched source 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 4d7e2b1f38 first: on the dependency outputs of the previous base, the typecheck and build failed on two @objectstack/core exports that bootstrap-platform-admin.ts imports on origin/main and that the old core build lacked, not on this cut.
  • packages/core/src/security/row-serving-door-exposure.pin.test.ts: 3 of 3 pass.
  • node scripts/check-adr-symbol-anchors.mjs exit 0, pnpm check:query-options-erasure exit 0, and the symbol-anchor gates under Readers. None of these is derived for this change set; each was run by hand.
  • dispatch-gates --commands: 64 commands. They are cut 4's 62, plus pnpm check:platform-checklist and pnpm --filter @objectstack/lint run check:doc-formula-expressions, which the checklist file adds. The --ran reconciliation shows 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN. The zero is derived: all 64 recorded an exit code and none is 3.

Per command (exit code):

  • 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-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-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/spec run check:duration-unit-keys 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:cross-package-test-inputs exit 0
  • pnpm check:doc-authoring 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: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:nul-bytes exit 0
  • pnpm check:org-identifier exit 0
  • pnpm check:page-declaration-shape exit 0
  • pnpm check:parse-guard exit 0
  • pnpm check:platform-checklist exit 0
  • pnpm check:pnpm-filter-targets exit 0
  • pnpm check:pnpm-flag-spellings exit 0
  • pnpm check:published-files exit 0
  • pnpm check:refd-timer-probe 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:watch-hint-literal exit 0
  • pnpm check:query-options-erasure exit 0
  • node scripts/check-adr-symbol-anchors.mjs exit 0
  • node scripts/check-spec-docblock-symbol-anchors.mjs exit 0
  • pnpm --filter @objectstack/spec check:liveness exit 0
  • node scripts/check-platform-checklist.mjs --changed-from origin/main exit 0

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa

… wall into security-plugin-write-tenant-wall.ts

The body of composeWriteTenantWall (step 3.7's wall, composed once per
write: the ADR-0123 D2 scope pre-test, the parts every row is held to and
the payload and stored-image judgements) moves unedited to a new module
beside the plugin. The method keeps its name, signature, docblock and
position and hands its body one host literal of exactly the members it
reads; tenancyPosture and orgScopingEnabled go as getters. The
WriteTenantWall and WriteWallRefusal interfaces stay; carriesActiveOrganization
stays (callerHasOrganizationScope reads it) and is exported and imported by
value. The two platform-checklist citations of the body's local
denyTenantPlacement are re-pointed to the new module, and the module gets
the ADR-0123 D2 anchor entry. Move-only: no verdict changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 04:41
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

17 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: organization_id (literal, 37 pages)
  • 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 fb63d396b15c91f3f16041817e9934e2dea93759 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json fb63d396b15c91f3f16041817e9934e2dea93759

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T05:35Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 cut 5 (claim 6357083478, Ruling-ref: 6353073966; os-dev-report 6357920593; card session branch claude/project-thread-3fzya4) · reviewed head 765326ddf527bc333e8fc25ce0e7f59dd9fae26b · merge base 4d7e2b1f38

  • Shape: the body of composeWriteTenantWall (329 lines) moves unedited into security-plugin-write-tenant-wall.ts. This is the step 3.7 Layer 0 tenant write wall: the ADR-0123 D2 scope pre-test, the caller and delegator parts, and the payload and stored-image judgements. It uses shape B as in cuts 1 to 4. No module-level declaration moves. carriesActiveOrganization gains export and is imported by value. This is the last of ruling item 4's held bodies. 4 files, +462 / −333.
  • Move proof (report): the body is token-identical, with a failing negative control.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree. turbo run build --filter=@objectstack/plugin-security ran at 4d7e2b1f38 and at this head. dist/index.d.ts and dist/index.d.mts are raw ea54b6c9538b135fee36e1309fddc763113443c0dddd174950cbd96c290536a2 and normalised 652f26b24f41bf031d428f708685abb7517d4247a765246d153d2bbeac13e444, the same at base and head. The raw diff is 0 lines.
    • The seat's own positive control uses a different member from the report's: head line 2027, private computeControlledByParentFilter, made public. Raw 623abb5efc8d416b… and normalised 65e25ab8e90d9a4f… both moved.
    • The report's control (line 1794, composeWriteTenantWall, made public and renamed) also moved both hashes.
  • ADR anchor reading, ruling item 2, re-run by the seat: check-adr-symbol-anchors exits 0 at base and head, both 2383 anchors … 466 symbol (440 declaration, 26 literal). In the report's pre-cut map, with the body blanked, no anchor changed class. #composeWriteTenantWall (ADR-0123:4) resolves on the kept shell. The new scripts/adr-anchors entry carries the ADR-0123 D2 paragraph, and docs/adr is untouched.
  • Reader outside the package: docs/qa/platform-checklist/areas/access-security.json had two citations anchored at security-plugin.ts#denyTenantPlacement, a local const inside the moved body. The seat read the diff: only the path changes, in both citations. Without that repoint, check-platform-checklist exits 1 with ABSENT SYMBOL (report trial). Seat re-runs, all exit 0 at base and at head:
    • check-platform-checklist: 16 areas, 329 items.
    • check-tenant-audit-census: 237 sites.
    • check-system-context-census: 148 sites.
  • Tests and gates (report): plugin-security 212 files, 4442 pass / 45 skipped, the same as base. dispatch-gates --ran 64 / 64, plus the hand-run anchor, liveness, checklist and erasure gates. CI on this head: 34 success and 12 expected skips, none failed or pending. Mergeable clean, and the seat's git merge-tree onto today's main is clean.
  • Body: the attribution header, both footers and the os-sales reviewer are present. Nothing was patched.
  • Declarations: refactor(plugin-security), Clause-②: no, Metadata-protocol: none, skip-changeset. No contract-review record or dogfood battery is owed. Out-of-scope findings: none.
  • Landing: not governed (check-governed-merges --pr 25378: 795 lines). Ready + auto-merge through the relay. The card stays open and dispatched until the card thread reports the series finished.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 05:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 05:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Any commits made after this event will not be merged.
Merged via the queue into main with commit be318db Oct 23, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3fzya4 branch October 23, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants