Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[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

Closed

Description

@os-support-ai

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 fixed group, so a single major entry 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

  1. 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 at a31ea2619.
  2. scripts/__tests__/check-changeset-no-major.test.ts asserts the repository itself carries no pending major changeset — 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)

  1. Land the major as 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 @objectstack alignment.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    CollaboratorAuthor

    Maintainer ruling recorded — maintainer, 2026-08-18, verbatim: 「接受你的建议」. Provenance: said directly to the repo:objectui PM seat in session session_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:

    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

  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    CollaboratorAuthor

    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) declared minor for a comparable break and will pass the same gate cleanly.

    PR #5207 (#5064) PR #5209 (#4314)
    The break re-keys a published export (11 keys → 8) retires a published authorable key (ActionSchema.confirm)
    Measured consumers zero zero producers
    Changeset label major minor, breaking semantics spelled out in the body
    Changeset Bump Policy red, parked expected green

    Both are honest actors. #5207's dev had an explicit maintainer authorisation for a major exception, 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 (flagged minor per this repo's version-alignment policy)", and the retirement is a ?: never tombstone 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 as minor.

    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:

    Every breaking change in this repo is already labelled non-breaking, with the break recorded only in changeset prose. Is that the intended contract, or an artifact of a gate that has no exception path?

    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

  3. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    CollaboratorAuthor

    ⚠️ I am revising my own recommendation. New measured evidence — the gate itself prescribes the answer, and this card was built on my not having read it.

    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:

    AssertionError: A majorpublishes all 39 fixed-group packages as the next major, off the @objectstack major objectui is pinned to. **Score itminor and describe the break in the body** — see AGENTS.md §版本号策略.

    + ["@object-ui/plugin-dashboard at .changeset/dashboard-components-rekey-5064.md:2"]

    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:

    • Option 2 was mischaracterised by me. I described it as "split the label from the break… costs the property that the changeset is the honest record". That framing was wrong: in this repo the changeset body is the designated place to record a break, by documented policy. The ledger is not being degraded; I had simply not read the policy it implements.
    • Option 3 (restructure the fixed group) is not warranted. The fixed group is a deliberate design pinning objectui's major to @objectstack's — not an accident to be worked around.
    • Option 4 (hold) — my recommendation, and the one ruled — is now the wrong call. It parks a finished, zero-consumer fix waiting on a question the repo answered before either of us asked it.

    Revised recommendation: rescore .changeset/dashboard-components-rekey-5064.md from major to minor, 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. The major was 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:

    • ⛔ Not rescoring on my own. A seat clearing its own red gate by relabelling a changeset is indistinguishable from the failure mode the gate exists to defeat, and I said so on this card before I had a motive to. That reasoning does not get weaker now that relabelling happens to be correct.
    • ⛔ PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 stays draft and unqueued until the maintainer says which way.
    • The maintainer's authorisation of a major exception 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

  4. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    CollaboratorAuthor

    ⚠️ 回翻:维护者已裁 option 1,但本卡的选项是我框错的 —— 停手,重新请裁

    维护者 2026-08-19 就本卡裁定:「照实 major 落地,版本时再处理」(option 1)。

    ⛔ 我没有执行,因为落地前去读门禁本体时,发现本卡四个选项建立在一个被仓内明文约定证伪的前提上。这是我的错误,不是维护者的。

    被证伪的前提

    本卡(以及 PR #5207 的正文、以及我在座位贴里的转述)一路假设:major 是这次破坏性变更的诚实标签,于是「降成非 major」就成了拿诚实换方便。

    AGENTS.md §版本号策略 说的正相反 —— 逐字:

    推论:changeset 里不要声明 major —— fixed 组任一 major 都会把全组推上去、脱离 objectstack 的节奏(如 17.x 期间被推到 18)。objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major 的那一次同步升级。

    同节还写着:

    objectui 的 major 不代表「它自身 API 的破坏性变更次数」。

    scripts/check-changeset-no-major.mjs 的头注独立复述同一条,并把逃生口的适用面钉死:

    The one legitimate major is the synchronized bump that follows objectstack across ITS major. Set OBJECTUI_ALLOW_MAJOR=1 for that release, and only then.

    结论:诚实的标签是 minor,不是 major

    在这个仓里,objectui 自身的破坏性变更本来就该标 minor,把 breaking 语义写进 changeset 正文。所以:

    • 我在本卡把 option 2/3(降成非 major)描述成「让 changeset 台账不再是 break 的真实记录」「与绑定条件 (b) 冲突」—— 这个描述是错的。按约定,minor + 正文写清 breaking 就是诚实记录的形态,不是妥协。
    • 反过来,option 1(照实 major)并不保全诚实,它违反的是一条明文约定,代价是把 39 个包推上 18.0.0、脱离 @objectstack 17.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 的那一次发版,且仅那一次」,这里用它就是拿逃生口用在它明说不适用的场景。

    修正后的选项(请重裁)

    1. 把 changeset 改成 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,那是条件写错了,应随本卡更正。
    2. 维持 major,由维护者带红人工合并 —— 明知违反 §版本号策略,并接受 39 包被推上 18.0.0 与 @objectstack 17.x 失配。若确认要这样,请直接合并 PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207,⛔ 我不会代做。
    3. 先改 §版本号策略本身(例如给 fixed 组开每包 major 的逃生口),再谈这张卡 —— 结构性改动,影响今后每一次 break 的记账,远超本卡。

    推荐 1。 它同时满足了原裁定真正想要的东西(诚实记录 break)和仓内约定,而且是唯一不需要例外、不需要人工带红合并的路径。

    ⛔ 本卡维持 needs-user-decision,PR #5207 维持 draft 未入队。本席仍不以任何路径清那两条红。


    Generated by Claude Code

  5. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    CollaboratorAuthor

    ✅ 维护者裁定(2026-08-19),本卡了结

    维护者原话:

    objectui ui 的 major 是要跟着 objectstack 走的,不可以自己发 major

    这确认了 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 = 拿诚实换方便」。这个框架是反的,而且它把维护者的裁决往错误方向引了一轮。正确的读法从一开始就写在 §版本号策略 里:

    objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)

    立卡前我没读那一节,只读了 dev 报告里对门禁的转述。门禁的存在本身就是那条约定的机械化,而我把它当成了需要被豁免的阻碍。

    后续动作

    1. PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 的 changeset 改 major → minor,breaking 语义写进正文(代码一行不改)。已派 dev 执行。
    2. plugin-dashboard 的 dashboardComponents 用组件类名作键(全仓唯一一个),README 教的手动注册循环因此注册 11 个没人写的 type、8 个真 type 一个没注册 #5064 的绑定条件 (b) 写着「scores @object-ui/plugin-dashboard major」—— 那条是错的,随本裁定更正为 minor。
    3. 两条红随之转绿,PR fix(plugin-dashboard)!: re-key dashboardComponents to the 8 registered schema types #5207 正常入队,无需任何例外、无需人工带红合并。
    4. 连带解锁: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: ObjectMetricWidgetProps still 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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions