Skip to content

refactor(plugin-security): move SecurityPlugin's controlled-by-parent master relation into security-plugin-cbp-relation.ts (#25223 cut 2) - #25310

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

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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

Cut 2 of the security-plugin.ts split, as ruled in 6353073966 (letter A, narrowed). The bodies of resolveCbpRelation and its two loud reports, reportCbpMasterUnresolved and reportRejectedReferenceAlias, move unedited to the new module packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts. Together they are the ADR-0055 §1 derivation of the one master a controlled_by_parent object takes its access from. The one module-level declaration only those bodies read, masterNotWritableRefusal, moves with them, and so do the two imports only it used.

It is move-only, and the card stays open for the later cuts. The amended claim is 6353132374. It supersedes the five-body claim 6352049267, whose head made check-adr-symbol-anchors exit 1. Branch: claude/project-thread-3fzya4, the harness-pinned branch for this session. It names no issue, so the claim comment is the identity record. The branch carries the withdrawn five-body commit and then the narrowing commit, and the PR's diff against its merge base 5801a4f2c is the narrowed cut alone.

Two bodies stay in security-plugin.ts byte for byte, by the ruling's item 1: the read leaf computeControlledByParentFilter and its chain refusal controlledByParentChainRefusal. So do CBP_LEAF_READ_VERBS, CBP_MAX_CHAIN_DEPTH, CbpRelation and every import they read. ADR-0055:26 #sharingModel and ADR-0055:58 #controlled_by_parent resolve only on tokens inside those two bodies. They move only after a separate Tier H docs PR repoints those anchors (ruling items 3 and 4).

The published .d.ts does not change

Under the series bar (ruling 6348891160 item 1), measured on base 5801a4f2 and on the narrowed head (the tree of the commit this PR adds):

  • Raw sha256 of dist/index.d.ts and dist/index.d.mts, base and head, all four the same: 803f5bfb7da0b00f8cd8d8f25172463d8dfb07d93fbdd8a9c6f4d4b7435e429a. The raw diff is 0 lines, so no union order moved either.
  • Normalised sha256, all four the same: b6b5c1b7fa6fa188f6e2f7e4cca3816a246b570f20d708643d116012aa86fa77.
  • 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 1870 (private resolveCbpRelation;):
    • Made public (resolveCbpRelation;): raw 1ff696eaab36c50ffffb459dc4a323a7b32fea15a9bd06d8cac43967ec92ac24, normalised 41486774e365f562d279585cb6b64e282b783b7ae822bb19ef41d434a89d7a12.
    • Renamed (private resolveCbpRelationRenamed;): raw 307edbdd465438d1ab2c5bcbf12d10854c3f8a4179ab90b49d065282ba6ec2ec, normalised 5cb31db9affddd093d0068889eaef49ed684a9ab499ce213a3a77d9ebcd159ce.
    • 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, not parsed. The sorted name list hashes f607fd765d6b277e08b3b46ccb16d1e2d133ec55658ffe40155aef25e1bf54f0 on all four.
  • The bundle JS bytes differ (index.js 68f4e3a8 to 6d2a398f, index.mjs 1f24e6d5 to 953c5d9a), because code moved within them.

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

What moved

Base line numbers are each method's signature at 5801a4f2. The last column lists the members the body reads, which is exactly what its host carries.

method base lines moved function async host members
resolveCbpRelation :8884-:8985 no cbpRelCache, ql, reportCbpMasterUnresolved, reportRejectedReferenceAlias
reportCbpMasterUnresolved :9007-:9042 no logger
reportRejectedReferenceAlias :9094-:9114 no logger

masterNotWritableRefusal (base :727-:737, with its docblock) moves too; resolveCbpRelation is its only reader. So do the two imports only it reads, controlledByParentMasterRefusal (with its two-line comment) and OWD_DELEGATING_ROW_WRITES.

The shape (#23050 shape B, as cut 1 applied it)

  • The class keeps each private method with its name, signature, docblock and position, because tsup emits private members with their JSDoc into the .d.ts. Each body is now one host literal plus fn.call(host, …), marked [#25223 cut 2].
  • CbpRelationHost types every member as the class member itself (SecurityPlugin['m']). Each moved function types its this as the Pick of the host over exactly the members its body reads, and each 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, so there is no runtime import cycle, and security-plugin.ts gains no export. CbpRelation is named in the module through the class's own signature (an alias over the non-null return type of SecurityPlugin['resolveCbpRelation']), so the interface keeps its one declaration beside cbpRelCache and the consumers that read it.
  • The two reports 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 derivation reaches. No moved body calls a function-valued field, so no receiver changes.
  • Fields: ql, logger and cbpRelCache go as getters, because none is declared readonly. start() assigns ql and logger after construction, and the metadata watch clears cbpRelCache in place. The host declares every field readonly, so a moved body cannot assign a member through it; the typecheck passes, so none does.
  • All three methods are synchronous, so no stub changes async. Nothing re-exports the module, so the package entry does not move.

Proof of move-only

  • Bodies, by AST token: for all 3 methods, the moved function's body has the same TypeScript token sequence as the base method body, and each of its 144 lines is the base line with two leading spaces removed. Parameters, return types and async match. A negative control, candidates.length === 1 changed to === 2 in resolveCbpRelation, fails the check.
  • Class side: each method keeps its docblock and signature. masterNotWritableRefusal is verbatim (11 lines) in the module and absent from security-plugin.ts. computeControlledByParentFilter, controlledByParentChainRefusal (docblock to closing brace), CBP_LEAF_READ_VERBS and CBP_MAX_CHAIN_DEPTH (docblock included) are byte-identical to base.
  • git's own view (git diff --color-moved=plain --color-moved-ws=allow-indentation-change 5801a4f2 HEAD over the two source files): 158 of 159 deleted lines and 178 of 283 added lines are detected as moved.
    • Deleted, not moved (1): OWD_DELEGATING_ROW_WRITES,, taken out of a multi-name import. It is re-imported in the module beside masterGovernsRowWrites.
    • Added, not moved: the stub bodies and the import of the new module with its comment. In the module: the license header, the module docblock, the imports, the CbpRelation alias, CbpRelationHost, one pointer docblock per function and the rewritten signature heads. The rest are lines git does not pair with a moved block, such as blanks and closing braces.
  • Size: 449 changed lines (+290 / -159, 3 files), under the 3000-line human-merge threshold. security-plugin.ts goes from 10,952 to 10,823 lines.

ADR anchors (ruling 6353073966 item 2)

  • node scripts/check-adr-symbol-anchors.mjs: exit 0 at base 5801a4f2 and exit 0 at head, both with 2383 anchors across 142 records resolve — 467 symbol (440 declaration, 27 literal), 1888 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, so it is run by hand here.
  • pnpm check:adr-anchors: exit 0, 76 anchored files (75 at base, plus the new entry). The new entry is scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-cbp-relation.ts.json, carrying ADR-0055 and the §1 invariant paragraph of the security-plugin.ts entry, verbatim. That paragraph describes the master derivation, which now lives in the new module. The security-plugin.ts entry is unchanged, because the methods, their docblocks and both consumers of the derivation remain in that file. docs/adr/** is not touched.
  • The anchor map covers every ADR symbol anchor naming security-plugin.ts: 28 anchors, 20 symbols, 10 ADRs. It was measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs, which is measurement, not a new gate. Base lines are at 5801a4f2. Every anchor resolves at head in security-plugin.ts with its base class, and none moves to the new module.

The table gives each anchor's class at base and head, the code lines it resolves on, and the member each line sits in. The member is shown after the line number. Every anchor stays in security-plugin.ts.

anchor cited at base class · lines (member) head class · lines (member)
#CBP_MAX_CHAIN_DEPTH ADR-0055:135 declaration · :630 (top-level) declaration · :635 (top-level)
#SecurityPlugin ADR-0049:49, ADR-0138:248, ADR-0138:265 declaration · :1384 (class) declaration · :1377 (class)
#SecurityPlugin.getMetadataReadableFields ADR-0138:153 declaration · :6126 (method) declaration · :6119 (method)
#SecurityPlugin.resolvePermissionSetsForContextUnmemoized ADR-0138:427 declaration · :7037 (method) declaration · :7030 (method)
#assertControlledByParentWrite ADR-0055:125 declaration · :9654 (method) declaration · :9525 (method)
#assertMasterRowEditable ADR-0055:125 declaration · :10132 (method) declaration · :10003 (method)
#collectRLSPolicies ADR-0105:93 declaration · :10389 (method) declaration · :10260 (method)
#computeControlledByParentFilter ADR-0055:5, ADR-0055:123, ADR-0139:246 declaration · :9200 (method) declaration · :9071 (method)
#computeDelegatedReadFilter ADR-0139:223 declaration · :6004 (method) declaration · :5997 (method)
#computeRlsFilter ADR-0055:24 declaration · :5256 (explainAccessForCaller), :7659 (method) declaration · :5249, :7652
#controlled_by_parent ADR-0055:58 literal · :9035 (reportCbpMasterUnresolved), :9209 (computeControlledByParentFilter), :9275 (controlledByParentChainRefusal) literal · :9080 (computeControlledByParentFilter), :9146 (controlledByParentChainRefusal)
#getReadFilter ADR-0096:181, :182, :183, :199, ADR-0139:222 declaration · :2296 (start), :5882 (method) declaration · :2289, :5875
#hasPlatformAdminPosture ADR-0099:317 declaration · :7686 (method) declaration · :7679 (method)
#isPrincipalLessContext ADR-0138:152 declaration · :484 (top-level) declaration · :489 (top-level)
#manage_metadata ADR-0099:68 literal · :233 (PLATFORM_ADMIN_ONLY_CAPABILITIES) literal · :238
#nav_permission_sets ADR-0126:363 literal · :1789 (init) literal · :1782
#refuseNoActiveOrganization ADR-0123:4 declaration · :8499 (local in composeWriteTenantWall) declaration · :8492
#rlsFilter ADR-0055:24 declaration · :4359 (local in start) declaration · :4352
#sharingModel ADR-0055:26 declaration · :9208 (local in computeControlledByParentFilter) declaration · :9079
#writeCheckPolicies ADR-0066:112 declaration · :1279 (top-level) declaration · :1272 (top-level)

Only one resolving site moves: #controlled_by_parent at base :9035. It is the quoted token inside reportCbpMasterUnresolved's message template, and it goes to the new module with that body, where it is still a literal. The anchor names security-plugin.ts, and there it keeps its other two sites. That is ruling 6353073966 premise 6, read on this cut's base.

Readers

No test, script, gate, baseline or docs anchor reads the moved lines by path, so nothing else is re-pointed.

  • The other symbol-anchor gates all exit 0: check-scripts-symbol-anchors, check-spec-docblock-symbol-anchors, pnpm --filter @objectstack/spec check:liveness, and check-platform-checklist --changed-from origin/main. The last of these judged 6 anchors and lost 0.
  • Line citations: no security-plugin.ts:N citation in the repo points into a moved range. The moved ranges are base :727-:738, :8884-:8985, :9007-:9042 and :9094-:9114.
    • Head line numbers shift by -3 to +6 for base lines :16-:726 (the import block changed) and by -7 for base :739-:8883.
    • The citations :132, :303, :309, :1614, :1690, :1736, :3215, :3281, :3519 and :4344 all land in those shifted ranges. They are in CHANGELOG.md, packages/objectql/CHANGELOG.md, docs/launch-readiness.md, docs/design/predicate-compilation-convergence.md, packages/objectql/src/engine.ts and packages/spec/liveness/object.json.
    • At base, every one of them already points at an unrelated line. For example, :1614 is a middleware docblock, not the system bypass, and :309 is interface WriteWallRefusal. So this cut does not make any of them wrong.
  • Census gates, run on base and head and compared:
    • Identical output: check-platform-object-tenancy-census, check-position-name-fold-loaders, check-registry-log-declared, check-rest-log-spy-declared, check-system-context-census, check-tenant-audit-census, check:cross-package-test-inputs, check:driver-memory-census and check:query-options-erasure. The last reports 62 unswept sites in 16 files at both base and head, and security-plugin.ts is not a baseline key.
    • Only the scanned counts move, by the one new file:
      • check-undeclared-dep-imports: 3286 to 3287 files, 2824 specifiers at both.
      • check:doc-authoring: 1410 to 1411 sources.
      • check:org-identifier: 3628 to 3629 files.
      • check:logger-receiver-detach: 3602 to 3604 files. The second extra file is apps/docs/.next/types/validator.ts, a gitignored local build output of the docs app. It is not in the tree CI checks out.
    • check-issue-citations judges a change set, so it has nothing to read at base. At head it judged 12 citations across 2 files, and all resolve.

Acceptance notes

  • Nothing outside this cut is touched: no other section of the class, no behaviour, no other lane's file, and no docs/adr/**.
  • Out of scope, noted for the seat per ruling 6353073966 items 3 and 7, and filed by the seat, not here:
    • The three ADR anchors that resolve only on tokens inside a body: ADR-0055:26 #sharingModel, ADR-0055:58 #controlled_by_parent and ADR-0123:4 #refuseNoActiveOrganization. They need a docs-only Tier H card.
    • A tooling finding for domain:cli: dispatch-gates --commands does not derive check-adr-symbol-anchors for this change set.

Gates

All gates ran at the narrowed head, the tree of 4c92fe8165, on base 5801a4f2. Since that base, origin/main moved to 09e07c259a, but nothing under packages/plugins/plugin-security/, docs/adr/ or scripts/adr-anchors/ changed.

  • pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under the verify lock): Test Files 210 passed (210), Tests 4403 passed, 45 skipped (4448). Base 5801a4f2 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, whose debt ledger holds 0 files / 0 errors.
  • 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.
  • node scripts/check-adr-symbol-anchors.mjs exit 0, pnpm check:query-options-erasure exit 0, plus the three symbol-anchor gates under Readers. None of these is derived for this change set; each was run by hand.
  • dispatch-gates --commands: 62 commands, 449 changed lines, under the threshold. The --ran reconciliation shows 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. The zero is derived: all 62 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/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: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

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa

… read derivation into security-plugin-cbp-read.ts (wip)

The bodies of five private methods (the ADR-0055 master relation, its two
reports, the read leaf and the chain refusal) move unedited, re-indented one
level, with CBP_LEAF_READ_VERBS and masterNotWritableRefusal, which only they
read. The class keeps every method, its signature and docblock, and hands
each body a host literal of exactly the members it reads. CBP_MAX_CHAIN_DEPTH
stays where ADR-0055 anchors it and is exported for the module to import.
The new module gets its own ADR-0055 anchor entry.

Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
Co-authored-by: Claude <noreply@anthropic.com>
…aster relation

Ruling 6353073966 (letter A): cut 2 moves only resolveCbpRelation and its
two reports, reportCbpMasterUnresolved and reportRejectedReferenceAlias,
into security-plugin-cbp-relation.ts, with masterNotWritableRefusal and the
two imports only it reads. computeControlledByParentFilter,
controlledByParentChainRefusal, CBP_LEAF_READ_VERBS and CBP_MAX_CHAIN_DEPTH
stay in security-plugin.ts byte for byte, because ADR-0055:26 #sharingModel
and ADR-0055:58 #controlled_by_parent resolve only on tokens inside those two
bodies. The module imports the class type-only, and security-plugin.ts gains
no export. The adr-anchors entry follows the module's new name.

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 22, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 22, 2026 23:02
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/api/error-catalog.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/automation/flows.mdx (via referenceTo (literal, a string literal in resolveCbpRelation))
  • content/docs/data-modeling/objects.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/deployment/cli.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/getting-started/common-patterns.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/kernel/runtime-services/sharing-service.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/attachments-access.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/authorization.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/index.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/permissions-matrix.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/rls.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/permissions/sharing-rules.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/protocol/kernel/error-handling.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/protocol/objectql/security.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/protocol/objectql/types.mdx (via reference_to (literal, a string literal in resolveCbpRelation))

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

  • content/docs/releases/implementation-status.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/releases/v15.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved), reference_to (literal, a string literal in resolveCbpRelation))
  • content/docs/releases/v17/17-0.mdx (via referenceTo (literal, a string literal in resolveCbpRelation))
  • content/docs/releases/v17/17-1.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/releases/v17/17-2.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/releases/v17/17-3.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved), reference_to (literal, a string literal in resolveCbpRelation))
  • content/docs/releases/v17/index.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))
  • content/docs/releases/v18/18-0.mdx (via controlled_by_parent (literal, a string literal in reportCbpMasterUnresolved))

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

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

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 09e07c259a633eb02fdb29bedfe911e808529f88

⚠️ 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 09e07c259a633eb02fdb29bedfe911e808529f88 → 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-22T23:38Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 cut 2 (amended claim 6353132374, Ruling-ref: 6353073966 letter A as narrowed; os-dev-report 6353666083; card session branch claude/project-thread-3fzya4) · reviewed head 4c92fe8165dbea94721b01c5c269f3cca044da4a · merge base 5801a4f2c9

  • Shape, as ruled: the bodies of resolveCbpRelation, reportCbpMasterUnresolved and reportRejectedReferenceAlias move into security-plugin-cbp-relation.ts. With them go masterNotWritableRefusal and the two imports only it reads. The class keeps each method shell (shape B: one Pick-typed host per method, ql / logger / cbpRelCache as getters). computeControlledByParentFilter, controlledByParentChainRefusal, CBP_LEAF_READ_VERBS, CBP_MAX_CHAIN_DEPTH and CbpRelation stay byte for byte. The module imports the class type-only, and security-plugin.ts gains no export. One new scripts/adr-anchors/ entry, and docs/adr/** is untouched. 3 files, +290 / −159.

  • Move proof (report): 3 of 3 bodies are AST-token identical with a failing negative control, and every moved line is the base line minus two spaces. --color-moved marks 158 of 159 deleted lines as moved; the one other line is OWD_DELEGATING_ROW_WRITES, taken out of a multi-name import.

  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree. turbo run build --filter=@objectstack/plugin-security ran at 5801a4f2c9 and at this head, and the seat ran the normaliser one-liner from the PR body:

    file raw base = head normalised base = head
    dist/index.d.ts 803f5bfb7da0b00f…435e429a b6b5c1b7fa6fa188…aa86fa77
    dist/index.d.mts 803f5bfb7da0b00f…435e429a b6b5c1b7fa6fa188…aa86fa77

    The raw diff is 0 lines. Positive control: head line 1870 private resolveCbpRelation made public gives raw 1ff696eaab36c50f… and normalised 41486774e365f562…, both moved. This matches the report's block hash for hash. Export lists: 63 names in ESM and CJS, base equals head (report).

  • ADR anchor reading, ruling item 2, re-run by the seat: node scripts/check-adr-symbol-anchors.mjs exits 0 at base and head, both printing 2383 anchors across 142 records resolve — 467 symbol (440 declaration, 27 literal). The report's map of the 28 anchors into security-plugin.ts shows none resolving only inside a moved body, so premise 6 holds.

  • Tests and gates (report, on this head): plugin-security 210 files, 4403 pass / 45 skipped, the same as base. dispatch-gates --ran 62 / 62, plus the hand-run anchor and liveness gates. CI: 38 success and 15 expected skips, none failed or pending, mergeable clean.

  • Declarations: refactor(plugin-security), Clause-②: no, Metadata-protocol: none, skip-changeset (ruling 6348891160 item 4). No contract-review record or dogfood battery is owed.

  • History: the branch carries the withdrawn five-body commit abdb89ad03 and a merge under the narrowing commit. The PR's net diff against its merge base is the ruled cut alone.

  • Out-of-scope findings:

  • Body: the seat appended the required footer through the relay.

  • Landing: not governed (check-governed-merges --pr 25310: 449 lines). Ready + auto-merge through the relay. The card stays open and dispatched; cut 3 is claimed afresh on the merged file.


Generated by Claude Code

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

Labels

size/m 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