From e09cbfe7c0f97fcbe22a94681a5a9a04bbcdd8f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 23:29:52 +0000 Subject: [PATCH] docs(changeset): the IANA time-zone columns changeset declares its accept-set narrowing as BREAKING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.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 Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --- .changeset/platform-iana-timezone-columns.md | 47 +++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/.changeset/platform-iana-timezone-columns.md b/.changeset/platform-iana-timezone-columns.md index 3459267763..91758b3703 100644 --- a/.changeset/platform-iana-timezone-columns.md +++ b/.changeset/platform-iana-timezone-columns.md @@ -4,7 +4,50 @@ feat(platform-objects): `sys_job.timezone` and `sys_report_schedule.timezone` are validated against the IANA domain (#15872) - +**BREAKING** accept-set narrowing on two published columns, shipped as `minor` +under the repo's launch-window convention for breaking changes. Both columns now +declare `valueDomain: 'iana_time_zone'`, so a value the shipped build stored +without complaint is refused from this release on. During the launch window the +bump level is not the carrier of breaking-ness and says nothing about whether a +release breaks you; this banner is the carrier, and the ADR-0087 disposition at +the foot of this changeset is the other one. + +**What stops being accepted.** A write to either column is now refused with the +ADR-0114 field error code `value_domain` unless the value is a member of the +IANA/tzdb set, tested with the `Intl.DateTimeFormat` probe. Three classes of +string that the previous build accepted are outside that set: + +- **UTC-offset spellings** — `UTC+8`, `GMT+0800`, `+08:00`. They name an offset, + not a zone, and no offset spelling is an IANA identifier. The tzdb's own + fixed-offset zones are members and keep working: `Etc/GMT-8` is accepted. +- **Windows / CLDR display names** — `China Standard Time`, + `Pacific Standard Time`. That is the Windows time-zone vocabulary, a different + naming scheme from tzdb, and no member of it is a tzdb identifier. +- **shape-valid identifiers for zones that do not exist** — `Mars/Olympus`. A + `Region/City` pattern cannot separate an unassigned identifier from a real + one; membership can, which is what the domain is for. + +**What keeps working.** Every genuine IANA identifier, including `UTC` — the +membership predicate is the `Intl.DateTimeFormat` probe, deliberately not the +`Intl.supportedValuesOf('timeZone')` enumeration, which omits `UTC`. That +matters here rather than academically: `'UTC'` is `sys_report_schedule.timezone`'s +own declared default. + +**Stored rows are unaffected — only writes are judged.** No upgrade step, no +backfill, no DDL. In the published words of the contract this declaration is +governed by (`packages/spec/src/data/field.zod.ts`, the `valueDomain` description): + +> 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`. + +So a deployment already holding `UTC+8` in one of these columns keeps it and +reads it back unchanged; what changes is the next write. The one thing to know +before upgrading is therefore an authoring fact, not a data-at-rest one: a +producer that writes one of the three spellings above starts getting a refusal +where it previously got a success, and for `sys_report_schedule` that refusal is +the point — see the behaviour note below. The platform's two oldest IANA time-zone columns predate `valueDomain` and disagreed with each other in three dimensions at once — length (100 vs 64), default (none vs `'UTC'`) and validation (neither). This closes the third: both now declare `valueDomain: 'iana_time_zone'`, the same declaration and the same `Intl.DateTimeFormat` membership probe that `sys_business_unit.timezone` and `sys_organization.timezone` carry (#14238). Four columns, one spelling of "is this a real zone". @@ -17,3 +60,5 @@ The platform's two oldest IANA time-zone columns predate `valueDomain` and disag - **the defaults still differ.** A default here is a consumer semantic, not a shape question. `sys_report_schedule` documents and implements a UTC default; `sys_job` has no reader at all, and minting one would change what an unset row means. - **the bounds still differ (100 vs 64).** `maxLength` is not only a write bound — it reaches DDL, and narrowing a physical `varchar(100)` is `driver-sql`'s `narrow_varchar` op at severity `error`, category destructive ("narrowing may truncate"). What the column physically holds in a deployment is not readable from the repo, so the convergence is a separate decision and #15872 stays open on it. Note what the domain declaration already costs the wider bound: no member is longer than 32 characters on the current Node baseline, so 100 now admits nothing 64 would not. + +