Skip to content

fix(release): re-date a pre-written current-series field on its GA's own version run - #25407

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-1r2arf
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-1r2arf

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #25356

Clause-②: no
Metadata-protocol: none

A code PR on one file, scripts/sync-release-index-currency.mjs (+322 / -47). It touches no content/docs/releases/** file, no workflow and no package.

What changed

The rewriter refused to touch a current series: field that already names the newest GA, date included, on every run. That refusal is right for prerelease cuts: its header records the 17.7.0 field that every 18.0.0-next.N cut re-dated. It was wrong on one run: the run whose changeset version writes the GA's CHANGELOG heading. On that run the run's date is the true one, and a field written by hand ahead of the cut is not. 18.0.0 shipped dated 2026-10-19 that way.

Now:

  • committedChangelogText(root) reads packages/spec/CHANGELOG.md as committed at HEAD with git show (GIT_* stripped by gitFreeEnv(), per GIT_ENV_ISOLATION_RULE). It uses no clock and no network. A HEAD git cannot read is a refusal, because either default would bring back one of the two defects.
  • gaVersionRunMajors() returns the majors whose newest GA heading is in the working tree and not at HEAD. Those are the majors on their GA's own version run.
  • rewriteStatusField(field, newest, date, { gaVersionRun }) re-dates a same-version field only when gaVersionRun is true. A re-date that changes nothing returns null, so a current index stays unwritten. The refused shapes ("final release:", trailing matter, prose) stay refused on the GA run too.
  • rewriteIndexText now requires headChangelogText and refuses without it. A caller that forgot it would otherwise silently get the 18.0.0 behaviour back. syncIndex reads it with committedChangelogText, so main() and the self-test take the same path.
  • main() labels a re-date in its output: "re-dated: this run's version pass wrote the newest v18 GA heading, so this is its version commit".
  • The header has a new section, "The one same-version field it DOES re-date", with the 18.0.0 measurement.

Every other run keeps today's behaviour. A prerelease cut writes only a prerelease heading, which is never a GA. A run with nothing pending writes no heading. A run after the version commit has landed finds the heading at HEAD.

Two refinements of the card's guard, and why

The card words the guard as "the newest GA at HEAD differs from the newest GA after changeset version". This PR keeps that idea with two changes:

  1. Per major, not for the whole repository. On the 18.1.0 run, a v17 field naming 17.7.0 still names 17.x's newest GA. Re-dating it to 18.1.0's day would be the 17.7.0 defect again. Control I pins this on the 18.0.0 shape: v18 is re-dated and v17 is left byte-identical.
  2. "Introduced", not "differs". A working tree that lost a GA heading HEAD carries also "differs", and it is no version commit. The predicate is "this major's newest GA heading is absent at HEAD", which is exactly "this run's changeset version wrote it". Control I pins the lost-heading case.

Where it runs, and the governed check

  • The script runs only from the root version script (pnpm run version). release.yml's version-pr job runs that twice: in "Render the post-version tree" (line 561) and as changesets/action's version-script (line 939). cut-rc.yml runs it on its snapshot (line 473). In all three, HEAD is the commit being rendered and the tree is clean before the pass; release.yml already diffs the output against HEAD (git diff --name-only HEAD). The version-pr checkout is shallow (depth 1), which still holds HEAD's tree.
  • node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-1r2arf at fbd6690: "governed-surface predicate: 0 of 1 path(s) hit the register (7 surfaces govern objectstack). ✅ NOT governed", size 369 changed lines (≤ 3000). No workflow file changes, so the lane is untouched.

Proof

Self-test (pnpm check:release-index-currency-sync, exit 0): "OK self-test: 58 cases pass". Before this change it was 42. The new battery Control I (16 cases, floored; roster floor 8 → 9) pins:

  • On the GA version run, a pre-written same-version field is re-dated, and the line is byte-for-byte the current entry.
  • A prerelease run leaves the pre-written field alone. So does a non-version run (CHANGELOG at HEAD = CHANGELOG in the tree).
  • The lost-heading case, the per-major 18.0.0 shape, and the field-level refusals all hold on the GA run.
  • rewriteIndexText refuses without headChangelogText.
  • End to end through syncIndex on a real throwaway git repository: re-dated on disk on the GA run; left alone after the version commit lands; refused when git cannot read the CHANGELOG at HEAD, with the index untouched.

Control H's end-to-end tree is now a real repository too: a prerelease cut on it still writes nothing.

Rehearsal on the real corpus (a throwaway repository holding this branch's index, spec CHANGELOG and the four scripts). The v18 field was pre-written by hand to (current series: 18.1.0, released 2026-10-21) and committed as HEAD. Then ## 18.1.0 was added to the CHANGELOG, as changeset version would add it:

Run Result
this branch v18: (current series: 18.1.0, released 2026-10-21) → (current series: 18.1.0, released 2026-10-23) — re-dated: …, exit 0
origin/main's script, same tree "… nothing rewritten", exit 0, field still released 2026-10-21 (the 18.0.0 defect)
this branch, after committing the version pass, a later date {"wrote":false,"rewrites":[],"gaVersionRun":[]}, field keeps 2026-10-23

The live tree: node scripts/sync-release-index-currency.mjs prints "every in-scope entry … (v16, v17, v18) already names the newest GA release of its major … nothing rewritten", exit 0, and the tree stays clean.

The 18.0.0 date this would have stamped. The Version Packages PR #24524's commit 31abb7184c was authored 2026-10-20T11:10Z, and npm published 18.0.0 at 2026-10-20T12:51Z (from #25354). So the version run would have stamped 2026-10-20, the true day.

Gates

At fbd6690 (this branch merged with origin/main 830a63a), node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 33 commands for the one path. Each was run with its exit code captured before any pipe. On the first pass, six returned PREREQUISITE NOT MET (exit 3: yaml and typescript not installed in the fresh worktree): check-ci-filter-parity, check-closing-keyword-parity (both modes), check-comment-mask-corpus, check:cli-command-ids and check:driver-memory-census. After pnpm install --frozen-lockfile all six exit 0. --ran reconciles: "✓ dispatch-gates --ran: 33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED". The script-specific families among them include check:release-index-currency-sync, check-scripts-symbol-anchors (both modes), check-self-test-wired (both modes), check-self-test-workflow-commands (both modes), check:entry-guard, check:nul-bytes and check:pm-dispatch-gates.

Run before the push: check-commit-card-trailers --range origin/main..HEAD ✓, check-regen-pending --pre-push exit 0.

No package source moves, so no package test or typecheck is owed. No test file outside the script names it (git grep), so its self-test is its whole suite. CI owns test:repo, Test Core, the type-check lanes and pnpm lint; this PR's check-runs answer them.

skip-changeset. The file lives in the private root package ("private": true), and scripts/ is in no published package's files[]. The label rides the create.

Acceptance notes

  • The stamped date is the version run's day, as before. On the version-pr lane that is the day of the last Version Packages refresh. It equals the npm publish day only when the PR merges the same UTC day as its last refresh, which held for 18.0.0. This PR does not change that; the header's releaseDate already states it.
  • Reported to the seat, not fixed here: a field written ahead of the CHANGELOG is rewritten BACK on a run that is not that GA's version run. Measured on the same throwaway repository before ## 18.1.0 was added: a run on the pre-written (current series: 18.1.0, released 2026-10-21) produced (current series: 18.0.0, released 2026-10-23). That is 18.0.0 with a date that is not 18.0.0's. This is the pre-existing stale-field path, which this PR keeps unchanged as the card requires. It is reachable from a version pass on a tree that carries such a field: a cut-rc.yml prerelease cut, or a manual run. check-release-section-coverage already reports such a field on main (advisory without --strict). Dedupe words: sync-release-index-currency, ahead of the cut, rewritten back, current series.
  • The attribution block's first line is an HTML comment that the session requires, and os-dev.md forbids HTML comments in GitHub text. Same shape as PR docs(releases): date 18.0.0 by its npm publish day, and the v17 page stops saying a plain install resolves 17.7.0 #25354; reported as a deviation.
  • The branch is the harness-pinned claude/project-thread-1r2arf. The claim (comment 6359770957) records it as the identity.

Generated by Claude Code

…own version run

sync-release-index-currency left a same-version status field alone on
every run, date included. That is right for prerelease cuts (the 17.7.0
re-dating its header records), but on the run whose `changeset version`
writes a GA's CHANGELOG heading the run's date is the true one, and a
field written by hand ahead of the cut is not: 18.0.0 shipped dated
2026-10-19 while its version pass ran and npm published on 2026-10-20.

The run is detected without a clock or the network, per major: the
newest GA's heading is in the working tree's spec CHANGELOG and not in
the one committed at HEAD (read with git show, GIT_* stripped). Only
then is a same-version field re-dated. Prerelease runs, runs with
nothing to version and runs after the version commit landed keep the
refusal. An unreadable HEAD is a refusal, and rewriteIndexText now
requires the HEAD CHANGELOG instead of defaulting either way.

Self-test: new battery Control I (16 cases, floor 9 batteries); Control
H's end-to-end tree is now a real throwaway repository.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013SFSGWz48S21trBAfXEXUx
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 14:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 14:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit b2c0752 Oct 23, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-1r2arf branch October 23, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sync-release-index-currency.mjs leaves a hand-written date in place on the version commit of the newest GA — 18.0.0 shipped dated a day early (#25148)

2 participants