Ultimate

29.x → 30.0.0, entry by entry

Released 2026-11-07: CHANGELOG.md's ## 30.0.0 breaking entries in their order, grouped by package, lowest tier first. No legacy path, no codemod, no shim. Rows are never renumbered; each row's CHANGELOG.md line names it as (#N) after the dash.

The upgrade, top to bottom

#DoWhat you see until you doEntries
1commit, then bunx @ultimat3/cli@latest upgrade --json: it pins every @ultimat3/*, raises the Bun floor, installs and runs the gateX_UPGRADE_DIRTY_TREE on a dirty tree—
2x db gen "same-tenant keys", commit packages/db/migrations and packages/db/schema/. A validate failing X_DB_FOREIGN_KEY_VIOLATION found rows already pointing across tenants: decide those rows, or state crossTenant: '<reason>' on an edge that means itX_DB_DRIFT from drift, and X_SCHEMA_DUMP_DRIFT for the new framework columns. An entity whose { table } differs from its name moves its keys to the table they always meant in the same migration. X_DB_DRIFT from a dump column no snapshot records is an edited migration: restore it8, 25, 46, 53
3x verify --only typecheck and close every error: a Date off a step result, step.waitForEvent with no schema, a hand-written TotpReplayGuard, DeliveryLedger, JobDriver or StorageDriver, a hand-built ReferenceDescription or PoolProfile, a Record<StageName, …>, 'queue-depth', REPLAYED_HEADER from @ultimat3/action, requestedUrls(), suspenseBoundaries, csrfBlocked with two arguments, a read of DownloadFile.body, ERROR_PAGE_LINKS.homepage, FormSchema or its siblings from @ultimat3/ui, JSX_NODE from @ultimat3/render, a hand-written JobHandle or JobsFacade, an exhaustive switch or Record over ErrorSource, a hand-built AppConfig, retentionPlan / RetentionPlan / Deploy from @ultimat3/pwa, retainBuildIds passed to generateServiceWorker, input.prompt off a DefineEvalInput, MCP_PROTOCOL_VERSION, an exhaustive switch over McpAuthRefusala compile error at each site8, 16, 21, 22, 25, 30, 32, 35, 39, 40, 43, 44, 45, 47, 50, 57, 58, 61, 62, 65, 66, 67, 68, 69, 70, 73
4x verify --only manifest: correct a misspelt name in mcpConfirmations({ tools }), or expose the action, or build a hand-written server at module scope in apps/<app>/mcp.ts. For a role load, add the import its fix names to apps/web/api/index.ts, or move the declaration into a module that imports no .tsx and no stylesheetX_MCP_CONFIRMATION_TOOL_UNKNOWN, X_ROLE_LOAD_INCOMPLETE18, 27
5x verify --only unit,contract,job: move each fixture the new refusals nameX_DB_TRANSACTION_CLOSED, X_INVARIANT_VIOLATED, a memory-driver fixture wiring two tenants refused, X_N_PLUS_ONE_QUERY from a memory-backed loop now counted, X_MAIL_KEY_MISSING from a mail test, an enqueue spy on a spread memoryJobDriver() that enqueueMany or the relay no longer calls3, 4, 8–14, 19, 20, 41, 48, 63
6grep -rnE "render: '(isr|static)'" apps/: a page varying per visitor becomes ssr, and every parameter a load or page reads goes in revalidate: { query: ['<name>'] }nothing fails: a signed-in load sees nobody, and a query the key dropped serves one document for every value23, 24
7grep -rnE "upsertAll|seed\.upsert" apps/ packages/: name each column a collision must overwrite. A wide insertAll that relied on partial progress splits its own batchnothing fails: a timestamp stops moving on re-upsert, or a failed batch leaves no rows11, 12, 13
8read mcpConfirmations({ tools }) against every agent({ tools }) and every MCP server: the agent's output reports the pending confirmation id, and a tool that must run unconfirmed leaves toolsthe write answers X_MCP_CONFIRMATION_PENDING and writes nothing17, 26
9before the next column drop: declare it retired, deploy, then dropX_MIGRATION_ROLLING_UNSAFE from x db gen5
10before the deploy: move a CDN rule keyed on public, immutable for /media; re-upload through the server an XML or +xml object over 64 KiB that awaits promotiona CDN miss; X_STORAGE_TYPE_REJECTED at promotion6, 7, 15
11grep -rnE "onShutdown|retry\(" apps/: reorder a close hook that depends on running first; wrap a retry or a write that must outlive its caller in runToCompletion(fn)nothing fails: a write or a retry stops with its caller1, 2, 3
12grep -rn "'null'" apps/ packages/, and read each list built from URL.parse(…).origin: list real origins with their schemeX_CORS_CONFIG_INVALID at boot28
13read each HeadTag with a non-http(s) href: serve an inline SVG favicon from site.favicon, or a same-origin path under assets/nothing fails in production; head.attribute_dropped once in dev29
14x manifest, commit openapi*.json and x.manifest.json, once, after every other editX_MANIFEST_STALE from manifest38, 42, 49
15x verify --only manifest for X_JOB_OUTLASTS_DRAIN: raise drain.workerRetireMs or lower the job's timeoutX_JOB_OUTLASTS_DRAIN naming the job, its limit and the budget52
16x verify --only i18n, then x i18n sync <locale> per locale it names, and translate what it seeds. Add the framework's mail.* keys meta.keys lists to packages/i18n/catalogs/<locale>.json by hand. A subject interpolates only the payload's top-level scalars: put each {slot} thereX_CATALOG_MISSING_KEYS; X_MAIL_KEY_MISSING from send()33, 34, 48
17x verify --only boundaries: move each packages/* file's runtime import of apps/* down. A scaffold from before 30.0.0 has two: packages/mcp/src/index.ts (export const appMcp = () => defineAppMcp({ name, include: 'exposed' }), no import of apps/web/api/health) and packages/db/src/seed.ts (const DEMO_ORG_LABEL = 'org:demo';)X_BOUNDARY_PACKAGE_TO_APP55
18x g query / x g backfill take --feature <slice>; a new table is x g resource <name>X_FEATURE_REQUIRED, zero files written54
19every upload action's input carries size: file.size; every disk.signedUrl(key, { method: 'PUT', maxBytes }) on s3 adds contentLength: file.size; S3_FORCE_PATH_STYLE is 0, 1 or blank in every environmentX_STORAGE_UPLOAD_SIZE_UNDECLARED; X_CONFIG_INVALID at boot; a 403 SignatureDoesNotMatch for a client PUTting another length36, 37, 56
20before the deploy: the autoscaler adapter gains max by (queue) (queue_demand) and an app's own chart its worker HPA metric; set drain.workerRetireMs (or 0 for the old stop) and re-deploy with x deploy --method helm. A scaffolded chart's preStop arrives by editing docker/helm/templates/_helpers.tpl: const end = Date.now() + {{ int $cfg.retireSeconds }} * 1000; for (let n = 1; Date.now() < end; n++)the HPA reads a metric nothing publishes; an old worker Terminating up to an hour while it holds a job30, 51
21before the deploy: a transaction's slow outbound await moves before withTransaction; a pod behind a pooler sets DATABASE_POOLER and both bounds with alter role, as X_DB_POOLER_OPTIONS_REFUSED prints; size DATABASE_POOL_MAX from the pool gaugesX_DB_STATEMENT_FAILED (25P03) on an idle transaction; X_DB_POOL_EXHAUSTED where a statement used to queue31, 32
22x verify --only budgets: it now builds when .x/build-stats.json is missing or older than your source. A route it finds over budget: shrink it, or raise the budget by the measured figure with the reason. Raise stepTimeoutMs.budgets in x.verify.json if the build outlasts the 5-minute step deadlineX_BUDGET_EXCEEDED on a route stale stats hid59
23optional: delete bin/check.ts and the check script, and run bun run verify (x verify) locally and in CI. A script that killed x dev by pid or pkill calls x stop --jsonnothing fails: bun run check still works and does not build twice60
24a compose deploy of an app scaffolded before 30.0.0: in docker/docker-compose.prod.yml's x-image block, tmpfs: [/tmp, '/app/.x:uid=1000,gid=1000'] (1000 is the USER of docker/Dockerfile), then x env init --production if the app has no .env.productionX_STORAGE_UNWRITABLE … EACCES at boot on every role; X_DEPLOY_ENV_MISSING before the first step—
25grep -rn "outboxRelay(" apps/ packages/: a relay that runs the row loop (a driver without enqueueMany, such as redisJobDriver) against very slow publishes passes batchSize: 100nothing fails: 500 rows must publish inside the 30 s claim lease, or they are claimed again64
26optional, a repository that copies ci.yml's tested job (CI-Parallel-Gate): give each reused job, the verdict job included, needs: tested and if: ${{ !cancelled() && needs.tested.outputs.tested != 'true' }} and no other ifwithout !cancelled(), a red or cancelled tested skips every job instead of running them—
27an MCP client: a browser page on another origin calling POST /mcp lists that origin in http.cors.origins; a script reaching x mcp serve through a LAN name or a tunnel uses localhost; a test pinning initialize's 2025-06-18 answer offers that revision403 X_MCP_ORIGIN_REFUSED; 421 X_MCP_HOST_REFUSED; protocolVersion: '2025-11-25'71, 72, 74
28x verify --only typecheck: add createRoot to each setSolidRuntime call, ui.first to a Record<UiKey, string>, errors to a hand-built descriptor, raw to a hand-built WorkloadToken; drop each read of QueryPoll.ms. Add failureCapture and time to a hand-built AppConfig, detectedCookie to a full TimeZoneConfig, queryGraph to a DevHost, mailTemplates to a hand-built DevSources, and the caller to each direct read of an MCP resource. Add the 48 keys entry 122 lists to a Record<UiKey, string>, a null check to each read of ChartPoint.value and each barRects() rect, none to a direct chartTable() call, and async to a hand-built refetch; make an interface that extends OpenGraph, BarChartProps or TabsProps a type … = … & { … }TS2345, TS2741, TS2339, TS2554, TS18047, TS2312, TS2322, TS237575, 77, 82, 83, 86, 111, 114, 115, 116, 117, 119, 121, 122, 123, 125
29x verify --only contract: give each guarded action and query an exercise, and fix what its generated input finds. A keyset list that drew a disabled Previous takes firstHrefX_CONTRACT_EXERCISE_UNDECLARED, X_CONTRACT_DEFECT, X_CONTRACT_EXERCISE_UNREACHED, X_UI_INVALID_VALUE76, 78, 80, 81
30grep -rn "refresh()" apps/: a caller that wanted focus on <main>, the title read out, or a navigation cancelled says so itselfnothing fails: focus stays where it was79
31x verify --only e2e: name what the audit lists — visible text, aria-label, <label for>, alt (alt="" for a decorative image), a <title>, <html lang>, nothing focusable under aria-hidden — or give that test { a11y: { allow: [{ role, selector, because }] } }. A raw-tab test (browser.session.newTab()) names what it lists too, or calls allowA11y([{ role, selector, because }]) before the navigation. A hand-written E2eBrowserPage implements or forwards accessibilityTree(request)X_E2E_A11Y_VIOLATION naming each element; X_TEST_FIXTURE_UNAVAILABLE from every e2eTest on a page without the method88
32x verify --only unit,contract: a test that asserted a factoriesFor value by its old name rule asserts the declared one, or builds it with defineFactory; a test that relied on a memory write surviving a failed memoryTransactor().run writes it outside the transactionan assertion on an old name-derived value (<column>-<index>, new Date(0), 'USD' in a …Currency text column) fails; X_SEED_GRAPH_UNSATISFIED; a row gone after a rolled-back transaction89, 90
33x exercise --json: paste each printed fix: entry into apps/web/exercise.ts's defineExercise({ … }) — an actor its policy allows, an input binding a real row — or give the primitive { off: '<why>' }; then x exercise <name> --json for that one. An app with no apps/web/exercise.ts writes import { defineExercise } from '@ultimat3/testing'; export const exercise = defineExercise({}); firstX_CONTRACT_EXERCISE_UNREACHED from the exercise step, one per primitive91
34for each entity exercise names: orgId: uuid().tenant() in its columns, x db gen '<table> tenant', and on a table that already holds rows x g backfill <name> --feature <slice> to fill it; or entity('<name>', { shared: '<why it belongs to no tenant>' })X_ENTITY_TENANT_UNDECLARED from the exercise step92
35x verify --only unit,contract,job: await every write inside a memoryTransactor().run; a test that asserted an onCommit effect right after a nested run asserts it after the root'sX_DB_TRANSACTION_CLOSED; an effect not yet run where the old one had run93
36x db gen, commit packages/db/schema/; x verify --only seo and add each description it names (or seo: { llms: false }); then x exercise --json and close each plan finding with the indexes: line and x db gen it prints, or a stated scan, expectedSeqScan or expectedQueryLoop reasonX_SCHEMA_DUMP_DRIFT; X_SEO_LLMS_DESCRIPTION_MISSING; X_QUERY_UNINDEXED, X_QUERY_SORT_UNSERVED, X_QUERY_SCALES_WITH_ROWS112, 113, 118
37x verify --only unit,contract,e2e: mock each module in the file that reads it, and move a test pinning a palette order, an empty chart gap cell or deltaOf(x, 0) === undefined to the new answer. Then x build --target static: put each image a finding names under apps/web/site/assets/ and pass asset('assets/<file>'), or drop avif from that route's formats. Read each served responsiveImage() over asset() for its own widths or formats, and each defineTheme() brand for chart-1 and chart-5 against bg-soft. A site route whose prerender() or load reads the app's own query passes entries: report.entries, llmsPages: report.llmsPages to siteSeo({ … }) in apps/web/prerender.tsX_TEST_MODULE_MOCK_UNRESTORABLE, or a test that reads the real module; X_UI_INVALID_VALUE; X_IMAGE_SOURCE_UNREADABLE, X_IMAGE_UNSUPPORTED; 404 X_ASSET_VARIANT_UNDECLARED; X_UI_CONTRAST_INSUFFICIENT; X_CLIENT_TRANSPORT_FAILED from the export120, 124, 126–130
38x verify --only boundaries: end each .island.tsx's mount with return render(() => <Island {...props} />, el) and type it (): () => void; a plain-DOM island returns a function that removes what it addedX_ISLAND_DISPOSER_MISSING131
39x verify --only typecheck,boundaries,contract-diff: give an out-of-tree AuthAdapter the nine PasskeyStore methods; read a function limit from descriptor.rateLimitPerCall; pass decide the signature and give a custom McpConfirmation its quorum, approvers, approvals; move a hand-rolled websocket to deviceSocket({ path, resolveToken, onFrame }); then x manifest and commit the new meter fieldTS2420, TS2339, TS2554; X_SOCKET_HAND_ROLLED; X_MANIFEST_DRIFT132–136
40give a custom PushDeviceStore noteBadToken and clearBadToken and pass TokenCache.invalidate the refused token; with history: true on Postgres, x db gen "history exact images", and parse a history reader's bigint/decimal value as a stringTS2420, TS2554; X_DB_DRIFT until the migration exists137, 138
41pass the grant's server-side policy to every signPart and completeUpload; give a hand-written LiveHandle retry() and a custom event bus's find the event id and afterId; replace enqueueInTx with handle.enqueue; a client calling with cached Basic credentials across sites sends a Bearer token or comes from a listed originTS2345, TS2741, TS2305; X_INVARIANT; X_CSRF_BLOCKED139–143

Entry by entry

Tier 0 — @ultimat3/core (1–2). Tier 1 — @ultimat3/db (3–5), @ultimat3/storage (6–7). Tier 2 — @ultimat3/entity (8–14), @ultimat3/http (15), @ultimat3/auth (16). Tier 3 — @ultimat3/action (17–20), @ultimat3/jobs (21–22). Tier 4 — @ultimat3/render (23–24), @ultimat3/notify (25). Appended after the tier order: @ultimat3/mcp (26–27), @ultimat3/http (28), @ultimat3/render (29). Appended by the second wave, by tier: @ultimat3/core (30), @ultimat3/db (31–32), @ultimat3/i18n (33–34), @ultimat3/storage (35–37), @ultimat3/http (38–40), @ultimat3/entity (41), @ultimat3/query (42–43), @ultimat3/action (44), @ultimat3/jobs (45–46), @ultimat3/render (47), @ultimat3/mail (48), @ultimat3/manifest (49), @ultimat3/testing (50), @ultimat3/cli (51–56). Appended by the third wave, by tier: @ultimat3/http (57), @ultimat3/ui (58), @ultimat3/cli (59–60). Appended by the wave 3 gate fix: @ultimat3/render (61). Appended by the fourth wave: @ultimat3/jobs (62–64), @ultimat3/core (65–66), @ultimat3/pwa (67–68), @ultimat3/ai (69), @ultimat3/mcp (70–73), @ultimat3/cli (74). Appended by the fifth wave: @ultimat3/ui (75–78), @ultimat3/render (79), @ultimat3/action (80–82). Appended by wave 5b: @ultimat3/auth (83), @ultimat3/cli (84), @ultimat3/mcp (85), @ultimat3/realtime (86), @ultimat3/core (87). Appended by wave 6a: @ultimat3/testing (88–89), @ultimat3/entity (90). Appended by wave 6b: @ultimat3/cli (91), @ultimat3/entity (92–93). Appended by wave 7: @ultimat3/cli (94), @ultimat3/testing (95), @ultimat3/auth (96–97), @ultimat3/jobs (98–100), @ultimat3/scraping (101), @ultimat3/db (102), @ultimat3/ai (103). Appended by wave 8: @ultimat3/cache (104), @ultimat3/db (105–106), @ultimat3/core (107), @ultimat3/db (108), @ultimat3/cli (109–110). Appended by wave 9: @ultimat3/core (111), @ultimat3/db (112), @ultimat3/seo (113), @ultimat3/http (114), @ultimat3/mcp (115–116), @ultimat3/admin (117), @ultimat3/cli (118). Appended by wave 10: @ultimat3/seo (119), @ultimat3/auth (120), @ultimat3/realtime (121), @ultimat3/ui (122–127), @ultimat3/testing (128), @ultimat3/cli (129–131). Appended by the issues PR (#990–#999): @ultimat3/auth (132), @ultimat3/action (133), @ultimat3/mcp (134), @ultimat3/manifest (135), @ultimat3/cli (136). Appended by sweep 1: @ultimat3/notify (137), @ultimat3/db (138). Appended by sweep 2: @ultimat3/storage (139), @ultimat3/realtime (140), @ultimat3/http (141), @ultimat3/jobs (142–143).

#SurfaceCosts you an edit if
1close-phase onShutdown ordera close hook relies on running before one registered after it: hooks now run last-registered first, so register in the order you want released. accept and inflight are unchanged
2retry()a retry must outlive its caller: it now stops when the ambient ctx.signal aborts and never waits past ctx.deadlineAt. Write runToCompletion(() => retry(work, policy)). Outside a request context nothing changes
3a write past requestTimeoutMs or a job's cancellationthe work must land anyway: it now rolls back. Wrap the whole of it, runToCompletion(() => withTransaction(fn)): wrapped inside the transaction, the statement lands and the COMMIT is still refused. It drops the deadline for what runs inside it, so give a raw fetch in there its own. A keyed idempotent action already runs in that scope, up to configureIdempotency({ scope, ceilingMs }), twice requestTimeoutMs by default; a handler past it is aborted and its key replays X_IDEMPOTENCY_RUN_CEILING
4a statement on a finished DbTxone outlives its scope: it is X_DB_TRANSACTION_CLOSED, not an autocommit. await it inside withTransaction(fn), or use outsideTransaction(fn) for work that must outlive the scope
5dropping a columnyou drop in one release: set retired: ['<c>'] and x db gen "retire <c>", deploy, then remove it and x db gen "drop <c>" --allow-destructive. One instance that stops before it starts: --rolling-unsafe
6promoteAttachmenta pending object's bytes do not match its type: it reads the first 64 KiB and refuses. An XML or +xml object over 64 KiB refuses too: upload it through the server. An object overwritten during promotion refuses X_STORAGE_OBJECT_CHANGED: promote again once the upload has finished
7the content sniffa file opens with over 4,096 B of whitespace: refused everywhere. Trim it before upload
8references() between two tenant-scoped entitiesalways once: x db gen "same-tenant keys" adds one unique index per referenced table and swaps the key online. A row already crossing tenants fails the validate: decide it, or state crossTenant: '<reason>' on an edge that means it, including two tenant columns meaning different things. A memory test wiring a parent and child across tenants moves its fixture. onDelete: 'set null' needs Postgres 15+. A null tenant column skips the check. A hand-built ReferenceDescription needs sameTenant: null and targetTable. An entity declared with a { table } that differs from its name, as a target: X_DB_DRIFT until x db gen
9update, updateWhere, transitiona value insert would refuse reached them: it is refused before the statement, not as X_DB_STATEMENT_FAILED, and a number written to a text column is an error, not a coercion. Pass the column's type
10orderBy on a json, bytes or array columnyou order by one: refused at plan time (it paged wrongly or threw past page 1). Order by a scalar
11db.x.upsertAll, seed.upsertyou relied on a collision resetting unnamed columns: name the column, { ...row, label: 'default', createdAt: now }. A timestamp expected to move on re-upsert (reviewedAt) is named or declared onUpdateNow(). Both X_INVARIANT_VIOLATED: rows naming different columns (give each row the same columns), and rows naming only the conflict target or primary key, which answer would write nothing: db.blocks.upsertAll([{ blockerId, blockedId }], { onConflict: ['blockerId', 'blockedId'] }) takes onMatch: 'nothing' when "already there" is fine, or a named column to overwrite
12repo.upsertAll on a hand-held repositoryone row holds label: undefined and another a value: X_INVARIANT_VIOLATED. Give every row a value, or none
13a wide insertAll / upsertAllyou relied on partial progress when one failed: it now leaves nothing. Split the batch yourself and call once per part
14a soft delete under a JS-only invariantthe stamped row breaks the rule: refused on Postgres too, and deleteWhere past MAX_ASSERTED_ROWS matched rows is refused. Relax the invariant for the stamped row, or delete in smaller where sets
15/media on a shared keya CDN rule keys on public, immutable: it misses from now on. Serve a public image from apps/web/site/assets/ via asset(), or from an app route using objectResponse. Existing variants under old keys go unread and are re-minted once
16a custom totpReplayyou wrote one: replace isUsed/remember with claim(subject, step, at): Promise<boolean>, answering true the first time, false for a replay, and rejecting when its store is down. A framework-booted app needs no edit: drop the option and the boot installs postgresTotpReplayGuard, whose table x_totp_steps is applied at boot (ROLE=migrate on an external database)
17a tool in both mcpConfirmations({ tools }) and agent({ tools })an agent calls it: it now waits for a person. Have the agent's output report the pending confirmation id, or remove the tool from tools to let the call run unconfirmed. With two deciders on one tool, the first registered answers in-app
18a decision action's module that reaches a documentmanifest goes red with X_ROLE_LOAD_INCOMPLETE: add the import its fix names to apps/web/api/index.ts, or move the declaration into a module that imports no .tsx and no stylesheet
19asProjectableAction(handBuilt)you compare by identity: it returns a copy behind the gate, and projecting that copy, or a spread of it, passes the gate once. Compare a hand-built tool by name
20a failed agent tool's texta test pins the rendered (fix: …) of an error that declares callerFix: the model now sees callerFix. Move the assertion with it
21step.run's answeryou read a value JSON changes: a Date is a compile error at the read, so write new Date(value); a miss stored from undefined is null, so compare === null; a value JSON cannot hold (a bigint, a cycle, NaN, an infinity, an invalid Date, a NUL, a lone surrogate) is X_STEP_OUTPUT_UNSERIALIZABLE; a keyed action answering such text is X_IDEMPOTENCY_OUTPUT_UNSERIALIZABLE. A step's object keys read in jsonb's order on every attempt. Runs in flight across the upgrade replay as they always did
22step.waitForEventyou wait on an event: schema is required and <T> is gone. Pass a schema
23isr and static routesa load reads the actor, a cookie, Authorization or any other inbound header: it now sees none, ctx.ip and ctx.peer are null, ctx.tz is the app's default, and ctx.url is the document's address, with no undeclared query. A page that varies per visitor is render: 'ssr'. With no declared origin, a process stores isr pages for 16 request origins: set APP_URL if you serve more
24an isr route with no revalidate.queryits load or page reads a query parameter: the route now serves one document for every value, and nothing can see that from the key alone. List it in revalidate: { query: ['<name>'] }
25x_notify_deliveries, DeliveryLedgeralways, for the column: x db gen, commit packages/db/schema/. An app with its own ledger also takes the table below. A channel whose send can outlast five minutes sets reclaimAfterMs above its slowest send, or a reclaimer overlaps it
26a tool in mcpConfirmations({ tools }) on a server you handed no confirmationsa caller relied on it running at once: it now waits. To let a call run unconfirmed, remove the tool from tools
27a name in mcpConfirmations({ tools })no exposed action, exposed query, MCP tool or in-process tool answers to it: X_MCP_CONFIRMATION_TOOL_UNKNOWN at manifest. Correct the spelling, or expose the action. A hand-written tool counts once its defineAppMcp has run when the app loads, so build that server at module scope in apps/<app>/mcp.ts
28cors.origins containing 'null'you list it, or build the list from URL.parse('localhost:3000').origin (no scheme): X_CORS_CONFIG_INVALID at boot, with or without credentials. List real origins with their scheme: configureHttp({ cors: { origins: ['http://localhost:3000'] } }). A public, credential-free API uses origins: ['*'], credentials: false
29a HeadTag hrefa link carries data:image/svg+xml, any other data: type, or a scheme outside http, https, mailto, tel, sms: it prints no href (android-app: and ios-app: stay on rel="alternate"; a raster data:image/ stays). Serve the file: site.favicon for an icon, or a same-origin path under assets/. head.attribute_dropped names the tag and attribute in dev
30the ScalingSignal 'queue-depth'you name it, or an adapter rule reads queue_depth for the worker HPA: it is 'queue-demand', SCALING_METRICS['queue-demand'] is queue_demand. Add the adapter rule max by (queue) (queue_demand). queue_depth stays, the ready-only gauge for a backlog alert
31a plain statement behind a full poolyou relied on it queueing past acquireTimeoutMs: it answers X_DB_POOL_EXHAUSTED. Raise DATABASE_POOL_MAX or fix the slow statement. A statement unanswered past the role's bound plus 5 s is X_DB_UNAVAILABLE and the pool is replaced; an autocommit write may have landed, and a job worker re-runs that attempt, so make such a write idempotent
32a transaction left idle, PoolProfilea transaction awaits slow outbound work: past 30 s (web, sync) or 120 s (worker, scheduler) the server closes it, X_DB_STATEMENT_FAILED (25P03). Do that work before opening it. A hand-built PoolProfile adds idleInTransactionMs. Behind a pooler, set DATABASE_POOLER and both bounds with alter role
33keys only a mail or a push namesx verify's i18n step reports them: x i18n sync <locale>, then translate
34an en key another locale lacksalways, for a key reached only through a template literal: x i18n sync <locale>. A ⟦…⟧ marker over such a key stays missing until replaced
35a third-party StorageDriveryou wrote one: implement streamRange(key, [first, last]), reading no byte before first, and putStream(key, stream, { maxBytes, … }), storing nothing past maxBytes
36grantUploadyour own action builds the grant: its input carries size: file.size (uploadFile already sends it), or X_STORAGE_UPLOAD_SIZE_UNDECLARED. grant.maxBytes is now that size, not the policy's; an s3 URL accepts exactly that many bytes, so another length is 403 SignatureDoesNotMatch
37disk.signedUrl(key, { method: 'PUT', maxBytes }) on s3you call it: refused. Add contentLength: file.size, or drop maxBytes for a URL that binds nothing
38the problem document's retrya golden test pins a whole body, or a middleware adds retry: errorRetry(error): the member is on every document, 401 and scope 404 included. Add it to the test; delete the middleware. x manifest regenerates openapi.json's Problem
39PIPELINE_STAGES, StageNamea Record<StageName, …>: add compress. A handler whose answer must not be coded sets no-transform
40csrfBlocked(pathname, reason, ip, origin)you call it: pass the request's ip and origin
41memoryRepo and the statements fixturea memory-backed test that uses statements loops: it now fails X_N_PLUS_ONE_QUERY / X_N_PLUS_ONE_WRITE. preload(), or expectedQueryLoop('<why>', …)
42a read's published rows:an app declaring rows: finds openapi*.json stale: x manifest, commit. Later, a member removed, retyped or made optional fails contract-diff with X_MANIFEST_BREAKING until the app's major moves
43DownloadFile.bodycode reads the field rather than handing it over: narrow the function form, (range?) => BodyInit, whose size is required
44REPLAYED_HEADERimport { REPLAYED_HEADER } from '@ultimat3/action': import it from @ultimat3/core
45a custom JobDriverimplement demand(): Promise<readonly QueueDemand[]>: per queue, the running rows not held by a worker whose live registry row says it drains, plus the due ready/delayed/suspended rows of an unpaused queue; list only queues above 0, sorted by code unit
46x_job_workers.buildalways, for the dump: x db gen, commit packages/db/schema/
47suspenseBoundaries, ModeSpec.needsSuspenseyou pass or read them (TS2353 / TS2339): delete the key or the read. To stream, wrap the slow read: <Hole fallback={<Skeleton lines={3} />}>{readRows}</Hole> from @ultimat3/render
48send()a mail holds a key the locale lacks, a catalog value still ⟦…⟧, or a {slot} the payload's top-level scalars do not carry: X_MAIL_KEY_MISSING, a framework mail.* mail included. Add the key, translate it, or put the field at the top level
49QueryFact.rows, Manifest.mountsan app declaring rows: or a mount: x manifest, commit. Later, a name dropped from a mount's scopes or a changed prefix fails contract-diff with X_MANIFEST_BREAKING until the app's major moves
50requestedUrls()you call it: requestLog().map((call) => call.url) answers the same list. NetworkSnapshot.seen is NetworkSnapshot.log
51the worker retirealways, on a chart: a rollout leaves an old worker Terminating while it holds a job, for up to retireSeconds. drain.workerRetireMs: 0 keeps the old stop. An app's own chart keeps its values until x deploy sets the retire
52a job's timeoutit outlasts the worker's budget: X_JOB_OUTLASTS_DRAIN at manifest. Raise drain.workerRetireMs, or lower the timeout
53packages/db/schema/04_tables/ against the newest snapshota column one holds and the other does not: an edited migration. Restore the file, then x db gen
54x g query, x g backfillyou named no slice: pass --feature <slice>, or x g resource <name> for a new table
55a packages/* runtime import of apps/*you have one: move the code down. import type passes, and packages/db/src/ may read an entity module
56S3_FORCE_PATH_STYLEit holds anything but 0, 1 or blank: X_CONFIG_INVALID at boot. Write 1
57ERROR_PAGE_LINKS.homepageyou read it: it is removed, and the default error page links only the repository. ERROR_PAGE_LINKS.repository is unchanged. An app that wants its own links ships apps/web/site/errors/<status>.html, as before
58FormSchema, FormValidationResult, FormValidationIssue, formatFieldPath, IssuePathSegment from @ultimat3/uiyou import one: take StandardSchemaV1, StandardResult<unknown>, StandardIssue, formatPath, StandardPathSegment from @ultimat3/schema. A hand-written schema handed to formBinding({ schema }) adds version: 1 and a vendor beside validate. parseFieldPath, fieldSelector and MAX_FIELD_INDEX are unchanged
59x verify's budgets stepyour stats were stale: it builds first (x build --target static --no-preflight --out .x/verify-static) and weighs fresh bytes, so a route stale stats hid is red. Shrink it or raise its budget by the measured figure; x verify can be one static build slower after an edit (8.2 s on dummy/social-media-clone)
60bin/check.ts, bun run checka script or CI step expects a new scaffold to have them: run bun run verify. An existing app keeps its own; it still works
61JSX_NODE from @ultimat3/renderyou import it: import { JSX_NODE } from '@ultimat3/core/render-node', the same global symbol. HOLE_NODE and ISLAND_NODE live there too
62a hand-written JobHandle, JobsFacade or JobDriveryou wrote one: each gains a required member. JobHandle.enqueueMany(inputs, options?) (a fake built from job() has it already). JobsFacade.enqueueMany(handle, inputs, options?), or build the facade with outboxJobsFacade(). JobDriver.enqueueMany(requests): answer each request as enqueue would, in order, in one round trip, opening with assertEnqueueMany('<driver>', requests); a driver with no batch primitive loops its own enqueue. It refuses a request carrying onConflict: 'error' (X_INVARIANT): send that one alone through driver.enqueue. OutboxStore.stageMany / markPublishedMany stay optional
63an enqueue spy on a spread memoryJobDriver()a test wraps the driver as { ...driver, enqueue: spy } and drives handle.enqueueMany or the outbox relay: both go through driver.enqueueMany, so the spy is never called. Wrap that member too
64outboxRelay()'s default batchSizeit claims 500 rows a pass, not 100. Pass batchSize: 100 to keep the old figure; the one reason to is a relay running the row loop (a driver without enqueueMany, such as redisJobDriver) against very slow publishes, where 500 rows must still publish inside the 30 s claim lease
65ERROR_SOURCES, ErrorSourcean exhaustive switch on ErrorSource with no default, or a Record<ErrorSource, …>: add a browser arm. It is what /_client-errors reports with, once clientErrors.enabled is set
66AppConfigyou build one by hand (a test fake, { … } as AppConfig aside): it gains the required assets and clientErrors (TS2741). Spread defineConfig({ name })'s answer, or add assets: { retain: undefined } and clientErrors: { enabled: false, anonymous: false, perMinute: 30 }
67retentionPlan, RetentionPlan, Deploy from @ultimat3/pwayou import them (TS2305): delete the call, which retained nothing unless your own code acted on its answer. To keep earlier builds' hashed files served across deploys, declare assets: { retain: { disk: '<a disk every web pod shares>', builds: <n> } } in app.config.ts, with that disk in defineStorage({ disks })
68ServiceWorkerConfig.retainBuildIds passed to generateServiceWorkeryou pass it (TS2353, excess property): delete the key. An activating worker keeps only its own build's caches; a tab open across a deploy gets earlier builds' chunks from the server, through assets: { retain: { disk, builds } }
69DefineEvalInputyou read input.prompt off one (TS2339): it is PromptEvalInput | AgentEvalInput; narrow with 'prompt' in input. A defineEval({ prompt, … }) call is unchanged, and a custom Scorer typed against { output; expected? } still compiles. An eval of a prompt an agent() runs may move to the agent arm, defineEval({ name, agent, cases: [{ name, input }], tools: { <tool>: (input) => <answer> }, scorers, baseline, tolerance }), with its baseline re-recorded by ULTIMATE_EVAL_RECORD=1 x test eval
70MCP_PROTOCOL_VERSION from @ultimat3/mcpyou import it (TS2305; it was '2025-06-18'): MCP_MODERN_VERSIONS[0] for the modern revision ('2026-07-28'), MCP_LEGACY_VERSIONS for the handshake ones. grep -rn MCP_PROTOCOL_VERSION apps packages finds every caller
71the MCP revision a server answersa client or test pins initialize's answer: it echoes an offered MCP_LEGACY_VERSIONS revision, else 2025-11-25 (was always 2025-06-18). A request whose _meta names 2026-07-28 is served statelessly, with no initialize or ping; an unserved version in _meta or MCP-Protocol-Version is 400 -32022. Legacy clients (initialize) keep working unchanged
72POST /mcp with an Originit is not this app and not in http.cors.origins: 403 X_MCP_ORIGIN_REFUSED, before resolveToken. Call from the app's own origin, list a sibling page's origin in http.cors.origins, or call from a client that sends no Origin
73McpAuthRefusalan exhaustive switch over it, or a Record<McpAuthRefusal, …>: add an 'origin-refused' arm (mcp.auth.origin-refused, onAudit's auth-refused with status 403)
74x mcp serve --transport httpyou reach it through a name that is not localhost, *.localhost or a loopback address: 421 X_MCP_HOST_REFUSED. Use http://localhost:<port>/mcp or http://127.0.0.1:<port>/mcp. GET/DELETE on it answer 405 X_METHOD_NOT_ALLOWED, not 404
75setSolidRuntimeevery island that registers the runtime: add createRoot to the solid-js import and to the call, setSolidRuntime({ createContext, useContext, createSignal, createMemo, createEffect, onCleanup, createRoot }). A six-name call no longer compiles (TS2345). A whole solid-js namespace still registers
76DataTable's rowKeytwo rows answering one key are X_UI_INVALID_VALUE: make the key the row's primary key — unique within rows, stable across updates, never an index — rowKey={(row) => row.id}
77UI_KEYS, UiKeya Record<UiKey, string>: add ui.first
78a cursor-mode Pagination with no prevCursorit no longer draws a disabled Previous. To give a keyset list a way back, pass firstHref (link mode, listUrl().firstHref) or onFirst (callback mode)
79refresh() from @ultimat3/render/clientcode that relied on the old behaviour, the table below
80.contract() on a non-public action or queryit calls your handler 16 times with generated legal input: pass { exercise: { actor } } or { exercise: { off: '<reason>' } }, the rules below
81sampleInputyou imported action's sample-input module by path; it is gone: import { sampleInput } from '@ultimat3/schema/sample'
82ActionDescriptor, QueryDescriptor, Querycode that assigned .method to a 'POST'-typed variable widens it to ActionHttpMethod. A hand-built descriptor adds errors: null; a hand-built Query adds contract
83WorkloadToken (@ultimat3/auth)you build one by hand, usually a test fake: add raw, the decoded payload (raw: {} where nothing reads it)
84a deployed role's app modulesa module throws on import or registration: the role now exits X_APP_MODULE_LOAD_FAILED instead of serving without it. x manifest --json names the module and why
85MCP tool descriptionsyou snapshot tools/list: an undescribed query reads Read with the "<name>" query., an admin action quotes its name. Set mcp: { description } to keep your own wording. exposedPrimitives() returns opted-in primitives only
86QueryPollyou read .ms: keep the number you passed to pollEvery
87.contract() / exerciseAction / exerciseQueryyou ran one with ULTIMATE_ENV or NODE_ENV set to staging or production: run it under bun test
88e2eTest, E2eBrowserPage (@ultimat3/testing)Every e2eTest now audits the browser's accessibility tree. Name what it lists — visible text, aria-label, <label for>, alt (alt="" for a decorative image), a <title>, <html lang>, nothing focusable under aria-hidden — or add { a11y: { allow: [{ role, selector, because }] } } to that test. A tab a suite drives itself (browser.session.newTab()) is audited too: name what it lists, or call allowA11y([{ role, selector, because }]) in that test before the navigation. A hand-written E2eBrowserPage passed to installE2eDriver({ page }) must implement or forward accessibilityTree(request). toAxNode is imported from @ultimat3/testing, not @ultimat3/cli's cdp-shot-a11y.ts
89factoriesFor (@ultimat3/testing)a test reads a value it never set: it is now the column's declared default, an enumerated value or its kind's value, never derived from the name. Set the value in the call (build({ column: … })) or a defineFactory. A registry whose $columns are not entity() columns throws X_SEED_GRAPH_UNSATISFIED: pass real entities
90memoryTransactor().run (@ultimat3/entity)a memory-store write inside it that names no tx must survive the transaction throwing: move it outside run. A write that names no tx now rolls back with the transaction, as on Postgres
91x verify (@ultimat3/cli)a registered action, query or job no generated run reaches as an allowed actor: the new exercise step is red with X_CONTRACT_EXERCISE_UNREACHED. Paste the entry x exercise --json prints into apps/web/exercise.ts, or give { off: '<why>' }. A script that pinned the step count reads one more (VERIFY_STEP_NAMES)
92entity() (@ultimat3/entity)an entity with no tenant column references, directly or through others, one that has one: X_ENTITY_TENANT_UNDECLARED from the exercise step. Add orgId: uuid().tenant() and x db gen '<table> tenant' (with x g backfill on a table holding rows), or declare shared: '<why>'
93memoryTransactor().run (@ultimat3/entity)a test writes after the transaction ended — an unawaited promise — and now gets X_DB_TRANSACTION_CLOSED; or it relied on a nested run's onCommit effect running at that nested commit: it now runs at the root's, and never after the root rolls back
94x exercise, the exercise step (@ultimat3/cli)a primitive hands tenant B a row of tenant A's, or changes one, when B passes A's ids: X_TENANT_LEAK / X_TENANT_WRITE_CROSSED. Read and write by input id through the repository scoped to ctx.actor; x exercise <name> --json reruns it
95SeedWriter (@ultimat3/testing)you wrote one by hand: add snapshot: async (rows) => rows.map(() => null) at least — TS2741 otherwise. A seeded count per tenant moved: an untenanted child of a tenant-owned table now has rows under each tenant
96the framework schema (@ultimat3/auth)you commit a schema dump: five x_oauth_* tables appear. x db gen, then commit packages/db/schema/
97VerificationPurpose (@ultimat3/auth)you switch over a purpose or type one as the two-value union: it is string. Add a default arm; declare a new purpose with defineVerificationPurpose
98publishEvent on the Postgres bus (@ultimat3/jobs)you commit a schema dump: x_job_steps_waiting_idx appears; x db gen, commit. A wait that relied on the 30 s poll to notice an event published by other means than publishEvent: eventPoll: '30s'
99StepStatus, step names (@ultimat3/jobs)you switch over StepStatus: handle started and cleared. A step named unwinding or compensate:<x>: rename it. A hand-written driver keeps compiling: resolveStep is optional
100enqueuing a tenant-declared job (@ultimat3/jobs)it names another org than its tenantId or the acting user's: X_JOB_TENANT_MISMATCH. Enqueue from the job's own org, or inside crossTenant(reason, fn) by an actor holding tenancy:cross when crossing is meant. A client that read the code as a 500 now gets 403. Rows already queued that way go dead: re-enqueue them, never x jobs retry
101ScrapePage, the wedge watchdog (@ultimat3/scraping)you wrote a ScrapePage fake: add activity(): void (the shipped fakes carry it). An attached browser now closes when the watchdog fires, not when the run unwinds: a run that waits on slow work the page cannot see calls page.activity()
102the schema dump (@ultimat3/db)the database holds a policy, hand-written or from rowSecurity: it is rendered under 10_policies/ and no longer named in unrendered.sql. x db gen, commit packages/db/schema/. A hand-built CatalogDescription adds the two row-security fields
103a prompt over the model's window (@ultimat3/ai)you matched X_AI_PROVIDER_UNAVAILABLE or the provider's text for it: the code is X_AI_CONTEXT_OVERFLOW, terminal, and agent() compacts and retries first, ending X_AGENT_CONTEXT_EXHAUSTED. A run over dense tool results compacts earlier: raise compact: { at } (up to 1) if it summarises too soon
104the Redis client (@ultimat3/cache, @ultimat3/jobs)you relied on Bun.redis queueing commands while Redis was down: a cache read is now a miss at once and enqueue is X_DRIVER_UNAVAILABLE. Catch it where a queued retry was implied. A client you hand in keeps its behaviour and gets the deadline
105the framework schema (@ultimat3/db, @ultimat3/admin)you commit a schema dump: x_migrations.destructive and x_error_groups appear. x db gen, commit packages/db/schema/
106a rollback (@ultimat3/db)you roll back across a migration a newer build recorded as destructive: X_MIGRATION_ROLLBACK_DESTRUCTIVE. Check the older build on the newer schema, then ROLLBACK_ALLOW_DESTRUCTIVE=<id>[,<id>] on the migrate role
107ClientErrorsConfig (@ultimat3/core)you build one by hand: TS2741 on vitals. { ...CLIENT_ERRORS_DEFAULTS, enabled: true }
108a statement inside a transaction (@ultimat3/db)one runs past the role's statement_timeout + 5 s on purpose: it now fails X_DB_UNAVAILABLE, where it hung. Raise the bound in that transaction: setLocal(tx, { statement_timeout: '<ms>' })
109x exercise, the exercise step (@ultimat3/cli)a job makes an effect outside step.run that a retry repeats: X_JOB_CRASH_DIVERGED. Wrap the effect the finding names in step.run('<name>', …); there is no pin
110the chart (docker/helm)your cluster serves the Prometheus operator and you did not want the ServiceMonitor or the alert rules: set serviceMonitor.enabled: false and alerts.enabled: false. A dashboard grouping on the scrape's labels sees app_kubernetes_io_instance
111AppConfig (@ultimat3/core)you build one by hand (a test fake, { … } as AppConfig aside): TS2741 on failureCapture and time. Spread defineConfig({ name })'s answer, or add failureCapture: FAILURE_CAPTURE_DEFAULTS and time: TIME_DEFAULTS
112the framework schema (@ultimat3/db)you commit a schema dump: x_failures appears and drift is X_SCHEMA_DUMP_DRIFT. x db gen, commit packages/db/schema/
113/llms.txt, the seo step (@ultimat3/seo)a public, static site/ page has a title and no description: X_SEO_LLMS_DESCRIPTION_MISSING. Add description to its defineRoute({ meta }), or set seo: { llms: false } in app.config.ts.
114TimeZoneConfig (@ultimat3/http)you build a full TimeZoneConfig or HttpConfig literal: TS2741 on detectedCookie. Spread DEFAULT_TZ_CONFIG; configureHttp({ tz }) is unaffected
115McpResource.read, ResourceRegistry.read (@ultimat3/mcp)you call either yourself: TS2554. Pass the admitted caller: resource.read(caller), registry.read(uri, caller). A resource whose read() takes no argument is unchanged
116DevCapabilities (@ultimat3/mcp)you wrote a DevHost: TS2741 on queryGraph. Implement queryGraph(question) — x impact <node> --json's data for { kind: 'impact' }, x graph --from --to --json's for { kind: 'path' } — or throw for a host with no graph
117DevSources (@ultimat3/admin/dev)you build one by hand: TS2741 on mailTemplates. Add mailTemplates: async () => [], or build it with defaultDevSources()
118x exercise, the exercise step (@ultimat3/cli)a statement no index serves, a limited read that sorts, or a loop over rows: X_QUERY_UNINDEXED, X_QUERY_SORT_UNSERVED, X_QUERY_SCALES_WITH_ROWS. x exercise --json; paste the printed indexes: line into that entity() and run its x db gen, or declare the reason: entity({ scan: '<why>' }), expectedSeqScan('<why>', fn), expectedQueryLoop('<why>', fn); a loop is usually a missing preload()
119OpenGraph (@ultimat3/seo)you extend it, or spread one while setting image: TS2312, TS2375. Write type MyOg = OpenGraph & { … }, and { ...og, image, capture: undefined }
120postgresAuthAdapter() (@ultimat3/auth)you build it with no client and rely on it keeping that client past a later setDbClient, or outside a withTransaction it is now called in: pass the client, postgresAuthAdapter(client)
121QueryAccessor.refetch (@ultimat3/realtime)you hand-build an accessor (a test fake): TS2322 on refetch. Write refetch: async () => {}. Where noFloatingPromises is on, void x.refetch(); or await x.refetch();
122UI_KEYS, UiKey (@ultimat3/ui)a Record<UiKey, string>: add the 48 keys entry 122 lists, or build it with uiCatalog(t). Code that read Object.keys(UI_KEYS) in declaration order reads it alphabetical
123ChartPoint, barRects, BarChartProps, chartTable (@ultimat3/ui)you read point.value or a barRects() rect as always there, extend BarChartProps, or call chartTable() yourself: TS18047, TS2312, TS2345. Check value === null and skip a null rect; type X = BarChartProps & { … }; pass none: t(UI_KEYS.chartNone)
124BarChart, Sparkline, LineChart, AreaChart, deltaOf (@ultimat3/ui)you pass a negative value you expected drawn at zero, a series whose values do not match keys, or test an empty gap cell or deltaOf(x, 0) === undefined: clamp it yourself (Math.max(0, v)); give each series one value per key, null for a gap (X_UI_INVALID_VALUE); assert ui.chart.none's text and the signed difference
125TabsProps (@ultimat3/ui)you extend it or index TabsProps['onChange']: TS2312, and onChange is | undefined. Name the mode you mean: TabsButtonProps, or TabsLinkProps for link tabs
126CommandPalette, filterItems (@ultimat3/ui)a test pins the filtered order, or that an accented label stays out: assert the ranked order, label prefix first
127CONTRAST_PAIRS, defineTheme() (@ultimat3/ui)your brand sets chart-1 or chart-5 under 4.5:1 against bg-soft, or fg-strong, accent or a status role under 3:1 on a surface: X_UI_CONTRAST_INSUFFICIENT names the pair and its ratio. Move that role until it passes
128the test preload (@ultimat3/testing)a test passed by reading a module another file mocked: mock it in the file that needs it. X_TEST_MODULE_MOCK_UNRESTORABLE on a file's first test: the file before it mocked a module that does not load. Hand that dependency to the code under test as a parameter, or run x test unit --isolate
129x build --target static, responsiveImage() (@ultimat3/cli)a static page names an image the export does not hold, or asks for avif: X_IMAGE_SOURCE_UNREADABLE, X_IMAGE_UNSUPPORTED. Put the file under apps/web/site/assets/ and pass asset('assets/<file>'), or an absolute URL; drop avif from that route's formats. A CDN rule keyed on ?w= now sees /_img/ paths
130/assets/* (@ultimat3/cli)a served responsiveImage() over asset() passes its own widths or formats, or a link adds ?q=: 404 X_ASSET_VARIANT_UNDECLARED. Drop the override, or store the image and serve it from /media, which answers any width
131.island.tsx mount (@ultimat3/cli)x verify --only boundaries is red with X_ISLAND_DISPOSER_MISSING: end mount with return render(() => <Island {...props} />, el) and type it (): () => void; a plain-DOM island returns a function that removes what it added (an AbortController's abort)
132AuthAdapter (@ultimat3/auth)an adapter you wrote is TS2420 until it implements PasskeyStore's nine methods; passkey-postgres.ts is the reference. The built-in adapters need nothing
133ActionDescriptor.rateLimit (@ultimat3/action)a reader that met a function limit there reads rateLimitPerCall; rateLimit is now the fixed limit or null
134McpConfirmationStore, McpConfirmation (@ultimat3/mcp)a custom store's decide takes the signature as its fifth argument and records it; a hand-built confirmation carries quorum, approvers and approvals. The Postgres store's three new columns are added at boot
135x.manifest.json, the contract diff (@ultimat3/manifest)x manifest adds actions[].meter: commit it. Introducing or tightening a meter is now breaking in x verify --only contract-diff
136a websocket upgrade (@ultimat3/cli)x verify --only boundaries is red with X_SOCKET_HAND_ROLLED on server.upgrade or Bun.serve({ websocket }): declare deviceSocket({ path, resolveToken, onFrame }) instead
137PushDeviceStore, TokenCache (@ultimat3/notify)a store you wrote is TS2420 until it implements noteBadToken and clearBadToken; a caller of invalidate() passes the token the provider refused. The built-in stores need nothing; x_push_devices.bad_tokens is added at boot
138history: true images (@ultimat3/db)x db gen "history exact images" reinstalls the trigger; a reader that did arithmetic on a bigint or decimal value in before/after parses the string; timestamps are UTC …Z. Rows already written keep the old spelling
139signPart, completeUpload (@ultimat3/storage)pass the policy the grant was made under — the app's own, never a value from the request; without it both throw X_INVARIANT. The browser's MultipartUploadClient is unchanged
140LiveHandle (@ultimat3/realtime)a handle you wrote is TS2741 until it implements retry(): boolean: re-send the subscribe of a refused registration and answer whether it did
141CsrfCheckInput, the csrf stage (@ultimat3/http)a caller of checkCsrf passes authorization: request.header('authorization'); a write a browser sends with only cached Basic, Digest or Negotiate credentials is now judged like a cookie session — send Authorization: Bearer, or list the origin in cors.origins
142enqueueInTx (@ultimat3/jobs)TS2305 on the import: call handle.enqueue(input) (or jobsFacade().enqueue) inside the transaction — it joins it and runs the refusals enqueueInTx skipped
143EventLookup.find (@ultimat3/jobs)a custom event bus returns each event's id and seeks strictly past (afterMs, afterId) when afterId is given. A job live across the deploy was queued with no tenant, so a plain enqueue of its key afterwards is a new job

Entry 25: a hand-written DeliveryLedger

A compile error until it does:

WasNow
claim(claim, at): Promise<boolean>claim(claim, at): Promise<DeliveryClaimAnswer>: { outcome: 'owned', claimId }, { outcome: 'sent' } or { outcome: 'held', retryAfterMs }
settle(claim, status, at): Promise<void>settle(claim, status, at, claimId): Promise<boolean>: false when claimId no longer owns a sending row, and nothing changes

A sending row is no longer re-claimable at once: it is held for reclaimAfterMs, default DEFAULT_DELIVERY_RECLAIM_MS (5 minutes). A retry landing inside it raises X_NOTIFY_DELIVERY_HELD and is rescheduled past the window with the attempt uncounted.

Entry 79: what refresh() no longer does

Only an app that relied on the old behaviour has anything to do:

Relied onNowDo
focus landing on <main> after refresh()focus stays on the control the visitor was in; <main> only when it is gonecall document.querySelector('main')?.focus() after await refresh() where that is wanted
the title read out after refresh()nothing is announcedannounce in the app's own live region
refresh() cancelling a navigation in flightit resolves and does nothing; that navigation landsawait navigate(location.href, { replace: true }) to replace it
refresh() re-fetching the page under an open modalnothing is fetchedcloseModal() first, then refresh()

Entry 80: the exercise assertion

.contract()'s fourth assertion calls the handler 16 times with input input: accepts, inside a rolled-back transaction.

CaseAnswerDo
no exercise, on a non-public action or queryX_CONTRACT_EXERCISE_UNDECLARED{ exercise: { actor } }, or { exercise: { off: '<reason>' } }. A public one (its policy admits an anonymous caller) runs as the signed-out caller with nothing declared
an input a generated uuid cannot satisfy: a row id, an org the policy comparesevery run stops before the handlera binder: { exercise: { actor, input: (drawn) => ({ ...drawn, postId, orgId }) } }
the handler throws uncoded, fails its output schema, or answers a coded 5xx on a legal inputX_CONTRACT_DEFECT; an errors:-undeclared code counts as a 5xxfix the handler. The fix: is the bun test <file> -t '<name>: handler survives generated input' that replays the run
no run reaches the handler: every run denied, or a sealed networkX_CONTRACT_EXERCISE_UNREACHEDa grant the actor holds, or a binder
the memory store (storeMode memory)each run's writes roll back, as on Postgres (since entry 90; before it, what a run wrote stayed until the next seed())nothing

Not breaking, but you will see it

What movedDo
with DATABASE_REPLICA_URL set, a request that wrote sets __Host-x-pos and x-ultimate-positionnothing. A proxy with a cookie allowlist passes __Host-x-pos; stripped, the next read may land on a standby that has not replayed the write
a job attempt that completed and was acked leaves ctx.signal un-aborted (As of 2026-11-07); before, its timers' writes and enqueues were refused X_ABORTED, "was cancelled"nothing. A body that read an aborted signal as "the run ended" reads the settle instead (onSettled); work that must outlive a FAILED attempt too runs in runToCompletion(() => work) from @ultimat3/core
onSettled's ctx.signal is livea hook that treated an aborted signal as "the run ended" reads settled.outcome instead. It is aborted at onSettledTimeout, 30 s across every try by default: raise it on a hook that takes longer
publishEvent inside a transaction commits and rolls back with itnothing, unless a rolled-back event must still resume a run: publish it in outsideTransaction(fn)
the drift step's rollingSince rail is off until setoptional: set rollingSince in x.verify.json to your newest applied migration id. x doctor reminds you
any other HeadTag attribute drops as attributePair does: on*, srcdoc, a non-http(s) href or src on base or scriptnothing to migrate: these never belonged in a document
disk.delete still leaves a variant's bytes, now unreadable once its source is gonean erasure of one object also calls deleteVariants({ disk, key })
a websocket upgrade from an unlisted origin is refused whatever sec-fetch-site it carriesadd a sibling host to allowedOrigins
stored isr entries re-render once, for the new keynothing
an isr route's isr.query.undeclared log line is goneremove an alert keyed on it
a public text answer of 1 KiB to 256 KiB with a declared length leaves as gzip with a weak etag; a signed-in one only when immutablea handler that must not be coded sets no-transform
html, textResponse and jsonResponse set content-lengtha handler that rewrites such a body drops the header
a mount declaring rateLimit lifts the token's per-route cap of 120 a minute, and a mount's 422s and pipeline 403s spend the allowancecheck the mount's number is the ceiling you meant
Link: rel="successor-version" on a mount names the mounted path, or is absent when this token cannot follow itnothing
rpc or queryClient pointed at a mount's prefix never reached itcall a mount from its own OpenAPI document
DEFAULT_CORS allows and exposes more headersan app with its own allowHeaders or exposeHeaders adds the new names itself
RemoteActionError.meta and a problemError's meta gain requestIda toEqual on a whole meta gains the key
a rendered fix: prints bunx x where the app's own bin existsan agent parsing the string for x adjusts
a positional x deploy <word> is refusedx deploy --image <ref>, or x deploy verify
x deploy verify with no DATABASE_URL passes with graded: false and reads no queuegive the step the database credentials to grade the workers
x new's docker/Dockerfile.dockerignore drops **/*.test.ts, **/*.test.tsx and docsoptional: append the three lines to your own; !docs/<path> re-includes a served file
x jobs show / inspectJob(driver, id).input is '[redacted]' for a job name this process has not registered with job()to read such a payload, register the job again (a stub job({ name, input: t.json(), … })) in the process that inspects it
a field marked .sensitive() (last in the chain) is masked in the audit record and x jobs show; one reachable from an action output or a query rows fails boot with X_SCHEMA_SENSITIVE_OUTPUToptional: mark what must never be written down. An app's own logger.info('x', input) is not covered: keep redactKeys or a Secret there
every builtin schema refusal carries rule (SCHEMA_RULES) and params, on the wire and on FormIssuekey a form's messageFor on issue.rule, not the English issue.message; keep a fallback, since a third-party schema library sends no rule
app codes declare with defineError({ code, title, status | offSocket, cause, fix }) from @ultimat3/core, which registers the status in the same calloptional: replace class X extends UltimateError with defineError; X.is(error) replaces instanceof, and a base class becomes a code prefix. x g guard error-declared adds the guard that keeps it so; x g guard bare-error --force refreshes the bare-error guard's fix. An app code with no status still answers 500
the Sentry envelope carries fingerprint: [errorFingerprint(report)]nothing: each existing issue reopens once under its new group. A reporter of your own groups the same way with errorFingerprint(report)
OTLP export retries 429, 502, 503, 504 and a transport failure, inside the exporter's timeoutMs (default 10 s)lower timeoutMs if a shorter hold on the in-flight batch matters more than delivery. A collector that accepted a batch then answered 503 receives it twice; span ids dedupe it
X_DEV_ALREADY_RUNNING's fix is x stop --jsona script that killed x dev by pid calls x stop --json, which returns once the next x dev can boot
the AI gateway skips a provider for 30 s once it fails five passes in a rowto keep re-spending every attempt on every call: providerGateway({ …, breaker: 'off' }); your own per provider: breaker: (provider) => breaker({ … })
after the replica breaker's cooldown, one read probes the replica and the rest take the primary until it answersnothing
x doctor lists raw-fetch as a shipped guard your app lacksbunx x g guard raw-fetch, then give each server fetch( a signal, or call outbound() from @ultimat3/core/outbound
DeadlineTimer and platformDeadlineTimer left @ultimat3/scraping's clock.ts (never on its barrel)import them from @ultimat3/core/outbound
breaker()'s admit() returns a ticket (or undefined) that succeeded, failed and released take; defaultBreakerClassify is not exportedpass the ticket back; write your own classify
registerErrorStatus's table is core's: the writers are on the internal subpath @ultimat3/core/internal/error-status, never a barrelnothing: call registerErrorStatus from @ultimat3/http, or declare an app code with defineError
x new writes .mcp.json and mounts a refusing POST /mcpoptional: copy .mcp.json ({ "mcpServers": { "<app>": { "command": "bunx", "args": ["@ultimat3/cli", "mcp", "serve", "--transport", "stdio"] } } }) and apps/web/mcp.ts (defineAppMcp({ include: 'exposed', resolveToken: apiKeyResolver(() => appAuth().adapter) })) from a fresh bunx create-ultimate, or set ai: { mcp: { expose: false } } to silence X_MCP_APP_UNMOUNTED. Spell commands bunx x … in AGENTS.md, CLAUDE.md and .claude/
x docs reads the wiki and every installed @ultimat3/* package; its hits movenothing: the --json shape is unchanged
an app test file through @ultimat3/testing/preload starts on the installed frozen instant: the clock is reset at every file boundarya file that relied on an EARLIER file's advanceClock calls setFrozenClock() itself
@ultimat3/core/render-nodes ships the node-tree walker for testsoptional: a test that copied a walker (a Symbol.for('ultimate.render.jsx') check over type/props) imports renderNodes, nodesOf, byTag, byComponent, withAttr, one, attachRef and fire from it; examples/dummy/apps/web/app/settings/page.test.ts is the worked example
x new writes AGENTS.md as a symlink to CLAUDE.md; x upgrade links an AGENTS.md byte-identical to CLAUDE.md and reports a different one as data.agentsMd: "differs"optional: merge what only AGENTS.md says into CLAUDE.md, then ln -sf CLAUDE.md AGENTS.md. The manifest step then holds CLAUDE.md to the 12 kB AGENTS.md budget: raise agentsMdMaxBytes in x.verify.json if it needs more. A Windows clone without core.symlinks checks the link out as a file reading CLAUDE.md
x env init --production writes .env.production; x deploy --method compose refuses without oneadd .env.production to .gitignore if yours does not list it (x env init does this itself)
x dev and the statements fixture report a loop of await job.enqueue(input) as X_N_PLUS_ONE_WRITE, naming handle.enqueueManyreplace the loop with await job.enqueueMany(inputs): one round trip per 1,000 inputs
a duplicate job enqueue with a different payload, or one still in flight, never threw X_IDEMPOTENCY_CONFLICT, whatever the wiki said: it answers { deduped: true, id } of the live row, and the first payload winsa catch for X_IDEMPOTENCY_CONFLICT around <job>.enqueue() never runs: read result.deduped. A job re-enqueuing itself from its own body arms nothing, because its own running row holds the key: re-arm from outside the run, for example a task on a cron
webhookReceiver() from @ultimat3/jobs is the one supported door for an inbound webhook; a pinned action (http: { path }) or a hand-mounted runtime.ts route keeps workingoptional: replace it with webhookReceiver({ name, path, tenant, verify, handle }). In verify(inbound) run the signature check (verifyBodyHmac(...), answering { text }, or verifyWebhookSignature(inbound.request(), { secret })) and return { eventId, payload } read from the verified data, never an unsigned header; move the work into handle. List it in defineApi({ jobs }), delete the runtime.ts line, and point the vendor at the new path, which must not sit under /api or /_x (X_WEBHOOK_RECEIVER_INVALID). A verify that parses the body catches the parser's error and throws its own code: Bun's JSON.parse message quotes the token it stopped at. Past the queue's live window handle runs at least once per eventId; the stored payload stays in x_jobs until jobs.doneRetentionMs / jobs.deadRetentionMs
importRows() from @ultimat3/jobs reads a CSV or ndjson object page by page into the app's handleoptional: replace a hand-rolled import with importRows({ name, input, tenant, source: () => disk('…'), key, format, columns, schema, handle, errors: { sink, prefix } }), writing with upsertAll in handle, since a page can be handled more than once. A cell exportRows() prefixed with ' comes back with the '
browser reporting, clientErrors in app.config.ts, is off by defaultoptional: clientErrors: { enabled: true } mounts /x-client-errors.js and POST /_client-errors and reports each app/ document's uncaught failures as X_CLIENT_ERROR. Signed-in members only, 30 reports a minute each; anonymous: true opens it to visitors with no session, limited per address
assets: { retain: { disk, builds } } keeps earlier builds' hashed files reachable across a deploy; off unless declaredoptional: name a disk every web pod can read (an S3-compatible bucket, or a volume shared on one box) and size the window, assets: { retain: { disk: 'deploys', builds: 3 } }. Each app and environment gets its own prefix (x-build-assets/<config.name>/<environment>/). A disk the app did not declare refuses the web boot with X_CONFIG_INVALID; a file over the disk's maxPutBytes is skipped and named in one assets.retain_skipped line
/admin/audit refuses a parameter it does not read with a 400 (X_ADMIN_FILTER_INVALID), not the unfiltered traildrop the parameter. An app that overrode admin.audit.empty for the row's history card moves that text to admin.audit.empty-row; admin.audit.empty is the trail's "nothing matches". An app's own AuditLog honours outcome, since and until in entries() with auditWindow(subject, query) and auditEntryMatches(entry, query), or the screen drops the entries that do not match and pages draw short
x ci gains tested; --branch, --run, --tail, --full and --event belong to runsnothing: x ci with no subcommand is x ci runs, as before
X_DB_LOCK_TIMEOUT's fix: reads x db diagnostics --json | jq '.data.blockers.rows[] | {pid, blockedBy, blockerState}'a script that matched the old psql "$DATABASE_URL" -c "select pid, state, query from pg_stat_activity …" text matches on the code instead. --limit on x db is accepted by diagnostics as well as backfill
x new writes storeMode(env) === 'memory' ? testDriver() : postgresDriver() in packages/db/src/client.tsoptional, one line: import { type Driver, database, postgresDriver, testDriver } from '@ultimat3/entity', then change the memory arm of selectDriver to testDriver(). Without it nothing changes; with it a test file that runs the Testing recipe reads and writes its migrated database through db, and a file that installs no client behaves as before. A web workspace that runs the recipe lists @ultimat3/db (and @ultimat3/cli) among its dependencies
hand-rolled polling in an island (setInterval(…) beside a visibilitychange listener)optional: visibleInterval(run, ms) from @ultimat3/core/page, or useQuery(ref, input, { poll: pollEvery(ms) }) for a read; x g guard raw-interval adds the guard that refuses the bare call. A channel event that re-renders the page is scheduleRefresh() from @ultimat3/render/client, not refresh()
errors: on action(), mutator() and query() is optionalan operation that does not declare it keeps its behaviour and its OpenAPI bytes. To close a contract, list each 4xx code the handler throws, importing the defineError class and listing its .code (the import registers the status before action() checks the list): errors: [PostNotFound.code, 'X_FORBIDDEN']. Then x verify --only contract: a 4xx the list does not name is X_ERROR_UNDECLARED, whose fix: names the code to add
http.path takes :param templates, and http.method is newnothing for an app that pins no parameterised path: every static route, its OpenAPI bytes and its manifest fact are unchanged. To serve a resource-shaped URL pin it on the primitive and write no route file: http: { path: '/orders/:id/refund', method: 'POST' }, each :param a required t.string key of input:. Call it with action.client() or read.client(); derivePath('refundOrder') throws, so use actionHttpPath('refundOrder', { id })
a body or search string naming a :param's keyit carries the URL's value or leaves the key out; a different value is 400 X_HTTP_PATH_INPUT_CONFLICT
every action pinned before 30.0.0 gains http: { path, method: 'POST' } in x.manifest.jsonadditive in contract-diff, not breaking: regenerate with x manifest
an app with no persist: true entity: a useQuery / useRecord island is no longer heldnothing: it paints earlier

Edit this page on GitHubThis page as markdown