Skip to content

refactor(metadata-protocol): move the stored-item conversion read out of protocol.ts into protocol-stored-item-conversion.ts (#25467 cut 23) - #26654

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

objectstack-fleet[bot] merged 3 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 23 of the protocol.ts split (#24586 ruling A, recorded in 6270097840), the first after the release pause (restart recorded on #25467 once 18.2.0 shipped). Cuts 1–22 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404 and #26449. The claim is 6439185861. It takes the cut 23 proposal of the cut 22 report (6433520681). Both members passed the AST screen and the read-seam census, and both moved unedited.

Two ObjectStackProtocolImplementation members move unedited into a new module, packages/metadata-protocol/src/protocol-stored-item-conversion.ts (#23050 shape B):

protocol.ts loses 17 net lines (29,182 to 29,165). 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 f9867020c6 after the restart. It names no issue, so the claim comment is the identity record. It holds the move commit 1d1579ed11 and two merges of main: 6d09a4555e, and 0741a0f66b, which was made because dispatch-gates itself had changed on main (scripts and docs only). Neither merge touches a file of packages/metadata-protocol/. The package's src/ tree is c7cd94f5 at the move commit and at both merges.

Serial with the file's in-flight PRs

Measured just before this PR was opened, against the merged head 0741a0f66b: of the 15 open PRs, none edits protocol.ts, and none names either member, withDeclaredPageTypeDefault or applyConversionsToStoredItem. One touches packages/metadata-protocol/src/ test files: #26337 (3 files). Its git merge-tree is clean against this head and against origin/main alone. No open PR edits the new module.

main moved one commit after the last merge: #26599 (dbf7e04adf), the spec retirement of conversion kits at or below 16.x. It does not touch protocol.ts. It does edit seven protocol.*stored*/conversion test files in this package and the spec conversion chain that applyConversionsToStoredItem runs. Its git merge-tree with this head is clean. This cut moves the call to that chain unedited, so the PR's merge ref runs #26599's tests against the moved code. Its one stale gate file (scripts/check-spec-docblock-symbol-anchors.mjs) is not among this cut's 46 derived commands, so main was not merged a third time.

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(, 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: 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)
convertStoredItem :6416 private 13 138
convertStoredItemDetailed :6452 private 15 187

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

  • The class keeps both members. Each keeps its name, docblock, signature and position. Each body now delegates, marked [#25467 cut 23].
  • convertStoredItemDetailed 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 convertStoredItemDetailed(type, data, onNotice, knownDatasources);, as cuts 16 and 21 did. The class signature keeps the ConversionNotice/ConversionTodoNotice types, so those type imports stay in protocol.ts.
  • convertStoredItem reads two members. StoredItemConversionHost 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 convertStoredItem>.
    • convertStoredItemDetailed is handed over as a closure, so the body's call still reaches the class method and, through its stub, the moved function.
    • storedConversionWarned is a private FIELD, a Set<string>, so it is handed over as a getter. The body only calls .has and .add on it, inside the onNotice arrow, and never reassigns it. So the warn-once set it reads and adds to is the protocol's own, shared with every other read, exactly as before.
    • The getter is the same hand-over cut 20 used for the getServicesRegistry field.
  • No member changes visibility.
  • Every caller still calls the class method. The this.convertStoredItem( sites at :7277, :9812, :10857, :10922, :11498, :18885, :25342, :25576 and :28359 (base) do not change. Neither does the cut 16 closure that hands it to storedOverlayEntries (:10211), or the remaining this.convertStoredItemDetailed( site (:21925).

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
withDeclaredPageTypeDefault non-exported function, :2263 yes (the save door, :20924) gains export in place; the new module imports it from ./protocol.js
applyConversionsToStoredItem the @objectstack/spec import, :170 none dropped from that import (its last reader moved); the new module imports it from @objectstack/spec
ConversionNotice, ConversionTodoNotice type imports on the same line yes (both stubs' declared signatures) stay; the new module imports them as types from there too
PLURAL_TO_SINGULAR the @objectstack/spec/shared import yes (58) stays; imported from there too

index.ts names its exports one by one, so the in-place export does not reach the published surface.

One stale prose reference, disclosed and not edited. The [commit ece4dad31] emitted-specifier comment under the imports (:172) says the file imports the @objectstack/spec root "for applyConversionsToStoredItem above". The root import stays, for applyConversionsToFlow and the two notice types, so the pin it explains still holds. The .d.ts readings below confirm that nothing about the emitted declarations moved. Editing that sentence is not move-only, so this PR leaves it 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). Neither body has a try. convertStoredItem's console.warn sits in an onNotice callback, not a catch, so the log-level rule does not grade it. 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 f9867020c6 and head 1d1579ed11 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 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): dd66cd91652093f2a0ce9a3b990290291bf3bcc40379d196a1bbe897c1ec43d7 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: f4f602c840264aa052ee96788778479ba2ddccbd68199ad68f4419ff564f6ccd at both ends, 778,346 bytes. Raw identity holds. The emitted specifiers did not move with the import change.
  • Again on the merged tree 6d09a4555e (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 second merge changed no package and no build input.
  • The two builds differ where they should.
    • index.js hashes 3aedf79b4657c927… at base and 8cc705b2d0a6a59b… at head (index.cjs b1d7383e753c4e1a… → 8af96d570232787c…).
    • convertStoredItem.call(host occurs 0 times in the base bundles and once in the head bundles (ESM and CJS alike).
    • function convertStoredItem( goes 0 → 1.
  • Positive control on the head tree. private was dropped from convertStoredItem on disk (restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to a39e3a50ba0baab4… (normalised 573d8a0a1d7bb1ca…, which differs from the bar's dd66cd91…) and print convertStoredItem(type: string, data: unknown): unknown;. 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 moved functions, StoredItemConversionHost or withDeclaredPageTypeDefault 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 f9867020c6 against the head module:

convertStoredItem: base tokens 138, head tokens 138, differ 0, text-equal(ws-collapsed, comments incl.) true
convertStoredItemDetailed: base tokens 187, head tokens 187, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=325 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=251 head=252 byte-identical=249

The two top-level statements that are not byte-identical are the @objectstack/spec import, which lost applyConversionsToStoredItem, and withDeclaredPageTypeDefault, which gained export. Nothing else changed in either.

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

MUTATED … "if (singular === 'flow')" -> "if (singular !== 'flow')"
convertStoredItem: base tokens 138, head tokens 138, differ 0, text-equal(ws-collapsed, comments incl.) true
convertStoredItemDetailed: base tokens 187, head tokens 187, differ 1, text-equal(ws-collapsed, comments incl.) false
MUTATED … "storedConversionWarned.has(key)" -> "storedConversionWarned.add(key)"
convertStoredItem: base tokens 138, head tokens 138, differ 1, text-equal(ws-collapsed, comments incl.) false
convertStoredItemDetailed: base tokens 187, head tokens 187, differ 0, text-equal(ws-collapsed, comments incl.) true
  • 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. Of the 251 base statements, 249 are byte-identical, leading comments included; the other two are the import and the export above. Head adds one statement, the new module's import.
  • Size. +111 / −30 over 2 files, 141 changed lines (the proposal's estimate was 150–200). protocol-stored-item-conversion.ts has 98 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: storedConversionWarned, applyConversionsToStoredItem, withDeclaredPageTypeDefault, carries a pre-protocol shape. So were the test files in packages/, docs/qa/ and the @objectstack/spec liveness ledgers.

  • By path: none reads the moved bodies. Of the 33 such files, the one textual hit is the gitignored, build-written packages/spec/src/migrations/registry.ts, which names applyConversionsToStoredItem (a @objectstack/spec export) in its migration entries and does not read protocol.ts.
  • Behaviour through the class method, no edit owed. protocol.flow-canonicalizer.test.ts and metadata-diagnostics.union-issues.test.ts (metadata-protocol) and view-overlay-owner-hidden-retirement.test.ts (spec) name convertStoredItem in comments only, and drive the read through the protocol. The first two pass at both ends, in the package suite.
  • 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 exits 0 at both ends.
    • The decision-letter count is unchanged at 1,881. The moved bodies carry no ADR letter; the docblocks that cite ADR-0087 D2 stay on the stubs.
    • The citation count goes 59,819 → 59,821 and the file count 8,353 → 8,354, from the new module's header.
  • Engine-double contract (check-engine-double-contract): exits 0 at both ends.
    • The updateData/deleteData seams keep their entries at new line numbers (13883 → 13866, 13908 → 13891).
    • productionFiles is 3,773 at base and 3,775 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–22.
  • 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 f9867020c6.

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 f9867020c6: 21,595 tests (21,576 passed, 0 failed, 19 skipped), 272 files.
    • Head 1d1579ed11: 21,595 tests (21,576 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 f9867020c6 (the new module removed) and restored from HEAD under a trap.
    • The merged head 6d09a4555e gives the same 21,595 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 2 paths in the register (13 surfaces), so the PR is NOT governed. It has 141 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 0741a0f66b, with no stale-tree warning, and all 46 exit 0. dispatch-gates --ran reports 46 derived, 46 run, 0 NOT-MEASURED and 0 UNRUN, and exits 0.
    • The same 46 also exited 0 on 6d09a4555e, after the full pnpm build (116/116).
    • That derivation flagged scripts/pm/dispatch-gates.mjs itself as stale against main, so main was merged again, and the derivation and every gate re-run.
  • check-durability-degradation-log-level --list on 0741a0f66b: 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 trial on this base, over 50 members, measured the same census-negative set as cut 22's trial: findEnvironmentRows 66, packagelessAddress 78, findServedOverlayRow 79, and foldStoredCollection, applyObjectRegistryMutation, unmanagedImportCollisions, rollBackUnmanagedImport, readRevertableCommit and storedFlowBindingAgrees 81. rethrowUnlessMetadataStoreUnprovisioned cannot move at all.
  • Public members whose return type is inferred.
  • The bodies kept by a reader: probeRecord, getMetaItemCached, findReferencesToMeta, refuseUnmintableMetaType, metaTypeNamespaceExists, viewContainerNameCollisionRefusal with overlaidShippedContainerViewNames, reportPageRequiresAbsentAtLoad, the 7 applyRegistryWriteThrough callers, readCodeLayerForCarryForward and ensureObjectStorage.
  • 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

… of protocol.ts (#25467 cut 23)

Move the bodies of convertStoredItem and convertStoredItemDetailed
unedited into protocol-stored-item-conversion.ts. convertStoredItem sits
behind a StoredItemConversionHost that hands over convertStoredItemDetailed
as a closure and the storedConversionWarned field as a getter;
convertStoredItemDetailed reads no member, so its stub calls the plain
function directly. withDeclaredPageTypeDefault gains export in place, and
applyConversionsToStoredItem, whose last reader moved, leaves
protocol.ts's @objectstack/spec import. The class keeps both methods,
their docblocks and declared signatures; their callers keep calling the
class methods.

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 dbf7e04adff66aba72e634c65aae754440a8d89d → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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-27T11:59Z

Head 0741a0f66b, report 6440919239 (on #25467), claim 6439185861. 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 23, the first cut after the 18.2.0 restart). 2 files: protocol.ts and the new protocol-stored-item-conversion.ts. skip-changeset (move-only). Clause-②: no, Metadata-protocol: none.
Move check (seat) Of the 30 non-blank lines the PR removes from protocol.ts, 28 are found verbatim (indent-stripped) in the new module. The other 2 are the @objectstack/spec import losing applyConversionsToStoredItem (its last reader moved) and withDeclaredPageTypeDefault gaining export in place. The 13 added lines in protocol.ts are that import, the new module's import, the export, and the two stubs (convertStoredItem through StoredItemConversionHost with a getter for storedConversionWarned; convertStoredItemDetailed as a direct call).
Evidence CI green on head 0741a0f66b (34 success, 16 skipped, 0 failed). Governed check: NOT governed, 141 changed lines. The report gives an AST compare of 325 tokens with 0 differ (two negative controls at 1), raw and sorted-union .d.ts identity (ruling 6348891160), the suite at 21,595 of 21,595 with 0 lost, added or changed, the read-seam census at 82 with an identical --list at base, head and merged, and 46 of 46 derived gates.
Disclosed The emitted-specifier comment at protocol.ts:172 (ece4dad) still names applyConversionsToStoredItem. The root import stays and the raw .d.ts did not move, so its pin holds. Left as is.
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