Repository navigation
Commit 052a5e1
fix(spec): render-projection-diff generates a base's git-ignored migration registry (#22750)
Fixes #22744
Clause-②: no
## What
`Type Check · source gates` runs `render-projection-diff.ts --base
HEAD^1`, which generates the two ADR-0087 projections inside a `git
archive` of the base. Since the migration registry became git-ignored
and generated (ruling B on #22554, landed as `ed1de8c2db`), that archive
carries only `registry.ts.template` and `entries/`, so both base
generators die on `Cannot find module '../src/migrations/registry'`.
Every merge-queue entry built on a base at or after `ed1de8c2db` fails
this step.
`realMaterializeBase` now calls a new `generateBaseRegistry(specDir)`
after the unpack and the `node_modules` symlinks:
- registry present in the archive (any base that committed it) - nothing
runs, today's path unchanged;
- registry absent and the base carries
`scripts/build-migration-registry.ts` - the BASE's own generator runs
with this checkout's `tsx`, cwd the base `packages/spec`. It resolves
its package root from its own path, so it writes the base's registry
from the base's template and entries (its built-in self-test runs first,
as on every generation);
- the generator exits non-zero, or exits 0 without writing the file -
thrown, which the existing catch in `renderProjectionDiff` reports as a
red base side with the generator's output.
The registry file is regenerated, never committed; ruling B is
untouched. The diff stays within the claimed surface:
`render-projection-diff.ts` (+48 -3) and
`render-projection-diff.test.ts` (+110 -0).
## Evidence: the real step, before and after
Both runs are from this worktree, against base `ed1de8c2db`, whose
archive lists `packages/spec/src/migrations/registry.ts.template` and no
`registry.ts`.
Before, with `origin/main`'s script (`ed1de8c2db`, no edit), `tsx
scripts/render-projection-diff.ts --base ed1de8c --out-dir ...` gave
exit 1:
```text
## Generated ADR-0087 projections — NOT rendered
- base ed1de8c = ed1de8c spec-changes.json: build-spec-changes.ts exited 1:
Error: Cannot find module '../src/migrations/registry'
Require stack:
- /tmp/render-projection-diff-jyRygH/tree/packages/spec/scripts/build-spec-changes.ts
- base ed1de8c = ed1de8c protocol-upgrade-guide.md: build-upgrade-guide.ts exited 1:
Error: Cannot find module '../src/migrations/registry'
```
After, with this branch (`2a4ccb4878`), the step exactly as `lint.yml`
spells it (`--base HEAD^1`, so HEAD^1 = `ed1de8c2db`) gave exit 0:
```text
✓ neither projection changes against HEAD^1 = ed1de8c
```
- Control leg, a base from before the registry left git: `--base
f59a73c` (its archive carries a committed `registry.ts`) gave exit 0,
`✓ neither projection changes against f59a73c`. The generator is
skipped there.
- The base generator itself, run by hand in a `git archive` of
`ed1de8c2db`: exit 0, `build-migration-registry --self-test: ok`, `✓
wrote src/migrations/registry.ts (419 semantic, 258 retired-key, 222
retired-def)`. `cmp` against this checkout's generated registry shows
the two are byte-identical.
## Tests
`render-projection-diff.test.ts` gets five new cases. They use the
file's existing injection seam: a `RunScript` runner passed to
`generateBaseRegistry`, plus `materializeBase` for the end-to-end case.
1. An archive with the generator and no registry runs the base
generator, once, in the base `packages/spec`.
2. A base that committed its registry, or one with no generator, is left
as it is (three layouts).
3. A failing generator, or one that exits 0 without writing, is thrown,
with the generator and exit code on the first line and its output after.
4. Through `renderProjectionDiff`: a base whose registry cannot be
generated is RED and named as the base.
5. The default runner, with real `tsx`, runs a stand-in generator by its
own path with the base package as cwd.
Results:
- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2 scripts/render-projection-diff.test.ts`: `Tests 14 passed
(14)` (9 existing + 5 new).
- `pnpm --filter @objectstack/spec run typecheck`: exit 0.
`check:scripts-typecheck` (`tsconfig.scripts.json`) compiles both
touched files; `--listFiles` names both.
## Ablations (from committed `2a4ccb4878`, via
`scripts/ablation-replace.mjs`, restore proven blob == HEAD)
- The generator call inside `generateBaseRegistry` replaced by a no-op
result: `Tests 4 failed | 10 passed (14)`. The four that fail are cases
1, 3, 4 and 5. Case 2 stays green, as expected, because it asserts that
nothing runs.
- The `generateBaseRegistry(specDir);` call deleted from
`realMaterializeBase`, then the real step run against `ed1de8c2db`: exit
1 with the same `Cannot find module '../src/migrations/registry'` for
both projections. `realMaterializeBase` is not exported and has no unit
pin of its own. Its wiring is pinned end to end by this very CI step,
which materialises `HEAD^1` on every pull request, and every such base
now lacks the registry.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, derived in this worktree at `2a4ccb4878` (2
paths against merge base `ed1de8c2d`), lists 59 commands. That is the
same list the dispatch carried.
All 59 ran at `2a4ccb4878` (the final commit), each exit code captured
before any pipe:
- **55 exit 0.** Among them are `node
scripts/check-adr-0087-registration.mjs --base origin/main` and
`--self-test`, `pnpm check:nul-bytes`, `pnpm check:test-source-alias`,
`pnpm check:cross-package-test-inputs`, `pnpm check:type-check-coverage`
and `pnpm check:type-check-debt`.
- **4 NOT MEASURED, exit 3 `PREREQUISITE NOT MET`:** `pnpm
check:dts-closure`, `pnpm check:dual-build-cjs-loads`, `pnpm
check:lean-entry-closure` and `pnpm check:sourcemap-no-sources-content`.
Each reads the built `dist/` of the whole workspace, and this worktree
has no workspace build. This narrowing is declared, and CI runs all
four. The diff changes no build input: `scripts/` is in no tsup entry,
and no `src/` file imports it.
- `dispatch-gates --ran`, with the exit-coded record, reports: `59
derived famil(ies) accounted for — 55 run, 4 NOT-MEASURED (4 DERIVED
from a recorded exit 3)`.
Lint is a narrowed run, measured rather than skipped. `eslint
--no-inline-config --format json` over the two touched files reports 2
files linted, 0 errors and 0 warnings. `eslint --print-config` gives
parserOptions `{"ecmaVersion":"latest","sourceType":"module"}`, with no
`project`. Type-aware linting is therefore off, so this diff cannot move
a verdict on any untouched file. The repo-wide `pnpm lint` is left to
CI.
## Other `git archive` / base-tree readers of `packages/spec`
`git grep` over `scripts/`, `packages/*/scripts`, `.github/` and
`packages/spec` finds three more archive call sites. None breaks the
same way:
- `scripts/check-adr-0087-registration.mjs` `generatedWitness` (the
archive at `:2089`). The change that made the registry git-ignored
already adapted it: when the template is tracked it runs the rev's own
registry generator, and a template with no generator is red. Measured:
calling `generatedWitness({ cwd, head: 'HEAD' })` at `2a4ccb4878`, whose
archive has no `registry.ts`, returns `problem: null` and a parsed
manifest with 2 `perMajor` records.
- `scripts/check-merged-result.mjs`: a generic merged-tree probe for
budget gates. It runs no spec generator.
- `scripts/objectui-changeset-digest.mjs:2304`: a self-test sandbox that
writes its own fixture `registry.ts`.
## Changeset
None, `skip-changeset` route. `packages/spec`'s `files[]` (`dist`,
`json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`,
`src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `spec-changes.json`,
`protocol-upgrade-guide.md`) does not include `scripts/`, and no `src/`
file imports `render-projection-diff`. Measured on a built spec tree
(`03181fc07b`): `grep -rl` for `renderProjectionDiff`,
`SUMMARY_DIFF_CAP` and `spec-projections-diff` over every `files[]` path
found 0 files; the positive control `MIGRATIONS_BY_MAJOR` in `dist` was
found in 4 files.
## Acceptance notes
- `realMaterializeBase` stays unexported. The new logic lives in the
exported `generateBaseRegistry`, which is what the unit cases pin.
Exporting the materialiser would need a git-history fixture, which this
file's header deliberately avoids.
- A base with neither a registry nor its generator (no such commit
exists today) keeps today's behaviour. Its projection generators fail
loudly on the missing module, so it is red, never silent.
- Once this is on `main`, the walled queue entries (#22735, #22740 and
any other dequeued entry) need a new merge-group run to pick it up. A
rerun of an old run reuses its old merge ref.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent d7b26df commit 052a5e1
0 file changed
0 commit comments