Skip to content

Commit fe53769

Browse files
refactor(metadata-protocol): move the stored-item conversion read out of protocol.ts into protocol-stored-item-conversion.ts (#25467 cut 23) (#26654)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X7JKgiM29GUcitRvoGGj5BU)_ 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): - `convertStoredItemDetailed`: the ADR-0087 conversion chain over a stored body, with its notices and TODOs handed back. `migrateStoredMetadata` reports these per row (#4327, #17321, #25279). - `convertStoredItem`: the read every stored row goes through. It warns once per conversion, row and process, and fills a page's declared `type` default (#20101). `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 ece4dad]` 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.com/claude-code) https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 416a760 commit fe53769

2 files changed

Lines changed: 111 additions & 30 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 23] The read-side conversion of a stored metadata item by
5+
* `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: the
6+
* ADR-0087 conversion chain over a stored body with its notices and TODOs
7+
* handed back (`convertStoredItemDetailed`, #4327/#17321/#25279), and the
8+
* read every stored row goes through, which warns once per conversion, row
9+
* and process and fills a page's declared `type` default
10+
* (`convertStoredItem`, #20101).
11+
*
12+
* A MOVE, not a redesign. Each function below is the body of the
13+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
14+
* re-indented one level and otherwise unedited: the same `flow` exemption,
15+
* the same chain call, the same warn-once key and text, and the same page
16+
* default.
17+
*
18+
* ## How the moved bodies reach the class
19+
*
20+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
21+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
22+
* The class keeps both methods — the published `dist/*.d.ts` lists their
23+
* names, docblocks and declared signatures. `convertStoredItemDetailed`
24+
* reads no member, so its stub calls the plain function directly.
25+
* `convertStoredItem`'s stub is one literal of exactly the
26+
* {@link StoredItemConversionHost} members it reads, plus a `.call`: the
27+
* `convertStoredItemDetailed` method as a closure, and the
28+
* `storedConversionWarned` field as a getter, so the warn-once set the body
29+
* reads and adds to is the protocol's own. Every other reader of either
30+
* method keeps calling the class method.
31+
*
32+
* What the bodies read of `protocol.ts`'s own module-level declarations they
33+
* import from it; what they import from elsewhere, they import from where
34+
* `protocol.ts` imported it. Nothing re-exports this module, so the published
35+
* surface does not move.
36+
*/
37+
38+
import { applyConversionsToStoredItem, type ConversionNotice, type ConversionTodoNotice } from '@objectstack/spec';
39+
import { PLURAL_TO_SINGULAR } from '@objectstack/spec/shared';
40+
import type { ObjectStackProtocolImplementation } from './protocol.js';
41+
import { withDeclaredPageTypeDefault } from './protocol.js';
42+
43+
/**
44+
* The `ObjectStackProtocolImplementation` members the functions below read —
45+
* every one of them, and no other; a function's `this` is the `Pick` of the
46+
* members it reads. Each is typed AS the class member (an indexed access on
47+
* the class), so a change to a member's signature on the class is a change
48+
* here too, and naming a member the class does not have is a compile error.
49+
*/
50+
export interface StoredItemConversionHost {
51+
readonly convertStoredItemDetailed: ObjectStackProtocolImplementation['convertStoredItemDetailed'];
52+
readonly storedConversionWarned: ObjectStackProtocolImplementation['storedConversionWarned'];
53+
}
54+
55+
/** The body of `ObjectStackProtocolImplementation#convertStoredItem`; the method's docblock there is its contract. */
56+
export function convertStoredItem(
57+
this: Pick<StoredItemConversionHost, 'convertStoredItemDetailed' | 'storedConversionWarned'>,
58+
type: string,
59+
data: unknown,
60+
): unknown {
61+
const singular = PLURAL_TO_SINGULAR[type] ?? type;
62+
const converted = this.convertStoredItemDetailed(type, data, (n) => {
63+
const name = (data as { name?: unknown } | null | undefined)?.name;
64+
const key = `${n.conversionId}|${singular}|${String(name ?? '')}`;
65+
if (this.storedConversionWarned.has(key)) return;
66+
this.storedConversionWarned.add(key);
67+
console.warn(
68+
`[Protocol] stored ${singular}/${String(name ?? '<unnamed>')} carries a pre-protocol shape; ` +
69+
`${n.message} The row itself is unchanged — re-save it (Studio edit → save, or run ` +
70+
`"os migrate meta --stored --apply") to persist the canonical shape.`,
71+
);
72+
}).item;
73+
return withDeclaredPageTypeDefault(singular, converted);
74+
}
75+
76+
/** The body of `ObjectStackProtocolImplementation#convertStoredItemDetailed`; the method's docblock there is its contract. */
77+
export function convertStoredItemDetailed(
78+
type: string,
79+
data: unknown,
80+
onNotice?: (notice: ConversionNotice) => void,
81+
knownDatasources?: ReadonlyMap<string, { readonly schemaMode?: unknown }>,
82+
): { item: unknown; notices: ConversionNotice[]; todos: ConversionTodoNotice[] } {
83+
const singular = PLURAL_TO_SINGULAR[type] ?? type;
84+
if (singular === 'flow') return { item: data, notices: [], todos: [] };
85+
const notices: ConversionNotice[] = [];
86+
const todos: ConversionTodoNotice[] = [];
87+
const item = applyConversionsToStoredItem(singular, data, {
88+
onNotice: (n) => {
89+
notices.push(n);
90+
onNotice?.(n);
91+
},
92+
onTodo: (t) => {
93+
todos.push(t);
94+
},
95+
...(knownDatasources ? { knownDatasources } : {}),
96+
});
97+
return { item, notices, todos };
98+
}

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL, canonicalMetaUrlType, metaUrlSp
167167
// live transport in through `attachMetadataMutationPubSub`.
168168
import type { IFieldFileService, IPubSub } from '@objectstack/spec/contracts';
169169
import { FIELD_FILE_SERVICE, RLS_MEMBERSHIP_RESOLVER_SERVICE } from '@objectstack/spec/contracts';
170-
import { applyConversionsToFlow, applyConversionsToStoredItem, type ConversionNotice, type ConversionTodoNotice } from '@objectstack/spec';
170+
import { applyConversionsToFlow, type ConversionNotice, type ConversionTodoNotice } from '@objectstack/spec';
171171
import { type FormView, type I18nLabel, isAggregatedViewContainer, expandViewContainer } from '@objectstack/spec/ui';
172172
// [commit ece4dad31] Emitted-specifier pin. This module's inferred public declarations
173173
// structurally mention `FormFieldInput` (FormView `sections[].fields`), and
@@ -411,6 +411,8 @@ import { readItemFromMetadataService } from './protocol-metadata-service-read.js
411411
import { reportSealedOverlayRows } from './protocol-sealed-overlay-report.js';
412412
// [#25467 cut 22] A row-less view name's expansion for the by-name read, and a container's expansion under its own name.
413413
import { expandUnderOwnName, resolveRowlessExpandedView } from './protocol-view-expansion-names.js';
414+
// [#25467 cut 23] The read-side conversion of a stored item: the ADR-0087 chain with its notices, and the warned-once read with the page `type` default.
415+
import { convertStoredItem, convertStoredItemDetailed } from './protocol-stored-item-conversion.js';
414416

415417
/**
416418
* Canonical Zod schema per metadata type lives in
@@ -2260,7 +2262,7 @@ export function projectStorableViewBody(
22602262
* `null`), and an explicit value is left as it is. Returns the input itself
22612263
* when nothing is filled.
22622264
*/
2263-
function withDeclaredPageTypeDefault(type: string, item: unknown): unknown {
2265+
export function withDeclaredPageTypeDefault(type: string, item: unknown): unknown {
22642266
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'page') return item;
22652267
if (!item || typeof item !== 'object' || Array.isArray(item)) return item;
22662268
if ((item as Record<string, unknown>).type !== undefined) return item;
@@ -6414,19 +6416,13 @@ export class ObjectStackProtocolImplementation implements
64146416
* its bytes.
64156417
*/
64166418
private convertStoredItem(type: string, data: unknown): unknown {
6417-
const singular = PLURAL_TO_SINGULAR[type] ?? type;
6418-
const converted = this.convertStoredItemDetailed(type, data, (n) => {
6419-
const name = (data as { name?: unknown } | null | undefined)?.name;
6420-
const key = `${n.conversionId}|${singular}|${String(name ?? '')}`;
6421-
if (this.storedConversionWarned.has(key)) return;
6422-
this.storedConversionWarned.add(key);
6423-
console.warn(
6424-
`[Protocol] stored ${singular}/${String(name ?? '<unnamed>')} carries a pre-protocol shape; ` +
6425-
`${n.message} The row itself is unchanged — re-save it (Studio edit → save, or run ` +
6426-
`"os migrate meta --stored --apply") to persist the canonical shape.`,
6427-
);
6428-
}).item;
6429-
return withDeclaredPageTypeDefault(singular, converted);
6419+
// [#25467 cut 23] The body moved to `protocol-stored-item-conversion.ts` (see `StoredItemConversionHost`).
6420+
const readStoredConversionWarned = () => this.storedConversionWarned;
6421+
const host: ThisParameterType<typeof convertStoredItem> = {
6422+
convertStoredItemDetailed: (...a) => this.convertStoredItemDetailed(...a),
6423+
get storedConversionWarned() { return readStoredConversionWarned(); },
6424+
};
6425+
return convertStoredItem.call(host, type, data);
64306426
}
64316427

64326428
/**
@@ -6455,21 +6451,8 @@ export class ObjectStackProtocolImplementation implements
64556451
onNotice?: (notice: ConversionNotice) => void,
64566452
knownDatasources?: ReadonlyMap<string, { readonly schemaMode?: unknown }>,
64576453
): { item: unknown; notices: ConversionNotice[]; todos: ConversionTodoNotice[] } {
6458-
const singular = PLURAL_TO_SINGULAR[type] ?? type;
6459-
if (singular === 'flow') return { item: data, notices: [], todos: [] };
6460-
const notices: ConversionNotice[] = [];
6461-
const todos: ConversionTodoNotice[] = [];
6462-
const item = applyConversionsToStoredItem(singular, data, {
6463-
onNotice: (n) => {
6464-
notices.push(n);
6465-
onNotice?.(n);
6466-
},
6467-
onTodo: (t) => {
6468-
todos.push(t);
6469-
},
6470-
...(knownDatasources ? { knownDatasources } : {}),
6471-
});
6472-
return { item, notices, todos };
6454+
// [#25467 cut 23] The body moved to `protocol-stored-item-conversion.ts`.
6455+
return convertStoredItemDetailed(type, data, onNotice, knownDatasources);
64736456
}
64746457

64756458
/**

0 commit comments

Comments
 (0)