Skip to content

refactor(spec): codemod split of conversions/registry.ts into per-entry modules (#29987) - #29993

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-enusuc
Nov 4, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-enusuc

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Requested by Yibo Wang · project thread

Fixes #29987

Ruling-ref: 6583399215
Clause-②: no (move-only)
Metadata-protocol: none
Skills-docs: follow-up filed as #29994 — .claude/skills/spec-property-retirement/SKILL.md (it still says a new conversion is written in conversions/registry.ts; after the split it is a module under conversions/entries/ plus one import line in registry.ts)

ADR-0087 / D3: no migration entry moves. migrations/ is untouched, and check-adr-0087-registration --base origin/main exits 0.

维护者速读

  • 改了什么:codemod scripts/codemod-split-conversions-registry.mjs 把 packages/spec/src/conversions/registry.ts(16,019 行)拆成 112 个条目模块(entries/<toMajor>/<id>.ts)加一个 entries/shared.ts;registry.ts 剩 354 行,只留导入和四张表。另有 4 个按路径读这个文件的读者同步放宽,build-schemas 的三处 D2 补救提示也改成了拆分后的写法。
  • 为什么改:[Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465 大文件拆分计划(裁决 6583399215)的一部分,已在今天的窗口里于最新 main(c2d353d2)上重新生成。这张表是冲突最多的文件之一,按条目拆开后,每条 conversion 单独成文件,评审和合并都更容易。
  • 风险与回滚:只搬不改。159 条语句、727,159 字节逐字节相同;ALL_CONVERSIONS 和每个 CONVERSIONS_BY_MAJOR[n] 的 id、顺序和函数源码在 base 与 head 完全一致;每次 head 构建的 134 个声明文件都与一次同状态的 base 构建逐字节相同(main 自己的 d.ts 构建在两种状态间交替,见 Tests)。回滚就是 revert 这个 PR。
  • 先看:packages/spec/src/conversions/registry.ts(新的宿主文件)、entries/shared.ts,以及下面的读者普查表。
  • 两分钟验证:pnpm install --frozen-lockfile,然后 node scripts/codemod-split-conversions-registry.mjs --verify --base origin/main(字节一致)和 --arrays --base origin/main(表相等)。
  • 需要你做的:不用做什么。你已批准在今天的窗口落地,CI 在 dca57060 上全绿,由 spec 席位送入合并队列。

What changes

  • New: scripts/codemod-split-conversions-registry.mjs. It sits beside the pilot's codemod-split-sql-driver.mjs and follows its conventions (ts-morph in memory, a plan mode, --write, --verify, --self-test).
  • Generated: packages/spec/src/conversions/entries/11/ (3), 15/ (2), 17/ (12) and 18/ (95) hold one module per conversion entry, named by its id. entries/shared.ts holds the 11 helpers that two or more entries read. The 31 helpers that only one entry reads move into that entry's module, following pilot ruling 6591753301. Every module is a leaf: it imports ../../types.js, ../../walk.js, ../shared.js and the spec modules registry.ts already imported (for example ui/assembled-views.zod.js), and never another entry.
  • Host: registry.ts keeps its module docblock, import type { MetadataConversion }, one import line per entry in base definition order, and OrderedConversion, inApplicationOrder, MAJOR_18_CONVERSIONS, CONVERSIONS_BY_MAJOR and ALL_CONVERSIONS byte for byte, including the @__PURE__ markers. The public import path conversions/registry and its two exports are unchanged.
  • Readers widened (ruling 6597555122):
    • packages/spec/scripts/conversions-major18-merge.test.ts now reads the import lines in registry.ts and each entries/18/ module.
    • scripts/check-adr-0087-registration.mjs reads the conversions ledger as registry.ts plus entries/**, and gains a CR1-CR3 battery.
    • scripts/docs-audit/affected-docs.mjs treats registry.ts plus entries/** as the conversion table.
    • scripts/adr-anchors/: the ADR-0120 anchor follows declared-index-unique-scope into its module, and the host keeps ADR-0087.
    • The scripts/regen-artifacts.mjs row note was updated.

Reader census (all 111 files on base c2d353d2 that name conversions/registry)

class files disposition
import 51 unaffected: both exports stay in registry.ts (3 tests vi.mock it, same exports)
prose only 47 unaffected (CHANGELOGs, docblocks, ADR text)
code reads the path or text 13 one row each below
code reader disposition
packages/spec/scripts/conversions-major18-merge.test.ts widened. The base version fails 3 of its cases on the split tree; the widened one passes 12/12
scripts/check-adr-0087-registration.mjs widened; self-test 710 assertions pass, --base origin/main exit 0
scripts/docs-audit/affected-docs.mjs widened. The pre-edit version fails 2 counterfactual cases on the split tree; the edited one passes 605/605
scripts/adr-anchors/packages__spec__src__conversions__registry.ts.json split: ADR-0120 moved with its entry, so a new anchor was added for entries/18/declared-index-unique-scope.ts. check:adr-anchors exit 0
scripts/regen-artifacts.mjs row note updated (prose of a NOT_DRIVER_MANAGED row)
packages/spec/scripts/build-schemas.ts reworded (c5fc1136): its three D2 remedies now name a module under src/conversions/entries/ plus its import and array line in src/conversions/registry.ts
packages/spec/scripts/build-schemas-check-mode.deletion-proofs.test.ts pin added (c5fc1136): the remedy names src/conversions/entries/ with the toMajor directory. It fails on the old remedy text
packages/spec/scripts/build-schemas-check-mode.retired-keys.test.ts pin added (c5fc1136): the remedy names src/conversions/entries/. It fails on the old remedy text. The two check-mode tests pass 29/29
packages/spec/src/root-entry-migrations-split.pin.test.ts unchanged: registry.ts is still in the root graph; passes
packages/spec/export-origins/root.json unchanged: the export origin is still registry.ts; check:export-origins exit 0
packages/spec/liveness/action.json unchanged. Its note cites registry.ts:5578, which was already stale on main before this PR
scripts/check-spec-docblock-symbol-anchors.mjs unchanged: the anchored symbols stay in the host; passes
scripts/check-future-spec-major.mjs unchanged: the path is only a self-test fixture label

To reproduce the census: git grep -l 'conversions/registry' c2d353d2 -- scripts packages/spec. The one file new since the Phase 0 base 5cdaddbc is packages/spec/src/system/auth-revoke-sessions-on-password-reset-retirement.test.ts, and it is an import.

Tests

Phase 1 readings: head dca57060 (a plain merge of main c2d353d2 into c5fc1136, no rebase), base c2d353d2. main did not touch packages/spec/src/conversions/ between 5cdaddbc and c2d353d2. Regenerating from main's registry.ts with --write leaves git diff HEAD empty, so the merge is the regenerate.

  • Byte-identity: --verify --base origin/main (c2d353d2) exits 0. It compares 159 statements (727,159 bytes). All 112 of 112 entries and all 42 helpers moved (11 into entries/shared.ts), and 5 statements stayed. It writes 113 modules and checks 584 import bindings. Undoing the split rebuilds the base registry.ts byte for byte.
  • Tables: --arrays --base origin/main exits 0. Exports match (ALL_CONVERSIONS, CONVERSIONS_BY_MAJOR). ALL_CONVERSIONS holds 112 ids, sha256 6dfde60307ccd336d6265e84bb095632acbbdbcd08483d73f5e83070c9728ae7, in the same order. CONVERSIONS_BY_MAJOR is {11: 3, 15: 2, 17: 12, 18: 95} with the same membership and order. 112 of 112 entry digests are equal, where a digest is JSON with the function source included.
  • Determinism: two --write runs from main's unsplit registry.ts (input sha256 3f5386899b7024a1e429d6a7b93cd41ce567f1171bfb5ee27974d1ffb2ec6c01) both give output tree sha256 960cdabf7a6532e0f0ffaa035cbe200a85b0eff2a1ddea58b0df515b4147d74b, the same as Phase 0, and leave git diff HEAD empty.
  • Declaration files (ruling 6348891160 item 1, read with 6441032442 B's second normalisation, S1 applied):
    • The tree was quiet. The four builds ran back to back in one locked script, in the order head-1, base-1, head-2, base-2. Nothing else ran in either worktree or on the box, and the tests and gates started only after the builds finished.
    • The builds: pnpm --filter @objectstack/spec build produced base-1, head-1, base-2 and head-2. Each build's 134 declaration files fall into one of two states, with set sha256 cb955a20e1de3c63… or cdac5d7b0c33761e…. base-1 and head-2 are in the first state; head-1 and base-2 are in the second.
    • The pairing: base-1 ≡ head-2 and base-2 ≡ head-1. Each pair is 134/134 raw byte-identical. dtsnorm.mjs passes on both pairs, and its control (a renamed declaration in action.zod-CQhac7BH.d.mts) is caught, with the normalised hash and the surface digest both DIFFER. The codemod's --dts base-1 head-2 reads PASS, with its control (a dropped readonly) caught.
    • Why the cross pair reads FAIL (9): --dts base-2 head-2 reads FAIL (9). The 9 are the 8 files api, contracts, marketplace and system index.d.ts / index.d.mts, plus the renamed automation-api.zod chunk pair. base-1 against base-2 differs in exactly the same way: 10 files and 34 statements, with string-literal members in another order inside z.ZodEnum record types and object shapes, and the declared-surface digest EQUAL on every file. This is main's own .d.ts build being nondeterministic from run to run, not the split. Every quiet head build equals a quiet base build byte for byte, so no reorder belongs to head.
    • A correction to Phase 0's acceptance note 3: the irregular head builds reported there were this same nondeterminism. They were not caused by the concurrent gates.
    • The readings are in the project files under issue-29987/phase1/.
    • check:api-surface and check:dual-source-exports exit 0.
  • Self-test: --self-test runs 40 cases in 6 batteries, and all pass.
  • Spec suite at the base's counts: base c2d353d2 and head dca57060 both give test 811/811 files and 24,376 tests plus 1 todo, and test:repo 90/90 files and 1,539 tests.
  • Built-entry checks: pure-schema-construction.test.ts, including the ./shared consumer bundle, is in the local suite above and passes.
  • Type-check: pnpm --filter @objectstack/spec typecheck exits 0 at head with NODE_OPTIONS=--max-old-space-size=6144. Without the flag, the check:test-typecheck step runs out of heap at about 2 GB on this box, at base and at head alike.

Acceptance notes

  1. Size. 35,227 changed lines (+19,405 / -15,822 in 124 files against c2d353d2) is over the 3,000-line threshold. The maintainer approved landing it in today's window.
  2. skip-changeset: the .d.ts meets ruling 6348891160 item 1 in its strongest form, raw identity against a base build of the same state. Following 6441032442 item 4 and the pilot PR refactor(driver-sql): codemod pilot — generated, byte-identity-proved section split of sql-driver.ts (#29520) #29598, the PR takes no changeset.
  3. Phase 0's irregular head builds. Phase 0 put them down to concurrent gate runs. That was wrong: Phase 1 shows main's spec .d.ts build alternating between two states on a quiet tree (see Tests). A .d.ts reading for any spec split therefore has to pair builds of the same state, and a single base-against-head pair can read FAIL with no change at all.
  4. Follow-ups, noted and not filed:

Verification

  • Regenerate (in the Phase 1 window, on the latest main, never a rebase): git checkout origin/main -- packages/spec/src/conversions/registry.ts, then node scripts/codemod-split-conversions-registry.mjs --write. --write deletes stale codemod-marked modules and refuses any file under entries/ that it did not write. If the regeneration moves different code, re-check the reader table above.
  • Proofs: node scripts/codemod-split-conversions-registry.mjs --verify --base origin/main, then --arrays --base origin/main.
  • .d.ts: build @objectstack/spec at least twice at base and twice at head on a quiet tree, pair builds of the same state, then run node scripts/codemod-split-conversions-registry.mjs --dts BASE_DIST HEAD_DIST. The report reading under 6441032442 B is dtsnorm.mjs, kept in the project files at issue-29987/dtsnorm.mjs, sha256 e0b4b0c3d6a29e99dea99b2a65b683b0649f3e7c6d4fa671b994d590d48219b4. Run it as node dtsnorm.mjs --typescript node_modules/typescript --dirs BASE_DIST HEAD_DIST --control, and expect exit 0 and "pass": true.
  • Gates at head dca57060: node scripts/pm/dispatch-gates.mjs --commands derives 96, and all 96 exit 0, including check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure and check:doc-formula-expressions, which are measurable now. --ran exits 0: 96 run, 0 NOT-MEASURED, 0 unrun.
  • CI at dca57060: 54 check runs succeeded and 10 were skipped; none failed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzg1QFj9ukYzafVoAqg

Body refreshed by spec seat 3 (Phase 1 on c2d353d / dca5706; contract review PASS 6628915927): 2026-11-04T11:09Z

…ry modules (#29987)

WIP: the codemod and its generated split, before the reader census.

Claude-Session: https://claude.ai/code/session_01WGufzg1QFj9ukYzafVoAqg
Co-authored-by: Claude <noreply@anthropic.com>
…ntry modules (#29987)

The ADR-0087 registration gate, the affected-docs self-test, the ADR anchor
and the major-18 merge test read the split registry with its entries.

Claude-Session: https://claude.ai/code/session_01WGufzg1QFj9ukYzafVoAqg
Co-authored-by: Claude <noreply@anthropic.com>
#29987)

Under the spec package's CommonJS default the tsx loader rewrites every imported
call to (0, import_x.name)(...), so a moved entry's function source differed only
by the loader's spelling of its import (93 of 112 digests). Both trees now load
from a scratch copy marked type: module, and the probe refuses a CommonJS-loaded
function instead of comparing it. Result: 112 of 112 digests equal.

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

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 607 documentable anchor(s).

149 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json c2d353d2ac538ccddfad083a1c9f6f168b9a231d.

⛔ 19 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 5 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 43 pages), defaultValue (literal, 41 pages), master_detail (literal, 40 pages), objectName (literal, 56 pages), sys_user (literal, 49 pages)
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 169 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 c2d353d2ac538ccddfad083a1c9f6f168b9a231d → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c2d353d2ac538ccddfad083a1c9f6f168b9a231d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head a8c5d94 · 2026-11-04T00:42Z

Schema face (packages/spec/src/conversions/**, move-only), so the review runs at CONTRACT_REVIEW_TIER. This is Phase 0; the landing head is regenerated in the window the maintainer orders.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a8c5d94d081c6858f792d977361b87f7564f1ff6
Local-runs: probe — an independent byte-identity re-check: a TypeScript-parser script over git show output, in the scratchpad; no repo script, build, test, gate or codemod was run, and the dev's residual JSON was read the same way
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/conversions/entries/** + scripts/codemod-split-conversions-registry.mjs (read whole), packages/spec/src/conversions/entries/shared.ts, entries/11/flow-node-http-callout-rename.ts (first), entries/18/flow-decision-mode-inclusive-explicit.ts (last), entries/15/view-visibleOn-to-visibleWhen.ts, entries/17/retry-policy-converged.ts, entries/18/page-component-filter-record-to-rule-array.ts, entries/18/declared-index-unique-scope.ts, packages/spec/scripts/conversions-major18-merge.test.ts, scripts/check-adr-0087-registration.mjs, scripts/docs-audit/affected-docs.mjs, scripts/regen-artifacts.mjs, scripts/adr-anchors/packages__spec__src__conversions__registry.ts.json, scripts/adr-anchors/packages__spec__src__conversions__entries__18__declared-index-unique-scope.ts.json, packages/spec/export-origins/root.json, packages/spec/tsconfig.json, packages/spec/package.json (sideEffects), scripts/pm/clause2-line.mjs, .claude/skills/spec-property-retirement/SKILL.md (main), .claude/skills/pm-dispatch/references/review-checklist.md, /mnt/project-files/issue-29987/* (report.md, dtsnorm.mjs sha256 e0b4b0c3… matches the PR body, dtsnorm-reading*.json, dtsnorm-residuals.json, codemod-dts logs, gates-ran.list), PR #29993 body, card #29987 and its 3 comments, rulings 6583399215 / 6582376616 / 6583664223 / 6591753301 / 6597555122 / 6348891160 / 6441032442, pilot PR #29598, check-runs on the head (two reads).

① Derived judgments

  • Public surface: unmoved. registry.ts still exports exactly CONVERSIONS_BY_MAJOR and ALL_CONVERSIONS at the same path and with the same explicit types (host lines 322 and 351 are the base text); no entry module is re-exported by any entry point (every entry's own export { NAME } is reached only through the host import); the 3 vi.mock('./conversions/registry', importOriginal) tests see the same two exports. packages/spec/export-origins/root.json still names src/conversions/registry.ts#ALL_CONVERSIONS and #CONVERSIONS_BY_MAJOR, which is still true; check:export-origins, check:api-surface, check:dual-source-exports exit 0 in the dev's run, Spec property liveness and Lint & Repo Gates are green on the head.
  • Membership and order: the whole host tail from the OrderedConversion docblock to EOF is byte-identical to base (my own compare, 12,413 bytes), so MAJOR_18_CONVERSIONS (95 rows, sorted by identifier, every order value), the literal arrays for 11 (3), 15 (2) and 17 (12) and the ALL_CONVERSIONS flatten are unchanged; 112 host imports, one per entry, bound to 112 modules each holding exactly that const NAME: MetadataConversion. The --arrays digest proof (112/112, ALL ids sha256 6dfde603…) is consistent with this; I did not rerun it because the tables and every referenced declaration are byte-identical by construction.
  • @__PURE__: both markers (18: /* @__PURE__ */ inApplicationOrder(…) and the ALL_CONVERSIONS flatten) are in the host, byte for byte. No entry module carries or needs one: the plan refuses any top-level expression statement, so every module holds declarations only; packages/spec/package.json declares sideEffects: false; the dev's pure-schema-construction.test.ts 7/7 includes the ./shared consumer bundle.
  • Ledgers: api-surface*, authorable-surface* and export-origins are untouched by the diff, and should be — api-surface is symbol-keyed (no path in it names conversions/registry), authorable-surface reads the Zod tree, and the export origin really is still registry.ts. retired-after.census.* reads ALL_CONVERSIONS through an import and the published dist, not by path: nothing to widen.

② Semver level

  • skip-changeset with no changeset fits: the diff publishes nothing observable from @objectstack/spec — 134/134 dist/*.d.ts and *.d.mts raw byte-identical on the quiet builds, same file set, control (a renamed declaration in action.zod-CQhac7BH.d.mts) caught on both the normalised and surface readings; exports unchanged. That is ruling 6348891160 item 1 in its strongest form and 6441032442 item 4 (skip-changeset stays; no patch for a declaration-order diff). Precedent: pilot PR refactor(driver-sql): codemod pilot — generated, byte-identity-proved section split of sql-driver.ts (#29520) #29598 merged 2026-11-03T16:26Z with skip-changeset, no .changeset/ file, its .d.ts passing only under the second normalisation — a weaker reading than this one. Check Changeset is green on the head.
  • Clause-②: no (move-only) reads as a bare no: scripts/pm/clause2-line.mjs reads the arm only when the first word in the parenthetical is of the widen/narrow family, so (move-only) is description, not a malformed arm (PR-body guards and PM dispatch-gates green). Correct: no accept set, export or declaration moves. Metadata-protocol: none is correct: nothing an author can write changes.

③ Boundary flags

  • (a) Irregular head builds: I read all 62 residual statements in dtsnorm-residuals.json. Every one is the same declaration with the property members of a z.ZodEnum{…} record type printed in another order (low/high, paused/pending, critical/background…); all 62 are equal after sorting lines, and the declared-surface digest is EQUAL. Two readings: (1) that class is INSIDE a declaration, so it is outside both sanctioned normalisers — 6348891160 covers only "a" | "b" union runs and 6441032442 B item 1(b) says a member that moves within its declaration fails (b) — and the dev's tool rightly reports pass: false for those builds rather than widening anything; had it been the reading of record it would be a fork on [Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465, not a pass. (2) It is build noise, not a declaration difference the split causes: 4 base builds and 2 quiet head builds are raw byte-identical to each other (set sha256 757dc13d… both sides), the two irregular builds disagree with each other, both ran beside unlocked gate runs in the same worktree, and the diff class is the TypeScript printer's type-creation-order artefact that both rulings already classify as meaningless to a consumer. The cause is not established (the --arrays scratch copy lives under packages/spec/.codemod-arrays-*, but tsconfig includes only src/**/*, so that copy does not enter the program). Verdict on (a): noise; the reading of record is the quiet one. Should-fix S1 below binds Phase 1 to a quiet reading.
  • (b) The 4 NOT MEASURED gates are hosted in CI on this head: check:dts-closure in Type Check · debt ledger (lint.yml:5892, success) and Type Check · consumer gates (lint.yml:6030, success); check:doc-formula-expressions in Type Check · consumer gates (lint.yml:6254, success); check:dual-build-cjs-loads and check:lean-entry-closure in Build Core (ci.yml:3040 and 3064, in progress at my second read). At review time the head has 24 success, 28 in progress, 0 failure/cancelled (Build Core, Test Core 1-8, Test UI 1-16, Type Check UI, Type Check · workspace pending). Phase 1 regenerates anyway, so the landing CI run is the one that counts.
  • (c) Skills-docs: .claude/skills/spec-property-retirement/SKILL.md line 176 still prescribes writing the MetadataConversion in conversions/registry.ts; after the split that is a module under entries/18/ plus one import line, and the widened merge test reds an inline definition. The dev declares the follow-up but files no card. Two more prescriptions now mis-describe the mechanics and cannot be fixed in a move-only PR: the MAJOR_18_CONVERSIONS docblock (host lines 181-185, must stay byte-identical) and the build-schemas.ts remedy text at 1231, 2801 and 3012 (pinned by two tests). Should-fix S2.

④ Design judgment

  • Deterministic: the script reads only the text of registry.ts into an in-memory ts-morph project (no node_modules, no build output, no clock); every emission is in source order or sorted; the self-test pins two runs tree-equal; the dev's two plan runs give tree sha256 960cdabf… on input 3f538689…. Re-run on a later main: plan refuses an input that already carries the [#29987 codemod] mark, so the documented git checkout origin/main -- registry.ts precedes --write; --write unlinks stale marked modules and refuses an unmarked file under entries/; a new conversion with a default/namespace import, a destructuring or an exported entry exits 2 (a fork to report, never a hand edit). Sound.
  • Byte identity: proof 1 compares raw getFullText() (leading comments and blank lines included), never normalised text, keyed by declared name, in base order per file, each base statement placed exactly once, and the undo rebuild must equal the base text. I re-implemented it independently with the TypeScript parser over git show output: base 159 statements, head 159 across host + 113 modules, 0 not in base, 0 missing, 0 duplicated, 0 out of order, 727,159 bytes (the dev's figure), undo tiles the base, host head (lines 1-17) identical. The .d.ts reading of record is raw identity; no normaliser decided anything.
  • Readers by path: on main 125 files name conversions/registry (the dev's 110 is the scripts + packages/spec subset at 5cdaddb); imports and prose dominate. Code readers: the 4 widened (merge test reads host imports plus each entries/18/ module with the legacy-shape controls kept; check-adr-0087-registration reads registry.ts plus entries/** .ts via conversionLedgerTextAt with CR1-CR3 and the commit census widened; affected-docs treats host plus entries/** as the replay table; ADR-0120 anchor follows its entry, host keeps ADR-0087 — the entry file carries ADR-0120 5 times); regen-artifacts row note. Checked beyond the PR's grep: agent-lifecycle-retirement.test.ts and agent-memory-store-retirement.test.ts exclude by the prefix packages/spec/src/conversions/, which covers entries/; check-spec-docblock-symbol-anchors.mjs names the host only in a repaired-row record; check-future-spec-major.mjs uses the path as a fixture label; root-entry-migrations-split.pin.test.ts still finds the host in the root graph. No missed path reader found.
  • Import order: definition order in the host is load-bearing only for the merge test's placement rule (by design, as the definition rule was) and for nothing at runtime — each per-major array is an explicit literal or inApplicationOrder(…) by order, every module is a leaf (verify refuses a leaf importing the host or another entry; grep finds none; 98 entries import only ../shared.js, itself a leaf), so evaluation order cannot change a table. ESLint is green on the head.
  • Hand edits: none. Commits 5553ee4 and a8c5d94 touch no generated file; all 114 generated files carry the mark; every statement is verbatim base text; header shapes match the generator's wrapLineComment.
  • Note (no finding, precedent): the script's --dts mode carries the 6348891160 union normaliser inside scripts/, which that ruling's item 3 said not to do; the pilot's script did the same and merged, and the second normalisation is correctly kept out of scripts/.

Findings:

  • S1 should-fix (Phase 1 procedure): the regenerate's .d.ts reading must be taken on a quiet tree (no concurrent gate run in the worktree) and the report must say so; if a quiet head build ever reproduces the record-member-order diff, it is a fork on [Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465 (outside both normalisers), not a pass under either. No code change.
  • S2 should-fix: file the follow-up card now and cite its number in the PR body (review-checklist: follow-up owed becomes #N), covering the SKILL.md line 176 prescription, the MAJOR_18_CONVERSIONS docblock placement rule and the build-schemas.ts remedy text, so an agent retiring a key between landing and that fix is not sent to write a definition into registry.ts.
  • No must-fix.

Implemented-by: claude/project-thread-enusuc
Reviewed-by: session_01WGufzfgvGexKn9vLHsy89K

VERDICT: PASS

… remedies (#29987)

After the split, a new D2 conversion is a module under
src/conversions/entries/<toMajor>/ plus its import and array line in
src/conversions/registry.ts. The three remedy texts in build-schemas.ts said
to add it in registry.ts. They now name both, and the deletion-proofs and
retired-keys check-mode tests pin the entries/ path (both fail on the old text).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzg1QFj9ukYzafVoAqg
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head dca5706 · 2026-11-04T10:46Z

Phase 1 delta review (the split regenerated on the latest main for the maintainer's landing window, plus the remedy rewording in c5fc1136) at CONTRACT_REVIEW_TIER.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract review — Phase 1 delta

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dca57060c584739094493510918ce8067e672d7f
Local-runs: probe — an independent byte-identity re-check of the split against the new base, a TypeScript-parser script over git show / git ls-tree output in the scratchpad (probe-29993/probe2.cjs); no repo script, build, test, gate or codemod was run, and the dev's Phase 1 readings were read as files
Scope: git delta a8c5d94d..dca57060 restricted to PR-owned files (3 files: packages/spec/scripts/build-schemas.ts, build-schemas-check-mode.deletion-proofs.test.ts, build-schemas-check-mode.retired-keys.test.ts), commit c5fc1136 whole, merge dca57060 (diff-tree --cc, first-parent diff over the 124 PR-owned paths), git log 5cdaddbc..c2d353d2 and c2d353d2..origin/main over packages/spec/src/conversions/, build-schemas.ts, the codemod and every PR-owned path, blob ids of registry.ts and the conversions/ tree at old base / new base / origin/main (b0c2209d), head registry.ts whole, every entries/** module (parsed), entries/shared.ts, codemod header and modes, PR #29993 body, comments on #29993 and #29987 from 2026-11-04T00:00Z, Phase 0 record 6619521721, dev report 6619319038, review 5766718516, card #29994, /mnt/project-files/issue-29987/phase1/* (dts-p1.log, dn-*.json), check-runs on the head, AGENTS.md ≈:280-300; /mnt/project-files/seat3-orders/29987-body-r2.md is not present.

① Derived judgments

  • Base movement: 21 commits 5cdaddbc..c2d353d2 and 7 more to origin/main; none touches packages/spec/src/conversions/, build-schemas.ts, the codemod or any of the 124 PR-owned paths. The registry.ts blob (b9631781) and the conversions/ tree (2ca969da) are identical at old base, new base and origin/main, so the plain merge is equivalent to a regenerate: git diff a8c5d94d dca57060 -- packages/spec/src/conversions/ scripts/codemod-split-conversions-registry.mjs is empty, the generated split on this head is the Phase 0 split byte for byte, and the body's regenerate recipe (git checkout origin/main -- registry.ts then --write) would write the same tree.
  • Merge content: diff-tree --cc dca57060 lists no file (no path differs from both parents), and the merge's first-parent diff over the PR-owned paths is empty. No conflict resolution, no hand merge, in or out of PR-owned files. Delta since the Phase 0 head is exactly c5fc1136 (+12/−5 in the 3 files above).
  • Pure move on the new base (probe): base registry.ts has 174 statements (15 imports + 159); head host keeps OrderedConversion, inApplicationOrder, MAJOR_18_CONVERSIONS, CONVERSIONS_BY_MAJOR, ALL_CONVERSIONS and 112 entry modules + shared.ts hold the other 154; all 159 compared by full text (leading comments and blank lines included), 727,159 bytes, each base statement placed exactly once, in base order per file, file docblock identical, both @__PURE__ markers in place. Entries: 112 = base's 112 const …: MetadataConversion declarations, {11: 3, 15: 2, 17: 12, 18: 95}, each module at entries/<toMajor>/<id>.ts read off its own literals, exporting exactly its conversion, importing neither the host nor another entry; host imports one per entry in base definition order; the only regenerated import is import type { MetadataConversion } (base also named ConversionApplication, which moved with the helpers to shared.ts). Tables byte-identical to base, so ALL_CONVERSIONS and every CONVERSIONS_BY_MAJOR[n] keep membership and order; the Phase 0 --arrays digest (112/112, 6dfde603…) still applies to the identical input.
  • Remedies (c5fc1136): the three D2 texts now say one module under src/conversions/entries/<toMajor>/ plus its import and array line in src/conversions/registry.ts. Accurate: a new conversion always joins the open major, whose table is one { conversion, order } line per entry (host ≈:190+), and the released majors' arrays are closed literals. Pinned: deletion-proofs asserts src/conversions/entries/<toMajor>/ on the checkManifestRemovals remedy (≈:2801) and retired-keys asserts src/conversions/entries/ on the check (b) remedy (≈:3012); both keep the src/conversions/registry.ts pin. The ≈:1231 remedy (surface doc) carries the same text with no direct pin; same wording, no finding.
  • Head checks: 64 check-runs, 54 success, 10 skipped (Check Changeset skipped under skip-changeset, Build Docs, opt-ins, UI lint), 0 failure, 0 pending, all completed by 10:29Z: Build Core, Test Core 1-8, Test UI 1-16, Type Check (source, consumer, debt ledger, workspace), Lint & Repo Gates, PM dispatch-gates, PR-body guards, Spec property liveness, Governed Surface Queue Guard.

② Semver level

  • Unchanged from Phase 0: skip-changeset, no changeset, Clause-②: no (move-only), Metadata-protocol: none; the merge adds no PR-owned change and c5fc1136 changes only error text in a build script and two tests. The .d.ts evidence for THIS head is in the project files only (issue-29987/phase1/): two base builds and two head builds; dn-base-1-head-2 and dn-base-2-head-1 are 134/134 raw identical with the control caught, while dn-b1b2 (base vs base) and dn-h1h2 (head vs head) differ in 10 files (api/contracts/marketplace/system index pairs, automation-api.zod-* chunk hash renames), surface EQUAL; the codemod --dts cross pair (dts-p1.log) reads FAIL (9). Reading: each head build is raw-identical to a base build and the residual is main's own build nondeterminism, not the split; raw identity (6348891160 item 1) holds pairwise, so skip-changeset stands.

③ Boundary flags

  • The PR body still carries the Phase 0 readings: Tests and Verification cite --verify --base 5cdaddbc and "134/134 raw byte-identical on quiet builds", and the reader census says build-schemas.ts and both check-mode tests are "unchanged" while c5fc1136 changed them (the trailer admits it; the rows do not). No comment on the PR or the card claims a Phase 1 .d.ts reading for dca57060, and nothing states that the four Phase 1 builds ran on a quiet tree, which Phase 0 S1 required of the regenerate's report.
  • The maintainer's APPROVED review (5766718516, 10:09:46Z) names dca57060, whose commit date is 10:11:31Z: the dev box's clock is about two minutes ahead; the review sits on the current head and is not dismissed. The approval predates the Phase 1 readings (files 10:22Z) and this record.
  • .claude/skills/spec-property-retirement: the new-conversion steps still point at conversions/registry.ts, which the #29987 codemod splits into entries/ plus an index #29994 covers the SKILL.md prescriptions only; the MAJOR_18_CONVERSIONS docblock rule "DEFINE it directly above the definition of the entry that follows it" (host ≈:181-185, byte-frozen in a move-only PR) is still not on any card.

④ Design judgment

  • Landing: no PR-owned path moved on main since the old base, so the queue merge onto b0c2209d carries no conflict and the generated tree is what a Phase 1 --write on the latest main produces; the next retirement that adds a conversion on main before this lands would re-open that, and the codemod's plan refusal of an already-marked input plus the documented checkout step cover it.
  • The base-vs-base .d.ts difference is a pre-existing build nondeterminism on main (same source, two builds, 10 files, chunk hash renames); it is outside this PR but worth a card, because every future move-only PR's declaration-file bar will trip on it unless readings are taken pairwise or the cause is fixed.

Findings:

Implemented-by: claude/project-thread-enusuc
Reviewed-by: session_01WGufzfgvGexKn9vLHsy89K

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

维护者速读

  • 改了什么:codemod scripts/codemod-split-conversions-registry.mjs 把 packages/spec/src/conversions/registry.ts(16,019 行)拆成 112 个条目模块(entries/<toMajor>/<id>.ts)加一个 entries/shared.ts;registry.ts 剩 354 行,只留导入和四张表。另有 4 个按路径读这个文件的读者同步放宽,build-schemas 的三处 D2 补救提示也改成了拆分后的写法。
  • 为什么改:[Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465 大文件拆分计划(裁决 6583399215)的一部分,已在今天的窗口里于最新 main(c2d353d2)上重新生成。这张表是冲突最多的文件之一,按条目拆开后,每条 conversion 单独成文件,评审和合并都更容易。
  • 风险与回滚:只搬不改。159 条语句、727,159 字节逐字节相同;ALL_CONVERSIONS 和每个 CONVERSIONS_BY_MAJOR[n] 的 id、顺序和函数源码在 base 与 head 完全一致;每次 head 构建的 134 个声明文件都与一次同状态的 base 构建逐字节相同(main 自己的 d.ts 构建在两种状态间交替,见 Tests)。回滚就是 revert 这个 PR。
  • 先看:packages/spec/src/conversions/registry.ts(新的宿主文件)、entries/shared.ts,以及下面的读者普查表。
  • 两分钟验证:pnpm install --frozen-lockfile,然后 node scripts/codemod-split-conversions-registry.mjs --verify --base origin/main(字节一致)和 --arrays --base origin/main(表相等)。
  • 需要你做的:不用做什么。你已批准在今天的窗口落地,CI 在 dca57060 上全绿,由 spec 席位送入合并队列。

席位意见:fable 契约复审两轮都是 PASS(Phase 0 和 Phase 1)。main 从未改动 conversions/,所以合并最新 main 就等同于重新生成。check-runs 全绿,os-zhuang 已在本 head 上 APPROVED,维护者在 10:11Z 批准了今天的窗口。席位送入合并队列。

席位核对 — ACCEPT · domain:spec#3 · head dca57060c584739094493510918ce8067e672d7f

  • Shape: 124 files, +19,405 −15,822. The codemod scripts/codemod-split-conversions-registry.mjs moves the 159 non-table statements of packages/spec/src/conversions/registry.ts into 112 entry modules (entries/{toMajor}/{id}.ts) plus entries/shared.ts, and leaves the host with its imports and the four tables. The diff also:
    • widens 4 by-path readers of the file;
    • rewords the three D2 remedies in build-schemas.ts (c5fc1136), with pins in two check-mode tests.
      dca57060 is a merge of origin/main (base c2d353d2) with no conflict resolution.
  • Premise measured: registry.ts was 16,019 lines at base. Its blob (b9631781) and the conversions/ tree are identical at the Phase 0 base, at c2d353d2 and at current origin/main, so the plain merge equals a regenerate.
  • Gates (check-runs at this head): 54 success, 10 skipped, 0 red. Dev runs at this head:
    • spec test: 811/811 files and 24,376 tests, equal at base and head;
    • test:repo: 90 files and 1,539 tests;
    • typecheck exits 0 (6 GB heap);
    • 96 derived gates, all exit 0, with 0 NOT-MEASURED.
  • Contract review:
    • Phase 0, 6619521721 on a8c5d94d: PASS.
    • Phase 1 delta, 6628915927 on this head: PASS. The review independently re-checked byte identity: 159 of 159 statements, 727,159 bytes, base order, and both @__PURE__ markers. It found no must-fix and two should-fixes, the body refresh (done in r2) and the follow-ups carried below.
  • Declarations: Clause-②: no (move-only), Metadata-protocol: none, skip-changeset (nothing publishes a different artifact). Ruling-ref: 6583399215.
  • Accepted deviations: the .d.ts proof reads as pairwise raw identity rather than a single base-vs-head pair. Four quiet builds were run back to back. base-1 ≡ head-2 and base-2 ≡ head-1, each 134/134, with the control caught. The cross pair differs in exactly the 10 files that base-vs-base also differs in (ZodEnum and record member order, surface digest EQUAL). That is main's own d.ts build nondeterminism, not the split.
  • Carried:
  • Landing: not on the governed register (0 of 124 paths), but 35,227 lines, over the 3,000-line human-merge bar. An account in GOVERNED_APPROVERS (os-zhuang, review 5766718516) APPROVED this head, and the maintainer approved the window in the project (「以上我都批准了,通知车道落地」, 10:11Z). So the owning seat lands it: pr_ready plus automerge_enable through the queue.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 4, 2026 11:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 4, 2026 11:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41819688971 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core — 失败步骤: Verify test shard results(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 0 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Dequeued, not this PR's: the merge-group CI run 41819688971 (merge commit f7a9e147) failed only its Test Core verdict job. Shards 1, 2, 3, 4, 6 and 7 all passed. Shard 8/8 never got a runner (it was still queued when the verdict ran), and shard 5/8 has no job at all. So the verdict read "2 of 8 declared shard(s) … published no positive attestation". No test failed. The PR head dca57060 is green, with 54 success and 0 red. The seat re-queues once.

Posted 2026-11-04T11:44Z.

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codemod split: generate per-entry files and an index for spec conversions/registry.ts with a deterministic script

2 participants