25.x → 26.0.0, entry by entry
Six entries — CHANGELOG.md's 26.0.0 breaking entries in their order, grouped by
package, lowest tier first. No legacy path, no codemod, no shim. Entry 1 can refuse sign-ins on
a host whose egress cannot reach the provider's jwks_uri — check it 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
| # | Do | What you see until you do | Entries |
|---|---|---|---|
| 1 | pin every @ultimat3/* to the one new version, bun install | nothing yet — a mixed install is untested | — |
| 2 | before the deploy, an app with an OIDC sign-in: from a production pod, curl -sS -m 5 https://www.googleapis.com/oauth2/v3/certs (or the jwks_uri of each provider you registered); where it cannot be reached, return 'token-endpoint-tls' for that provider only from oauthLogin(auth, { idTokenKeys }) (entry 1 shows the function) | X_OAUTH_EXCHANGE_FAILED (stage jwks) on every sign-in through that provider | 1 |
| 3 | bun test — a test that feeds exchangeOAuthCode, completeOAuthLogin or oauthLogin an unsigned id token: sign it and serve the key set at the provider's jwksUri from the injected fetch, or pass keys / idTokenKeys: 'token-endpoint-tls' | X_OAUTH_TOKEN_INVALID where the test signed in; an extra key-set URL in a recorded endpoint list | 1 |
| 4 | bun run typecheck for the removed exports: APPLE_PROVIDER, ServerHandle.describe(), AgentRecovery, Recovery, ROUTE_FILENAME.api | TS2305 / TS2339 at each site | 2, 3, 5 |
| 5 | find apps -path '*/api/*' \( -name route.ts -o -name page.tsx \) — move each into an action, or a plain route in runtime.routes | X_ROUTE_FILE_INVALID at boot | 4 |
| 6 | grep -rnE "x (jobs (ls|rm)|(actions|queries|entities) describe|db branch (ls|drop))\b" .github scripts bin package.json docs and swap each for list, delete, show | X_CLI_UNKNOWN_COMMAND naming the new verb | 6 |
| 7 | x verify | green, or a finding whose fix: is the edit | — |
Entry by entry
Tier 2 — @ultimat3/auth (1–2), @ultimat3/http (3). Tier 4 — @ultimat3/render (4),
@ultimat3/scraping (5). Tier 5 — @ultimat3/cli (6).
| # | Surface | Costs you an edit if |
|---|---|---|
| 1 | exchangeOAuthCode, completeOAuthLogin, oauthLogin — the id token's signature | your egress cannot reach a provider's jwks_uri, or a test hands the exchange an unsigned id token. 25.x accepted any id token read off the token endpoint over TLS; 26.0.0 checks its signature against the provider's published key set whenever the provider has a jwksUri (providerJwks, cached per provider). X_OAUTH_TOKEN_INVALID for a bad signature, X_OAUTH_EXCHANGE_FAILED (stage jwks) for an unreachable key set. Opt one provider out with oauthLogin(auth, { idTokenKeys: (provider) => provider === 'bigco-sso' ? 'token-endpoint-tls' : undefined }) — undefined keeps the default; providerJwks(providerFor(id)) there throws for GitHub, which publishes no jwks_uri; completeOAuthLogin(auth, { idTokenKeys }) and exchangeOAuthCode(…, { keys }) take the value directly |
| 2 | APPLE_PROVIDER, BUILTIN_OAUTH_PROVIDERS, BUILTIN_OAUTH_PROVIDER_IDS | you import APPLE_PROVIDER (TS2305) or list 'apple' in defineAuth({ providers }) (an unknown provider). Apple answers a form_post and the callback is a GET, so it never completed a login. Delete it, or registerOAuthProvider your own |
| 3 | ServerHandle.describe() | you call it. TS2339; read x routes --json, or call describeRoutes(table) from @ultimat3/http |
| 4 | api/**/route.ts, ROUTE_FILENAME, RouteEntry.surface | a route file lives under api/: X_ROUTE_FILE_INVALID (was X_ROUTE_MODE_INVALID). Make it an action, or a plain route in runtime.routes. ROUTE_FILENAME is Record<RouteSurface, string>; type a surface with the new RouteSurface export of @ultimat3/render |
| 5 | recover: 'agent', AgentRecovery, Recovery | a scrape passes 'agent' (refused at scrape(); 25.x threw X_NOT_IMPLEMENTED on the first moved selector) or imports a removed type. Write a RecoveryHook function and call your own llm() from it; import type { Recovery } becomes RecoveryHook. @ultimat3/scraping is tier 4 now — nothing to edit in an app |
| 6 | x jobs ls, x jobs rm, x actions|queries|entities describe, x db branch ls, x db branch drop | a script runs an old verb: X_CLI_UNKNOWN_COMMAND with the new one in its fix. list, delete, show, list, delete; a bare x jobs still lists, and --json is unchanged |