Skip to content

fix(release): leave a current-series field written ahead of its GA cut for that GA's own version run - #25488

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-zhlg06
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-zhlg06

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #25408

Clause-②: no
Metadata-protocol: none

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

What changed

The rewriter treated any current series: field that did not name its major's newest GA as stale, and stamped it to that GA with the run's date. A field written by hand ahead of a GA cut names a NEWER version than the newest GA. So every version pass that was not that GA's own moved it backwards, under a date that belongs to neither release. Such passes are an rc cut, a run with nothing new, and a lower version's GA run.

Now:

  • aheadOfNewestGa(named, newest) is true when the field names a newer version of the same major. It is a numeric triple compare, so 17.10.0 is ahead of 17.9.0. A different major is never ahead, because no run of this major can make that field true. A version that is not x.y.z throws, because either default would bring back a defect.
  • rewriteStatusField returns null for such a field on every run, with the GA-run flag set or not. So a lower version's GA run (an 18.0.1 patch) also leaves an 18.1.0 field alone.
  • currencyVerdict({ indexText, changelogText }) returns { findings, deferred }. It is the gate's own indexCurrencyFindings for every major, except a major whose field is ahead of the cut, which becomes a deferral instead of a finding. currencyFindings is now its findings, and syncIndex returns deferred as well.
  • main() prints one line per deferral and exits 0. On the measured shape the line reads: "v18: (current series: 18.1.0, released 2026-10-21) left as written — 18.1.0 is newer than v18's newest GA in packages/spec/CHANGELOG.md (18.0.0), so the field was written ahead of the 18.1.0 cut. Only 18.1.0's own version run can date it, and it will; rewriting it now would move it back to 18.0.0 under a date that belongs to neither release. If 18.1.0 will not ship, correct the field by hand. check-release-section-coverage reports it until then." The "nothing rewritten" line no longer claims that every entry names the newest GA when a deferral exists.
  • The header gains a section, "A field written AHEAD of the cut", and a bullet under "What it deliberately does NOT rewrite".

A field BEHIND the newest GA is stamped forward exactly as before. The GA's own version run still re-dates the field (the path #25407 added), now starting from the pre-written version.

Why exit 0 and not a stop

The dispatch allowed either, "whichever the existing exit ladder supports". The ladder's exit 1 is the loud stop for shapes that need a hand-written sentence: a contradicted "final release:" or a missing parenthetical. It fails pnpm run version. That would fail every cut-rc.yml cut ("Version the workspace", line 473) and every version-pr refresh (release.yml lines 561 and 939) from the day a field is pre-written until its GA. No sentence improves that state, and the GA's own run resolves it. So the field is deferred and the run says why.

The deferral is per major. A refused shape on another major in the same index still reaches the verdict and stops the run (pinned in Control J).

The gate is unchanged and still reports the field: advisory on main without --strict, and red under --strict in release-coverage-patrol.yml. Turning on --strict anywhere else is a workflow edit and outside this card.

Proof

Throwaway repositories, before and after. Each repository holds scripts/, the spec CHANGELOG (newest GA 18.0.0) and the release index from origin/main 0b8dbea. The v18 field was changed to (current series: 18.1.0, released 2026-10-21) and committed as HEAD. Then the CHANGELOG was changed as each pass would change it, and node scripts/sync-release-index-currency.mjs was run on 2026-10-23:

Shape origin/main 0b8dbea this branch
nothing pending → (current series: 18.0.0, released 2026-10-23), exit 0 left as written (the notice quoted above), exit 0, git diff empty
## 18.1.0-rc.0 added (an rc cut) → (current series: 18.0.0, released 2026-10-23), exit 0 left as written, exit 0, git diff empty
## 18.1.0 added (18.1.0's own version run) → (current series: 18.1.0, released 2026-10-23) — re-dated: …, exit 0 the same

Self-test (pnpm check:release-index-currency-sync, exit 0): "OK self-test: 75 cases pass", up from 58. The new battery Control J has 17 cases and is floored; the roster floor moves from 9 to 10. It pins:

  • An ahead field is left byte-identical on a run with nothing to version, on an rc cut of that very version, and on the GA run of a lower version (17.3.1). It is refused at the field level with or without the GA-run flag.
  • The other direction on the same run: a field behind the newest GA (17.2.0) is still stamped forward, version and date.
  • The compare is numeric (17.10.0 against 17.9.0, patch-level, a version against itself). A v17 field naming 18.4.0 is not ahead and is stamped as stale. A version that is not x.y.z is refused.
  • The gate's own indexCurrencyFindings still reports the field, while the verdict carries it as one deferral and no finding. A refused "final release:" on v16 in the same index still reaches the verdict.
  • The notice names the field, the newest GA, whose run dates it and the remedy, and carries no tracker number.
  • On 17.4.0's own version run the field is re-dated and nothing is deferred.
  • End to end through syncIndex on checkout-shaped git repositories, on the measured 18.1.0 shape: no write and the file byte-identical with nothing pending and on an 18.1.0-rc.0 cut; re-dated on disk on 18.1.0's own run.

Ablations. Each was applied to the committed head, the self-test re-run, and the file restored (git hash-object identical):

Removed Self-test
the rewriter's refusal of an ahead field exit 1, 6 Control J failures
the verdict's deferral exit 1, 5 failures
numeric compare, replaced by a string compare exit 1, 1 failure
the same-major guard exit 1, 1 failure
the refusal of a version that is not x.y.z exit 1, 1 failure

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

Gates

At 6d07608 (base 0b8dbea), node scripts/pm/dispatch-gates.mjs --commands derived 33 commands for the one path. Each ran with its exit code captured before any pipe, and all 33 exited 0. --ran reconciles: "✓ dispatch-gates --ran: 33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED (a DERIVED zero — all 33 recorded an exit code and none of them is 3)". The script-specific families among them include check:release-index-currency-sync, check-scripts-symbol-anchors, check-self-test-wired and check-self-test-workflow-commands (each in both modes), check:entry-guard, check:nul-bytes and check:pm-dispatch-gates. pnpm exec eslint scripts/sync-release-index-currency.mjs exits 0.

The branch then merged origin/main ea33919. None of the incoming commits touches this script, the gate it imports, git-env.mjs or invoked-as.mjs, and the v18 field on main still reads (current series: 18.0.0, released 2026-10-20). At the merged head 9697865 the self-test (75 cases), the live run and the three-shape measurement above were run again with the same results. The pre-push check:commit-card-trailers passed on both pushes.

node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-zhlg06: "0 of 1 path(s) hit the register (7 surfaces govern objectstack). ✅ NOT governed", 281 changed lines (≤ 3000).

No package source moves, so no package test or typecheck is owed. 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

  • A field naming a GA that never ships (the series closes first) is deferred on every run, and the gate keeps reporting it. The notice names that case and its remedy, a correction by hand. Whether a planned GA was abandoned is a human judgement, so it is not mechanised.
  • A final release: field written ahead of its cut is not deferred. It is a refused shape and still stops the run, as before. No such field is in the corpus, and it is outside this card.
  • Follow-up, named and not done: running check-release-section-coverage --strict on PRs is a workflow edit (Tier H) and outside this card.
  • 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 fix(release): re-date a pre-written current-series field on its GA's own version run #25407; reported as a deviation.
  • The branch is the harness-pinned claude/project-thread-zhlg06. The claim (comment 6368342248) records it as the identity.

Generated by Claude Code

…t for that GA's own version run

sync-release-index-currency read a `current series:` field naming a version
newer than its major's newest GA heading as stale, and rewrote it backwards
to the older GA under the run's date on every version pass that was not that
GA's own (a prerelease cut, a refresh with nothing new, a lower version's GA
run). Such a field is early, not stale: only its GA's own version run can date
it, and that run already re-dates it.

The rewriter now leaves it byte-identical on every run, judged per major with
a numeric triple compare, and prints why. The verdict holds that field out as
a deferral instead of a finding, so `pnpm run version` keeps passing on
cut-rc and version-pr until the GA; the gate itself still reports the field.
A field behind the newest GA is still stamped forward. Control J pins both
directions, end to end on checkout-shaped repositories.

Claude-Session: https://claude.ai/code/session_01GUr4Vb6HcvEh7N4qrqhPyk
Co-authored-by: Claude <noreply@anthropic.com>
…ead as ahead, and keep the notice case a reported failure

Claude-Session: https://claude.ai/code/session_01GUr4Vb6HcvEh7N4qrqhPyk
Co-authored-by: Claude <noreply@anthropic.com>
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

2 participants