Ultimate

26.x → 27.0.0, entry by entry

Two entries — CHANGELOG.md's 27.0.0 breaking entries in their order, grouped by package, lowest tier first. No legacy path, no codemod, no shim. Entry 2 can 404 a read your app relies on: find every un-scoped key a page links before the deploy (upgrade step 2). A later slice appends its rows below the last one and never renumbers; each row's CHANGELOG.md line names it as (#N) after the dash.

The upgrade, top to bottom

#DoWhat you see until you doEntries
1pin every @ultimat3/* to the one new version, bun installnothing yet — a mixed install is untested—
2before the deploy: find apps -type f -exec grep -nE '/_storage/|/media/|signedUrl\(' {} + — for each key that is NOT built with scopedKey(orgId, …) (no org/<id>/ prefix) and that readers must still fetch, add its prefix to defineStorage({ shared: { <disk>: ['<prefix>/'] } }); leave everything else unshared404 X_STORAGE_KEY_UNSHARED on that link2
3bun run typecheck — a hand-written AsyncContext or Storage objectTS2741 naming exit or isShared1, 2
4x verifygreen, or a finding whose fix: is the edit—

Entry by entry

Tier 0 — @ultimat3/core (1). Tier 1 — @ultimat3/storage, with @ultimat3/cli's routes (2).

#SurfaceCosts you an edit if
1AsyncContext.exit(fn)you implement AsyncContext yourself rather than calling asyncContext(): TS2741. Add exit: (fn) => fn() for a context with no store, or your store's own exit
2GET /_storage/:disk/*key, GET /media/*key, StorageConfig.shared, Storage.isShareda page, island or client reads a key outside org/<id>/ through either route — a brand asset on a disk, a shared upload. 26.x served it to every holder of storage:read; 27.0.0 answers 404 X_STORAGE_KEY_UNSHARED unless defineStorage({ disks, shared: { uploads: ['brand/'] } }) lists its prefix for that disk (whole segments ending in /, never org/, never the whole disk — X_CONFIG_INVALID at boot otherwise). Or move a truly public file to apps/web/site/assets/ and name it with asset(). A hand-built Storage adds isShared(disk, key) (TS2741). Tenant-scoped reads are unchanged

Edit this page on GitHubThis page as markdown