Skip to content

refactor(metadata-protocol): move the authoring gate's deployment-posture probes out of protocol.ts into protocol-authoring-posture-probes.ts (#25467 cut 26) - #26873

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-ybjm3h
Oct 27, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Part of #25467
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840

Cut 26 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cuts 1–25 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404, #26449, #26654, #26704 and #26807. The claim is 6449989109. It takes the cut 26 proposal of the cut 25 report (6448075845), a themed group. All four members passed the AST screen and the read-seam census, and all four moved unedited.

Four ObjectStackProtocolImplementation members move unedited into one new module, packages/metadata-protocol/src/protocol-authoring-posture-probes.ts (#23050 shape B). They are the runtime authoring gate's deployment-posture probes: the four reads of this deployment that assertRuntimeAuthoringRules hands the gate on every write.

protocol.ts loses 17 net lines (29,127 to 29,110). The cut is move-only, and the card stays open for later cuts.

Branch claude/project-thread-ybjm3h is the harness-pinned branch for this thread. It was restarted from main aeb9cf0ddc (after cut 25's merge). It names no issue, so the claim comment is the identity record. It holds the move commit 98200caa10 and one merge of main, a885bdbbdf (10 commits). Unlike the last cuts, this merge does touch packages/metadata-protocol/. It brings in #26811 (#26644, findData/getData in protocol.ts), which merged while this cut was being measured, and the seed-loader change. protocol.ts auto-merged with no conflict. So the merged tree was measured against main itself as well (see "The merged tree against main").

Serial with the file's in-flight PRs

Measured just before this PR was opened, against the merged head a885bdbbdf: of the 38 open PRs, two edit protocol.ts, and each one's git merge-tree is clean against this head.

Two more touch only test files of this package, each merge-tree clean against head: #26851 (2 files) and #26831 (1). #26811, which the claim asked about, had merged before this PR was opened, so it is part of the merge above rather than an overlap.

main moved six commits after the merge: #26742, #26769, #26799, #26823, #26827 and #26829. Two edit test files of this package: protocol.item-name-grammar.test.ts (#26769) and runtime-authoring-gate.email-template-writes.test.ts (#26799). None edits protocol.ts or the gate's source, and git merge-tree with this head is clean. The derivation found nothing stale, so main was not merged again. The PR's merge ref runs those tests against this cut.

The order's screen (private statics, new Error(, the read-seam census)

All four members passed:

  • No private-static read. The AST walk found no access on the ObjectStackProtocolImplementation identifier and none on this.constructor.
  • No error constructed. No body has new Error(, so no PRODUCERS row is owed.
  • Read-seam census. check-durability-degradation-log-level --list was taken on the base and again on the cut: 88 read seam(s) and an identical list once [@:][0-9]+ is stripped. The full readings are under "The durability --list".

What moves

Member Base line Visibility Body lines AST tokens (params + return + body) Reads
orgWallEnforced :7394 private 5 24 no member
tenancyPostureInForce :7428 private 5 34 getServicesRegistry (field)
rlsMembershipResolverKeys :7450 private 12 120 getServicesRegistry (field)
hierarchyResolverMounted :7478 private 8 55 getServicesRegistry (field)

protocol-authoring-posture-probes.ts has 114 lines.

The shape (#23050 shape B, as cuts 1–25 apply it)

  • The class keeps all four members. Each keeps its name, docblock, signature and position. Each body now delegates, marked [#25467 cut 26].
  • orgWallEnforced reads no member. This was measured by an AST walk over this accesses, with as casts, parentheses and ! stripped, and bare this checked. So its stub is a plain call, return orgWallEnforced();, as cuts 16, 21 and 23 did.
  • The other three read only the constructor-supplied getServicesRegistry field. AuthoringPostureProbesHost types it as the class member itself (ObjectStackProtocolImplementation['getServicesRegistry']). Each stub hands it over as a getter (the hand-over of cuts 20 and 24). So each probe reads the registry at call time, and a resolver registered after this protocol is assembled is seen by the next write, as before.
  • No member changes visibility.
  • Every caller still calls the class method. The only callers are the four closures in cut 8's assertRuntimeAuthoringRules stub (:7102, :7103, :7106, :7107, base). They do not change.

Module-level declarations, by reader: four imports leave protocol.ts

Measured by an AST walk over each body's identifiers, against protocol.ts's top-level declarations and import bindings (property names excluded):

Name Where protocol.ts imported it Other readers in protocol.ts What this cut does
postureEnforcesWall @objectstack/spec/security none (docblock mentions only) dropped from that import; the new module imports it from there
anonymousFormIntakePosture @objectstack/metadata-core none dropped from that import
RLS_MEMBERSHIP_RESOLVER_SERVICE @objectstack/spec/contracts none dropped from that import (FIELD_FILE_SERVICE stays)
HIERARCHY_SCOPE_RESOLVER_SERVICE ./runtime-authoring-gate.js none dropped from that import
resolveTenancyPosture @objectstack/types yes (:15259) stays; imported from there too
TenancyPosture (type) @objectstack/spec/security yes (the stub's declared return type) stays; imported as a type too

Two import comments move with their imports. Each explained exactly the name that left:

  • the [#6285] comment over the @objectstack/spec/security import ("ADR-0105 D1's authority on 'does this deployment wall organizations?'… the posture, and only the posture, is what the runtime authoring gate is told");
  • the [#21476] comment over anonymousFormIntakePosture inside the @objectstack/metadata-core import.

Both now sit, byte-identical, over the same imports in the new module. Leaving them in protocol.ts would have left two comments explaining imports it no longer has.

The published .d.ts

The series bar is ruling 6348891160: sorted-union identity, measured first, with raw identity reported when it holds. Both hold for this cut, including on the merged tree against main. As the claim warned, four imports leave protocol.ts and nine lines leave the import block, so the emitted specifiers and the cut 25 comment-offset trap were watched. Neither moved: the raw hash is identical at both ends.

Base aeb9cf0ddc and head 98200caa10 were built with pnpm --filter @objectstack/metadata-protocol build against one dependency tree. The closure was built at head by a full pnpm build (116/116). For the base build, the package's src/ was swapped tree-only to base (the new module removed) and restored from HEAD under a trap. Afterwards git diff HEAD was empty, git status was clean, and both touched blobs equalled HEAD's.

  • Normalised sha256 (the ruling's bar): 6de2d665caaedb0a17a1db1c77288a3a8108df3e78edb5f7d4e490f09fbccde4 at both ends, on dist/index.d.ts and dist/index.d.cts. The normaliser sorted 80 string-literal union runs. Sorted-union identity holds.
  • Raw sha256: b73f06345eff65ebf7ffa0180caf62d8b3ebb16a29931060a115b6d949d4e203 at both ends, 777,497 bytes. Raw identity holds.
  • The two builds differ where they should.
    • index.js hashes f40d769609a618d7… at base and fda9ac7d969284cc… at head (index.cjs 4144276cf3ac53bc… → 736a7dde582b26bb…).
    • ….call(host goes 0 → 1 for the three host-reading members in both bundles, and orgWallEnforced has no .call.
    • Each function …( declaration goes 0 → 1.
  • Positive control on the head tree. private was dropped from rlsMembershipResolverKeys on disk (restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to 90baecbeff563e05… (normalised f75a46f7798ad08d…, which differs from the bar's 6de2d665…) and print rlsMembershipResolverKeys(): readonly string[] | undefined;. So the bar can see a change on these members.
  • Export tables. The ESM and CJS bundles, loaded at runtime from the package, each export the same 78 names at base and head. The sorted list hashes to 9f18a0177ac736ed… on all of them. None of the four functions, and not AuthoringPostureProbesHost, appears.

The merged tree against main

The merge brought in #26811's protocol.ts change, so the merged tree a885bdbbdf was measured against main e1bfde3601, its second parent, in the same way. The closure was rebuilt by a full pnpm build (116/116), and main's package src/ was swapped in tree-only and restored under a trap. The merged tree differs from main only by this cut: 2 files, +138 / −41.

  • .d.ts: raw 455d53280fb9c922… (777,661 bytes) and normalised c5edca148ff159d4…, the same at main and merged. Both identities hold.
  • index.js: c2202f9429366633… at main, c9423dc904760728… merged. Export tables: 78 names, 9f18a0177ac736ed… on both.
  • AST compare, main's protocol.ts against the merged one: 233 tokens, 0 differ.
  • Suite: main and merged both give 21,688 tests (21,662 passed, 0 failed, 26 skipped, 274 files), per test identical.

No changeset: skip-changeset, per ruling 6348891160.

The durability --list

check-durability-degradation-log-level exits 0 at both ends with identical output: 88 read seam(s) at both ends. Its --list is identical once [@:][0-9]+ is stripped (164 lines, and no entry changes file).

  • Each moved try guards a service-registry get or the posture read, none in the gate's read vocabulary (find/findOne/count).
  • No seam enters or leaves the census.
  • On the merged tree the verdict still reads 88 read seam(s).

Proof of move-only

Bodies, by AST token. The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters (less the added this), the return type and the body. It compares base protocol.ts at aeb9cf0ddc against the head module:

orgWallEnforced: base tokens 24, head tokens 24, differ 0, text-equal(ws-collapsed, comments incl.) true
tenancyPostureInForce: base tokens 34, head tokens 34, differ 0, text-equal(ws-collapsed, comments incl.) true
rlsMembershipResolverKeys: base tokens 120, head tokens 120, differ 0, text-equal(ws-collapsed, comments incl.) true
hierarchyResolverMounted: base tokens 55, head tokens 55, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=233 differ=0 members=4
CLASS members base=301 head=301 order-same=true byte-identical=297 stubs-with-identical-docblock+signature=4 other=[]
TOP base=255 head=256 byte-identical=251

Negative controls, one per member. Each changed one token in an in-memory copy of the head module, and only the mutated member moved (differ 1; the other three stayed 0):

"return true;"            -> "return false;"             orgWallEnforced differ 1
"get('tenancy')"          -> "get('tenant')"             tenancyPostureInForce differ 1
"typeof k === 'string'"   -> "typeof k === 'number'"     rlsMembershipResolverKeys differ 1
"resolver !== undefined && resolver !== null" -> "… || …"  hierarchyResolverMounted differ 1
  • Class side. There are 301 members at base and at head, in the same order. The 297 that did not move are byte-identical to base, leading comments included. All four stubs sit at their base positions and keep their docblocks and signatures byte-identical.

  • Top level of protocol.ts. Of the 255 base statements, 251 are byte-identical, leading comments included. The other four are the import statements that each lost one name:

    • @objectstack/spec/security (with its [#6285] comment);
    • ./runtime-authoring-gate.js;
    • @objectstack/metadata-core (with the [#21476] comment inside it);
    • @objectstack/spec/contracts.

    Nothing else changed in any of them. Head adds one statement, the new module's import.

  • Size. +138 / −41 over 2 files, 179 changed lines (the proposal's estimate was 160–220).

Readers of the moved lines

The tests and scripts that read protocol.ts by path were grepped for the four member names, the four dropped imports and the two moved comments' tags (#6285, #21476). These are the 26 files that name protocol.ts and call readFileSync. So were the test files in packages/, docs/qa/ and the @objectstack/spec liveness ledgers.

  • By path: none reads a moved line. The one textual hit is orgWallEnforced in protocol.bracketed-refusal-opener-absence.test.ts. It is the gate's input key, passed to evaluateRuntimeAuthoringGate, not the member.

  • Behaviour through the gate, no edit owed. These pass the same keys as gate inputs, or drive the gate through the protocol:

    • protocol.platform-schedule-org-gate.test.ts, runtime-authoring-gate.public-form-intake.test.ts and runtime-authoring-gate.hierarchy-scope.test.ts (metadata-protocol);
    • validate-membership-key-declarations.test.ts and validate-rls-predicate-enforceability.membership-keys.test.ts (lint);
    • explain-engine.test.ts (plugin-security).

    The metadata-protocol ones pass at both ends, in the package suite.

  • The liveness ledgers. No row points at any of the four. Carried forward: manifest.json's dependencies row → protocol.ts#resolveWritePackageScope (cut 19), and seed.json → protocol.ts#applySeedBodies (cut 14). These are packages/spec/**, outside this card.

  • ADR anchors. check-adr-anchors exits 0 at both ends.

    • The decision-letter count goes 1,898 → 1,897. The moved [#6285] comment cites ADR-0105 D1 and left the anchored protocol.ts, as in cuts 8 and 12.
    • The citation count is unchanged at 59,994, since that citation now counts in the new module, which has no ADR in its own header. The file count goes 8,405 → 8,406.
  • Engine-double contract (check-engine-double-contract): exits 0 at both ends.

    • The updateData/deleteData seams keep their entries at new line numbers (13838 → 13821, 13863 → 13846).
    • productionFiles is 3,804 at base and 3,806 at head. The +2 are the new file and the gitignored packages/spec/src/migrations/registry.ts that the spec build writes in the head tree, as in cuts 20–25.
  • Identical output at both ends: check-adr-symbol-anchors, check-system-context-census, check-tenant-audit-census, check-query-options-erasure-ratchet, check-filter-alias-parity and check-durability-degradation-log-level. check-nul-bytes differs only in the scanned file count, +1. The base-end readings were taken in a clean detached worktree at aeb9cf0ddc.

Verification

  • tsc --noEmit in packages/metadata-protocol exits 0 at head and on the merged tree.
  • pnpm exec eslint --no-inline-config packages/metadata-protocol exits 0 with no output (the whole touched package, with the root lint script's flags).
  • packages/metadata-protocol suite, compared per test name and status. Each leg ran vitest --maxWorkers=2 with the JSON reporter under the shared verify lock.
    • Base aeb9cf0ddc: 21,648 tests (21,622 passed, 0 failed, 26 skipped), 273 files.
    • Head 98200caa10: 21,648 tests (21,622 passed, 0 failed, 26 skipped), 273 files.
    • None lost, none added, none changed status. The base leg is the package src/ swapped tree-only from aeb9cf0ddc (the new module removed) and restored from HEAD under a trap.
    • The merged head a885bdbbdf and main e1bfde3601 both give 21,688 tests, per test identical (0 lost, 0 added, 0 changed).
  • check-platform-checklist --changed-from origin/main (merged tree): OK, 0 anchors lost.
  • Governed surface. check-governed-merges --branch claude/project-thread-ybjm3h finds 0 of 2 paths in the register (13 surfaces), so the PR is NOT governed. It has 179 changed lines, under the 3,000-line human-merge bar.
  • Derived gates. dispatch-gates --commands --repo objectstack-ai/objectstack derives 46 gates on 2 changed paths from a885bdbbdf, with no stale-tree finding. All 46 exit 0 after the full pnpm build (116/116). dispatch-gates --ran reports 46 derived, 46 run, 0 NOT-MEASURED and 0 UNRUN, and exits 0.
  • check-durability-degradation-log-level --list on a885bdbbdf: 88 read seam(s).

Carry-forward

These stay in protocol.ts:

  • The read-seam members (cut 21): findEnvironmentRows, packagelessAddress, findServedOverlayRow, foldStoredCollection, applyObjectRegistryMutation, unmanagedImportCollisions, rollBackUnmanagedImport, readRevertableCommit and storedFlowBindingAgrees. rethrowUnlessMetadataStoreUnprovisioned cannot move at all.
  • Public members whose return type is inferred.
  • The bodies kept by a by-path reader:
    • probeRecord, getMetaItemCached, findReferencesToMeta, refuseUnmintableMetaType and metaTypeNamespaceExists.
    • viewContainerNameCollisionRefusal with overlaidShippedContainerViewNames, and reportPageRequiresAbsentAtLoad.
    • The 7 applyRegistryWriteThrough callers, readCodeLayerForCarryForward and ensureObjectStorage.
    • emitMetadataMutation.
  • The private-static readers: refusePackagedBaseRemoval, packagedBaseRefusal, runPublishSideEffects, promoteDraftForPublish, publishPackageDrafts and discardPackageDrafts.
  • The field writers: warnClosureReadUnavailable and resolveSduiManifest.

🤖 Generated with Claude Code

https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6

…ture probes out of protocol.ts into protocol-authoring-posture-probes.ts (#25467 cut 26)

Move-only, shape B. orgWallEnforced, tenancyPostureInForce,
rlsMembershipResolverKeys and hierarchyResolverMounted move, unedited, into
one module. orgWallEnforced reads no member, so its stub calls the plain
function; the other three get the getServicesRegistry field as a getter.
postureEnforcesWall, anonymousFormIntakePosture,
RLS_MEMBERSHIP_RESOLVER_SERVICE and HIERARCHY_SCOPE_RESOLVER_SERVICE lose
their last reader in protocol.ts and leave its imports, with the comments
that explained them.

Claude-Session: https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

7 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
  • the SDK route bridge reached 55 of 207 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 — 11 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 a20824f3661810909cfd701c29da1ba04e4a081d → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-27T19:47Z

Head a885bdbbdf, report 6451437092 (on #25467), claim 6449989109. No contract review is owed: there is no packages/spec/** diff, no governed path, no narrowing and no Clause-②: yes.

item reading
Shape Draft, base main, Part of #25467 (cut 26). 2 files: protocol.ts and one new module for the claimed theme, protocol-authoring-posture-probes.ts (orgWallEnforced, tenancyPostureInForce, rlsMembershipResolverKeys, hierarchyResolverMounted). skip-changeset (move-only). Clause-②: no, Metadata-protocol: none.
Move check (seat) 38 of the 41 non-blank lines the PR removes from protocol.ts are found verbatim (indent-stripped) in the new module. The other 3 are import lines that lost specifiers whose last reader moved: postureEnforcesWall, RLS_MEMBERSHIP_RESOLVER_SERVICE and HIERARCHY_SCOPE_RESOLVER_SERVICE. Each specifier is imported again in the new module, and protocol.ts keeps the narrowed imports. The added lines are the module's import and four stubs: orgWallEnforced is a plain call, since it reads no member, and the other three go through AuthoringPostureProbesHost with getServicesRegistry as a getter. Cut 8's gate-stub closures keep calling the class members.
Evidence CI green on head a885bdbbdf (34 success, 16 skipped, 0 failed). Governed check: NOT governed, 179 changed lines. The report gives an AST compare of 233 tokens with 0 differ (each negative control moves only its own member), raw .d.ts identity at both ends with the normalised hash identical (ruling 6348891160), the suite at 21,648 tests per-test identical, the read-seam census at 88 with an identical --list, and 46 of 46 derived gates. After the merge brought in #26811, the merged tree against main gave .d.ts identity, 21,688 tests per-test identical and an AST compare with 0 differ. #26856 and #26861 also edit protocol.ts; both are merge-tree clean against this head, and tsc exits 0 on the head merged with #26856.
Disclosed The check-adr-anchors decision-letter count goes from 1,898 to 1,897, because the moved [#6285] comment cites ADR-0105 D1 and leaves the anchored protocol.ts (as in cuts 8 and 12).
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, once every check on this head is green or an expected skip.

Generated by Claude Code

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