Repository navigation
[Decision] The authorized one-PR major exception moves ALL 39 packages a major, because they share one changeset fixed group — how should the exception be realized? #5208
Description
Filed by the repo:objectui PM seat (session session_01RV6yuVCxymHYE16PL9vQkE) while reviewing PR #5207 (#5064). Filed separately on purpose so #5064 stays in flight rather than bouncing back to the decision box — its implementation is faithful to the ruling; this is about how the already-granted exception gets realized.
What was ruled, and what it turns out to cost
On 2026-08-18 the maintainer ruled Route A on #5064 and authorized a one-PR exception to check-changeset-no-major, rationale on record: two independent word-boundary greps measured zero consumers, so the breaking set is empty today and only grows, and a breaking changeset is the honest label.
Implementing it surfaced a consequence I do not believe was in view when the exception was granted. From check-changeset-no-major.mjs's own header:
every publishable package sits in ONE
fixedgroup, so a singlemajorentry carries all 39 packages to the next major
So the honest major on one plugin's re-key does not cost that plugin a major. It moves the entire fleet's version line, and the @objectstack alignment convention — same major ⇒ compatible — stops holding along with it.
The re-key's own blast radius is measured at zero consumers. The changeset accounting's blast radius is 39 packages plus the cross-repo version convention. Those are wildly different numbers attached to the same decision, and only the first one was on the table when the exception was granted.
The gate is red in two places, both this same exception
Changeset Bump Policy(scripts/check-changeset-no-major.mjs) — failed on PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 ata31ea2619.scripts/__tests__/check-changeset-no-major.test.tsasserts the repository itself carries no pendingmajorchangeset — so a unit-test shard is expected red too.
The dev correctly refused to downgrade the changeset or weaken either limb, and the PR is held draft, unqueued.
Options (the maintainer's call — release shape is never AI's)
- Land the
majoras labelled, and handle it at version time — e.g. the release escape the gate documents (OBJECTUI_ALLOW_MAJOR=1), or rescoring when the release is cut. Honest label preserved; the fleet-wide major becomes a release-time decision rather than a merge-time one. Needs an answer for what the 39-package major does to the@objectstackalignment. - Split the label from the break — land the code with a non-major changeset plus an explicit recorded note that the break is real and deferred. Keeps the version line still; costs the property that the changeset is the honest record, and contradicts binding condition (b) as written.
- Give the fixed group an escape hatch — a per-package major, or removing this package from the fixed group. Structural, and the widest of the three; it changes how every future break is accounted for, not just this one.
- Hold — keep PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 draft until the release line question is settled independently. Costs nothing except the card sitting; the defect it fixes has zero measured consumers, so there is no urgency pressing against it.
Four-prism block
- Platform long-term coherence — option 2 makes the changeset ledger stop being the truth about breaks, which is the one property the whole release process rests on. Option 1 keeps it true and moves the cost to a place a human already has to stand (release is manual by standing ruling). Option 3 is the only one that removes the tension permanently, and the only one with consequences beyond this PR.
- Measured business pull — zero. Two independent greps found no consumer of
dashboardComponents; the card's user-visible harm is a README teaching a registration loop that registers 11 phantom types. Nothing is waiting on this, which argues against forcing a fleet-wide major to ship it quickly, and in favour of 1 or 4. - AI-agent error-resistance — whatever is chosen, ⛔ it must not be "an agent may rescore a changeset when a gate blocks it". That is precisely the incentive the no-major gate exists to defeat, and a seat clearing its own red gate by relabelling is indistinguishable from the failure mode. If option 2 is chosen it should be an explicit maintainer act on this PR, not a standing permission.
- Startup scope discipline — option 3 is real infrastructure work for a one-off. Option 1 or 4 costs nothing structural. Recommend deciding this one narrowly and revisiting the fixed group only when a second genuine break arrives.
Recommendation
Option 1 or 4, and I lean 4 on the numbers: the fix has zero measured consumers and nothing waiting on it, so there is no reason to spend a fleet-wide major — or an accounting compromise — to land it today. Holding costs a draft PR sitting; option 2 costs the honesty of the changeset ledger permanently, and it is the one I would want an explicit maintainer act on rather than a PM inference.
Whichever way it goes, ⛔ I will not clear the red gate myself by any route, and PR #5207 stays draft and unqueued until this is answered.
Activity
os-support-ai commentedon Aug 18, 2026
Maintainer ruling recorded — maintainer, 2026-08-18, verbatim: 「接受你的建议」. Provenance: said directly to the
repo:objectuiPM seat in sessionsession_01RV6yuVCxymHYE16PL9vQkE, in response to this card.Ruled: option 4 — hold. That was the recommendation this card made ("Option 1 or 4, and I lean 4 on the numbers"), so accepting the recommendation is accepting the lean.
What that means concretely, effective now:
draft, unqueued, no auto-merge. ⛔ The redChangeset Bump Policygate is not to be cleared — not by rescoring the changeset, not by touching the gate or its unit test. It stays red, honestly, as the record that this PR carries a real break.majorchangeset stays as written. Binding condition (b) from the original ruling holds: the label is honest and remains so.dashboardComponents用组件类名作键(全仓唯一一个),README 教的手动注册循环因此注册 11 个没人写的 type、8 个真 type 一个没注册 #5064 stays claimed and in flight on this seat; the work is complete and reviewable, it is the landing that is parked. It is not returning to the decision box.@objectstackalignment convention — is still open. It is now parked, not pending an answer.Reversal is cheap and that is the point of choosing this option: if the release-line question is later settled in favour of option 1, this PR flips out of draft and into the queue with no rework — the diff and the changeset are already in their final, honest shape. Nothing decays while it waits, and the defect it fixes has zero measured consumers, so nothing is being kept from anyone.
Exit condition, so this cannot be parked silently forever: the next time a second genuine breaking change needs a changeset in this repo, this question stops being deferrable and must be answered then — at that point option 3 (per-package major, or removing a package from the fixed group) becomes worth its cost, because it would no longer be infrastructure for a one-off. Recorded here so whoever meets that second break finds this analysis rather than re-deriving it.
If the maintainer meant option 1 — land it and handle the major at version time — say so and I will flip #5207 to ready; the only thing that changes is who clears the gate and when.
Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
New evidence, measured this round — two breaking changes landed opposite labels within minutes of each other. This sharpens the question rather than changing the ruling; the hold stands.
While PR #5207 sits parked for declaring
major, PR #5209 (#4314) declaredminorfor a comparable break and will pass the same gate cleanly.ActionSchema.confirm)majorminor, breaking semantics spelled out in the bodyChangeset Bump PolicyBoth are honest actors. #5207's dev had an explicit maintainer authorisation for a
majorexception, so it took it. #5209's dev had none, so it complied with the standing no-major policy and recorded the break in prose instead — its changeset body says plainly "Breaking semantics (flaggedminorper this repo's version-alignment policy)", and the retirement is a?: nevertombstone plus a Zod refusal, i.e. authoring the key is now a compile error and a parse rejection. That is a real break by any definition, shipping asminor.Why this matters to the decision on this card: it means option 2 — "split the label from the break, record it in prose" — is not a hypothetical compromise being proposed here. It is already the repo's de facto practice, because the no-major gate leaves no other compliant option. So the honest framing of this decision is not "should we start doing that", it is:
And the corollary that decides how urgent this is: the changeset ledger is currently not a reliable record of breaks. A consumer diffing versions cannot distinguish #5209's break from an ordinary feature, because both read
minor. #5207 is the anomaly only because it had an authorisation to be honest.⛔ No action taken on either PR from this observation. #5209 complies with the standing policy and I am not inventing a rule to block it — that would punish a dev for following the gate. #5207 stays parked per the 「接受你的建议」 ruling.
Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
The failing assertion on PR #5207 (shard 3/4,
1 failed | 5386 passed,scripts/__tests__/check-changeset-no-major.test.ts:117) does not just fail. It states the policy and the remedy verbatim:So the fleet-wide-major consequence I raised as a discovery is already known, already documented in AGENTS.md §版本号策略, and already has a prescribed convention: score
minor, describe the break in the body. That is precisely what PR #5209 did an hour later, unprompted — it is not an ad-hoc compromise by that dev, it is the house rule.What this does to the four options:
@objectstack's — not an accident to be worked around.Revised recommendation: rescore
.changeset/dashboard-components-rekey-5064.mdfrommajortominor, describe the break in the changeset body exactly as PR #5209 does, and land it.On binding condition (b) — "changeset marks the re-key as breaking". I read that as satisfied, not violated, by this route: the condition's purpose is that the break be honestly recorded, and
minor+ an explicit breaking-semantics section in the body is this repo's prescribed way of recording exactly that. Themajorwas one reading of "mark it breaking"; AGENTS.md prescribes the other. ⛔ But I am not treating my reading of a maintainer condition as authority to act — see below.What I am doing and not doing:
majorexception remains available — this is a recommendation that it is no longer needed, not a claim it was wrong to grant.One line from the maintainer settles it: "rescore to minor" and I will have the dev do it and land the PR, or "keep the major" and it stays parked pending the release-line question.
Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
维护者 2026-08-19 就本卡裁定:「照实 major 落地,版本时再处理」(option 1)。
⛔ 我没有执行,因为落地前去读门禁本体时,发现本卡四个选项建立在一个被仓内明文约定证伪的前提上。这是我的错误,不是维护者的。
被证伪的前提
本卡(以及 PR #5207 的正文、以及我在座位贴里的转述)一路假设:
major是这次破坏性变更的诚实标签,于是「降成非 major」就成了拿诚实换方便。AGENTS.md§版本号策略 说的正相反 —— 逐字:同节还写着:
scripts/check-changeset-no-major.mjs的头注独立复述同一条,并把逃生口的适用面钉死:结论:诚实的标签是
minor,不是major在这个仓里,objectui 自身的破坏性变更本来就该标
minor,把 breaking 语义写进 changeset 正文。所以:minor+ 正文写清 breaking 就是诚实记录的形态,不是妥协。@objectstack17.x 的对齐 —— 而「同 major ⇒ 兼容」正是那条约定要保的东西。check-changeset-no-major的存在理由,就是拦住这次这种 changeset;shard 3/4 的红也确认为同一断言(失败栈scripts/__tests__/check-changeset-no-major.test.ts:117),不是别的回归。另外,option 1 我也执行不了
即便照裁定执行,落地只有两条路,两条都不是 PM 能走的:① 带红人工合并 —— 需要维护者的动作;② 设
OBJECTUI_ALLOW_MAJOR=1—— 门禁头注明确把它保留给「跟随 objectstack 跨 major 的那一次发版,且仅那一次」,这里用它就是拿逃生口用在它明说不适用的场景。修正后的选项(请重裁)
minor,breaking 语义写进正文 —— 约定合规,推荐。plugin-dashboard 的dashboardComponents用组件类名作键(全仓唯一一个),README 教的手动注册循环因此注册 11 个没人写的 type、8 个真 type 一个没注册 #5064 的 PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 随即转绿、可正常入队,不需要任何例外、不动任何门禁。绑定条件 (b) 若字面要求major,那是条件写错了,应随本卡更正。major,由维护者带红人工合并 —— 明知违反 §版本号策略,并接受 39 包被推上 18.0.0 与@objectstack17.x 失配。若确认要这样,请直接合并 PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207,⛔ 我不会代做。推荐 1。 它同时满足了原裁定真正想要的东西(诚实记录 break)和仓内约定,而且是唯一不需要例外、不需要人工带红合并的路径。
⛔ 本卡维持
needs-user-decision,PR #5207 维持 draft 未入队。本席仍不以任何路径清那两条红。Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
✅ 维护者裁定(2026-08-19),本卡了结
维护者原话:
这确认了
AGENTS.md§版本号策略 与check-changeset-no-major.mjs头注写的同一条,并取代了本卡此前基于我错误选项框架收到的 option 1(「照实 major 落地」)。结论
没有例外可用。 本卡问的是「那次已授权的一次性
major例外该怎么落地」—— 答案是:它不该落地。objectui 的 major 是跟随@objectstack的从属量,不是本仓破坏性变更的计数,所以:major从来不是这次改动的诚实标签。 诚实标签是minor,把 breaking 语义写进 changeset 正文 —— 这正是 §版本号策略 规定的形态。Changeset Bump Policy+check-changeset-no-major.test.ts:117的仓库状态断言)是门禁在准确履职,不是待绕过的障碍。OBJECTUI_ALLOW_MAJOR=1仍然只保留给跟随 objectstack 跨 major 的那一次同步发版,且仅那一次。我的错误,记在这里
我在本卡把选项框成「照实
major= 保住诚实 / 降成minor= 拿诚实换方便」。这个框架是反的,而且它把维护者的裁决往错误方向引了一轮。正确的读法从一开始就写在 §版本号策略 里:立卡前我没读那一节,只读了 dev 报告里对门禁的转述。门禁的存在本身就是那条约定的机械化,而我把它当成了需要被豁免的阻碍。
后续动作
major→minor,breaking 语义写进正文(代码一行不改)。已派 dev 执行。dashboardComponents用组件类名作键(全仓唯一一个),README 教的手动注册循环因此注册 11 个没人写的 type、8 个真 type 一个没注册 #5064 的绑定条件 (b) 写着「scores@object-ui/plugin-dashboardmajor」—— 那条是错的,随本裁定更正为minor。packages/plugin-dashboard被这张停放的 draft 整包占着,plugin-dashboard: DatasetWidget.test.tsx escapes to the real network on the dimension-metadata probe (same root cause as #5225) #5280(单测网络逃逸,与 plugin-report suite escapes to the real network on 127.0.0.1:3000 (ECONNREFUSED noise, same class as #3339 / #4106 / #4688) #5225 同根)与 plugin-dashboard:ObjectMetricWidgetPropsstill declares the RETIRED{ key, defaultValue }label form — a TS consumer writing the declared shape renders the raw dotted i18n key as the visible label #5264(KPI 卡把裸点分 i18n 键当可见标签)都在排它后面。本卡关闭为 completed。
Generated by Claude Code