Repository navigation
Commit 5b12503
Fixes #22482
Clause-②: no
The per-major section of `spec-changes.json` and the protocol upgrade
guide are now generated from the ADR-0087 registries at publish,
verified there, and shipped in the `@objectstack/spec` tarball and on
the GitHub Release, by the lane the `release` section already uses
(`scripts/release-spec-changes.sh`). `check:spec-changes` and
`check:upgrade-guide` stop comparing a committed copy: they generate
both in memory and go red when generation fails. Every pull request
renders the generated diff against its base. This is ruling `6078203801`
on #22449 (B′), its condition (1), and the pin in triage `6081379232`.
The committed copies and their `merge=os-regen` routes stay; deleting
them is #22485, which remains open. The guide's public address and the
pointer stub are #22483. ADR-0087 D4 is #22484.
## What changed
**One generation entry point per projection.** `build-spec-changes.ts`
and `build-upgrade-guide.ts` keep their `build()` functions and now
share one command-line contract,
`packages/spec/scripts/lib/projection-cli.ts`:
| Mode | Does |
|---|---|
| no flag | writes the committed copy, as before (kept until #22485) |
| `--check` | generates in memory and writes nothing. ⛔ No committed
copy is read. Exit 1 when generation fails, naming the projection and
the cause |
| `--out FILE` | writes the generated bytes to `FILE`. The publish lane
and the diff renderer use it |
The publish lane, the pull-request check and the pull-request diff all
run these same two scripts, so they cannot disagree.
**At publish** (`scripts/release-spec-changes.sh`):
- `--prepare` (release.yml) now also writes the guide into the package:
`build-upgrade-guide.ts --out packages/spec/protocol-upgrade-guide.md`.
It already rewrote all of `spec-changes.json` from the registries,
per-major records included. That part is unchanged.
- `--generate` (new, for cut-rc.yml) writes both registry projections
and **no** per-release section. That is what an rc tarball has always
carried. It reads nothing from npm.
- `--verify` packs the artifact and runs
`check-release-spec-changes.mjs` with a new `--registry DIR`: a fresh
generation of both projections. The packed copies must equal it. For the
manifest that covers everything except the publish-time fields: the
`release` section and the aggregate's one-release export diff. For the
guide it is byte equality. After `--prepare` the existing per-release
checks run unchanged. After `--generate` the lane declares
`--no-release-section`.
- `--attach` uploads both files in one `gh release upload` call.
- `packages/spec/package.json` `files[]` lists
`protocol-upgrade-guide.md`. `scripts/check-published-files.mjs`
registers it, as its REGISTERED invariant requires.
- `cut-rc.yml` runs `--generate` and `--verify` before its publish.
Before this change it packed whatever copy the tree held.
**At pull request** (lint.yml, `Type Check · source gates`, a required
lane with no paths filter):
- The two check steps now generate in memory.
- A new step runs `packages/spec/scripts/render-projection-diff.ts
--base HEAD^1`. On a pull request, `HEAD^1` is the base tip the merge
ref sits on. The step generates both sides with the same `--out`
generators: this checkout's, and the base's own generators in a `git
archive` of the base. It renders the diff into the job summary and a
`::notice` annotation. A per-record headline names the registry ids each
record gained or lost. It writes `spec-projections.diff`, and the next
step uploads that file as the `spec-projections-diff` artifact, only
when the diff is non-empty. A side that cannot be generated is red. A
diff, however large, is never red.
## The PM's hypotheses, measured first
**H1, the pre-pack hook: confirmed, and the guide half was missing.**
Dry run on `9411faa1ba` (the base), `RELEASE_VERSION=17.7.0`, previous
`17.6.0` read from npm. `--prepare` exit 0, `--verify` exit 0.
- The packed tarball held `CHANGELOG.md LICENSE README.md api-surface
liveness llms.txt package.json prompts spec-changes.json src` and **no
guide**: `docs/` ships in no package.
- The packed `perMajor` equalled the committed one, and so did the
aggregate's registry half. `--prepare` already regenerates the whole
manifest, per-major records included, at the point the ruling asks for.
- In release.yml both `--prepare` and `--verify` run before `Publish to
npm + push version tags` (`pnpm run release` = build + build-console +
`changeset publish`). `--attach` runs after `Create GitHub Releases`.
The spec build (`gen:schema`, `gen:openapi`, tsup) writes neither
projection.
Same dry run on this branch, `--prepare` exit 0 and then `--verify` exit
0:
- The tarball now also carries `protocol-upgrade-guide.md`.
- `--verify` prints `✓ registry projections verified against a fresh
generation: spec-changes.json (2 per-major record(s), 121 converted /
406 migrated) and protocol-upgrade-guide.md (1590904 bytes) match the
artifact.`
- Then `✓ release 17.6.0 → 17.7.0 verified against both tarballs: 83
added, 28 removed, 64 converted, 329 migrated. aggregate export diff
17.6.0 → 17.7.0 verified: 83 added, 28 removed.` That line is
byte-identical to the base run's.
**H2, every reader of the committed copy, at `9411faa1ba`.** For each:
what it reads after this PR, before #22485 deletes the copies.
| Reader | Reads after this PR |
|---|---|
| `check:spec-changes`, `check:upgrade-guide` | nothing: generation in
memory |
| `packages/spec/scripts/check-generated.ts` (GATED rows for both) |
runs the two checks by name. They no longer go stale-red, so `--fix`
never regenerates the copies. The rows' `artifact` text still names the
copies (wording for #22485) |
| `scripts/regen-artifacts.mjs` + `.githooks/pre-commit`
(`merge=os-regen`) | a merge deferral of either path is discharged by
the two checks, with no regeneration. The copy keeps whatever the merge
produced |
| `scripts/pm/os-regen-merge.sh` and the pm-dispatch hot-file row
(`.claude/skills/pm-dispatch/SKILL.md:296`) | its regeneration step
(`check:generated --fix`) no longer regenerates the two copies |
| `scripts/check-adr-0087-registration.mjs:2184` | still reads the
committed `spec-changes.json` at HEAD, as its parser-rot witness
(projection ⊆ source). It now reads an aging copy. That copy stays a
subset while registration is insertion-only, but a **withdrawn** id
would false-red it. #22485 must give it a generated witness before
deleting the copy |
| `scripts/check-future-spec-major.mjs:351`, `:363` | exemption rows
with `covers` counts over both tracked files. The frozen copies keep
their counts |
| `scripts/check-doc-authoring.mjs:1597` | walks the frozen
`docs/protocol-upgrade-guide.md` (#22483 turns it into a stub) |
| release.yml `version-pr` (`run_gate … check:spec-changes /
check:upgrade-guide`) | generation only. The step comment is corrected |
| release.yml publish job and Releases backfill | never read it:
`--prepare` overwrites it in the tree. Now also writes the guide |
| cut-rc.yml | **packed it as-is** before this PR. Now `--generate` plus
`--verify` |
| `packages/spec/package.json` `files[]` | ships whatever is at
`packages/spec/spec-changes.json` at pack time, which is now always the
lane's generation |
| `.claude/skills/spec-property-retirement/SKILL.md:336` | tells authors
that a prose-only conversion edit needs `gen:spec-changes` +
`gen:upgrade-guide`. That is no longer needed. Governed `.claude/**`,
not touched here |
| comment-only: `pr-automation.yml:905`,
`scripts/pm/dispatch-gates.mjs:5667`, and the retirement pins'
`EXCLUDED_PREFIXES` | they do not read the content |
Cloud was moved by cloud#2750. It runs `gen:spec-changes` in its pinned
checkout, and this PR leaves that script's default output path as it
was.
**H3, the window before #22485: the lanes that ship generate. One lane
did not, and now does.**
- release.yml publish and backfill overwrite the committed bytes.
Measured: a drifted tree copy, with `websocket-durations-unit-in-key`
dropped from `perMajor[17 → 18]`, was rewritten by `--prepare`, and
`--verify` exited 0.
- cut-rc.yml packed the committed bytes with no generation step. In the
window that would publish a stale `perMajor`. After #22485 it would
publish no `spec-changes.json` at all, because `npm pack` silently skips
a missing `files[]` entry.
- The lane is dormant today: `.changeset/pre.json` has tag `next`, and
cut-rc refuses anything but `rc`. The minimal guard is `--generate` +
`--verify` before its publish. Measured on the same drifted tree:
`--generate` overwrote it, then `--verify` exit 0 with `✓ no per-release
section is owed`. The packed manifest keys are the same as an rc
tarball's today: no `release`.
- The guard's red was also measured. A stale copy packed after
generation gives `--verify` exit 1, naming `perMajor[17 → 18].migrated:
1 id(s) the registries project and the artifact OMITS:
websocket-durations-unit-in-key`. A tarball without the guide gives exit
1 `ships no protocol-upgrade-guide.md`. `--verify` with neither
`--prepare` nor `--generate` gives exit 1 `Nothing was verified.`
**H4, rendering on the pull request: the job summary, a notice
annotation and an artifact, with no write path.** Measured in
`.github/workflows`:
- `$GITHUB_STEP_SUMMARY` is used in 13 workflows.
- `actions/upload-artifact@v7` is used in ci.yml (9 steps), cut-rc,
merged-branch-reaper, showcase-smoke, test-nightly-tiers and
coverage-nightly.
- PR comments go through `issues.createComment` under `pull-requests:
write` in docs-drift-check.yml (advisory), merge-queue-triage.yml and
cross-repo-issue-closer.yml.
- The required `typecheck-source-gates` lane holds `contents: read`
only.
So the diff renders where that lane can write without a token: its
summary, an annotation, and an artifact. No new write path is added.
## Pins
- `packages/spec/scripts/projection-cli.test.ts` (11 tests):
- `--check` neither reads nor writes a stale committed copy, and is
green with none;
- a generation failure is red in `--check` and in both write modes, and
writes nothing;
- `--out` writes only there;
- usage errors build nothing;
- the two real generators route through the runner and are
byte-deterministic across runs, which `--verify` depends on.
- `packages/spec/scripts/render-projection-diff.test.ts` (9 tests):
- an entry-only change renders its id in the headline, the diff and the
artifact;
- unchanged renders "no change" with no artifact;
- a head or base side that fails to generate is red;
- a base that predates `--out` is read from the path it writes, and the
head never falls back;
- plus the id delta, the fence and truncation.
- `scripts/check-release-spec-changes.mjs --self-test`: batteries 23 →
31 (P1–P8, the registry half). R1–R17 are untouched and green.
- `scripts/release-verify-npm.mjs --self-test` battery 13 floor 11 → 13
(113 → 115 cases). The backfill generates the guide in the version
commit's tree and attaches the publish job's bytes.
- **`release` section unchanged:** R1–R17 are unchanged, and the H1
verdict line for the section is byte-identical before and after.
One-shot proofs, not kept as tests:
- **An entry-only change needs no regeneration commit.** I added a
step-18 semantic entry file plus `gen:migration-registry` (the
registry's generated regions) and regenerated no projection.
- `check:spec-changes` exit 0 (407 migrated) and `check:upgrade-guide`
exit 0.
- Control: the base's `build-spec-changes.ts --check` on the same tree
exits 1, `spec-changes.json is stale`.
- `render-projection-diff.ts --base HEAD` exits 0: `spec-changes.json
+14 −0 (perMajor 17 → 18: +1 migrated (zz-demo-entry-only); aggregate 16
→ 18: +1 migrated (zz-demo-entry-only)) · protocol-upgrade-guide.md +3
−0`.
- Restored, `git diff HEAD` empty.
- **A registry that fails to generate turns the check red.** The same
entry with a non-string `replacement`:
- `check:spec-changes` exits 1: `✗ spec-changes.json could not be
generated from the ADR-0087 registries. Nothing was written.`, then the
ZodError.
- The renderer exits 1: `NOT rendered — head spec-changes.json:
build-spec-changes.ts exited 1`.
- Restored.
- **Ablation of battery 13's new case.** I dropped the guide from the
`gh release upload` line through `scripts/ablation-replace.mjs`: anchor
1 → 0, blob `d9cde2bfbe94` → `516d5f9dc619`. The self-test exited 1 on
exactly that case. Restored, blob equals HEAD.
## Verification
All readings at `1245a3058c` (this branch's head) unless a line says
otherwise.
- **Derived gate union.** `node scripts/pm/dispatch-gates.mjs
--commands` printed 128 commands. All 128 ran here, each with its exit
code recorded, and all exited 0. `--ran` reports: `128 derived, 128 run,
0 NOT-MEASURED, 0 UNRUN`.
- An earlier pass at `58d075abdc` recorded exit 3 (`PREREQUISITE NOT
MET`, no build) on the four gates that read built `dist/`:
`dts-closure`, `dual-build-cjs-loads`, `lean-entry-closure` and
`sourcemap-no-sources-content`.
- The spec `repo`-project tests then built the workspace's `dist/` in
this worktree. The final pass measured all four green.
- This covers every command the dispatch named, plus what the
re-derivation added: `check-declaration-mirrors` (both),
`release-verify-npm --self-test`, `check:engine-double-contract`,
`check:objectql-double-limit`, `check:query-options-erasure`,
`check:ratchet-remedy-authority`, `check:release-spec-changes` and
`check:where-matcher`.
- **Spec suites, under `scripts/pm/os-verify-lock.sh`.**
- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2`: VERDICT command-exit 0, 632 files passed, 1 skipped,
18847 tests passed. Taken at `58d075abdc`; `1245a3058c` adds only a
three-line comment.
- `pnpm --filter @objectstack/spec exec vitest run --project repo
--maxWorkers=2`: VERDICT command-exit 0, 54 files, 915 tests passed.
- `pnpm --filter @objectstack/spec typecheck`: VERDICT command-exit 0.
The scripts program lists all four new script files.
- **The two new test files:** 20 of 20 tests pass.
- **ESLint, narrowed and measured:**
- Population: the 9 changed JS/TS files, all under `eslint.config.mjs`'s
`**/*.{ts,…}` object.
- `--no-inline-config --format json` reports 9 files: 0 errors, 0
warnings, 0 ignored notices.
- The config enables no type-aware linting (no `parserOptions.project`),
so this diff cannot move a verdict on a file it does not touch.
- The repo-wide `pnpm lint` is CI's.
- **Main.** The branch is 6 commits behind `origin/main` `d303b3e7af`.
One of those commits (`ee8751d41e`) regenerated the two committed
copies, which this diff does not touch. No commit touches a file in this
diff, and `git merge-tree` is clean. CI runs on the merge ref.
## File surface
Two extensions beyond the claim's list, both forced:
- `packages/spec/package.json` `files[]`: the same file as the
in-surface check scripts. The ruling ships the guide "in the package",
and H1 asks the tarball to carry it.
- `scripts/check-published-files.mjs` `EXTRA_ENTRIES`: one entry, which
its REGISTERED invariant requires for any new `files[]` entry.
Also touched, reading them as the lane's own parts:
- `scripts/check-release-spec-changes.mjs`: the gate `--verify` runs.
- `scripts/release-verify-npm.mjs`: battery 13 executes the lane's
release.yml steps.
- `cut-rc.yml`: a release workflow step that calls the script.
## Acceptance notes
- `check-generated.ts` GATED rows still call the two committed files
their artifacts; the deletion card (#22485) owns that wording.
- `check-adr-0087-registration.mjs`'s parser-rot witness reads the
committed copy; #22485 must re-point it before deleting the copy (see
H2).
- The in-package guide `packages/spec/protocol-upgrade-guide.md` is
written only by the lane and is not gitignored. A local `--prepare` or
`--generate` leaves it untracked, the same way `--prepare` already
leaves `spec-changes.json` modified. Neither is ever committed by the
lane.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f782f17 commit 5b12503
15 files changed
Lines changed: 1667 additions & 128 deletions
File tree
- .changeset
- .github/workflows
- packages/spec
- scripts
- lib
- scripts
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
876 | 876 | | |
877 | 877 | | |
878 | 878 | | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
879 | 904 | | |
880 | 905 | | |
881 | 906 | | |
| |||
928 | 953 | | |
929 | 954 | | |
930 | 955 | | |
931 | | - | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
932 | 959 | | |
933 | 960 | | |
934 | 961 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5621 | 5621 | | |
5622 | 5622 | | |
5623 | 5623 | | |
5624 | | - | |
| 5624 | + | |
| 5625 | + | |
| 5626 | + | |
| 5627 | + | |
| 5628 | + | |
| 5629 | + | |
| 5630 | + | |
| 5631 | + | |
5625 | 5632 | | |
5626 | 5633 | | |
5627 | | - | |
| 5634 | + | |
5628 | 5635 | | |
5629 | 5636 | | |
| 5637 | + | |
| 5638 | + | |
| 5639 | + | |
| 5640 | + | |
| 5641 | + | |
| 5642 | + | |
| 5643 | + | |
| 5644 | + | |
| 5645 | + | |
| 5646 | + | |
| 5647 | + | |
| 5648 | + | |
| 5649 | + | |
| 5650 | + | |
| 5651 | + | |
| 5652 | + | |
| 5653 | + | |
| 5654 | + | |
| 5655 | + | |
| 5656 | + | |
| 5657 | + | |
| 5658 | + | |
| 5659 | + | |
5630 | 5660 | | |
5631 | 5661 | | |
5632 | 5662 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
444 | 444 | | |
445 | 445 | | |
446 | 446 | | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
452 | 457 | | |
453 | 458 | | |
454 | 459 | | |
| |||
1253 | 1258 | | |
1254 | 1259 | | |
1255 | 1260 | | |
1256 | | - | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
1257 | 1264 | | |
1258 | 1265 | | |
1259 | 1266 | | |
| |||
1783 | 1790 | | |
1784 | 1791 | | |
1785 | 1792 | | |
1786 | | - | |
1787 | | - | |
1788 | | - | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
1789 | 1802 | | |
1790 | 1803 | | |
1791 | 1804 | | |
| |||
1886 | 1899 | | |
1887 | 1900 | | |
1888 | 1901 | | |
1889 | | - | |
| 1902 | + | |
| 1903 | + | |
1890 | 1904 | | |
1891 | 1905 | | |
1892 | 1906 | | |
1893 | 1907 | | |
1894 | | - | |
1895 | | - | |
| 1908 | + | |
| 1909 | + | |
1896 | 1910 | | |
1897 | 1911 | | |
1898 | 1912 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | | - | |
| 263 | + | |
| 264 | + | |
264 | 265 | | |
265 | 266 | | |
266 | 267 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | | - | |
13 | | - | |
| 11 | + | |
14 | 12 | | |
15 | | - | |
16 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
17 | 25 | | |
18 | 26 | | |
19 | 27 | | |
20 | 28 | | |
21 | 29 | | |
22 | 30 | | |
23 | | - | |
| 31 | + | |
24 | 32 | | |
25 | 33 | | |
26 | 34 | | |
| |||
43 | 51 | | |
44 | 52 | | |
45 | 53 | | |
46 | | - | |
47 | | - | |
48 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
49 | 57 | | |
50 | 58 | | |
51 | 59 | | |
| |||
66 | 74 | | |
67 | 75 | | |
68 | 76 | | |
69 | | - | |
| 77 | + | |
70 | 78 | | |
71 | 79 | | |
72 | 80 | | |
| |||
83 | 91 | | |
84 | 92 | | |
85 | 93 | | |
| 94 | + | |
86 | 95 | | |
87 | 96 | | |
88 | 97 | | |
| |||
294 | 303 | | |
295 | 304 | | |
296 | 305 | | |
297 | | - | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
298 | 315 | | |
299 | | - | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
319 | 327 | | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
0 commit comments