Skip to content

refactor(metadata-protocol): move saveConflict and listLiveMetadataTypes out of protocol.ts, each into its own module (#25467 cut 24) - #26704

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 24 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cuts 1–23 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404, #26449 and #26654. The claim is 6441745164. It takes the cut 24 proposal of the cut 23 report (6440919239). As the claim orders, the two members are not one theme, so each moves into its own module. Both passed the AST screen and the read-seam census, and both moved unedited.

Two ObjectStackProtocolImplementation members move unedited, each into a new module (#23050 shape B):

protocol.ts loses 13 net lines (29,165 to 29,152). 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 fe53769a8e (cut 23's merge). It names no issue, so the claim comment is the identity record. It holds the move commit 34b0e4a3c9 and one merge of main, f430c6a947 (8 commits). The merge touches no file of packages/metadata-protocol/. The package's src/ tree is 917e56be at the move commit and at the merge.

Serial with the file's in-flight PRs

Measured just before this PR was opened, against the merged head f430c6a947: of the 16 open PRs, none edits protocol.ts, and none names either member or either new module. Three touch packages/metadata-protocol/src/ test files: #26337 (3 files, among them protocol.recovery-doors-emit-mutation.test.ts), #26628 (protocol.meta-types-degenerate-derivation.test.ts) and #26701 (protocol.metadata-store-outage.test.ts). Each one's git merge-tree is clean against this head. #26628's test drives getMetaTypes, so listLiveMetadataTypes runs through the class method there. It stubs getRegisteredTypes and does not read protocol.ts by path.

main moved one commit after the merge: #26682 (4373103165, auth sign-out). It touches no file of this package, git merge-tree with this head is clean, and the derivation found nothing stale, so main was not merged again.

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

Both members passed:

  • No private-static read. The AST walk found no access on the ObjectStackProtocolImplementation identifier and none on this.constructor.
  • No error constructed. Neither body has new Error(. saveConflict returns the refusal that metadataConflictRefusal builds. No PRODUCERS row is owed.
  • Read-seam census. check-durability-degradation-log-level --list was taken on the base and again on the cut: 82 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) New module
listLiveMetadataTypes :8494 private 15 105 protocol-live-metadata-types.ts (66 lines)
saveConflict :21469 private 16 163 protocol-save-conflict.ts (68 lines)

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

  • The class keeps both members. Each keeps its name, docblock, signature and position. Each body now delegates, marked [#25467 cut 24].
  • saveConflict reads two members, both methods. SaveConflictHost types each as the class member itself (ObjectStackProtocolImplementation['…']). The function types its this as their Pick, and the stub annotates its literal as ThisParameterType<typeof saveConflict>. metadataConflictRefusal and recordOptimisticConflictAudit are handed over as closures, so the body's calls reach the class methods exactly as before.
  • listLiveMetadataTypes reads two members, both fields. LiveMetadataTypesHost types them the same way. engine (a private field) and getServicesRegistry (the constructor-supplied optional function field) are handed over as getters. This is the hand-over cut 20 used for getServicesRegistry. So the body reads the protocol's own values at call time, and the ?.() on an absent registry behaves as before.
  • No member changes visibility.
  • Every caller still calls the class method. getMetaTypes' this.listLiveMetadataTypes() (:8513, base) does not change, and neither do the save door's two throw await this.saveConflict(err, request, writeSource) sites (:21243, :21454).

Module-level declarations, by reader

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 has it Other readers in protocol.ts What this cut does
ConflictError (type position, saveConflict's parameter) the @objectstack/metadata-core import yes (7: five instanceof sites, among them the save door's two, metadataConflictRefusal's parameter and the stub's signature) stays; the new module imports it as a type from there too

listLiveMetadataTypes' body reads no module-level declaration. Its module imports nothing but the class's type. No import of protocol.ts loses its last reader, and no declaration gains export.

The :172 comment is not touched. The claim allowed correcting the stale [commit ece4dad31] emitted-specifier comment (disclosed in cut 23) if this cut touched that import block. This cut adds its two imports after cut 23's import and does not touch the @objectstack/spec block, so the comment stays for a later edit.

The durability --list

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

  • listLiveMetadataTypes has a try around the metadata service's getRegisteredTypes(). That callee is outside the gate's read vocabulary (find/findOne/count), so the catch was not a read seam before the move and is not one after it.
  • saveConflict has no try.
  • On the merged tree the verdict still reads 82 read seam(s).

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.

Base fe53769a8e and head 34b0e4a3c9 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 (both new modules removed) and restored from HEAD under a trap. Afterwards git diff HEAD was empty, git status was clean, and all three touched blobs equalled HEAD's.

  • Normalised sha256 (the ruling's bar): 310d157f1455a7b76c3d9a60e905ac5e07debb23ec4a6a615b510870341bdbec 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: 724eaa784128c37a7e4b59d85e21212ba6378a5b07279853c97828cd8368e932 at both ends, 777,143 bytes. Raw identity holds.
  • Again on the merged tree f430c6a947 (closure rebuilt by a full pnpm build, 116/116): the same raw and normalised sha256, and the same index.js/index.cjs hashes, as the pre-merge head.
  • The two builds differ where they should.
    • index.js hashes 8cc705b2d0a6a59b… at base and 0cb9bc1754636851… at head (index.cjs 8af96d570232787c… → 40c568bf03d0672b…).
    • listLiveMetadataTypes.call(host and saveConflict.call(host each occur 0 times in the base bundles and once in the head bundles (ESM and CJS alike).
    • function listLiveMetadataTypes( and function saveConflict( each go 0 → 1.
  • Positive control on the head tree. private was dropped from saveConflict on disk (restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to 980c4b207280c4e8… (normalised f5a4a75faae310fd…, which differs from the bar's 310d157f…). 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. Neither moved function nor either …Host interface appears.

No changeset: skip-changeset, per ruling 6348891160.

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 fe53769a8e against the two head modules:

listLiveMetadataTypes: base tokens 105, head tokens 105, differ 0, text-equal(ws-collapsed, comments incl.) true
saveConflict: base tokens 163, head tokens 163, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=268 differ=0 members=2
CLASS members base=301 head=301 order-same=true byte-identical=299 stubs-with-identical-docblock+signature=2 other=[]
TOP base=252 head=254 byte-identical=252

Negative controls, one per member. Each changed one token in an in-memory copy of a head module:

MUTATED packages/metadata-protocol/src/protocol-live-metadata-types.ts "getRegisteredTypes()" -> "getRegisteredTypez()"
listLiveMetadataTypes: base tokens 105, head tokens 105, differ 1, text-equal(ws-collapsed, comments incl.) false
saveConflict: base tokens 163, head tokens 163, differ 0, text-equal(ws-collapsed, comments incl.) true
MUTATED packages/metadata-protocol/src/protocol-save-conflict.ts "return conflict;" -> "return err;"
listLiveMetadataTypes: base tokens 105, head tokens 105, differ 0, text-equal(ws-collapsed, comments incl.) true
saveConflict: base tokens 163, head tokens 163, differ 1, text-equal(ws-collapsed, comments incl.) false
  • Class side. There are 301 members at base and at head, in the same order. The 299 that did not move are byte-identical to base, leading comments included. Both stubs sit at their base positions and keep their docblocks and signatures byte-identical.
  • Top level of protocol.ts. All 252 base statements are byte-identical, leading comments included. Head adds two statements, the new modules' imports.
  • Size. +152 / −31 over 3 files, 183 changed lines.

Readers of the moved lines

The tests and scripts that read protocol.ts by path (the files that name protocol.ts and call readFileSync) were grepped for both member names and for the bodies' text: metadataConflictRefusal, recordOptimisticConflictAudit, has been modified since you loaded it, schemaTypes, runtimeTypes, MetadataService not available. So were the test files in packages/, docs/qa/ and the @objectstack/spec liveness ledgers.

  • By path: none reads the moved bodies. Of the 26 such files, the one textual hit is a // MetadataService not available comment inside an inline fixture of scripts/check-durability-degradation-log-level.mjs. That fixture is its own code string and does not read protocol.ts.
  • By name, outside the package: packages/spec/src/meta-spelling/metadata-url-spelling.ts names listLiveMetadataTypes() in a comment only. No edit is owed.
  • The liveness ledgers. No row points at either member. Carried forward: manifest.json's dependencies row → protocol.ts#resolveWritePackageScope (cut 19), and seed.json → protocol.ts#applySeedBodies (cut 14). Both still quote lines that moved to a seam module. check:liveness checks only that the #symbol is named in the file, and both stubs keep it. Re-pointing them is a packages/spec/** edit, outside this card.
  • ADR anchors. check-adr-anchors gives identical output at both ends: 1,871 decision-letter citations, and 59,671 citations across 8,363 files. Neither moved body cites an ADR. listLiveMetadataTypes' docblock (ADR-0131 D6) stays on the stub, and neither new module's header cites one.
  • Engine-double contract (check-engine-double-contract): exits 0 at both ends.
    • The updateData/deleteData seams keep their entries at new line numbers (13866 → 13863, 13891 → 13888).
    • productionFiles is 3,777 at base and 3,780 at head. The +3 are the two new files and the gitignored packages/spec/src/migrations/registry.ts that the spec build writes in the head tree, as in cuts 20–23.
  • Identical output at both ends: check-adr-anchors, 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, +2. The base-end readings were taken in a clean detached worktree at fe53769a8e.

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, base and head, compared per test name and status. Each leg ran vitest --maxWorkers=2 with the JSON reporter under the shared verify lock.
    • Base fe53769a8e: 21,599 tests (21,580 passed, 0 failed, 19 skipped), 272 files.
    • Head 34b0e4a3c9: 21,599 tests (21,580 passed, 0 failed, 19 skipped), 272 files.
    • None lost, none added, none changed status. The base leg is the package src/ swapped tree-only from fe53769a8e (both new modules removed) and restored from HEAD under a trap.
    • The merged head f430c6a947 gives the same 21,599 tests, per test identical to the pre-merge head (0 lost, 0 added, 0 changed).
  • check-platform-checklist --changed-from origin/main (merged tree): OK. 1 anchored file is in the change set, 29 anchors were judged, and 0 were lost.
  • Governed surface. check-governed-merges --branch claude/project-thread-ybjm3h finds 0 of 3 paths in the register (13 surfaces), so the PR is NOT governed. It has 183 changed lines, under the 3,000-line human-merge bar.
  • Derived gates. dispatch-gates --commands --repo objectstack-ai/objectstack derives 46 gates on 3 changed paths from f430c6a947, with no stale-tree finding, and 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 f430c6a947: 82 read seam(s).

Carry-forward

These stay in protocol.ts:

  • The read-seam members (cut 21). A member named in a protocol.ts read-seam entry of check-durability-degradation-log-level --list, as the guarded wrapper or the guarding member, stays until the gate follows imported wrappers or a ruling accepts the loss. The census-negative set is the one cuts 22–23 measured (this cut re-trialled only its own members and the cut 25 candidates): 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 (among them applyRemoteMetadataMutation), readCodeLayerForCarryForward and ensureObjectStorage.
    • emitMetadataMutation: its body is one of the two this.notifyMutationListenersLocal( sites that protocol.recovery-doors-emit-mutation.test.ts counts in protocol.ts.
  • The private-static readers: refusePackagedBaseRemoval, packagedBaseRefusal, runPublishSideEffects, promoteDraftForPublish, publishPackageDrafts and discardPackageDrafts.
  • The field writers: warnClosureReadUnavailable.

🤖 Generated with Claude Code

https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6

…pes out of protocol.ts into their own modules (#25467 cut 24)

Move-only, shape B. saveConflict (the save door's 409 for a ConflictError,
with its decision-audit row) moves to protocol-save-conflict.ts;
listLiveMetadataTypes (the SchemaRegistry's types and the metadata
service's, de-duplicated) moves to protocol-live-metadata-types.ts. Each
class member keeps its name, docblock and signature as a stub that hands a
host to the moved function.

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

10 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 name(s) were too generic to anchor anything (single lowercase words)
  • 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 43731031654ddabb4f87844140b8c872845ab5c0 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 43731031654ddabb4f87844140b8c872845ab5c0

⚠️ 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-27T13:51Z

Head f430c6a947, report 6443136164 (on #25467), claim 6441745164. 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 24). 3 files: protocol.ts and two new modules, one per theme as the claim asked: protocol-save-conflict.ts (saveConflict) and protocol-live-metadata-types.ts (listLiveMetadataTypes). skip-changeset (move-only). Clause-②: no, Metadata-protocol: none.
Move check (seat) All 29 non-blank lines the PR removes from protocol.ts are found verbatim (indent-stripped) in the two new modules. The 18 added lines are the two imports with their header comments and the two stubs: listLiveMetadataTypes through LiveMetadataTypesHost with getters for engine and getServicesRegistry, saveConflict through SaveConflictHost with closures for metadataConflictRefusal and recordOptimisticConflictAudit. Every caller keeps calling the class members.
Evidence CI green on head f430c6a947 (34 success, 16 skipped, 0 failed). Governed check: NOT governed, 183 changed lines. The report gives an AST compare of 268 tokens with 0 differ (each negative control at 1), raw and sorted-union .d.ts identity (ruling 6348891160) with the positive control moving the hash, the suite at 21,599 tests per-test identical at base, head and merged, the read-seam census at 82 with an identical --list, and 46 of 46 derived gates. No open PR edits protocol.ts.
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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 13:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 88fb255 Oct 27, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ybjm3h branch October 27, 2026 14:40
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