You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d79eecf
Browse filesBrowse the repository at this point in the historyBrowse files
refactor(spec): codemod split of conversions/registry.ts into per-entry modules (#29987) (#29993)
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBKGNRxbkYw4a3n8552mQyGC)_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
补救提示也改成了拆分后的写法。
- 为什么改:#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' c2d353d --
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 #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:**
- The `MAJOR_18_CONVERSIONS` docblock in the host still says to define a
conversion directly above the entry that follows it. It has to stay
byte-identical in a move-only PR, so the merge test now gives the
post-split spelling: import it directly above that entry's import.
- The skills line above names the second follow-up (#29994, which also
covers this docblock).
## 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.com/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
---------
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments