Skip to content

fix(release): judge the major-boundary template stamps on the version-pr lane, and stop prerelease cuts re-dating the last GA - #22095

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22085-version-lane-major-boundary
Oct 7, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22085-version-lane-major-boundary

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22085. This PR carries the half of the card that holds on measurement. The half it does not carry needs a decision first (see "What still stops the refresh").

Clause-②: no

What this changes

  1. sync-release-index-currency no longer re-dates the newest GA on a prerelease cut. rewriteStatusField re-stamped today's date into a current series: field that already named the newest GA. The gate it serves (indexCurrencyFindings) judges the version only. The date it holds is the release date of the version the run moves the field TO, so a run that moves nothing (every next or rc cut) is not that version's version commit. A same-version field is now left alone. Battery B's case that pinned the re-date is inverted, and a new battery, Control H: a prerelease cut never re-dates the newest GA (7 cases), pins the next cut, the rc cut, a positive control (a stale entry on the same later day still gets the version and that day), and syncIndex end to end on a temp checkout (no write, bytes identical). The roster floor goes from 7 to 8.

  2. release.yml version-pr › Validate the post-version tree validates the blank template's three major-boundary paths instead of refusing them. It reuses gates the repo already runs, with no build:

    • pnpm --filter create-objectstack test: the template rendered by the scaffolder's own copy and identity rewrite, from a template packed the way npm ships it, plus the ratchets in template-consistency.test.ts that judge all three stamps against create-objectstack's NEW major;
    • pnpm --filter @objectstack/spec check:template-manifests.

    protocol-version.ts gets its gates too:

    • protocol-version.test.ts as ONE file (5 s). It does not need the whole spec suite, which is what the step's comment used to claim;
    • check:spec-changes and check:upgrade-guide, the two artifacts derived from PROTOCOL_MAJOR.

    It stays refused, for the measured reason below. The refusal is now collected instead of exiting first, so a boundary run reports every gate's verdict together. Every pnpm filter carries --fail-if-no-match, because a filter that matches nothing exits 0 having run nothing (measured: 0 without it, 1 with it).

Pin: the boundary train, replayed (throwaway tree, never committed)

Tree: this branch at 3079e4aef0 plus PR #22084's .changeset/pre.json and .changeset/22080-v18-line-opens.md, carried by one local commit that was never pushed. The steps were extracted from this branch's release.yml with a YAML parser and run verbatim with RUNNER_TEMP / GITHUB_OUTPUT / GITHUB_STEP_SUMMARY set.

step exit reading
Render the post-version tree (the full root version script) 0 69 of 69 fixed members at 18.0.0-next.0, pre.json unchanged (mode: pre, tag: next). PROTOCOL_VERSION 17.0.0 → 18.0.0. Template stamps at ^18.0.0 / '^18'. 9 docs pins moved to 18.0.0-next.0. Release index: "already names the newest GA … nothing rewritten"
release index v17 entry unchanged (current series: 17.7.0, released 2026-10-06); git diff HEAD -- content/docs/releases/index.mdx empty. Before this PR, the same replay wrote released 2026-10-07 (reproduced on PR #22084's head 52f7a509c6)
shape assertion pass 235 paths moved, 0 outside the reviewed surface. It was 236 before this PR; the difference is the release index
Validate the post-version tree 1 11 gates green: the 9 existing content gates except check:release-notes, plus the create-objectstack suite, check:template-manifests and the lockstep test. 3 gates red: check:release-notes, check:spec-changes, check:upgrade-guide. 1 unvalidated: protocol-version.ts
Restore the pre-version tree 0 37 pending changesets, tree clean

Control on an ordinary train. This branch without the two opening files is the 17.8.0 refresh #21988 gets today. Render exits 0 (194 paths, and the release index stamps 17.8.0, released 2026-10-07, version and date together). Validate exits 0 with all 14 gates green, holding 44 s. Restore exits 0.

Negative control for the template gate. On the boundary tree, specVersion in the blank manifest was set back to ^17.0.0. template-consistency.test.ts then exits 1 with 2 failed, and the file was restored by hash (0956082cfd8e both sides).

Baseline. main's own step, replayed on PR #22084's head, exits 1 at the old blanket refusal and names all 4 paths. This reproduces the measurement the card rests on.

Why protocol-version.ts is still refused (measured, not guessed)

Moving the protocol major at version time does more than move a constant:

  • Two derived artifacts go stale. On the boundary tree, check:spec-changes exits 1 ("spec-changes.json is stale") and check:upgrade-guide exits 1. Both pass on the pre-version tree (control). Regenerating them in the throwaway changes packages/spec/spec-changes.json by 6342 lines, and adds a 1024-line Protocol 17 → 18 section to docs/protocol-upgrade-guide.md. The version pass regenerates neither. Both gates run in the required TypeScript Type Check job, so a version PR let through as things stand would turn main red on its next ordinary PR.
  • The handshake refuses this repository's own example apps. assertProtocolCompat runs on the app load seam (packages/runtime/src/app-plugin.ts:421). Probed with the post-version constant, checkProtocolCompat gives ^17 → incompatible (OS_PROTOCOL_INCOMPATIBLE) and ^18 → ok. On the pre-version tree it is the reverse. examples/app-crm, app-showcase and app-todo, plus the two packages in app-multi-package, declare engines: { protocol: '^17' }. No gate this lane can afford boots them.

So the refusal stays, as Done-when 1's last clause provides ("The refusal stays for any major-only path still unvalidated"), with its message rewritten to say exactly this.

What still stops #21988 refreshing into 18.0.0-next.0

These are three decisions, set out with options and a recommendation in the card's os-dev-report. None of them is made here:

  1. How the protocol major moves at the opening. Either the version pass regenerates the two artifacts and restamps in-repo engines.protocol (the version chain grows), or an ordinary PR with CI moves the protocol major ahead of the version PR (the lockstep test's definition changes).

  2. check:release-notes is red at the boundary (it is already in this step). It counts ## 18.0.0-next.0 as "shipped a 18.x release" and asks for content/docs/releases/v18.mdx plus its meta.json entry. That content is release-owned, and this PR does not write it.

  3. Docs image pins during the next line. These were measured and not edited (Done-when 4). On the first prerelease, sync-docs-image-tags moves 9 pins from 17.7.0 to 18.0.0-next.0:

    • docker/README.md: 3 image tags and 1 build-arg;
    • content/docs/deployment/self-hosting.mdx: 3 image tags and 1 npm pin;
    • content/docs/upgrading.mdx: 1 image tag.

    Meanwhile npm view gives latest: 17.7.0 (and rc: 17.0.0-rc.6, no next yet) for @objectstack/cli, @objectstack/spec and create-objectstack. The ghcr latest tag does not move for a prerelease (docker-publish.yml:83).

Verification

  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at a4cbcfdb16): 50 commands, all exit 0, exit codes written to disk before any pipe. --ran: 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN. The battery pnpm check:pm-dispatch-gates passed 1976 cases in 997.8 s.
  • node scripts/sync-release-index-currency.mjs --self-test: exit 0, 42 cases.
  • Ablation, committed first. With ablation-replace.mjs, the pre-fix logic (const rewritten = …; return rewritten === field ? null : rewritten;) was put back in place of the fix. The self-test exits 1 with exactly 5 failures: battery B's inverted case and 4 of Control H's 7. H's three controls stay green, as they should. The file was restored with blob equal to HEAD and git diff HEAD empty. A first, cruder ablation that only deleted the guard line also reddened B and C. That mutation was too strong, so the faithful one above is the reading.
  • Not run locally: the whole spec suite (the step no longer needs it), and the repo-wide lint farm, which belongs to CI.

Acceptance notes (noted, not fixed here)

  • The comment above Create or update the "chore: version packages" PR in release.yml still says pnpm run version is FOUR rewriters. It is five (sync-release-index-currency.mjs joined).
  • cut-rc.yml says "On an RC cut this rewriter writes NOTHING". That was false for the date before this PR (an rc cut on a later day re-dated the entry) and is true after it. cut-rc.yml is not edited, by the card.
  • release.yml's step comment said the lockstep test was "reachable only through the whole @objectstack/spec suite". That is corrected in place, because it is the step this card owns.

Changeset: none. The diff touches .github/workflows/release.yml and scripts/sync-release-index-currency.mjs, and neither is in any package's files[].

Commits carry this repository's model-free trailer pair (AGENTS.md).


Generated by Claude Code

claude added 3 commits October 7, 2026 14:22
…e release index

sync-release-index-currency stamped today's date into a current-series field
that already named the newest GA, so every pre-mode cut (18.0.0-next.N, and
X.Y.Z-rc.N on the RC lane) moved the v17 entry's release date to the cut's day.
The gate judges the version only, and the date belongs to the version commit
that moves the field; a same-version field is now left alone. Battery B's
pinned re-date case is inverted and Control H pins the prerelease cut.

Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw
Co-authored-by: Claude <noreply@anthropic.com>
…or boundary instead of refusing them

Validate the post-version tree refused all four major-boundary paths for want
of a gate. The blank template's three stamped files are now judged by gates
the repo already runs (create-objectstack's suite, which renders the template
and ratchets its stamps against the scaffolder's new major, and
check:template-manifests). protocol-version.ts gets its lockstep test (one
file, 5 s) and the two artifacts derived from PROTOCOL_MAJOR (check:spec-changes,
check:upgrade-guide), and stays refused: moving the protocol major makes the
handshake refuse the example apps' engines.protocol, which no gate on this
lane boots. The refusal is collected, so one boundary run names every verdict.

Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37654789143 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • (没拿到 job 级信息,点上面的 run 链接看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 1 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd 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.

2 participants