docs(changeset): the IANA time-zone columns changeset declares its accept-set narrowing as BREAKING - #16430
Conversation
…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
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 34069423383 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
The queue failure is not this PR's — measured, not asserted. Re-queued once; the root cause is carded.
What failedQueue build 34069423383 → Temporal Conformance (live PG + MySQL) → Dequeued at Why it is not this PR's, by content
The triage bot's own rule is the one that applies: 「超时( ⭐ 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 And that test has no timeout of its own:
⭐ Firing control — six packages in this repo do set one, and the sharpest is the other live-database driver:
⇒ the one driver package whose suite talks to two live servers, with a What this PR still isThe changeset it corrects is on Generated by Claude Code |
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 undercontent/docs/releases/.What was wrong (measured on
origin/mainat 0a038cc)#16296 declared
valueDomain: 'iana_time_zone'onsys_job.timezoneandsys_report_schedule.timezone. That is an accept-set narrowing: values the shippedbuild 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-asteriskBREAKINGtoken = 0,
NON-BREAKING ADDITION= 1,adr-0087marker = 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.mjsclassifies only from the author's own declaration — a case-insensitive match for the
two-asterisk
BREAKINGtoken on the changeset body — so it never matched and the gatereported 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 tominorand states it in its own words:
As it stood, this would have shipped to consumers as a
CHANGELOG.mdtelling anupgrading author the release cannot break them, immediately before a write that worked
yesterday is refused.
The three edits
opens, directly under the summary line, with a
**BREAKING**banner in the shape ofthe in-repo precedent (
.changeset/core-plugin-type-closed-set.mdatd8024f0):the narrowing is named as such and stated to ship as
minorunder the launch-windowconvention, with the banner and the disposition named as the carriers. The bump is
unchanged at
'@objectstack/platform-objects': minor—majoris refused repo-wideduring the window.
unchanged —
not-required (no-migration-prescription)— and it is still the honest onenow that the changeset declares breaking:
valueDomainis checked on the written valueonly, so
objectstack migrate metahas nothing to rewrite and there is no tombstone tomint. Verified mechanically as well as by reading: the marker count on the file is 1
(the gate refuses two), the gate's own
readDispositionparses it, and its ownfindMigrationPrescriptionreturnsnullon the new body — so the exemption is notcontradicted by a prescription the body accidentally carries. The disposition's own
text no longer opens with the words that caused the miss.
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,UTCincluded —membership is the
Intl.DateTimeFormatprobe, deliberately not theIntl.supportedValuesOf('timeZone')enumeration that omitsUTC, and'UTC'issys_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/maxLengthtransition-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.nextRunAtfinding, the
sys_job-is-written-never-read finding, and the pinned "deliberately NOTconverged" 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 thegates' own output. Run in the worktree at
e09cbfe7c, merge base0a038cc06.The gate that had to change,
node scripts/check-adr-0087-registration.mjs:✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen).✓ 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
majorbump." It also printsℹ️ LEVEL AXIS: NOT MEASURED — no clause-② declaration was readable for this PR, because nopull_requestpayload existslocally; CI reads that payload and judges the axis there.
Direct classification with the gate's own exported functions, before and after:
breakingDeclarationwent frombreaking=false signals=[]tobreaking=true signals=["BREAKING"];findMigrationPrescriptionisnullin both.The rest of this card's derived gate family.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderives 17 runnable families from the committed changeset (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.mjsandcheck-comment-mask-corpus.mjs. Four of them firstreturned
PREREQUISITE NOT METin the fresh worktree (missingyaml,typescript,@typescript-eslint/parser) — read as NOT MEASURED, not as green — and were re-run to areal 0 after
pnpm install.node scripts/check-changeset-fixed.mjs, which the derivationflags 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 lintwas narrowed, and the narrowing is measured rather than asserted. Therepo-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
filesentry ineslint.config.mjsnames JS/TS extensions and none names Markdown. (b) The count comesfrom
--format json: linting the changed file directly yields one result witherrorCount: 0and the single messageFile ignored because no matching configuration was supplied— zero rules applied, zero files linted. (c) The diff moves no.ts, no ESLintconfig and no input to a type-aware program, so no untouched file's verdict can move.
Why this PR carries
skip-changesetrather than a changeset of its ownIt 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 Changesetjob counts--diff-filter=Aunder.changeset/*.md, and thisPR 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