Skip to content

One timezone rule everywhere, and CI runs the Bun this repo runs - #265

Merged
sebyx07 merged 3 commits into
mainfrom
fix/tz-validator-and-bun-14
Aug 20, 2026
Merged

One timezone rule everywhere, and CI runs the Bun this repo runs#265
sebyx07 merged 3 commits into
mainfrom
fix/tz-validator-and-bun-14

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Two things 6.0.0 needs before it is tagged. bun run verify14 of 19 passed, 5 skipped, 0 failed.

1. The timezone rule reached three packages and stopped — #257

6.0.0 made it structural in @ultimat3/time: a zone is Area/Location, or UTC. Three packages kept asking Intl, which answers "can I format this" and never "is this IANA".

defaultTimeZone: 'CET'   →  boots clean, throws on the first format call,
                            from a stack naming no configuration
t.timezone accepts '+01:00'  →  off a request body or a query string

The t.timezone half is not ICU drift — ES2024 Intl has resolved +01:00 under every runtime this framework has shipped on. It guards caller input, so the accepted value reached a format call that failed three layers later.

Three sites, three different correct fixes, and the difference is the point:

Package Tier Fix
core 0 states the rule itself — cannot import time
schema 0 same, and it is the one guarding caller input
entity 2 imports isValidTimeZone and deletes its probe — the tier table permits the real thing, so a third statement would be wrong

A fourth site was already correct: packages/jobs/src/task.ts:125 calls the validator, with a comment saying why. Noted so the class reads as closed.

Two structural statements of one rule may not drift. packages/cli/src/timezone-validator-pin.test.ts (tier 5, the lowest that can see all of them) asserts four predicates agree — time's, core's, schema's, and t.timezone itself, which is what an app actually calls. A correct copy wired to nothing still ships the bug: mutating only the wiring fails 34 cases.

It checks the shared corpus and a sweep over Intl.supportedValuesOf('timeZone'). That sweep is the half a hardcoded list cannot replace — it catches a rule that got too narrow. Proved by narrowing schema's to /^[A-Za-z_]+\/[A-Za-z_]+$/, which the corpus waves through and the sweep fails on America/Argentina/Buenos_Aires.

Neither tracked app migrates. Every SUPPORTED_ZONES entry and every defaultTimeZone in this repo and in x new's scaffold is already Area/Location or UTC.

2. CI runs the Bun this repo runs

The dev box ran 1.4.0 while CI was pinned 1.3.x. That let a green local gate sit on top of a change CI rejected — Bun 1.4 bundles an unresolvable require() inside a catch as a runtime throw where 1.3.14 fails the build, and a PR merged red because of it. That divergence is worse than any single bug.

CI, release.yml, three Dockerfile stages, two scaffold stages, and @types/bun at three sites — including scaffold-repo.ts, which would otherwise hand every new app types a series behind its own image.

scripts/bun-pin.test.ts asserts the five pin sites agree and that none says latest. It counts matches as well as checking them, because an empty match list agrees with everything.

engines.bun stays >=1.3.0, deliberately. It is a consumer install floor; the thing bumped is a toolchain pin. Bun.Image — the newest surface the framework touches — answers function on 1.3.14, verified inside oven/bun:1.3-slim, and that image installs from the committed lockfile under --frozen-lockfile. Raising it would forbid an install that demonstrably works.

The honest cost, stated rather than hidden: nothing in CI exercises 1.3 after this, so >=1.3.0 becomes an untested claim. Closing that is a 1.3 matrix leg or a deliberate floor move in the next major — both their own work.

The v2 lockfile hazard did not materialise. Bun 1.4's install left lockfileVersion: 1, and 1.3.14 still reads it. The install did correct pre-existing drift: the lockfile recorded @ultimat3/*: ^1.2.0 for the demo app whose manifest says 5.0.1.

Verified by building the image and running it — /app/x --version answers 5.0.1 inside distroless as nonroot, exercising --compile, --define and --external @babel/preset-typescript on 1.4 end to end.

3. Docs

PUBLISHING.md gains "Releasing without a human in the loop", because the flow was never written down: publish a GitHub Release for the tag, CI publishes to npm, no token involved. .env is needed for exactly one thing — the one-time bootstrap of a never-published package. Plus the gh api call that approves the environment, and a pre-approval checklist, because for an unattended release those checks are the review.

wiki/Upgrading.md's defaultTimeZone row said "boots clean, then throws downstream — nothing will tell you". That was an accurate description of the defect this PR fixes.

Filed while here: #264 — two tests assert wall-clock behaviour and lose under 8-worker contention. One claims constant time, which a loaded box cannot honestly demonstrate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KgsU1WBJMAjnLaazvfuJmD


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

sebyx07 and others added 3 commits August 20, 2026 17:51
… table forbids importing it

6.0.0 made the rule structural in `@ultimat3/time` — a zone is `Area/Location`, or
`UTC` — because `Intl` answers "can I format this" and never "is this IANA", and
ICU 78 made the two disagree. Three packages kept asking `Intl`, so the rule
reached `task()` and every `@ultimat3/time` entry point and stopped everywhere
else.

`defaultTimeZone: 'CET'` booted clean and threw on the first format call, from a
stack naming no configuration (#257). `t.timezone` is worse and is not ICU drift:
it accepts `+01:00` off a request body or a query string, and ES2024 `Intl` has
resolved that under every runtime this framework has shipped on.

Three sites, three different correct fixes, and the difference is the point:

- `core` (tier 0) may not import `time`, so it states the rule itself —
  `time-zone-name.ts`, 43 lines, pointing at `zone-canonical.ts` for the
  reasoning rather than re-deriving it.
- `schema` (tier 0) is the same case for the same reason, and is the one that
  guards caller input.
- `entity` (tier 2) may import `time` (tier 1), so it does, and its local probe
  is deleted. A third structural statement here would have been wrong: the tier
  table permits the real thing.

A fourth site was already correct — `packages/jobs/src/task.ts:125` calls the
validator and its comment says why. Noted so the class reads as closed.

Two structural statements of one rule may not drift, so
`packages/cli/src/timezone-validator-pin.test.ts` asserts all four predicates
agree — `time`'s, core's, schema's, and `t.timezone` itself, which is the one an
app actually calls. A correct copy wired to nothing still ships the bug; mutating
the wiring alone fails 34 cases.

It checks the shared corpus AND a sweep over `Intl.supportedValuesOf('timeZone')`.
The sweep is the half a hardcoded list cannot replace: it catches a rule that got
too NARROW. Proved by narrowing schema's to `/^[A-Za-z_]+\/[A-Za-z_]+$/`, which
the corpus waves through and the sweep fails on `America/Argentina/Buenos_Aires`.

Neither tracked app migrates: every `SUPPORTED_ZONES` entry and every
`defaultTimeZone` in this repo and in `x new`'s scaffold is already
`Area/Location` or `UTC`.

Refs #257

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgsU1WBJMAjnLaazvfuJmD
…comment

This box ran Bun 1.4.0 while CI was pinned `1.3.x`, and today that let a green
local `bun run verify` sit on top of a change CI rejected: Bun 1.4 bundles an
unresolvable `require()` inside a `catch` as a runtime throw, where 1.3.14 fails
the build. A PR merged red because of it. Until the versions agree, a green local
gate is not evidence of a green CI, which is worse than any single bug.

`@types/bun@1.4.0` published today, which removed the last blocker.

CI and release.yml move to the `1.4.x` SERIES — still not `latest`, for the
reason the existing comment gives: `latest` lets a major land unannounced in
every job. `docker/Dockerfile` moves to `oven/bun:1.4-slim` (x3), the scaffold's
image to `1.4-alpine` (x2), and `@types/bun` to `^1.4.0` at all THREE sites —
including `scaffold-repo.ts`, which would otherwise give every new app types a
series behind the image it builds.

`scripts/bun-pin.test.ts` asserts all five pin sites agree and that none says
`latest`. It counts matches as well as checking them, because an empty match list
agrees with everything — replacing a `FROM oven/bun:` line with `debian:trixie-slim`
is what proves that guard fires.

`engines.bun` stays `>=1.3.0` in all 41 manifests, deliberately. It is a CONSUMER
install floor and the thing being bumped is a TOOLCHAIN pin. `Bun.Image` — the
newest surface the framework touches — answers `function` on 1.3.14, verified
inside `oven/bun:1.3-slim`, and that image installs from the committed lockfile
under `--frozen-lockfile`. Raising the floor would forbid an install that
demonstrably works. The honest cost: nothing in CI exercises 1.3 after this, so
`>=1.3.0` becomes an untested claim — closing that is a 1.3 matrix leg or a
deliberate floor move in the next major, and both are their own work.

`scripts/setup.ts`'s `REQUIRED_BUN` does move, for a different reason: it is the
repo's only statement of which Bun a CONTRIBUTOR should run, and a contributor
whose Bun differs from CI's is not running the gate CI runs. The hand-restated
number in its `cause:` now interpolates the constant. There is a second
`REQUIRED_BUN` in `packages/cli/src/app-root.ts` behind `x doctor` — a consumer
floor, correctly left at 1.3.0, and the split is documented so nobody "fixes" the
wrong one.

The v2 lockfile hazard did not materialise: Bun 1.4's install left
`lockfileVersion: 1` in place, and 1.3.14 still reads it. `bun install` did
correct pre-existing drift — the lockfile recorded `@ultimat3/*: ^1.2.0` for the
demo app whose manifest declares `5.0.1`.

Verified by building the image and running it: `/app/x --version` answers `5.0.1`
inside distroless as `nonroot`, which exercises `--compile`, `--define` and
`--external @babel/preset-typescript` on 1.4 end to end. `oven/bun:1.4-slim` is
still Debian 13 trixie, so the `cc-debian13` runtime pairing holds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgsU1WBJMAjnLaazvfuJmD
…e tags

`PUBLISHING.md` gains "Releasing without a human in the loop", because the flow
was never written down: publish a GitHub Release for the tag, and CI publishes to
npm. No token is involved — the workflow authenticates over OIDC, which is what
stamps every tarball with provenance.

`.env` is NOT part of that flow. It is needed for exactly one thing: the one-time
bootstrap of a package that has never been published, which no trusted publisher
can do because a publisher cannot attach to a package that does not exist yet.
Publishing with `NPM_TOKEN` anywhere else is what made 2.0.0 the one release with
`_npmUser: sebyx07` and no attestations.

The section also carries the `gh api` call that approves the `npm-publish`
environment without the UI, and a pre-approval checklist — because when a release
runs unattended, those checks ARE the review.

`wiki/Upgrading.md`'s `defaultTimeZone` row said "boots clean, then throws
downstream — nothing will tell you". That was an accurate description of a defect
this branch fixes; it now describes the refusal.

`wiki/Error-Codes.md`'s `X_BUN_VERSION` cause named one floor. There are two: the
consumer floor `x doctor` checks, and the contributor floor `scripts/setup.ts`
checks, which tracks CI's series and is higher.

Four docs quoted `oven/bun:1.3-*` image tags the tree no longer builds. The one
in `docker/README.md` that is historical narrative correctly stays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgsU1WBJMAjnLaazvfuJmD
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in: 44 minutes

Limit details: You’ve used the included review currently available. Your 77 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

You’re in a promotional period — use the checkbox below to run this review for free:

  • Run review for free

On-demand reviews are free for the next 31 days. After that, they cost $0.25 per reviewed file.

How can I continue?

Run this review now using the option above, or comment @coderabbitai review --use-credits.

You can also wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66cdecd5-8025-430c-8797-2478df2e5bae

📥 Commits

Reviewing files that changed from the base of the PR and between a297ed0 and 711732c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !**/bun.lock
📒 Files selected for processing (28)
  • .github/actions/setup/action.yml
  • .github/workflows/release.yml
  • CHANGELOG.md
  • PUBLISHING.md
  • docker/Dockerfile
  • docker/README.md
  • docs/ops/01-kubernetes.md
  • docs/ops/README.md
  • dummy/social-media-clone/package.json
  • package.json
  • packages/cli/src/templates/scaffold-container.ts
  • packages/cli/src/templates/scaffold-repo.ts
  • packages/cli/src/timezone-validator-pin.test.ts
  • packages/core/src/config.test.ts
  • packages/core/src/config.ts
  • packages/core/src/time-zone-name.test.ts
  • packages/core/src/time-zone-name.ts
  • packages/entity/CLAUDE.md
  • packages/entity/src/columns.test.ts
  • packages/entity/src/columns.ts
  • packages/schema/src/time-zone-name.ts
  • packages/schema/src/validators-builtins.test.ts
  • packages/schema/src/validators.ts
  • scripts/bun-pin.test.ts
  • scripts/setup.ts
  • wiki/Error-Codes.md
  • wiki/Tutorial-05-Deploy-Free.md
  • wiki/Upgrading.md

Comment @coderabbitai help to get the list of available commands.

@sebyx07
sebyx07 merged commit f6c1f2e into main Aug 20, 2026
37 checks passed
@sebyx07
sebyx07 deleted the fix/tz-validator-and-bun-14 branch August 20, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant