Skip to content

docs(changeset): the IANA time-zone columns changeset declares its accept-set narrowing as BREAKING - #16430

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/fix-iana-timezone-changeset-breaking-banner
Sep 7, 2026
Merged

docs(changeset): the IANA time-zone columns changeset declares its accept-set narrowing as BREAKING#16430
zhuangjianguo merged 1 commit into
mainfrom
claude/fix-iana-timezone-changeset-breaking-banner

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Part of #15872. This PR corrects the changeset that PR #16296 landed —
.changeset/platform-iana-timezone-columns.md — before the next release consumes it.
Text only: one changeset body, no .ts, no docs, no other changeset, nothing under
content/docs/releases/.

What was wrong (measured on origin/main at 0a038cc)

#16296 declared valueDomain: 'iana_time_zone' on sys_job.timezone and
sys_report_schedule.timezone. That is an accept-set narrowing: values the shipped
build accepted — UTC+8, China Standard Time, Mars/Olympus — are refused after it.
Its changeset nevertheless described itself as "A NON-BREAKING ADDITION" and carried
zero **BREAKING** banners. Readings on that file at 0a038cc: two-asterisk BREAKING
token = 0, NON-BREAKING ADDITION = 1, adr-0087 marker = 1.

Root cause of the miss. The file's only occurrence of the token sat inside the words
"A NON-BREAKING ADDITION", which has no ** prefix. check-adr-0087-registration.mjs
classifies only from the author's own declaration — a case-insensitive match for the
two-asterisk BREAKING token on the changeset body — so it never matched and the gate
reported the PR non-breaking. The file said "non-breaking" in prose and was classified
non-breaking by machine, for the same reason.

That matters because during the launch window the bump level is not the carrier.
scripts/check-changeset-no-major.mjs (lines 55-66) pushes breaking changes to minor
and states it in its own words:

Until then it is NOT the carrier … The mandatory information carriers for
breaking-ness in the meantime are the **BREAKING** banner the author writes in the
changeset body and the ADR-0087 migration-ledger disposition
… They are not
documentation niceties — during the window they are the only signal there is.

As it stood, this would have shipped to consumers as a CHANGELOG.md telling an
upgrading author the release cannot break them, immediately before a write that worked
yesterday is refused.

The three edits

  1. The claim is replaced by a banner. "A NON-BREAKING ADDITION" is gone; the body now
    opens, directly under the summary line, with a **BREAKING** banner in the shape of
    the in-repo precedent (.changeset/core-plugin-type-closed-set.md at d8024f0):
    the narrowing is named as such and stated to ship as minor under the launch-window
    convention, with the banner and the disposition named as the carriers. The bump is
    unchanged at '@objectstack/platform-objects': minormajor is refused repo-wide
    during the window.
  2. Exactly one ADR-0087 disposition, re-verified rather than assumed. The category is
    unchanged — not-required (no-migration-prescription) — and it is still the honest one
    now that the changeset declares breaking: valueDomain is checked on the written value
    only, so objectstack migrate meta has nothing to rewrite and there is no tombstone to
    mint. Verified mechanically as well as by reading: the marker count on the file is 1
    (the gate refuses two), the gate's own readDisposition parses it, and its own
    findMigrationPrescription returns null on the new body — so the exemption is not
    contradicted by a prescription the body accidentally carries. The disposition's own
    text no longer opens with the words that caused the miss.
  3. The consumer delta is stated. Three classes of spelling stop being accepted:
    UTC-offset forms (UTC+8, GMT+0800, +08:00), Windows/CLDR display names
    (China Standard Time), and shape-valid identifiers for zones that do not exist
    (Mars/Olympus). Every genuine IANA identifier keeps working, UTC included —
    membership is the Intl.DateTimeFormat probe, deliberately not the
    Intl.supportedValuesOf('timeZone') enumeration that omits UTC, and 'UTC' is
    sys_report_schedule.timezone's own declared default. And stored rows are unaffected,
    quoted from the published contract at packages/spec/src/data/field.zod.ts:
    "Checked on the WRITTEN value only (the min/max/maxLength transition-gate class):
    a stored value outside a domain declared later is never re-read and survives unrelated
    edits — only a write carrying a non-member is refused, with the field error code
    value_domain."

The measured analysis #16296's changeset already carried — the ReportService.nextRunAt
finding, the sys_job-is-written-never-read finding, and the pinned "deliberately NOT
converged" section — is kept verbatim. Only the defective claim, the disposition text and
the missing consumer delta changed.

Gates — exit codes and the lines they printed

Exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?), verdicts quoted from the
gates' own output. Run in the worktree at e09cbfe7c, merge base 0a038cc06.

The gate that had to change, node scripts/check-adr-0087-registration.mjs:

  • BEFORE (same command, changeset unmodified) — exit 0:
    ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen).
  • AFTER — exit 0:
    ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.
    .changeset/platform-iana-timezone-columns.md [BREAKING] not-required (no-migration-prescription)

node scripts/check-changeset-no-major.mjs — exit 0:
"✓ This diff introduces no major bump." It also prints ℹ️ LEVEL AXIS: NOT MEASURED — no clause-② declaration was readable for this PR, because no pull_request payload exists
locally; CI reads that payload and judges the axis there.

Direct classification with the gate's own exported functions, before and after:
breakingDeclaration went from breaking=false signals=[] to breaking=true signals=["BREAKING"]; findMigrationPrescription is null in both.

The rest of this card's derived gate family. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derives 17 runnable families from the committed change
set (10 by path, 7 declared whole-tree). All 17 ran and all 17 exit 0, including
check:changeset-gate-self-tests, check:objectui-changeset, check:published-files,
check-empty-changeset.mjs (both modes), both gates' --self-test,
release-rehearsal-clone.mjs --self-test, check:nul-bytes,
check-closing-keyword-parity.mjs and check-comment-mask-corpus.mjs. Four of them first
returned PREREQUISITE NOT MET in the fresh worktree (missing yaml, typescript,
@typescript-eslint/parser) — read as NOT MEASURED, not as green — and were re-run to a
real 0 after pnpm install. node scripts/check-changeset-fixed.mjs, which the derivation
flags because its roster sits under .changeset, was run too: exit 0,
✓ .changeset/config.json "fixed" group is in sync with 69 public workspace packages.

pnpm lint was narrowed, and the narrowing is measured rather than asserted. The
repo-wide ESLint sweep is CI's run; locally it was scoped to the one changed file. (a) The
population comes from ESLint's own config, not from a guess: every files entry in
eslint.config.mjs names JS/TS extensions and none names Markdown. (b) The count comes
from --format json: linting the changed file directly yields one result with
errorCount: 0 and the single message File ignored because no matching configuration was supplied — zero rules applied, zero files linted. (c) The diff moves no .ts, no ESLint
config and no input to a type-aware program, so no untouched file's verdict can move.

Why this PR carries skip-changeset rather than a changeset of its own

It declares no release of its own — it publishes nothing from any package and changes no
package's behaviour; it corrects the prose of a changeset that is already pending. Adding a
second changeset would mint an extra CHANGELOG line for a text repair to another CHANGELOG
line. The Check Changeset job counts --diff-filter=A under .changeset/*.md, and this
PR adds no file there, so the label is the accurate declaration, not a workaround.

Draft on purpose: this is a release-facing text correction and the release lane is the
maintainer's.


Generated by Claude Code

…cept-set narrowing as BREAKING

`.changeset/platform-iana-timezone-columns.md` described an accept-set
narrowing as "A NON-BREAKING ADDITION" and carried no `**BREAKING**` banner.
The token's only occurrence sat inside those words, with no `**` prefix, so
`check-adr-0087-registration.mjs` (which classifies from the author's own
declaration, `/\*\*BREAKING/i` on the body) read the changeset as
non-breaking — prose and machine agreeing for the same reason.

During the launch window the bump level is not the carrier of breaking-ness
(`check-changeset-no-major.mjs` pushes breaking changes to `minor`), so the
banner and the ADR-0087 disposition are the only signals there are. As it
stood this would have shipped a CHANGELOG telling an upgrading author the
release could not break them, immediately before a write that worked
yesterday is refused.

The body now carries the `**BREAKING**` banner in the shape of the in-repo
precedent, keeps the bump at `minor`, keeps exactly one ADR-0087 disposition
(`not-required (no-migration-prescription)`, still the honest one: the domain
is checked on the written value only, so no consumer has a metadata rewrite
to perform), and states the consumer delta — which spellings stop being
accepted, that every genuine IANA identifier including `UTC` keeps working,
and that stored rows are unaffected, quoted from the published contract text
in `packages/spec/src/data/field.zod.ts`.

Text only: no `.ts`, no docs, no other changeset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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

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

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

  • Temporal Conformance (live PG + MySQL) — 失败步骤: Run driver-sql suite against both live servers

    × §2 a millisecond-precision delta cursor does not SKIP the updated row 5004ms
    FAIL  src/sql-driver-11224-update-stamp-precision.test.ts > #11224 — the UPDATE door stamps at the audit column's precision (live mysql) > §2 a millisecond-precision delta cursor does not SKIP the upd
      ↳ 失败原因: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时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,按失败测试文件聚合):

  • src/sql-driver-11224-update-stamp-precision.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 7, 2026
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 7, 2026

zhuangjianguo commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

The queue failure is not this PR's — measured, not asserted. Re-queued once; the root cause is carded.

domain:engine dispatching seat, session session_01ARYe3yQTQCUFm5qPYNgKaJ, 2026-09-07T01:0xZ.

What failed

Queue build 34069423383Temporal Conformance (live PG + MySQL)src/sql-driver-11224-update-stamp-precision.test.ts §2, on the live MySQL cell:

× §2 a millisecond-precision delta cursor does not SKIP the updated row 5004ms
  ↳ Error: Test timed out in 5000ms.

Dequeued at 00:45:57Z, reason CI_FAILURE.

Why it is not this PR's, by content

question reading
files this PR changes 1.changeset/platform-iana-timezone-columns.md, +46 / −1 (merge base 0a038cc06)
of those, under packages/drivers/driver-sql/ 0
failure kind timeout, not AssertionError

The triage bot's own rule is the one that applies: 「超时Test timed out in …)多半是负载/时序,不是本 PR 的回归」. A changeset body edit cannot slow a live MySQL round-trip. ⇒ Re-queued once — added_to_merge_queue@2026-09-07T00:49:01Z — which is the single legitimate re-run for this class, and it is now spent.

⭐ The root cause, and it is not a flaky assertion — the budget was never sized for a live server

§2 has nothing to de-race: it deliberately does no sleeping (「No sleep and no backdating」, the file's own §1 note), because the same-millisecond path is exactly what it measures. What it does do is 4 × ROUNDS = 24 live round-trips (createreadAuditupdate → the server-side cursor comparison, six times) inside one test.

And that test has no timeout of its own:

probe reading
explicit per-test timeouts in that file 0
explicit timeouts across 20 sibling driver-sql live-cell suites 0
testTimeout in packages/drivers/driver-sql/vitest.config.ts absent ⇒ vitest's default 5000 ms

Firing control — six packages in this repo do set one, and the sharpest is the other live-database driver:

package testTimeout
packages/drivers/driver-mongodb 30_000
packages/services/service-datasource 60_000
packages/spec 60_000
packages/qa/http-conformance 30_000
packages/plugins/plugin-auth 10_000
packages/metadata-fs 10_000
packages/drivers/driver-sql — none —

⇒ the one driver package whose suite talks to two live servers, with a globalSetup that provisions a schema (Postgres) and a database (MySQL) per file, is the only live-DB driver with no budget at all — while its nearest sibling has 30 s. That is the defect, and it is filed as #16434. ⛔ Not fixed here: this PR is a changeset-body correction and a test-config change would be an unrelated rider on it.

What this PR still is

The changeset it corrects is on origin/main right now reading **BREAKING = 0 and NON-BREAKING ADDITION = 1 (control: the file is present = 1, origin/main ac76425f0). A changeset is consumed at release, so this stays time-sensitive until it lands.


Generated by Claude Code

Merged via the queue into main with commit 9ac5eee Sep 7, 2026
31 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/fix-iana-timezone-changeset-breaking-banner branch September 7, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants