Ultimate

24.x → 25.0.0, entry by entry

Forty entries — CHANGELOG.md's 25.0.0 breaking entries in their order: Bun first, then grouped by package, lowest tier first. No legacy path, no codemod, no compatibility shim — every break is a build error or an X_* error naming the rewrite. Most of the major is deletion: a second spelling, a second projection or a second page shape removed, and the keys and re-exports a deprecation promised to remove. Entries 2 and 8 refuse the boot of an app that still writes a deleted or an undeclared key. Entry 12 is silent: a per-action bucket in configureHttp stops limiting anything, so move it before the deploy — upgrade step 9. Entries 22–24 refuse every AI call until the app registers its own models and names one — upgrade step 8. Entries 31–40 landed after the cut, so they sit below 30 rather than with their packages. Entry 38 is silent: a worker Deployment that set WORKER_QUEUES stops claiming every queue it does not list — upgrade step 12. 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
1bun upgrade to 1.4.2 and move the app's own image to a 1.4.2 baseX_BUN_VERSION from x and x doctor1
2pin every @ultimat3/* to the one new version, bun installnothing yet — a mixed install is untested—
3delete the removed keys from app.config.ts and each overlay — the seven of 25.0.0, the ten of earlier majors, and any key the refusal says is not one (fix the typo it suggests); declare the locales in defineCatalogs (packages/i18n/src/index.ts); move a non-/mcp ai.mcp.path to the first defineAppMcp({ path }) in apps/<app>/mcp.ts; move drainTimeoutMs to drain: { deadlineMs } and delete buildIdHeader from configureHttpTS2353 in a typed config; X_CONFIG_INVALID naming the key and its replacement, or the nearest real key, at the first import2, 8
4bun run typecheck for the renames and removed exports: factory spellings (every create<Thing> and new of a class now a type), every value a second package re-exported, the renamed value names, checkEnvExample, fingerprint, nearestName, promptHash, entity's Page, SQL_*, createNatsDriver and createRedisDriver importsTS2305 / TS2724 at each import; TS1485 / TS1362 at a class now exported as a type3, 5–7, 14, 15, 20, 28, 31, 32
5bun run typecheck for the MCP projection, the MCP block and the page shape: .tool() and toolFromAction → toolFrom, QueryMcp / McpExposure / McpAnnotationHints → core's McpExposureDeclaration family, mcp.name → the primitive's name, endCursor / hasNextPage → nextCursor / hasMore, a storage page's objects / truncated / cursor → rows / hasMore / nextCursorTS2339 / TS2305 / TS2353 at each site7, 10, 11, 18
6bun run typecheck for the members a custom implementation owes: AuditRecord.name / .primitive, IdempotencyStore.keepsRedaction and settle(…, redacted), WebhookLedger.isDisabled, ChangeEvent.write, ChangeEvent.table for .entity, DigestAppend.appender, a StorageDriver.list() that answers Page<StorageListEntry>; drop counted from every ack; widen a Menu trigger's aria-controls; read listenerFor instead of listeners; narrow a read of config.jobs.concurrency with typeof … === 'number'; pass an OAuthGrantContext where code calls a ResolveOAuthGrantsTS2741 / TS2554 / TS2322 / TS2339 / TS2353 at each site4, 7, 13, 16, 17, 21, 25, 26, 33, 34, 37, 40
7bun run typecheck for ai: pass models to each anthropicProvider(…), replace OPENAI_MODEL_IDS with the app's idsTS2554 / TS2305 at each site23
8before the deploy, where the app calls a model: registerModel every model it names (by convention in its own models.ts), and name one on each declaration or as providerGateway({ defaultModel }); a test registers its rows in beforeEach and passes model to echoProvider()'s requestsX_AI_MODEL_UNKNOWN before any provider call; X_AI_MODEL_UNRESOLVED at a call that names none22–24
9before the deploy: move each rateLimit.buckets.<actionName> from configureHttp to that action's rateLimit: { limit, windowMs }X_CONFIG_INVALID at boot, naming the bucket and the action12
10x env example (an app exporting envSchema), x manifest, commit .env.example, x.manifest.json and openapi.jsonX_ENV_EXAMPLE_DRIFT / X_MANIFEST_STALE from the manifest step11, 35
11x verify --only boundaries,unit,contract,e2e and fix what it fails: move an app wildcard off a bearerMount prefix, list every projected tool under a scope, give each offline scrape a robots: { ignore } reason, move a page's repo read into a query, pass an e2e's external DATABASE_URL / S3_ENDPOINT in startE2eApp({ env })X_ROUTE_CONFLICT; X_MCP_SCOPE_UNCOVERED at boot; X_SCRAPE_ROBOTS_DISALLOWED; X_BOUNDARY_ROUTE_TO_DB; an e2e that read the external database's rows fails against the embedded one9, 19, 27, 30, 36
12read each script or CI job that runs x shot --cdp-url against a hosted browser, passes --to or --dry-run to x jobs drain, or reads data.next from x jobs list --json; before the deploy, read each worker Deployment that sets WORKER_QUEUES and list every queue it must serve; pass concurrency: 5 to a hand-built jobWorker that must keep 5 slotsX_CDP_CALL_FAILED where a capture used to proceed; X_CLI_BAD_FLAG; undefined where a cursor was; jobs.worker.queue-unserved at a worker's boot and jobs left ready on the queues it no longer claims; 8 jobs at once where 5 ran7, 15, 29, 38, 39
13x verifygreen, or a finding whose fix: is the edit—

Entry by entry

Every package (1). Tier 0 — @ultimat3/core (2–5). Tier 1 and up — the factory spellings of db, cache, storage, auth, action, jobs, realtime, mail, notify, ai and testing (6). Tier 2 — @ultimat3/entity (7), @ultimat3/http (8–9). Tier 3 — @ultimat3/action and @ultimat3/query (10–14), @ultimat3/jobs (15–16), @ultimat3/realtime (17). Tier 4 — @ultimat3/mcp (18–19), @ultimat3/manifest (20), @ultimat3/notify (21), @ultimat3/ai (22–24), @ultimat3/ui (25). Tier 5 — @ultimat3/testing (26), @ultimat3/scraping (27), @ultimat3/cli (28–30). After the cut — @ultimat3/ai (31), the value names of every package (32), @ultimat3/jobs (33). Sweep 12c — @ultimat3/core (34), @ultimat3/cli (35), @ultimat3/testing (36); its review round — @ultimat3/auth (37), @ultimat3/cli (38), @ultimat3/jobs (39), @ultimat3/realtime (40).

#SurfaceCosts you an edit if
1engines.bun, x, x doctoryou run Bun 1.4.0 or 1.4.1. X_BUN_VERSION; bun upgrade, and a 1.4.2 base image in the app's own Dockerfile
2app.config.ts: any key it does not declare, at any depth; keys removed: locales, defaultLocale, defaultTimeZone, defaultCurrency, theme.tokens, jobs.driver, ai.mcp.path, and from earlier majors realtime.heartbeatMs, database.urlEnv, database.poolSize, database.schema, pwa.installPrompt, auth.afterSignInPath, ai.modelEnv, cache.driver, cache.urlEnv, realtime.tiera layer writes one. X_CONFIG_INVALID naming the path and either its replacement or, for a key that never existed, the nearest real one (did you mean drain?); TS2353 in a typed config; TS2339 where code reads config.locales. The locales and their default are defineCatalogs({ default, locales }) in packages/i18n/src/index.ts; the zone is passed per call, the currency per Money; the theme is defineTheme in apps/web/shared/theme.ts; the driver is setJobDriver(postgresJobDriver({ executor })). Delete ai.mcp.path: every MCP endpoint, #0 included, mounts at its own defineAppMcp({ path }) (default /mcp) — if yours was not /mcp, pass it as path to the first defineAppMcp in apps/<app>/mcp.ts. McpConfig is { expose }. Of the ten older keys only cache.driver was read (→ cache: { tiers: [...] }); the rest were read by nothing — delete the line, and the fix: names what does the job (REMOVED_CONFIG_KEYS, packages/core/src/config-removed.ts)
3assertEnvExample, EnvExampleDriftError, resolveSpeculationyou import one. checkEnvExample(…) returns findings instead of throwing — or let x verify --only manifest run it; speculation is defineConfig({ navigation: { speculation } })
4AuditRecord, normalizeAuditRecord, NormalizedAuditRecorda sink or test reads record.action or builds a record without name and primitive. TS2339 / TS2741. Read and write name and primitive; no DDL changed
5Deprecation, DeprecationField, DeprecationRender, recordDeprecatedCall, renderDeprecation; the values createClientFlight (now clientFlight), DEFAULT_CLIENT_RETRY, isSuperseded, isTransientFailure, getAuditSink, setAuditSink, resetAuditSink; every other value a second package re-exported — the Entry 5 table below, including formatIssues (mcp), MAX_MONEY_SCALE (money), ULTIMATE_ERROR_BRAND (core), JOB_STATES and TEST_TYPES (cli)you import one from a package that is not its home. TS2305; change the module specifier per the table — @ultimat3/core unless the row names another, and under core's own name where the row gives one. action and query re-export types only; scraping's throwIfAborted(signal) is signal.throwIfAborted()
6every createMemory*, createPg*, pgSchedulerState, createPostgresClient; a Memory* / InMemory* / Pg* / BuiltinAdapter class new-ed as a value; every create<Vendor>Driver; storage's memoryDriver; every other create<Thing> factory; ai's new AnthropicProvider / EchoProvider / HashEmbedder / RemoteEmbedder / BudgetLedger, mcp's new McpServer, query's new Builderyou call one. TS2305 / TS2724; TS1485 / TS1362 where the class is now a type only. Rename per the table below. Arguments are unchanged; a type a factory takes or builds is renamed to the factory's spelling (TS2724 at the old name — the type-renames table below), so import type { BuiltinAdapter } is PostgresAuthAdapter and MemoryAdapter is MemoryAuthAdapter. A budget ledger is the gateway's (gateway.callLedger(keys), gateway.scope(…)); a query builder is from(…)'s. createBranch, createSession and createApp keep their names: they are verbs
7Page from @ultimat3/entityyou annotate a findMany or .page() result with it. Import Page from @ultimat3/core (or @ultimat3/query): findMany answers it, so the page now carries hasMore too. A hand-written Repo builds its page with core's pageOf(rows, nextCursor) (TS2741 / TS2322 at a { rows, nextCursor } literal). And ListPage from @ultimat3/storage: list() answers Page<StorageListEntry> — read rows / hasMore / nextCursor for objects / truncated / cursor (null on the last page), pass cursor: page.nextCursor; a custom driver returns pageOf(entries, nextCursor). And a script reading x jobs list --json: data.next is data.nextCursor, beside data.hasMore
8configureHttp({ drainTimeoutMs }), defineHttpConfig({ drainTimeoutMs }); the same two with buildIdHeaderyou pass one. X_CONFIG_INVALID, refused by key so a spread is caught too. drainTimeoutMs: declare drain: { deadlineMs } in app.config.ts — every role, web included, drains on it. buildIdHeader: delete it; the header is core's BUILD_ID_HEADER (x-ultimate-build), the only one any client sends
9bearerMountthe app declares a wildcard at the mount's own <prefix>/*rest. X_ROUTE_CONFLICT at registration; the mount answers every method under its prefix
10.tool(), toMcpTool(s), toQueryTool(s), isExposed, toolFromAction, toolFromQuery, McpToolDescriptor, McpInvokeOptions, QueryTool*you project or call a tool by hand. toolFrom(x) for an action or a query (toolFromAction renamed, no alias), toolListEntry(tool) for the tools/list entry, invoke(x, input, { ctx, surface: 'mcp' }) to call, (await sourceFor(q, input, { surface: 'mcp' })).execute() to read, isMcpExposed(x.mcp) from @ultimat3/core
11query Page, the ?_first= envelope, openapi.jsona client reads endCursor or hasNextPage. Read nextCursor and hasMore; pass nextCursor as after (_after= on the wire). Run x manifest. And a client keeps the LAST page's cursor — to poll for rows appended after it, or because it reads nextCursor without hasMore: the last page's nextCursor is now null (it is null exactly when hasMore is false, on every surface), so a while (page.nextCursor) loop no longer fetches an empty page past the end. To tail a listing, keep the cursor of the last page that had one, or re-read the first page
12http.rateLimit.buckets.<actionName>you limited an action through a named bucket. It is refused at boot with X_CONFIG_INVALID (a bucket keyed by a mounted action's or query's name): delete it from configureHttp and declare rateLimit: { limit, windowMs } on the action
13IdempotencyStoreyou wrote your own. Declare keepsRedaction: true, keep settle's 4th argument redacted and return it as IdempotencyRecord.redacted
14SQL_* exports of action, jobs, notify, adminyou import a statement other than a table DDL (SQL_AUDIT_TABLE, SQL_IDEMPOTENCY_TABLE, SQL_JOBS_TABLE, SQL_NOTIFY_*_TABLE, SQL_ADMIN_AUDIT_TABLE) — SQL_CLAIM, SQL_OUTBOX_RELEASE and SQL_NOTIFY_INBOX_MARK_READ included. Call the store that runs it, or copy the text into the app
15createNatsDriver, NatsDriverOptions, createRedisDriver, RedisDriverOptions; the --to and --dry-run flags of x jobs drainyou import one — every method threw X_NOT_IMPLEMENTED. Postgres is the durable driver: postgresJobDriver({ executor }). Or a script passes either flag: X_CLI_BAD_FLAG; x jobs drain is still planned (X_NOT_IMPLEMENTED), so delete the call
16WebhookLedgeryou wrote your own. Add isDisabled(endpointId): Promise<boolean>
17ChangeEventa custom change feed builds one. Pass write: null where it has no write name
18McpExposure and its name; QueryMcp, QueryMcpAnnotations, QueryListParams, QueryListFilterOp (query); McpAnnotationHints (action)a hand-built primitive handed to toolFrom sets mcp: { name } — rename the primitive instead. Or you name one of the types: import McpExposureDeclaration, McpAnnotationHints, McpListParams, McpListFilterOp from @ultimat3/core. An action's block is ActionMcp, Omit<McpExposureDeclaration, 'listParams'>
19defineAppMcp({ scopes })the map leaves a projected tool out — include: 'exposed' and hand-written tools such as whoami included. X_MCP_SCOPE_UNCOVERED at boot; list each tool under a scope
20contentHash from @ultimat3/manifestyou import it. fingerprint(body) from @ultimat3/core, the same 16 hex characters
21DigestAppend, DigestStoreyou wrote a store. Key its replay on the required appender
22DEFAULT_MODEL, a call with no modela declaration, its prompt and the gateway all leave the model out. X_AI_MODEL_UNRESOLVED naming the three places; echoProvider() needs model too, and so does a request to a provider called directly (anthropicProvider, openAiProvider), which runs request.model and nothing else. describeAgents() answers model: null there
23anthropicProvider() (24.x: new AnthropicProvider()), ANTHROPIC_MODEL_IDS, OPENAI_MODEL_IDSyou construct a provider without models or with the built-in list. TS2554 / TS2305; an empty list is X_AI_REQUEST_INVALID. Pass the ids the app registered
24the built-in catalogue, registerOpenAiModels()the app names a model it never registerModel-ed. X_AI_MODEL_UNKNOWN before any provider call. Register each model at boot, with its source and date beside each number
25Menu trigger propsyou type aria-controls as string. It is string | undefined; a closed menu carries none
26FakeElement.listenersa test reads it. element.listenerFor(name), or island.fire(selector, type)
27an offline scrape() test with fakeBrowser / fixtureBrowserit declares no robots: { ignore }. X_SCRAPE_ROBOTS_DISALLOWED under bun test; write the reason the run is permitted
28nearest from @ultimat3/cliyou import it. nearestName from @ultimat3/core
29x shot --cdp-urlthe provider refuses the browser-target attach. X_CDP_CALL_FAILED; point at a browser that allows it, or drop the flag
30the boundaries stepa page, layout or route imports a slice's repo. X_BOUNDARY_ROUTE_TO_DB; read through a query
31contentHash from @ultimat3/aiyou import it. promptHash(input), the same argument and the same hash; contentHash is @ultimat3/render/server's byte hash only
32a value name two packages declared: forbidden, unauthenticated, json, text, validate, money, isEnabled, describeRoutes, channel, guard, guardBeforeInput, resetRegistry, explain, actorLabel, allowed, denied, relationsFor, invariant, uuid, cacheKeyFor, DEFAULT_RETRY, NO_TENANT, chunk, normalize, tokenize, introspect, isDestructive, assertNoDrift, safeUrl, formatBytes (ui), resetIdCounter, srcsetFor, frozenClock, testClock (scraping), sessionExpiredyou import one. TS2305 / TS2724; rename per the Entry 32 table below — same arguments, same answer, no alias. Scraping's testClock is testScrapeClock: @ultimat3/testing's testClock is the former createTestClock, a different clock
33AckOptions.counteda custom JobDriver.ack reads it, or a caller passes it. TS2353 at a literal; delete it — every ack counts
34JobsConfig.concurrency (JobsConcurrency)code reads config.jobs.concurrency as a number. It is number | Readonly<Record<string, number>>, slots per queue; narrow with typeof concurrency === 'number', and read a queue's row as concurrency[queue] ?? JOBS_CONCURRENCY_DEFAULT (both exported by @ultimat3/core). An empty table, a blank queue name or a count below 1 is X_CONFIG_INVALID
35.env.example, x env examplethe app exports envSchema. The projection now ends in a Framework section — ULTIMATE_CURSOR_SECRET, STORAGE_SIGNING_SECRET — so the committed file is X_ENV_EXAMPLE_DRIFT in the manifest step; run x env example and commit it. A key the schema already declares is not repeated
36startE2eAppan e2e relied on an exported DATABASE_URL or S3_ENDPOINT reaching the app. Neither is inherited any more; pass it: startE2eApp({ root, env: { DATABASE_URL: '<the external url>' } })
37ResolveOAuthGrantscode calls a ResolveOAuthGrants-typed function with one argument — typically an app's unit test of its grants function. TS2554; pass an OAuthGrantContext (provider, tokens, idTokenClaims, userinfo()) as the second argument. A one-argument implementation still assigns to the type
38WORKER_QUEUESa worker Deployment sets it and relied on also serving jobs.queues and every registered job's queue (24.x read nothing by that name). It is now exactly the listed queues: jobs on any other queue stay unclaimed by that worker, which warns jobs.worker.queue-unserved at boot naming them. List every queue the Deployment must serve, or unset the variable for the union. An empty entry (banks,,mail) is X_CONFIG_INVALID
39jobWorker({ concurrency })you build a jobWorker by hand and pass no concurrency, or a table that leaves a queue out. That queue runs JOBS_CONCURRENCY_DEFAULT (8, @ultimat3/core) slots, was 5; pass concurrency: 5 to keep it. A worker x boots is unchanged
40ChangeEvent.entity (@ultimat3/realtime)a custom ChangeFeed, test or consumer builds or reads a change event. Rename entity: to table: — it holds the relation (table) name on every producer. TS2353 / TS2339. A sync node still reads a 24.x replicator's entity off the bus, so the rolling deploy needs no ordering

Entry 6, the renames:

PackageWasIs
dbcreatePostgresClientpostgresClient
cachecreateMemorySemanticCachememorySemanticCache
jobscreateMemoryDriver, createPgDrivermemoryJobDriver, postgresJobDriver
jobscreatePgLeader, createPgLeaseLeader, pgSchedulerStatepostgresLeader, postgresLeaseLeader, postgresSchedulerState
jobscreatePgEventBus, createPgOutboxStorepostgresEventBus, postgresOutboxStore
jobscreateMemoryEventBus, createMemoryOutboxStore, createMemoryStepStorememoryEventBus, memoryOutboxStore, memoryStepStore
jobscreateMemoryLeaseStore, createMemorySchedulerState, createMemoryBackfillLedgermemoryLeaseStore, memorySchedulerState, memoryBackfillLedger
mailcreateMemoryDrivermemoryMailDriver
notifycreateMemoryDeliveryLedger, createMemoryDigestStore, createMemoryInboxStore, createMemoryPreferenceStorememoryDeliveryLedger, memoryDigestStore, memoryInboxStore, memoryPreferenceStore
notifycreatePgDeliveryLedger, createPgDigestStore, createPgInboxStorepostgresDeliveryLedger, postgresDigestStore, postgresInboxStore
storagememoryDriver, MemoryDriverOptionsmemoryStorageDriver, MemoryStorageDriverOptions
authnew BuiltinAdapter(…), new MemoryAdapter(…)postgresAuthAdapter(…), memoryAuthAdapter(…)
actionnew MemoryIdempotencyStore(…)memoryIdempotencyStore(…)
realtimenew MemoryLocalStore(), new MemoryQueueStore()memoryLocalStore(), memoryQueueStore()
realtime/servernew InMemoryAdvisoryLock(…), new PgAdvisoryLock(…)memoryAdvisoryLock(…), postgresAdvisoryLock(…)
realtime/servernew InMemoryChangeFeed(…), new PgLogicalReplicationFeed(…); PgOutputDecodermemoryChangeFeed(…), postgresChangeFeed(…); not exported — the feed decodes
mailcreateSmtpDriver, createResendDriver, createLogDriver, createUnconfiguredDriversmtpMailDriver, resendMailDriver, logMailDriver, unconfiguredMailDriver
ainew PgVectorStore(…), new MemoryVectorStore(…), new MemoryBudgetStore()postgresVectorStore(…), memoryVectorStore(…), memoryBudgetStore()
testingcreateSubscribeDriver()subscribeDriver()
authcreateAuthLimiter, createJwksClient, createKdfGate, createPkce, createTotpReplayGuardmemoryAuthLimiter, jwksClient, boundedKdfGate, pkcePair, memoryTotpReplayGuard
cachecreateCacheStack, createCdnTier, createLruTier, createMemoTier, createRedisTiercacheStack, cdnTier, lruTier, memoTier, redisTier
corecreateClientFlight, createContext, createFence, createFlightGateclientFlight, ctxOf, generationFence, flightGate
corecreateLogger, createRaster, createSingleFlightstructuredLogger, blankRaster, singleFlight
dbcreatePgliteClient, createRecordingClientpgliteClient, recordingClient
httpcreatePipeline, createRateLimiter, createRequestContext, createRouter, createServerhttpPipeline, rateLimiter, requestContext, httpRouter, httpServer
i18ncreateTranslatorcatalogTranslator
jobscreateJobsFacade, createLimiter, createOutboxRelay, createPaceroutboxJobsFacade, concurrencyLimiter, outboxRelay, backfillPacer
jobscreateScheduler, createStepRunner, createWorkerjobScheduler, stepRunner, jobWorker
realtimecreateOutbox, createSyncNodelocalOutbox, syncNode
realtime/servercreateEntry, createFrameRouter, createReplicatorqueryEntry, frameRouter, changeFeedReplicator
mcpcreateDevServer, createMcpServer, new McpServer(…)devMcpServer, mcpServer, mcpServer(…)
pwacreateInstallControllerinstallController
rendercreateIslandCollector, createIsrControllerislandCollector, isrController
ainew AnthropicProvider(…), new EchoProvider(…), new HashEmbedder(…), new RemoteEmbedder(…)anthropicProvider(…), echoProvider(…), hashEmbedder(…), remoteEmbedder(…)
aicreateGateway, CreateGatewayInput; new BudgetLedger(…)providerGateway, ProviderGatewayInput; gateway.callLedger(keys) or gateway.scope(…)
uicreateFocusTrap, createFormBinding, createRovingTabindex, createToastStorefocusTrap, formBinding, rovingTabindex, toastStore
testingcreateTestClock, createTestMail, createTestNetwork, createTestStatementstestClock, testMail, testNetwork, testStatements
testingcreateRunJobs, createLiveNode, createTemplateSqltestJobs, liveNode, templateSql
scrapingcreateArtifactWriter, createPacer, createPrompt, createRingartifactWriter, scrapePacer, scrapePrompt, boundedRing
scrapingcreateRobotsGate, createSecretBag, createUsageMeterrobotsGate, secretBag, usageMeter
querynew Builder(…)from(…)

Each class above stays nameable as a type (import type { MemoryVectorStore }) — under the factory's spelling where the two differed. Entry 6, the type renames (TS2724 at the old name):

PackageWasIs
jobsPgDriverOptions, MemoryDriverOptions, PgEventBusOptionsPostgresJobDriverOptions, MemoryJobDriverOptions, PostgresEventBusOptions
jobsPgOutboxOptions, MemoryOutboxOptions, PgLeaseLeaderOptionsPostgresOutboxStoreOptions, MemoryOutboxStoreOptions, PostgresLeaseLeaderOptions
mcpCreateMcpServerInput, CreateDevServerInputMcpServerInput, DevMcpServerInput
aiPgVectorStore, PgVectorStoreInput, CreateGatewayInputPostgresVectorStore, PostgresVectorStoreInput, ProviderGatewayInput
authBuiltinAdapter, MemoryAdapterPostgresAuthAdapter, MemoryAuthAdapter
notifyPgDigestStore, PgDigestStoreOptions, PgInboxStore, PgInboxStoreOptionsPostgresDigestStore, PostgresDigestStoreOptions, PostgresInboxStore, PostgresInboxStoreOptions
notifyPgDeliveryLedger, PgDeliveryLedgerOptions, MemoryLedgerOptionsPostgresDeliveryLedger, PostgresDeliveryLedgerOptions, MemoryDeliveryLedgerOptions
realtime/serverInMemoryAdvisoryLock, PgAdvisoryLock, PgAdvisoryLockOptionsMemoryAdvisoryLock, PostgresAdvisoryLock, PostgresAdvisoryLockOptions
realtime/serverInMemoryChangeFeed, InMemoryChangeFeedOptions, PgLogicalReplicationFeed, PgLogicalReplicationOptionsMemoryChangeFeed, MemoryChangeFeedOptions, PostgresChangeFeed, PostgresChangeFeedOptions
storageMemoryDriverOptionsMemoryStorageDriverOptions

Entry 5, each value at its home — @ultimat3/core unless the right-hand column names another:

Was imported fromValuesImport from
actionBUILD_ID_HEADER, IDEMPOTENCY_HEADER, pluralize, actorOf@ultimat3/core
actiontoBucket@ultimat3/http
action, queryadmitsAnonymous, policyPermissions, policyCapability@ultimat3/policy
queryCursorInvalidError, actorOf; derivePath@ultimat3/core; as queryPath
queryMAX_PAGE_SIZE@ultimat3/entity
httpescapeHtml, readCookie, REQUEST_TIMEOUT_HEADER, isCanonicalWebhookField, WEBHOOK_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_SIGNATURE_VERSION, WEBHOOK_TOPIC_HEADER@ultimat3/core
jobsWEBHOOK_FIELD_MAX, webhookHeaders, webhookMac, webhookSignature, webhookSigningString, classifyThrown; types WebhookMacInput, WebhookSigningInput@ultimat3/core
flags · time · auth, storagefnv1a · localeInvalid · timingSafeEqual@ultimat3/core
i18nDEFAULT_LOCALE, directionOf, isRtl, localeSegment; FRAMEWORK_CATALOG_LOCALE@ultimat3/core; as DEFAULT_LOCALE
mailescapeHtml; MAIL_CATALOG_LOCALE@ultimat3/core; as DEFAULT_LOCALE
aiMAX_SEMANTIC_CACHE_SCOPES@ultimat3/core, as MAX_CACHED_FORMATTERS
cacheTIER_ORDER; createSingleFlight, types FlightJoin, SingleFlight@ultimat3/core, as CACHE_TIERS; as singleFlight
renderformatBytes, RENDER_MODES, HYDRATE_STRATEGIES, OFFLINE_STRATEGIES, THEME_STORAGE_KEY, CLIENT_PATH_STYLE_META, CLIENT_PERSIST_META, CLIENT_SCOPE_META, CLIENT_BUILD_META, CLIENT_SYNC_META, CLIENT_SYNC_WORKER_META@ultimat3/core
renderNAVIGATION_HEADER, NAVIGATION_SURFACE_HEADER, NAVIGATION_SCOPE_HEADER, NAVIGATION_LOCATION_HEADER@ultimat3/core, as CLIENT_NAVIGATION_HEADER, CLIENT_NAVIGATION_SURFACE_HEADER, CLIENT_NAVIGATION_SCOPE_HEADER, CLIENT_NAVIGATION_LOCATION_HEADER
rendertagKeys@ultimat3/cache
pwaformatBytes, BUILD_ID_HEADER; BUILD_ID_META; APP_UPDATE_AVAILABLE@ultimat3/core; as CLIENT_BUILD_META; as APP_UPDATE_MESSAGE
uiTHEME_STORAGE_KEY@ultimat3/core
adminREDACTED; ADMIN_AUDIT_TABLE, SQL_ADMIN_AUDIT_TABLE@ultimat3/core; @ultimat3/admin/schema
realtimeisJsonObject@ultimat3/core
scrapingANY_HOST, hostDecision, hostMatches; types HostRule, HostDecision@ultimat3/core
scrapingDEFAULT_CONTENT_TYPE@ultimat3/storage
scrapingthrowIfAborted(signal)nowhere: signal.throwIfAborted()
mcp · money · coreformatIssues · MAX_MONEY_SCALE · ULTIMATE_ERROR_BRAND@ultimat3/schema
cliJOB_STATES@ultimat3/jobs
cliTEST_TYPES@ultimat3/testing

Two re-exports stay, pinned in scripts/lib/package-reexports.ts: schema's t through the eight primitive barrels (action, ai, entity, jobs, mail, mcp, notify, query), and core's describeValue and isIsoDateTime over the declared core → schema edge. bun run flight-copies refuses any other (X_HELPER_COPY), and a blind export * in shipped package source.

Entry 32, one name per meaning:

PackageWasIs
authforbidden, unauthenticatedauthForbidden, authUnauthenticated
policyforbiddenpolicyForbidden
httpjson, text, validatejsonResponse, textResponse, validateBody
money · pwa · render · notifymoney() · isEnabled · describeRoutes · channelfromMinor() · hasCapability · describePages · deliveryChannel
actionguard, guardBeforeInput, resetRegistryguardAction, guardActionBeforeInput, resetActions
queryguard, guardBeforeInput, resetRegistry, explainguardQuery, guardQueryBeforeInput, resetQueries, explainQuery
adminactorLabel, allowed, denied, relationsForadminActorLabel, adminAllowed, adminDenied, relationDataFor
coreinvariant, InvariantOptions, uuidassertCoded, AssertCodedOptions, uuidV7
aicacheKeyFor, DEFAULT_RETRY, NO_TENANTpromptCacheKey, DEFAULT_GATEWAY_RETRY, NO_VECTOR_TENANT
aichunk, normalize, tokenizechunkDocument, normalizeVector, wordTokens
dbintrospect, isDestructive, assertNoDriftintrospectSchema, isDestructiveMigration, assertNoSchemaDrift
mailsafeUrlsafeMailHref
uiformatBytes, resetIdCounter, srcsetForformatFileSize, resetUseIdCounter, variantSrcset
testingfrozenClock(now, body)withFrozenClock(now, body)
scrapingtestClock, sessionExpiredtestScrapeClock, scrapeSessionExpired

@ultimat3/i18n's t('key') and @ultimat3/schema's t keep one name on purpose: no file imports both (scripts/factory-names-pins.ts holds the measurement).

Before → after

Entry 2 — app.config.ts, and where each key went:

 export const config = defineConfig({
   name: 'my-app',
-  locales: ['en', 'es'],
-  defaultLocale: 'en',
-  defaultTimeZone: 'UTC',
-  defaultCurrency: 'USD',
-  theme: { defaultMode: 'system', tokens: { brand: '#0a84ff' } },
-  jobs: { driver: 'postgres' },
-  ai: { mcp: { expose: true, path: '/agents' } },
+  theme: { defaultMode: 'system' },
+  ai: { mcp: { expose: true } },
+  drain: { deadlineMs: 25_000 }, // entry 8: was configureHttp({ drainTimeoutMs })
 });

 // apps/web/mcp.ts — an endpoint's path is its own; /mcp needs no line
+export const mcp = defineAppMcp({ path: '/agents', /* name, tools, resolveToken … */ });

 // packages/i18n/src/index.ts — the one declaration of the locales and their default
+export const catalogs = defineCatalogs({ default: 'en', locales: { en, es } });

 // at each format call — there is no ambient zone
-formatDate(at, { locale });
+formatDate(at, { locale, zone: 'Europe/Paris' });

Entry 10 — the one MCP projection:

-import { toMcpTool, isExposed } from '@ultimat3/action';
-import { toQueryTool } from '@ultimat3/query';
+import { invoke } from '@ultimat3/action';
+import { isMcpExposed } from '@ultimat3/core';
+import { toolFrom, toolListEntry } from '@ultimat3/mcp';
+import { sourceFor } from '@ultimat3/query';

-const entry = publishPost.tool();                       // or toMcpTool(publishPost)
+const entry = toolListEntry(toolFrom(publishPost)); // an action or a query
-await toMcpTool(publishPost).invoke(input, { ctx });
+await invoke(publishPost, input, { ctx, surface: 'mcp' });
-await toQueryTool(postList).read(input);
+await (await sourceFor(postList, input, { surface: 'mcp' })).execute();
-if (isExposed(publishPost)) …
+if (isMcpExposed(publishPost.mcp)) …

Entry 11 — a page:

 const page = await client.postList({ _first: 20 });
-if (page.hasNextPage) next = page.endCursor;
+next = page.nextCursor; // null exactly when !page.hasMore — the last page carries no cursor

Entry 12 — a per-action limit:

-configureHttp({ rateLimit: { buckets: { publishPost: { capacity: 10, refillPerSecond: 0.1 } } } });
 export const publishPost = action({
   input: t.object({ id: t.uuid }),
+  rateLimit: { limit: 10, windowMs: 60_000 },
   …
 });

Entries 4, 13, 16, 17 and 21 — the members a custom implementation owes:

-const record = { action: 'publishPost', /* … */ };
+const record = { name: 'publishPost', primitive: 'action', /* … */ };

 const store: IdempotencyStore = {
+  keepsRedaction: true,
-  settle: (key, value, reservationId) => …,
+  settle: (key, value, reservationId, redacted) => …, // keep it; return it as record.redacted
 };

 const ledger: WebhookLedger = {
+  isDisabled: (endpointId) => Promise.resolve(disabled.has(endpointId)),
 };

-await feed.emit({ entity, op, before, after, lsn, txid, orgId, at });
+await feed.emit({ entity, op, before, after, lsn, txid, orgId, at, write: null });

Entries 22–24 — the app brings its models. Every number below is a placeholder: copy the vendor's published limits and prices into the app's own models.ts, with the source and the date beside each, as examples/dummy/apps/web/app/models.ts does.

import { anthropicProvider, configureAi, providerGateway, registerModel } from '@ultimat3/ai';

const usd = (minor: number) => ({ minor, currency: 'USD' }) as const;

export const appModel = registerModel({
  id: 'your-model-id',
  family: 'your-vendor',
  contextWindow: 200_000,
  maxOutput: 32_000,
  inputPerMillion: usd(300),
  outputPerMillion: usd(1_500),
  cacheMinimumTokens: 1_024,
  reasoning: { effort: true, adaptive: true, disableThinkingUpTo: undefined },
});

configureAi({
  gateway: providerGateway({
    providers: [anthropicProvider({ models: [appModel.id] })],
    defaultModel: appModel.id,
  }),
});
-new AnthropicProvider()
+anthropicProvider({ models: [appModel.id] })                 // entry 6: a factory, not a class
-openAiProvider({ apiKey, models: [...OPENAI_MODEL_IDS] })
+openAiProvider({ apiKey, models: [appModel.id] })
-beforeEach(() => registerOpenAiModels());
+beforeEach(() => registerModel(testModelRow));
-new EchoProvider().generate({ prompt })
+echoProvider().generate({ prompt, model: appModel.id })
-createGateway({ providers, defaultModel })
+providerGateway({ providers, defaultModel })

Entries 6 and 32 — a factory name and three value names, after the edit:

import { structuredLogger, uuidV7 } from '@ultimat3/core';
import { jsonResponse } from '@ultimat3/http';
import { fromMinor } from '@ultimat3/money';

const log = structuredLogger(); // was createLogger()

export function quote(): Response {
  const id = uuidV7(); // was uuid()
  const price = fromMinor(1_999, 'USD'); // was money(1_999, 'USD')
  log.info('quoted', { id, minor: price.minor });
  return jsonResponse({ id, price }); // was json({ id, price })
}

Entry 7 — a storage page and the x jobs list --json page:

 let cursor: string | null = null;
 do {
   const page = await disk.list({ prefix: 'avatars/', cursor });
-  for (const entry of page.objects) seen.push(entry.key);
-  cursor = page.truncated ? page.cursor : null;
+  for (const entry of page.rows) seen.push(entry.key);
+  cursor = page.nextCursor; // null exactly when !page.hasMore
 } while (cursor !== null);

-x jobs list --json | jq -r '.data.next'
+x jobs list --json | jq -r 'select(.data.hasMore) | .data.nextCursor'

Not breaking, but you will see it

SurfaceWhat changed
x shot, the PWA manifests, the service worker, the prerender, the speculation rules, the sitemap, x g --locales, x i18n, the e2e default localeread the locales from the app's defineCatalogs() through one reader, appLocaleSet(root) (@ultimat3/i18n/app-catalogs), never app.config.ts — an app with no packages/i18n/src/index.ts is en alone on every one of them (UNDECLARED_LOCALES)
x jobs lista full last page no longer prints --after <cursor> to an empty page
Helm grace periodssized from drain.deadlineMs (the worker's from drain.workerDeadlineSeconds when > 0, since 26.0.0), which x deploy --method helm passes on every upgrade
x jobs drainstill planned: X_NOT_IMPLEMENTED before it boots the queue or leases a job; it takes no --to or --dry-run (entry 15), since no durable second driver ships
describeAgents()model and modelFrom are null where nothing names a model; 'built-in-default' is gone
fix linesno fix: names a vendor's model as the default
pwa.offline.fallback, .image, .fontmust be paths on this origin: //host, /\host and absolute URLs are X_CONFIG_INVALID at config load
a raw MCP tool with no destructivebilled to the write bucket and listed as (action); set destructive: false on a hand-registered read
BarChartrenders a <figure> around <svg role="img">; class lands on the figure
x shot --matrixdirectories are <theme>-<w>x<h>, were <theme>-<w>
/admin homecounts a resource only when it declares count: true
x env check, x doctorreport ULTIMATE_CURSOR_SECRET and STORAGE_SIGNING_SECRET off one list, the published development cursor key counted as unset; x env check judges a table as the boot does (a table naming no environment is deployed), x doctor only where this machine's environment is named staging or production; neither reports the storage key when S3_ENDPOINT selects object storage. X_CURSOR_SECRET_DEV at boot prints the same fix, export ULTIMATE_CURSOR_SECRET="$(openssl rand -hex 32)"
ROLE=worker on SIGUSR2retires: stops claiming, finishes every held job, aborts nothing, logs jobs.worker.retiring / jobs.worker.retired, drains, exits 0. Both Helm charts send it from a worker preStop when roles.worker.retireSeconds > 0 (default 0, off) and add those seconds to the worker's grace period. A no-op on Windows
a table two producer kinds publisha sync node keeps the kind it read first for that table and drops the other's changes, logged once as X_REALTIME_PRODUCER_CONFLICT
x dev restartretires the worker first and waits, on stderr, for its running jobs to finish

Where the sites are

grep -rnwE "locales|defaultLocale|defaultTimeZone|defaultCurrency|tokens|driver|drainTimeoutMs|buildIdHeader|heartbeatMs|urlEnv|poolSize|schema|installPrompt|afterSignInPath|modelEnv|tier" app.config.ts apps packages --include=*.ts
grep -n "mcp" app.config.ts
grep -rnwE "createMemory[A-Z]\w*|createPg[A-Z]\w*|pgSchedulerState|createPostgresClient|create[A-Z]\w*Driver|(Nats|Redis|Memory)DriverOptions|memoryDriver|MemoryIdempotencyStore|(Pg|Memory)VectorStore|MemoryBudgetStore|(Builtin|Memory)Adapter|Memory(Local|Queue)Store|(InMemory|Pg)AdvisoryLock|InMemoryChangeFeed|PgLogicalReplicationFeed|PgOutputDecoder" apps packages --include=*.ts --include=*.tsx
grep -rnwE "assertEnvExample|EnvExampleDriftError|resolveSpeculation|normalizeAuditRecord|NormalizedAuditRecord|contentHash|nearest" apps packages scripts --include=*.ts --include=*.tsx
grep -rnwE "create(AuthLimiter|JwksClient|KdfGate|Pkce|TotpReplayGuard|CacheStack|CdnTier|LruTier|MemoTier|RedisTier|ClientFlight|Context|Fence|FlightGate|Logger|Raster|SingleFlight|Pipeline|RateLimiter|RequestContext|Router|Server|Translator|JobsFacade|Limiter|OutboxRelay|Pacer|Scheduler|StepRunner|Worker|Outbox|SyncNode|Entry|FrameRouter|Replicator|DevServer|McpServer|InstallController|IslandCollector|IsrController|Gateway|PgliteClient|RecordingClient|FocusTrap|FormBinding|RovingTabindex|ToastStore|TestClock|TestMail|TestNetwork|TestStatements|RunJobs|LiveNode|TemplateSql|ArtifactWriter|Prompt|Ring|RobotsGate|SecretBag|UsageMeter)|CreateGatewayInput|AnthropicProvider|EchoProvider|HashEmbedder|RemoteEmbedder|BudgetLedger|McpServer|Builder|authLimiter|totpReplayGuard" apps packages --include=*.ts --include=*.tsx
grep -rnwE "PgDriverOptions|MemoryDriverOptions|PgEventBusOptions|PgOutboxOptions|MemoryOutboxOptions|PgLeaseLeaderOptions|CreateMcpServerInput|CreateDevServerInput|PgVectorStore(Input)?|BuiltinAdapter|MemoryAdapter|Pg(DigestStore|InboxStore|DeliveryLedger)(Options)?|MemoryLedgerOptions|InMemoryAdvisoryLock|InMemoryChangeFeed(Options)?|PgAdvisoryLock(Options)?|PgLogicalReplication(Feed|Options)" apps packages --include=*.ts --include=*.tsx
grep -rnwE "BUILD_ID_HEADER|IDEMPOTENCY_HEADER|pluralize|toBucket|admitsAnonymous|policyPermissions|policyCapability|actorOf|CursorInvalidError|MAX_PAGE_SIZE|derivePath|escapeHtml|readCookie|REQUEST_TIMEOUT_HEADER|isCanonicalWebhookField|WEBHOOK_[A-Z_]+|webhook(Headers|Mac|Signature|SigningString)|Webhook(Mac|Signing)Input|classifyThrown|fnv1a|localeInvalid|timingSafeEqual|DEFAULT_LOCALE|directionOf|isRtl|localeSegment|FRAMEWORK_CATALOG_LOCALE|MAIL_CATALOG_LOCALE|MAX_SEMANTIC_CACHE_SCOPES|FlightJoin|SingleFlight|TIER_ORDER|formatBytes|RENDER_MODES|HYDRATE_STRATEGIES|OFFLINE_STRATEGIES|THEME_STORAGE_KEY|CLIENT_[A-Z_]+_META|NAVIGATION_(SURFACE_|SCOPE_|LOCATION_)?HEADER|tagKeys|BUILD_ID_META|APP_UPDATE_AVAILABLE|REDACTED|(SQL_)?ADMIN_AUDIT_TABLE|isJsonObject|ANY_HOST|host(Decision|Matches)|Host(Rule|Decision)|throwIfAborted|DEFAULT_CONTENT_TYPE|formatIssues|MAX_MONEY_SCALE|ULTIMATE_ERROR_BRAND|JOB_STATES|TEST_TYPES" apps packages --include=*.ts --include=*.tsx
grep -rnwE "forbidden|unauthenticated|json|text|validate|money|isEnabled|describeRoutes|channel|guard|guardBeforeInput|resetRegistry|explain|actorLabel|allowed|denied|relationsFor|invariant|InvariantOptions|uuid|cacheKeyFor|DEFAULT_RETRY|NO_TENANT|chunk|normalize|tokenize|introspect|isDestructive|assertNoDrift|safeUrl|resetIdCounter|srcsetFor|frozenClock|testClock|sessionExpired" apps packages --include=*.ts --include=*.tsx | grep "@ultimat3/"
grep -rnwE "McpExposure|QueryMcp|QueryMcpAnnotations|QueryListParams|QueryListFilterOp|McpAnnotationHints|ListPage|truncated|counted" apps packages --include=*.ts --include=*.tsx
grep -rnE "(Deprecation\w*|recordDeprecatedCall|renderDeprecation|createClientFlight|DEFAULT_CLIENT_RETRY|isSuperseded|isTransientFailure|(get|set|reset)AuditSink).*from '@ultimat3/(action|query)'" apps packages --include=*.ts --include=*.tsx
grep -rnE "\.tool\(\)|toMcpTools?\b|toQueryTools?\b|isExposed\b|toolFromAction|toolFromQuery|McpToolDescriptor|McpInvokeOptions|QueryTool(Descriptor|ReadOptions|Answer)" apps packages --include=*.ts --include=*.tsx
grep -rnwE "endCursor|hasNextPage|SQL_[A-Z_]+|AuditRecord|IdempotencyStore|WebhookLedger|ChangeEvent|DigestAppend|DigestStore|FakeElement" apps packages --include=*.ts --include=*.tsx
grep -rnE "Page\b.*from '@ultimat3/entity'|bearerMount\(|scopes:|buckets:|aria-controls|robots:|fakeBrowser\(|fixtureBrowser\(" apps packages --include=*.ts --include=*.tsx
grep -rnwE "DEFAULT_MODEL|ANTHROPIC_MODEL_IDS|OPENAI_MODEL_IDS|registerOpenAiModels|AnthropicProvider|EchoProvider|defaultModel|registerModel" apps packages --include=*.ts --include=*.tsx
grep -rnE "cdp-url|x shot|jobs drain|jobs (ls|list)|data\.next\b" .github scripts package.json bin
grep -rnE "jobs\.concurrency|startE2eApp\(|jobWorker\(|ResolveOAuthGrants|resolveGrants" apps packages --include=*.ts --include=*.tsx
grep -rn "WORKER_QUEUES" . --include=*.yaml --include=*.yml --include=*.env --include=Dockerfile* --exclude-dir=node_modules

The typecheck step finds 3–7, 10, 11, 13–18, 20, 21, 23, 25–28, 31–34, 37 and 40 at the import or the member, and 2 in a typed app.config.ts. Entries 2 and 8 also refuse at the first import of the config (X_CONFIG_INVALID), 19 at boot, 9 at route registration and 22 and 24 at the first model call. The manifest step finds 11's stale openapi.json and 35's stale .env.example, boundaries finds 30, and the unit and e2e suites find 27. Nothing finds 12 — a bucket keyed by an action's name is read by nobody — or 29 until a capture runs; read each configureHttp for 12 and each x shot --cdp-url caller for 29. A shell script that parses x jobs list --json (7) or passes --to to x jobs drain (15) fails only when it runs: the last grep above finds it. Nothing finds 36 but an e2e that reads the external database's rows; the startE2eApp grep finds each caller. Nothing finds 38 or 39 either — a worker leaves an unlisted queue's jobs ready with one boot warning, and a hand-built jobWorker simply runs more at once — so the last two greps list every WORKER_QUEUES and every jobWorker( to read. Entry 1 is x doctor's first line.

Edit this page on GitHubThis page as markdown