# 7.x → 8.0.0, entry by entry

**Six breaking entries, from one whole-repo bug sweep.** Five are compile errors the moment you
upgrade. The sixth is a **silent** behaviour change, and it is the one to read even if nothing else
here applies to you. No codemod.

| # | Surface | Costs you an edit if |
|---|---|---|
| 1 | `@ultimat3/realtime` has two entries | you import a **server** name — NATS, pg replication, the sync node, the channel hub |
| 2 | `IdempotencyStore.settle` / `fail` take a reservation id | you call either, **or implement the interface** |
| 3 | `pwa.installPrompt`, `auth.afterSignInPath`, `ai.modelEnv` deleted | your `app.config.ts` sets one |
| 4 | `@ultimat3/manifest` drops `canonical` | you imported it |
| 5 | `@ultimat3/render` drops `matchRoute` / `RouteMatch` | you imported either |
| 6 | `SQL_CANCEL` projects its columns | you asserted on that constant's text |

### 1. `@ultimat3/realtime` splits into `.` and `./server`

```diff
- import { ChannelHub, createSyncNode, LiveQueryRegistry } from '@ultimat3/realtime';
+ import { ChannelHub, createSyncNode, LiveQueryRegistry } from '@ultimat3/realtime/server';
```

Client names — `useLive`, `liveHookFor`, `LiveClient`, the offline queue, rebase, the wire protocol,
cursors — are **unchanged on `.`**. A file importing both halves now writes both imports.

Why: the single barrel carried `useLive` beside `openNatsClient`, so `bun build --target=browser` on
an entry importing *only* the hook failed with *"Browser build cannot require() Node.js builtin:
`stream/web`"*, out of `nats`. **The island this framework tells you to write could not be bundled.**

The two barrels are **disjoint** — `./server` re-exports no client name — so which half a symbol
lives in is checkable rather than conventional. If an import stops resolving, the name moved to
`./server`; nothing was deleted.

### 2. `IdempotencyStore.settle` and `fail` take the reservation id

```diff
- await store.settle(key, value);
+ await store.settle(key, value, reservation.record.id);
```

`reservation` is what `store.reserve(key, hash)` answered. Same shape for `fail`.

**Read this if you implement the interface — it is the one silent entry in this major.** A store with
the old two-parameter method **still compiles**, because a shorter function is assignable to a longer
signature, and it **silently loses the fence**. Both statements now match on **id and state**, so a
straggler from a slow first attempt can no longer overwrite a replacement reservation still in
flight. The `fail` half was the worse one: a straggler's failure marked a *live* replacement
`failed`, and the replacement's own settle was then fenced out.

### 3. Three config fields are deleted

```diff
- pwa: { enabled: true, offline: 'runtime', installPrompt: true },
+ pwa: { enabled: true, offline: 'runtime' },
- auth: { signInPath: '/signin', afterSignInPath: '/dashboard' },
+ auth: { signInPath: '/signin' },
- ai: { mcp: { expose: true, path: '/mcp' }, modelEnv: 'ANTHROPIC_MODEL' },
+ ai: { mcp: { expose: true, path: '/mcp' } },
```

**There is no replacement key, because there was never a behaviour.** Each was declared, defaulted,
merged, and read by nothing. Use `createInstallController` from `@ultimat3/pwa`, send the visitor
from your own sign-in route, and pass `model` on the `llm()` request.

`ai.modelEnv`'s own doc comment argued for its deletion: *"an intention, not a behaviour… nothing
consumes the merged value… So the exact thing this key exists to prevent — a model string baked into
the image — is what actually happens."*

Same precedent as `JobsConfig.driver` in 5.0.0 and `realtime.heartbeatMs` in 4.0.0. All three fail at
**typecheck only** — and an app that builds its config into a variable before passing it loses
excess-property checking and sees no error at all. `scripts/config-readers.ts` now keeps the class out.

### 4. `@ultimat3/manifest` no longer exports `canonical`

Use `canonicalJson` from `@ultimat3/core`. It was the third of five copies of one serialiser;
`manifest`'s fed `buildId` **and the contract-diff equality**, so a `-0`/`NaN`/`Date` fold could make
a breaking API change diff as *"no change"* and ship silently.

### 5. `@ultimat3/render` no longer exports `matchRoute` or `RouteMatch`

Two exported route matchers existed with different precedence. `@ultimat3/http`'s trie is the live
one; render's had zero consumers repo-wide.

### 6. `SQL_CANCEL` projects its columns instead of `returning *`

It fed `toJobRecord`, which does `Number(row.run_at)` — so against a text-decoding `PgExecutor` every
timestamp came back `NaN`. Only an edit if you asserted on the constant's SQL text.
