Skip to content

Commit 9f57dcc

Browse files
refactor(metadata-protocol): move the commit plan items parse out of protocol.ts into protocol-commit-items.ts (#25467 cut 32) (#27406)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu2hxhTGwfufb41w8DfLVVBp)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 ## 维护者速读 - 改了什么:把 `protocol.ts` 里一个只有 10 行的私有方法 `parseCommitItems`(把提交记录的 `items` 列读回成回滚计划)原样搬进新文件 `protocol-commit-items.ts`,类上的方法保留并直接调用它。 - 为什么改:#25467 拆分超大文件 `protocol.ts` 的第 32 刀,按裁决只搬不改,让这个文件继续变小、好读。 - 风险与回滚:极低。逻辑逐 token 相同,发布的 `.d.ts` 原始字节两端完全相同,包测试 21,800 条逐条一致;回滚就是还原这一个 PR。 - 先看:`packages/metadata-protocol/src/protocol.ts` 的 `parseCommitItems` 方法体(现在只剩一行调用)与 `CommitPlanItem` 前新增的 `export`;再看新文件 `protocol-commit-items.ts`。 - 两分钟验证:`pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.recovery-doors-emit-mutation.test.ts`,预期全部通过(它经 `revertCommit` 读回提交计划)。 - 需要你做的:无,席位落地。 ## What changes Cut 32 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–31 landed; cut 31 is #27181. The claim is `6487683544`. It takes the cut 32 proposal of the cut 31 report (`6465513012`): one member on its own. It passed the AST screen and the read-seam census and moved unedited. One `ObjectStackProtocolImplementation` member moves unedited into a new module, `packages/metadata-protocol/src/protocol-commit-items.ts` (#23050 shape B): `parseCommitItems`, which reads a stored `sys_metadata_commit` row's `items` column back as its revert plan. An array is the plan as stored; a string is parsed as JSON; anything else, or a string that is not a JSON array, reads as no items. | Member | Base line | Visibility | Body lines | AST tokens (params + return + body) | Reads | |---|---|---|---|---|---| | `parseCommitItems` | `:26479` | private, return type declared (`CommitPlanItem[]`) | 10 | 73 | no class member; module-level type `CommitPlanItem` | - **The class keeps the member**: name, signature and position. It has no docblock. Its body is now one call, `return parseCommitItems(raw);`, marked `[#25467 cut 32]`. - **No host.** The body reads no class member (no `this`), so the stub calls the plain function directly, as cut 26 did for `orgWallEnforced`. - **Every caller still calls the class method**: `listCommits` (`this.parseCommitItems(r.items)`, base `:26577`) and `revertCommit` (`this.parseCommitItems(row.items)`, base `:26693`). Neither changes. - **No member changes visibility.** - `protocol.ts` goes from 29,514 to 29,508 lines. `protocol-commit-items.ts` has 44 lines. The cut is move-only, and the card stays open for later cuts. Branch `claude/project-thread-xbievt` is the harness-pinned branch for this thread; it names no issue, so the claim comment is the identity record. It holds the move commit `dda5102340` on `main` `c4c515ad3c`, one merge of `main` `9b33088b78` (4 commits up to `7e200b4a68`, none in `packages/metadata-protocol/`), and `a09398108f`, a comment-only fix to the new module's header (it named the caller `listPackageCommits`; the method is `listCommits`). ### One top-level edit beyond the move: `CommitPlanItem` gains `export` The dispatch's correction holds: `CommitPlanItem` is **not an import**. It is `protocol.ts`'s own non-exported module-level type alias (base `:2751`). The moved function's signature and body name it, so it gains the `export` keyword in place, with nothing else in its statement changed, and the new module imports it with `import type`. This follows `addressPackages` (cut 16), `withDeclaredPageTypeDefault` (cut 23) and `noPendingDraftError` (cut 31). - `packages/metadata-protocol/src/index.ts` names its re-exports from `./protocol.js` one by one and does not name it, so the published surface does not move: the `.d.ts` and export-table readings below are identical at both ends. - It keeps its other readers in `protocol.ts` (the `commitItems` plan built at publish, the commit-write argument type, both callers' results) and stays there. ## Tests Nothing is added or changed under test; the cut is move-only. The package suite is compared per test at both ends (see Verification). The member is driven end to end through `revertCommit` by the recovery-door suites (`protocol.recovery-doors-*.test.ts`, `protocol.object-registry-write-through-spelling.test.ts`), which serve `sys_metadata_commit` rows from a fake driver, and through `listCommits` by the commit-timeline suites (`protocol-14038-list-commits-created-at-iso.test.ts`, `protocol.commit-timeline-instant-order.test.ts`); all pass at both ends. ## Acceptance notes - **The order's screen.** No private-static read and no `this.constructor` read (the body has no `this` at all). No `new Error(`, so no `PRODUCERS` row is owed. The body's `try`/`catch` guards `JSON.parse`, not a storage read, so it is outside the read-seam rule's vocabulary; the census does not move (below). - **The cut 25 docblock-offset trap did not fire.** The raw `.d.ts` is byte-identical at both ends after the final byte layout, measured again after the comment-only commit. - **Readers of the moved lines: none by path.** Grepped `parseCommitItems` and `CommitPlanItem` across the repository at `HEAD` (outside the two touched files): 0 hits. Of the 32 files that name `protocol.ts` and call `readFileSync`, none names the member or its body text; three name `sys_metadata_commit` only as behavioural fixtures run through the protocol. No `@objectstack/spec` liveness row and no `docs/qa/platform-checklist` anchor names the member. - **Liveness ledger follow-up, carried forward** (`packages/spec/**`, outside this card): `manifest.json`'s `dependencies` row → `protocol.ts#resolveWritePackageScope` (cut 19) and `seed.json` → `protocol.ts#applySeedBodies` (cut 14). No row points at this cut's member. - **Serial with in-flight PRs.** At `pr_create`, of the 23 open PRs none edits `protocol.ts` or names the member. The only one touching `packages/metadata-protocol/` is #26517 (Version Packages: `CHANGELOG.md`, `package.json`). Probed in a throwaway bare clone with no merge driver, it conflicts with this head in exactly the 4 `ui/packages/*/package.json` files it already conflicts with on `main` `ad2c2869bb`; none is in this package. `main` moved one commit after the merge (`ad2c2869bb`, `plugin-grid`); `git merge-tree` of this head with it is clean, so `main` was not merged again. - No changeset: `skip-changeset`, per ruling `6348891160`. ## Verification ### Proof of move-only The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters, the return type and the body, base `protocol.ts` at `c4c515ad3c` against the head module: ``` parseCommitItems: base tokens 73, head tokens 73, differ 0, text-equal(ws-collapsed, comments incl.) true CLASS members base=304 head=304 order-same=true byte-identical=303 stub-leading+signature-identical=true other=[] TOP base=266 head=267 byte-identical=264 (class statement excluded) TOP base-only: ["type CommitPlanItem = { type: string; name: string; existedBefore: boolean; prevVersion: number | null; member?: string };"] TOP head-only: ["import { parseCommitItems } from './protocol-commit-items.js';","export type CommitPlanItem = { type: string; name: string; existedBefore: boolean; prevVersion: number | null; member?: string };"] ``` Negative controls, one token changed in an in-memory copy of the head module: ``` "typeof raw === 'string'" -> "typeof raw === 'object'" differ 1 "Array.isArray(p) ? p" -> "Array.isArray(raw) ? p" differ 1 ``` - **Class side.** 304 members at both ends, same order; the 303 that did not move are byte-identical to base, leading comments included; the stub keeps its leading trivia and signature byte-identical. - **Top level.** Of the 265 non-class base statements, 264 are byte-identical; the other is `CommitPlanItem`, whose only change is the added `export`. Head adds the module import (with its one-line `[#25467 cut 32]` comment). - **Size.** +49 / −11 over 2 files, 60 changed lines (the cut 31 report's estimate was 40–70). ### The published `.d.ts` (ruling `6348891160`) Base `c4c515ad3c` and head were built with `pnpm --filter @objectstack/metadata-protocol build` against one dependency tree (the closure built by a full `turbo run build`, 115/115). For the base build the package `src/` was swapped tree-only to base (the new module removed) and restored from `HEAD` under a trap; afterwards `git status --porcelain` was empty and both touched blobs hashed equal to `HEAD`'s. | Reading | Base `c4c515ad3c` | Head `a09398108f` | |---|---|---| | raw sha256 `dist/index.d.ts` = `dist/index.d.cts` | `4cb92b2e20135dd78d07fd2800b1d2a050a432b923cd511d5b50f3ed6de29a58` (784,960 bytes) | same | | normalised sha256 | `a4dcc2306638c29983bceaf7258df71a29a3020a3ec26904c9830a0cecd0c70e` | same | | raw-diff lines | 0 | | | `index.js` / `index.cjs` sha256 (16) | `20239680cb0cf515` / `f3ac1e9bdd5b9e96` | `e5abe0490663649f` / `1c36338d33aa16a5` | | `function parseCommitItems(` in each bundle | 0 | 1 | | export tables, ESM and CJS (sorted names + newline) | 78 names, `e47ac7c753bcb8be` | same; neither `parseCommitItems` nor `CommitPlanItem` | **Both identities hold: raw and sorted-union.** The head reading was taken on the move commit, on the merged tree and again on `a09398108f`; all give the same hashes. Normaliser, verbatim: ``` node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum ``` **Positive control** on the head tree: `private` dropped from `parseCommitItems` on disk (restored from `HEAD` under a trap) and the package rebuilt. Both `.d.ts` files move to raw `ca90763579860f2c…` (785,652 bytes), normalised `b7639852d59f8343…` (differs from the bar's `a4dcc230…`), printing `parseCommitItems(raw: unknown): CommitPlanItem[];`. So the bar can see a change on this member. ### Gates and suites <details><summary>Package suite, tsc, eslint</summary> - `packages/metadata-protocol` suite, vitest `--maxWorkers=2`, JSON reporter, under the shared verify lock. Base `c4c515ad3c` (src swapped tree-only, restored from `HEAD` under a trap), the move commit `dda5102340` and the final head `a09398108f`: all three 21,800 tests (21,774 passed, 0 failed, 26 skipped), 283 files. Per test name and status over 21,787 distinct names: 0 lost, 0 added, 0 changed. - `tsc --noEmit` in `packages/metadata-protocol`: exit 0. - `pnpm exec eslint --no-inline-config packages/metadata-protocol`: exit 0, no output. </details> <details><summary>Hand gates, base and head</summary> Base readings in a clean detached worktree at `c4c515ad3c`; head in the branch checkout. - `check-durability-degradation-log-level`: exit 0 at both ends, output identical, `89 read seam(s)`. `--list` identical once `[@:][0-9]+` is stripped (165 lines); no entry changes file. 89 is the claim's base. - Identical output, exit 0 at both ends: `check-adr-anchors`, `check-adr-symbol-anchors`, `check-system-context-census`, `check-tenant-audit-census`, `check-query-options-erasure-ratchet` (50 unswept non-test sites in 15 files), `check-filter-alias-parity`. - `check-nul-bytes`: exit 0 at both ends; scanned files +1 (the new module). - `check-engine-double-contract`: exit 0 at both ends; the `updateData`/`deleteData` seams keep their entries at new line numbers (13997 → 13999 and 14022 → 14024: the import's two lines sit above them, and the move's eight removed lines below); `productionFiles` 3,876 → 3,878 (the new file and the gitignored spec `registry.ts` the build writes in the head tree, as in cuts 20–31). - `check-platform-checklist --changed-from origin/main`: OK, 32 anchors judged, 0 lost. - `check-governed-merges --branch claude/project-thread-xbievt`: 0 of 2 paths in the register (13 surfaces), NOT governed; 60 changed lines, under the 3,000-line bar. </details> <details><summary>Derived gates</summary> `dispatch-gates --commands --repo objectstack-ai/objectstack` on `a09398108f` derives 46 gates on 2 changed paths (merge base `7e200b4a6`). All 46 exit 0 after a full `turbo run build` (115/115). `dispatch-gates --ran` reports 46 derived, 46 run, 0 NOT-MEASURED, 0 UNRUN, and exits 0. On the final head the hand gates were re-read: `check-adr-anchors` output is identical to `main` `7e200b4a68`'s (60,467 citations, 1,900 decision letters; the +1 citation and file against `c4c515ad3c` come from the merged `main` commits), and `check-durability-degradation-log-level` is identical to base, `--list` included. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgVaAsaDVwT6XfwB5yk --- _Generated by [Claude Code](https://claude.ai/code/session_01L6bhgVaAsaDVwT6XfwB5yk)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 45c9be5 commit 9f57dcc

2 files changed

Lines changed: 49 additions & 11 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 32] A stored commit row's `items` column read back as its revert
5+
* plan, from `ObjectStackProtocolImplementation`, moved out of `protocol.ts`
6+
* whole: `parseCommitItems`, which `listCommits` and `revertCommit` call. An
7+
* array is the plan as stored; a string is parsed as JSON; anything else, or a
8+
* string that is not a JSON array, reads as no items.
9+
*
10+
* A MOVE, not a redesign. The function below is the body of the
11+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
12+
* re-indented one level and otherwise unedited.
13+
*
14+
* ## How the moved body reaches the class
15+
*
16+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
17+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
18+
* The class keeps the method — the published `dist/*.d.ts` lists its name
19+
* and declared signature. The body reads no class member, so the method's
20+
* body calls the plain function, with no host. Every caller keeps calling the
21+
* class method.
22+
*
23+
* The plan item type is `protocol.ts`'s own, and is imported from there as a
24+
* type. Nothing re-exports this module, so the published surface does not
25+
* move.
26+
*/
27+
28+
import type { CommitPlanItem } from './protocol.js';
29+
30+
/** The body of `ObjectStackProtocolImplementation#parseCommitItems`. */
31+
export function parseCommitItems(
32+
raw: unknown,
33+
): CommitPlanItem[] {
34+
if (Array.isArray(raw)) return raw as CommitPlanItem[];
35+
if (typeof raw === 'string') {
36+
try {
37+
const p = JSON.parse(raw);
38+
return Array.isArray(p) ? p : [];
39+
} catch {
40+
return [];
41+
}
42+
}
43+
return [];
44+
}

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ import { legacyOrganizationRowsOf, withoutLegacyOrganizationRows } from './proto
427427
import { foldObjectExtendersFromRegistry } from './protocol-object-extender-fold.js';
428428
// [#25467 cut 31] The disabled-package face of the by-name item read.
429429
import { readMetaItemFace } from './protocol-meta-item-face.js';
430+
// [#25467 cut 32] A stored commit row's `items` column read back as its revert plan.
431+
import { parseCommitItems } from './protocol-commit-items.js';
430432

431433
/**
432434
* Canonical Zod schema per metadata type lives in
@@ -2748,7 +2750,7 @@ function mutationEventMember(type: string, member: string | null | undefined): {
27482750
* timeline serves it on every item of a bundled type, the canonical member
27492751
* spelled out ({@link ObjectStackProtocolImplementation.listCommits}).
27502752
*/
2751-
type CommitPlanItem = { type: string; name: string; existedBefore: boolean; prevVersion: number | null; member?: string };
2753+
export type CommitPlanItem = { type: string; name: string; existedBefore: boolean; prevVersion: number | null; member?: string };
27522754

27532755
/** [#22877] The identity of a plan item or a draft within one batch: two members of one name are two. */
27542756
function commitPlanKey(item: { type: string; name: string; member?: string }): string {
@@ -26479,16 +26481,8 @@ export class ObjectStackProtocolImplementation implements
2647926481
private parseCommitItems(
2648026482
raw: unknown,
2648126483
): CommitPlanItem[] {
26482-
if (Array.isArray(raw)) return raw as CommitPlanItem[];
26483-
if (typeof raw === 'string') {
26484-
try {
26485-
const p = JSON.parse(raw);
26486-
return Array.isArray(p) ? p : [];
26487-
} catch {
26488-
return [];
26489-
}
26490-
}
26491-
return [];
26484+
// [#25467 cut 32] The body moved to `protocol-commit-items.ts`; it reads no member.
26485+
return parseCommitItems(raw);
2649226486
}
2649326487

2649426488
/**

0 commit comments

Comments
 (0)