diff --git a/work-consolidation.md b/work-consolidation.md
index 86db67d..a961c3b 100644
--- a/work-consolidation.md
+++ b/work-consolidation.md
@@ -15,10 +15,16 @@
> (#67–#73) shipped without this file moving once; it drifted ~330 tests and two API re-measures
> behind the code, and six issues it listed as open had already shipped. See
> `.claude/skills/doc-engineer/assets/docs-quality-checklist.md`.
+>
+> It happened again immediately: PRs **#86–#107** (2026-09-15→17) shipped twenty-two changes, sixteen
+> §1f rows, and ~800 tests before this file moved. The rule postdates most of them, which is the
+> excuse and not a defence — this sync is the catch-up, and the rule now applies from here.
-- **Consolidated:** 2026-08-15 · **Last synced to code:** 2026-09-14 · **Last re-measured against the live API:** 2026-09-14 (`GET /api/openapi.json` under a Bearer token from the `.env` test account: **232 paths / 303 operations**, OpenAPI 3.1.0, `InterlinedList API 0.1.0`; the client builds **200** request builders covering **194** spec operations) · **Branch:** `dev` @ `01bc1a9` (⚠️ `main` is **28 commits behind** — catch it up before a release cut) · **Bundle:** `com.interlinedlist.macos` · **Team:** `BJA9558E4B`
+- **Consolidated:** 2026-08-15 · **Last synced to code:** 2026-09-17 · **Last re-measured against the live API:** 2026-09-14 (`GET /api/openapi.json` under a Bearer token from the `.env` test account: **232 paths / 303 operations**, OpenAPI 3.1.0, `InterlinedList API 0.1.0`; the client builds **200** request builders covering **194** spec operations) · **Branch:** `dev` @ `e39e9ab` (⚠️ `main` is far behind — catch it up before a release cut) · **Bundle:** `com.interlinedlist.macos` · **Team:** `BJA9558E4B`
- **Structure:** [§1 Immediate work (do now)](#1-immediate-work--unblocked-do-now) — including [§1c verb defects](#1c-live-verb-defects--fix-first) and [§1d the 2026-09-05 parity batch](#1d-new-feature-areas-2026-09-05-re-measure) → [§2 Blocked work (backend / spike-first)](#2-blocked-work--backend-gated-or-spike-first) → [§3 Final work (release & App Store)](#3-final-work--release--app-store)
-- **Test baseline (all green, measured 2026-09-14 on `dev` @ `01bc1a9`):** InterlinedKit **478** · InterlinedDomain **946** · InterlinedPersistence **140** · App target **954** (`** BUILD SUCCEEDED **` + `** TEST SUCCEEDED **`, gate run three times). Packages run under plain `swift test`; the App target needs `CODE_SIGNING_ALLOWED=NO` on this machine. *(Growth since the 2026-09-07 figures — Kit 398 / Domain 701 / Persistence 135 / App 702 — is G22 DM, G23 lists sharing, G24 documents tree, G25 orgs lifecycle, G35 view preferences and the public-permalink fix, i.e. PRs #67–#73. Earlier baselines: 2026-09-05 Kit 314 / Domain 619 / App 622; 2026-08-16 Kit 286 / Domain 590 / App 566.)*
+- **Test baseline (all green, measured 2026-09-17 on `dev` @ `e39e9ab`):** InterlinedKit **521** · InterlinedDomain **1100** · InterlinedPersistence **147** · App target **1113** · SyncAgent **96** (`** BUILD SUCCEEDED **` + `** TEST SUCCEEDED **`). Packages run under plain `swift test`; the App target needs `CODE_SIGNING_ALLOWED=NO` on this machine; `SyncAgent/` is **not in the Xcode scheme** and is covered by `swift test --package-path SyncAgent`. ⚠️ The kit's live `ContractTests` and the agent's `LiveAPISmokeTests` were **skipped, not run** — the `.env` account was rate-limited by the 2026-09-15/17 recon; re-run them before a release cut. *(Growth since 2026-09-14 — Kit 478 / Domain 946 / Persistence 140 / App 954 — is the 2026-09-15→17 wave: PRs #86–#107. Earlier baselines: 2026-09-07 Kit 398 / Domain 701 / Persistence 135 / App 702; 2026-09-05 Kit 314 / Domain 619 / App 622; 2026-08-16 Kit 286 / Domain 590 / App 566.)*
+
+- ⚠️ **Toolchain divergence.** This machine is on **Xcode 27**; CI (`.github/workflows/ci.yml`) pins **26.3**. That gap is not cosmetic: the macOS 27 SDK added `SwiftUI.Document`, which collided with `InterlinedDomain.Document` and stopped `dev` compiling locally while **CI stayed green** ([#98](https://github.com/CompositeCode/interlinedlist-macos-native/issues/98) / PR #99). Any domain type sharing a name with a SwiftUI symbol is one SDK update from the same break, and CI cannot see it. Decide whether to bump the pin or pin the local toolchain.
- **Distribution model:** notarized **`.pkg`** (+ `.dmg`) is the **current** ship path (closed-source private repo, no `LICENSE`). Mac App Store is a **later** path on a separate branch. Billing is handled by the web app — the native app has **no** in-app-purchase surface; it only *reads* `customerStatus` to gate subscriber features.
---
@@ -172,7 +178,7 @@ Live and available to the test account: `GET /api/ai/status` returns `{"subscrib
**G17 · Applications: synced settings + device registry — ✅ SHIPPED 2026-09-06 (PR #25 + #30). Size M.** *(Tail: the Applications pane still needs main-workstation / rename / remove / copy-to-shared — GitHub #56.)*
-`GET/PUT/DELETE /api/user/app-settings/{appKey}`, `GET /api/user/app-settings/{appKey}/bootstrap?deviceId=…`, `GET/POST /api/user/app-settings/{appKey}/devices`, `GET/PUT /api/user/app-settings/{appKey}/devices/{deviceId}/settings`, `PATCH/DELETE /api/user/app-settings/{appKey}/devices/{deviceId}`. Per `/help/app-settings` this is the platform's *own* mechanism for companion apps: **shared settings** follow the account to every machine, **per-machine settings** stay pinned to one computer, one machine is the "main workstation" whose config seeds a brand-new device on first sign-in, and devices can be renamed or deregistered. This is the sanctioned home for the macOS app's preferences **and** the Document Sync Agent's per-machine configuration, replacing purely local `UserDefaults` state. **✅ SHIPPED 2026-09-06** (PR #25 + #30). ⚠️ **The "register an `appKey`" instruction above was wrong — no registration mechanism exists.** A read-only live probe found the segment is a free-form namespace: `GET /api/user/app-settings//devices` → `200 {"devices":[]}` for a key the server has never seen, and `OPTIONS` on the parent → `allow: DELETE, GET, HEAD, OPTIONS, PUT`. The client uses `interlinedlist-macos` (`AppEnvironment.appSettingsKey`); it must stay **stable**, since changing it orphans stored settings. **Also learned live:** 404 is the ordinary first-run state, not a failure — an app key with nothing stored 404s, and `bootstrap` returns `404 {"source":"none"}` for an unseen device; both now map to empty rather than throwing. **Still unverified:** the *populated* payload shapes, since nothing is stored yet — the DTOs stay tolerant.
+`GET/PUT/DELETE /api/user/app-settings/{appKey}`, `GET /api/user/app-settings/{appKey}/bootstrap?deviceId=…`, `GET/POST /api/user/app-settings/{appKey}/devices`, `GET/PUT /api/user/app-settings/{appKey}/devices/{deviceId}/settings`, `PATCH/DELETE /api/user/app-settings/{appKey}/devices/{deviceId}`. Per `/help/app-settings` this is the platform's *own* mechanism for companion apps: **shared settings** follow the account to every machine, **per-machine settings** stay pinned to one computer, one machine is the "main workstation" whose config seeds a brand-new device on first sign-in, and devices can be renamed or deregistered. This is the sanctioned home for the macOS app's preferences **and** the Document Sync Agent's per-machine configuration, replacing purely local `UserDefaults` state. **✅ SHIPPED 2026-09-06** (PR #25 + #30). ⚠️ **The "register an `appKey`" instruction above was wrong — no registration mechanism exists.** A read-only live probe found the segment is a free-form namespace: `GET /api/user/app-settings//devices` → `200 {"devices":[]}` for a key the server has never seen, and `OPTIONS` on the parent → `allow: DELETE, GET, HEAD, OPTIONS, PUT`. The client uses `interlinedlist-macos` (`AppEnvironment.appSettingsKey`); it must stay **stable**, since changing it orphans stored settings. **Also learned live:** 404 is the ordinary first-run state, not a failure — an app key with nothing stored 404s, and `bootstrap` returns `404 {"source":"none"}` for an unseen device; both now map to empty rather than throwing. **Populated shapes verified 2026-09-16** (PR #102), and five modelled guesses were wrong: writes require `baseVersion` (the family is **compare-and-set**, answering `409` with the winning document); the device fields are `deviceName`/`isDefault`, **not** `name`/`isMainWorkstation`; `PATCH` requires at least one of those two; `POST`/`PATCH` wrap the device in `{"device":…}`; and `bootstrap` returns **one** document plus a `source` tag rather than the shared+device pair modelled. Two behaviours are better than assumed: a delete **names the promoted successor**, and `POST …/devices` is an **upsert keyed on `deviceId`** — so unconditional registration would reset a renamed machine on every pane visit. The agent's own configuration moved here in PR #107 ([#104](https://github.com/CompositeCode/interlinedlist-macos-native/issues/104)).
**G18 · Notification preferences — ✅ SHIPPED 2026-09-06 (PR #25). Size S.**
@@ -307,33 +313,49 @@ what caused the 2026-09 drift — follow the link.)*
| G25 orgs + org LinkedIn | [#54](https://github.com/CompositeCode/interlinedlist-macos-native/issues/54) | ✅ shipped, PR #67 |
| G35 view preferences | [#43](https://github.com/CompositeCode/interlinedlist-macos-native/issues/43) | ✅ shipped, PR #73 |
| Public permalink + embed | [#38](https://github.com/CompositeCode/interlinedlist-macos-native/issues/38) | ✅ shipped, PR #72 |
-| G30 AI availability copy | [#39](https://github.com/CompositeCode/interlinedlist-macos-native/issues/39) | 🔴 live defect — `AI.swift:143` |
-| G37 entitlement matrix | [#40](https://github.com/CompositeCode/interlinedlist-macos-native/issues/40) | 🔨 built, unmerged |
-| G38 email-verification gate | [#41](https://github.com/CompositeCode/interlinedlist-macos-native/issues/41) | 🔨 built, unmerged |
-| `accountStatus` unmodelled | [#42](https://github.com/CompositeCode/interlinedlist-macos-native/issues/42) | 🔨 built, unmerged |
-| G32 My Profile | [#44](https://github.com/CompositeCode/interlinedlist-macos-native/issues/44) | ☐ unbuilt — do first |
-| G33 Integrations | [#47](https://github.com/CompositeCode/interlinedlist-macos-native/issues/47) | ☐ unbuilt |
-| G34 profile settings | [#46](https://github.com/CompositeCode/interlinedlist-macos-native/issues/46) | ☐ unbuilt |
-| G31 sidebar IA | [#45](https://github.com/CompositeCode/interlinedlist-macos-native/issues/45) | ☐ unbuilt — **last** in the cluster |
-| G36 profile location | [#57](https://github.com/CompositeCode/interlinedlist-macos-native/issues/57) | ☐ unbuilt |
-| Applications pane tail | [#56](https://github.com/CompositeCode/interlinedlist-macos-native/issues/56) | ☐ unbuilt |
-| Lists form polish | [#50](https://github.com/CompositeCode/interlinedlist-macos-native/issues/50) | ☐ unbuilt |
-| GitHub-backed list creation | [#51](https://github.com/CompositeCode/interlinedlist-macos-native/issues/51) | ☐ unbuilt |
-| **G40 saved list views** | [**#81**](https://github.com/CompositeCode/interlinedlist-macos-native/issues/81) | ☐ **newly found 2026-09-14** |
+| G37 entitlement matrix · G38 email-verification gate · `accountStatus` | [#40](https://github.com/CompositeCode/interlinedlist-macos-native/issues/40) · [#41](https://github.com/CompositeCode/interlinedlist-macos-native/issues/41) · [#42](https://github.com/CompositeCode/interlinedlist-macos-native/issues/42) | ✅ shipped, PR #83 |
+| G30 AI availability copy | [#39](https://github.com/CompositeCode/interlinedlist-macos-native/issues/39) | ✅ shipped, PRs #83 + #88 |
+| G32 My Profile | [#44](https://github.com/CompositeCode/interlinedlist-macos-native/issues/44) | ✅ shipped, PR #90 |
+| G33 Integrations | [#47](https://github.com/CompositeCode/interlinedlist-macos-native/issues/47) | ✅ shipped, PR #93 — *partial, see below* |
+| G34 profile settings | [#46](https://github.com/CompositeCode/interlinedlist-macos-native/issues/46) | ✅ shipped, PR #92 |
+| G31 sidebar IA | [#45](https://github.com/CompositeCode/interlinedlist-macos-native/issues/45) | ✅ shipped, PR #94 |
+| Applications pane tail | [#56](https://github.com/CompositeCode/interlinedlist-macos-native/issues/56) | ✅ shipped, PR #102 |
+| Lists form polish | [#50](https://github.com/CompositeCode/interlinedlist-macos-native/issues/50) | ✅ shipped, PR #101 — *3 of 4 items; From Template has no route* |
+| **G40 saved list views** | [#81](https://github.com/CompositeCode/interlinedlist-macos-native/issues/81) | ✅ shipped, PR #105 |
+| App-target test flake | [#82](https://github.com/CompositeCode/interlinedlist-macos-native/issues/82) | ✅ fixed, PR #86 |
+| `/api/lists/{id}` decode bug · list-schema wire contract | [#75](https://github.com/CompositeCode/interlinedlist-macos-native/issues/75) · [#85](https://github.com/CompositeCode/interlinedlist-macos-native/issues/85) | ✅ fixed, PR #87 |
+| Notification tray semantics | [#80](https://github.com/CompositeCode/interlinedlist-macos-native/issues/80) | ✅ fixed, PR #89 |
+| Notification type vocabulary | [#95](https://github.com/CompositeCode/interlinedlist-macos-native/issues/95) | ✅ fixed, PR #96 |
+| Sync-outbox FIFO ordering | [#84](https://github.com/CompositeCode/interlinedlist-macos-native/issues/84) | ✅ fixed, PR #100 |
+| Xcode 27 `Document` collision | [#98](https://github.com/CompositeCode/interlinedlist-macos-native/issues/98) | ✅ fixed, PR #99 |
+| `APIError` drops structured bodies | [#103](https://github.com/CompositeCode/interlinedlist-macos-native/issues/103) | ✅ shipped, PR #106 — *consumers not yet wired* |
+| Sync-agent config → per-machine settings | [#104](https://github.com/CompositeCode/interlinedlist-macos-native/issues/104) | ✅ shipped, PR #107 |
+| Docs consolidation | [#65](https://github.com/CompositeCode/interlinedlist-macos-native/issues/65) | ✅ shipped, PR #97 |
+| G36 profile location | [#57](https://github.com/CompositeCode/interlinedlist-macos-native/issues/57) | 🟡 **read-only half shipped** (PR #92); setter blocked on [#91](https://github.com/CompositeCode/interlinedlist-macos-native/issues/91) |
+| GitHub-backed list creation | [#51](https://github.com/CompositeCode/interlinedlist-macos-native/issues/51) | ⛔ needs a repository on the `.env` account |
+| LinkedIn destination picker | [#79](https://github.com/CompositeCode/interlinedlist-macos-native/issues/79) | ⛔ `posting-targets` is empty — needs a linked LinkedIn account |
+| DM decoder tightening | [#76](https://github.com/CompositeCode/interlinedlist-macos-native/issues/76) | ⛔ needs a populated `items[]` — **one DM to the recon account** |
+| DM deep-link producer | [#77](https://github.com/CompositeCode/interlinedlist-macos-native/issues/77) | ⛔ `.directMessage` kind shipped (PR #96); producer needs a real DM row |
+| Document presence | [#78](https://github.com/CompositeCode/interlinedlist-macos-native/issues/78) | ⛔ envelopes confirmed 2026-09-17; `users[]` row needs a second account |
+| Profile location cannot be cleared | [#91](https://github.com/CompositeCode/interlinedlist-macos-native/issues/91) | ⛔ **upstream** — no route unsets it; blocks #57 |
| Scheduled post editing | [#55](https://github.com/CompositeCode/interlinedlist-macos-native/issues/55) → [#74](https://github.com/CompositeCode/interlinedlist-macos-native/issues/74) | ⛔ upstream route does not exist |
-| `/api/lists/{id}` decode bug | [#75](https://github.com/CompositeCode/interlinedlist-macos-native/issues/75) | 🔴 latent |
-| DM decoder tightening | [#76](https://github.com/CompositeCode/interlinedlist-macos-native/issues/76) | ☐ needs a captured payload |
-| DM deep-link producer | [#77](https://github.com/CompositeCode/interlinedlist-macos-native/issues/77) | ☐ dead seam |
-| Document presence | [#78](https://github.com/CompositeCode/interlinedlist-macos-native/issues/78) | ☐ deferred |
-| LinkedIn destination picker | [#79](https://github.com/CompositeCode/interlinedlist-macos-native/issues/79) | ☐ after #47 |
-| Notification tray semantics | [#80](https://github.com/CompositeCode/interlinedlist-macos-native/issues/80) | 🔴 parity bug |
-| G28 dashboard · G29 blog · G39 subscription | [#61](https://github.com/CompositeCode/interlinedlist-macos-native/issues/61) | ❓ **unblocked 2026-09-14** — product decision only |
-| List folders | [#49](https://github.com/CompositeCode/interlinedlist-macos-native/issues/49) | ✋ **decided: not returning to macOS** |
-| G9 push / APNs | [#59](https://github.com/CompositeCode/interlinedlist-macos-native/issues/59) | 🔬 spike |
+| G28 dashboard · G29 blog · G39 subscription | [#61](https://github.com/CompositeCode/interlinedlist-macos-native/issues/61) | ❓ product decision only — option 3 **unblocked** |
+| List folders | [#49](https://github.com/CompositeCode/interlinedlist-macos-native/issues/49) | ✋ decided: not returning — `docs/decisions/0007` |
+| G9 push / APNs | [#59](https://github.com/CompositeCode/interlinedlist-macos-native/issues/59) | 🔬 spike — also needs a device-token route (none in the spec) |
| G10 multi-account | [#60](https://github.com/CompositeCode/interlinedlist-macos-native/issues/60) | ⛔ session-only, re-verified 401 |
-| Backend asks | [#58](https://github.com/CompositeCode/interlinedlist-macos-native/issues/58) | ⛔ blocked |
+| Backend asks | [#58](https://github.com/CompositeCode/interlinedlist-macos-native/issues/58) | ⛔ blocked — two new asks added 2026-09-16 |
| Release: pkg/dmg · sync agent · App Store | [#62](https://github.com/CompositeCode/interlinedlist-macos-native/issues/62) · [#63](https://github.com/CompositeCode/interlinedlist-macos-native/issues/63) · [#64](https://github.com/CompositeCode/interlinedlist-macos-native/issues/64) | §3 |
+**Three issues share one blocker.** [#76](https://github.com/CompositeCode/interlinedlist-macos-native/issues/76), [#77](https://github.com/CompositeCode/interlinedlist-macos-native/issues/77) and [#78](https://github.com/CompositeCode/interlinedlist-macos-native/issues/78) each need a payload
+only a *second* account can produce — a DM's `items[]` row, its notification row, and a presence
+`users[]` row. One other account sending one message, and opening one shared document, resolves all
+three. It is the highest-leverage unblock on this list.
+
+**Four more silent-decode defects were found and fixed in this wave** — the list-schema contract
+([#85](https://github.com/CompositeCode/interlinedlist-macos-native/issues/85)), the notification type vocabulary ([#95](https://github.com/CompositeCode/interlinedlist-macos-native/issues/95)), the Mastodon provider token
+([#47](https://github.com/CompositeCode/interlinedlist-macos-native/issues/47)) and the app-settings device fields ([#56](https://github.com/CompositeCode/interlinedlist-macos-native/issues/56)). Every one had **green tests written
+against fabricated fixtures**. The standing count in §1's constraints should read **seven**, not three.
+
**G40 · Saved list views — NEWLY FOUND 2026-09-14 ([#81](https://github.com/CompositeCode/interlinedlist-macos-native/issues/81)). Size M.**
@@ -353,12 +375,33 @@ gates on nothing. The shape implies a real collaboration model — **shared** vi
plus **personal** views owned by the caller, with a fork operation to escape someone else's shared
view and an `isDefault` flag per user.
-`config` is a **string**, not an object, so its grammar is unspecified by the schema — **probe a real
-saved view from the web before modelling it**, exactly as the G21 link-metadata and G25 members
-defects should have been. Do not guess this one.
+✅ **SHIPPED 2026-09-17** (PR #105). The probe that preceded it disproved two things this section
+asserted, and both are worth keeping written down.
+
+**`config` is an object, not a string.** The request-body schema declares `{"type":"string"}` — that is
+a generator artefact. The `ListView` schema declares it untyped and required, and sending a JSON
+object returns `201`.
+
+**It does not encode column order, visibility or sort.** This section presumed it did. `config` is a
+server-normalized whitelist of exactly four keys — **`mode`, `density`, `filters`, `search`** — and ten
+column/sort keys sent alongside were **silently stripped** with no error.
+
+Two further traps the capture caught, both now pinned by tests:
+
+- `createdAt` / `updatedAt` are marked **required** in the `ListView` schema and are **absent from
+ every live response**. A strict decoder mirroring the spec fails on every row.
+- The spec's own response example shows `config.filters: [{"key":"read","op":"eq","value":false}]`.
+ That filter was sent live and **dropped to `[]`**. Do not build a fixture from it.
+
+The validation asymmetry is encoded in the types: `scope` is a **closed** `personal|shared` enum
+because the server hard-fails an unknown token, while `mode` and `density` carry `.unknown(String)`
+because the server **silently defaults** — so an unrecognised value is a real state that must not be
+overwritten on save.
-**Next step:** sequence after the account cluster. It is additive — nothing currently
-shipped is wrong without it.
+⚠️ **Still unconfirmed:** the `filters` *element* grammar. The recon account's only list has an empty
+schema, so every filter references an unknown column key and is dropped — grammar failure and
+column-not-found are indistinguishable. Preserved opaquely rather than guessed. Re-probe once a
+schema-bearing list exists.
## 2. Blocked work — backend-gated or spike-first