# ELA — merge-readiness verdict: `fix/e2e-remediation-20260906`

**Date:** 2026-09-12 (overnight run)
**Scope reviewed:** `main f15bfab5` … `HEAD fbd2ece4`, committed diff only (uncommitted tracked changes excluded).
**Evidence base:** Codex CLI review on devbox (fresh Linux test run, disposable PostgreSQL 17, four per-finding refutation passes) + independent Opus verification of all four major findings against the working tree.
**Raw review artifacts:** `/Users/stevanl/dev/reports/overnight-2026-09-12/ela/findings.json`, `/Users/stevanl/dev/reports/overnight-2026-09-12/ela/notes.md`

---

## 1. Verdict

# NO-GO for merge as it stands.

Codex's provisional verdict was NO-GO. I agree, and my reasoning is narrower and harder than "six findings exist":

1. **The branch cannot go merge-green.** `admin-visual-regression` is a required `pull_request` job in `.github/workflows/ci.yml`, and it runs `npm run admin:visual` over the whole spec. The fresh Linux run was **19 failed / 9 passed**, five of them the branch's own `case_read_version_required` regression (Finding 4). A required gate that is red by construction is a merge stopper independent of severity grading.
2. **Two confirmed defects sit directly on the product's core assurance** — proving a lawyer actually reached a detainee. Finding 1 shows the member a "Lawyer contact is verified" hero on a *replacement* firm's engagement and permits resolved closure on the previous firm's evidence. Finding 2 makes the replacement firm's resolution report abort the whole case-command transaction (hard 500) with only manual ops recovery. For a life-safety product, "major, not blocker" is the right severity label but the wrong merge decision.
3. **Finding 3 is a fail-closed emergency-dispatch refusal** triggerable by unvalidated member input, in exactly the code paths this branch exists to un-gate. It is currently blunted only by the production dispatch guard — the guard this remediation work is meant to eventually lift. Merging the code that lifts the guard on top of the defect is the wrong order.
4. **No blockers, and every major has a bounded, known fix.** None of the four require redesign. So the correct posture is not "reject the branch" — it is "four fixes plus a re-run converts this to GO."

**What converts NO-GO to GO:** the conditions in §7. Nothing found in this review requires re-architecting the remediation; it requires four targeted fixes, one new forward migration, and a clean re-run of the affected gates on the final commit.

**Production release is a separate and independent NO-GO**, and remains so even after the merge conditions are met. Ordinary production emergency entry points are deliberately gated, DSAR fulfilment is uncomposed and returns 503 before any erasure, migration/grant/worker adoption is unrehearsed on the real deployment, and provider/device/legal evidence is open. *Merging must preserve those holds.* This review authorises no deployment.

---

## 2. Tests

Fresh run, clean `git archive HEAD`, Linux, Node 24.21.0 unless noted; PostgreSQL 17.10 in a disposable loopback container. No production schema, data or role was touched.

### Green

| Gate | Result |
|---|---|
| `format:check` | PASS — all files Prettier-clean |
| `lint` (after dependency build) | PASS (exit 0) |
| `typecheck` (after dependency build) | PASS (exit 0) |
| `build` | PASS |
| `contract:test` (API contracts) | PASS — 1,502 tests, **1,457 pass / 0 fail / 45 skip** |
| `contract:breaking` | PASS — OpenAPI v1 matches baseline; v2 case reads declared |
| `admin:test` | PASS — 246/246 |
| `mobile:security:test` | PASS — 1,107 pass / 1 skip |
| `cases:test` / `comms:test` / `auth:test` / `kyc:test` / `realtime:test` / `ui:test` / `workers:test` / `events:test` / `observability:test` / api-types | PASS — 97 / 82 / 26 / 38 / 54 / 55 / 19 / 3 / 4 / 16 |
| `lifecycle:fixture-gate` (with explicit GIT_DIR/GIT_WORK_TREE) | PASS 5/5 |
| `local:smoke:ci`, `arrest-rehearsal:run`, `validate:eas`, `env:example:check`, `event-taxonomy:check`, `prd:tasks:check`, `observability:pii-check`, `provider-readiness:check`, zero-IAP guards | PASS |

Aggregate unit total across the green suites: **≈1,747 passed, 1 skipped** (upstream stage summary; the per-suite counts above are the verified components).

### Red / inconclusive

| Gate | Result | Read |
|---|---|---|
| `admin:visual` (required CI job) | **FAIL — 19 failed / 9 passed** | 5 failures are the branch's own v1/v2 regression (Finding 4). Rest: Linux baseline/font diffs, missing Linux snapshots, a contrast-debt assertion, a document-popup timeout, obsolete Triage heading. **No baseline was approved or changed.** |
| `admin:visual:artifacts` | FAIL (exit 1) | Expected target selector not found. |
| API integration, full, Node 24, serial | **TIMEOUT (exit 124)** — 166 tests, 88 pass / 52 fail / 26 cancelled | Large composition test's 120 s parent deadline cascades into cancellations. |
| API integration, full, serial (completed run) | **FAIL** — 322 tests, 272 pass / **44 fail** / 5 cancelled / 1 skip | Mixture of real assertion failures, harness cancellations and stale expectations. |
| API integration, remaining files, prepared DB | FAIL — 274 tests, 260 pass / 10 fail / 4 cancelled | Includes the contradictory DSAR truth subtests (Minor 2). |
| `contract:test` under INTEGRATION=1 + live DB | FAIL — 1,729 tests, 1,640 pass / 69 fail / 5 cancelled / 15 skip | Opt-in DB-backed variant; not the clean contract gate above. |
| Schemathesis fuzz (`--checks all --max-examples 25`) | **FAIL — 90 distinct failures** over 2,811 cases across 96 v1 operations | Undocumented responses, positive-data schema rejection, negative-data acceptance, method/auth ordering. **Not 90 independent new product bugs.** |
| Cold `npm run ci` (Node 20) | **FAIL** at lint — 4,686 errors | CI order runs lint before the first TypeScript build; after building the graph lint and typecheck pass. **Not 4,686 real source defects** — but the cold gate as written is still red. |
| `admin:smoke`, `admin:browser-smoke` | Assertions passed, harness did not exit (124/143) | Success text is *not* converted into an exit-0 run. |
| Legacy contract probe (opt-in, DATABASE_URL) | FAIL — 33 pass / 2 fail / 1 skip | OTP test's replaced in-memory users handle; one signed-link test expected 200, got 410. Not evidence of an auth defect. |
| Evidence gates: analytics, arrest-rehearsal, closed-beta, legal-policy, observability, oncall, pdpl, pentest, provider-credentials, public-launch, residency, server-hardening, store, visual-regression, workers | FAIL | Intentionally red where signed/provider/device/live artifacts are missing. `production:readiness:check` additionally crashes on a pre-existing missing `HelpContactOpsScreen.tsx` read; `visual-regression:examples:check` expects 38 states against an unchanged 52-state matrix. **These pre-existing gate defects are not attributable to this diff.** |

Not executable in this environment (so uncovered, not passed): all iOS/Android device and simulator visual suites, Apple Watch / APNs / FCM / StoreKit native tests, live provider/SMS/email/KYC/IAP scripts, `just check` (just not installed), and the archived 2026-09-05 journey scripts.

Dependency note: `npm ci` reports 23 vulnerabilities (10 moderate, 12 high, 1 critical). The lockfile and dependency declarations are **unchanged by this branch**; no remediation was in scope.

---

## 3. Confirmed findings (Opus-verified)

0 blockers · 4 major · 2 minor · 0 nits after refutation. All four majors below were independently verified by me against the branch; I attempted refutation on each and failed.

### MAJOR 1 — Replacement engagements inherit an earlier firm's verified status
`apps/api/src/member-verification-work.ts:17`

**Evidence.** `cases.verification_status` is case-wide and is only ever written by the `verification_tasks_sync_case_status` trigger (`db/ela-062/001_verification_outcomes_submit.sql:86-140`), `verification-followup-store.ts:96`, and `dispute-triage-store.ts:95`. The canonical case-command write path never touches it — the UPDATE at `apps/api/src/case-relational-command-sql.ts:126-138` sets state/assigned_at/runtime_data only, and `splitRelationalCaseState` strips `verificationStatus` out of runtime_data (`apps/api/src/case-relational-state.ts:65`). **No code or migration resets it on engagement change.**

Reachable without admin override: verification tasks are created while the case is still `assigned` (`db/verification-durable-002/001_canonical_verification_tasks.sql:129`), so firm A can submit `verified_client_contact`, then hand the case over via the firm-facing withdrawal endpoint (`server.ts:48168-48195` → `withdrawAcceptedFirm` at `server.ts:30187`). After B accepts: new `assigned_at`, `runtime_data.firmId = B`, `verification_status` still `verified`.

Member-facing harm: line 17 emits case-wide `coalesce(c.verification_status,'pending')` while `work` (lines 23-28) is correctly engagement-scoped. This is the only member verification reader (`server.ts:48437-48468`, 503 with no in-memory fallback). In `apps/mobile/src/services/activeCaseHero.ts` the `verification_status === "verified"` branch (line 138) is evaluated **before** the `work` action branch (line 144), so the member sees "Lawyer contact is verified." and is never asked to confirm firm B reached them. Same ordering in `activeCaseTimeline.ts:307/334`.

Closure: the `mark_verified` override (`server.ts:29420-29429`) is gated on the stale `verificationStatus === "verified"`, contradicting its own comment. Refinement to Codex's claim: it also requires `liveCase.state === "verification_pending"`, so the case must re-reach that state first — narrower window, same defect.

Corroboration: the sibling operator reader already does it right, per-task from `vt.outcome` with a `current_assignment` flag (`apps/api/src/operator-verification-work.ts:17-38`). No test covers verified-A → reassign-B → B-pending.

**Fix.** Derive current verification from the current accepted firm/acceptance timestamp, or reset it atomically on engagement change — note this means *adding* `verification_status` to the case-command UPDATE, which today does not write that column at all. Require current-engagement verification for closure. Reorder the mobile hero/timeline branches so pending work wins over a stale case-level flag. Add the missing regression.

*Caveat:* the cited PostgreSQL 17 repro was not re-run locally (prod DB is SSH-only) and `refute-verification-carry.log` is not in the working tree; the confirmation rests on code and migration reading, which agrees with the reported repro.

### MAJOR 2 — Previous member-end timestamp blocks the replacement firm's resolution report
`db/verification-durable-002/001_canonical_verification_tasks.sql:77`

**Evidence.** A member end on an `assigned` case sets `memberEndRequestedAt` and moves to `verification_pending` (`server.ts:30545`). `applyForceAssignFirm`'s displacement branch (`server.ts:28477-28486`) clears `firmReportedResolvedAt` but **not** `memberEndRequestedAt`. The two automated reassignment paths do clear both (`server.ts:30297-30301`, `30376-30380`) — the leak is specific to the ops force-assign path. Neither accept (`30126-30137`) nor `reportResolvedByFirm` (`30447-30456`) clears it, and `splitRelationalCaseState` strips only verificationStatus/verificationWork/manualTakeoverAt (`case-relational-state.ts:64-67`), so the stale timestamp lands in `cases.runtime_data` while `assigned_at` is recomputed to **firm B's** acceptance. Migration line ~77 then takes `min(firmReportedResolvedAt, memberEndRequestedAt)` as `entry_at` → firm A's earlier clock → `due_reason='case_entry' and due_at >= assigned_at` fails.

Reachable: `verification_pending -> assigned` is in `allowedAdminOverrideTransitions` (`server.ts:994`); the trigger fires on `update of state, assigned_at, runtime_data`.

I reproduced the DB mechanism in a faithful isolated model of the trigger and constraint on local PG 14: check-constraint violation, **`on conflict do nothing` does not swallow it**, case left in `assigned` — so the replacement firm's resolution report aborts the entire case-command transaction (hard 500) and never lands.

Test gap: `apps/api/src/case-storage-composition.integration.test.ts:830-870` covers exactly this reassign→accept→report sequence, but only with `firmReportedResolvedAt` — the field force-assign *does* clear — so it passes. The pre-rollout inspection queries in `docs/qa/2026-09-06-end-to-end-remediation/MIGRATIONS.md:245-249` do not detect a stale member-end clock either.

Two overstatements in the original finding, not enough to downgrade: ops can still close via `close_case`, and a fresh member-end request overwrites the stale timestamp; and `MIGRATIONS.md:228-230` says the migration is frozen after local use with no production grants, so this is a **pre-rollout defect, not a live production outage**.

**Fix.** A **new forward migration** (do not edit the frozen file) that derives verification-entry timestamps only from the current accepted engagement and clears prior-engagement end fields on reassignment while retaining audit history. Clear `memberEndRequestedAt` in the force-assign displacement branch alongside `firmReportedResolvedAt`. Add the member-end A → B accepts → B reports regression.

### MAJOR 3 — Dollar-delimiter text can block relational emergency commands
`apps/api/src/case-relational-command-sql.ts:114`

**Evidence.** `textSql` only doubles single quotes and `jsonSql` is a raw `JSON.stringify` (lines 18-19); neither escapes `$`. Lines 114-121 interpolate `${rows}` twice inside a fixed `do $identity$ … end $identity$;` body. `splitRelationalCaseState` puts the whole dispatch record into `dispatch_data`, including the member-supplied `idempotency_key` — `z.string().trim().min(12).max(120)` (`packages/api-types/src/index.ts:2874`), **length bounds only, no charset restriction anywhere**. A key like `ab$identity$cd` closes the body early. The same pattern exists at `apps/api/src/prearm-command-store.ts:189`, whose body carries `location.address_input` (free text, max 240) and `station_id`; only `client_arm_id` has a charset regex.

SQL is shipped as raw text to `psql -f -` with `ON_ERROR_STOP=1` (`server.ts:12312-12375`) inside `begin; … commit;`, so a tag collision is a parse error that aborts the transaction — **the emergency case or pre-arm does not persist.**

The house remedy is one file away: `dsarIdentityChallenge.ts:264-267` hex-encodes literals with the comment *"prevents a supplied identifier from terminating a DO block's dollar quote."* The two new builders deviate from it, with no test.

Cross-case amplification is real: every command serialises **all** cases, the legacy commit path has no dollar-quoted body so a poisoned key persists fine under legacy storage, and legacy adoption's `do $checked$` does not interpolate the payload — so adoption succeeds and lands the poison in `cases`, after which every relational case command fails for every member.

Why major, not blocker: it takes a hand-crafted request from an authenticated, entitled member; the new path fails closed with no partial write; and both `POST /cases/dispatch` and `POST /cases/pre-arm` return 503 in production mode outside the reviewer allowlist (`server.ts:45788-45797` and the pre-arm route ~45415). Today the vector is live in staging/drill environments; **the production guard that blunts it is exactly what this branch exists to lift.**

**Fix.** Stage request data outside procedural dollar-quoted bodies (reference staged rows inside them), or hex-encode literals consistently per the DSAR precedent. Cover both new input and adopted legacy payloads. Add a delimiter test.

*Caveat:* `logs/sql-delimiter-probe.log` and `refute-sql-delimiter.log` are not in the working tree (no `logs/` dir on this branch), so the PG 17 error text could not be re-read; the code-level proof does not depend on them.

### MAJOR 4 — Required browser journeys still read v1 after the v2 case-read cutover
`apps/api/src/server.ts:53475`

**Evidence.** The guard at `server.ts:53474-53477` returns 409 `case_read_version_required` on `GET {v1}/admin/cases/:id` whenever `toV1AdminLocation(latest_live_location)` is null or `verification.due_at` is null. Same guard at `52107-52115` (v1 live list) and `48042-48050` (firm brief); helper at `36320`.

Why it fires: `latest_live_location` is derived only from recorded GPS points (`server.ts:28845-28884`), and a case created via `POST /admin/cases/local-sms` records no GPS point (`server.ts:44876` stores only accuracyM/addressInput/locationSource). So the v1 branch 409s.

Why the test dies: `apps/admin/tests/visual/portal-routes.spec.ts:1737` seeds the LR-008b case with `location_override {accuracy_m: null, source: "no_signal"}` — no GPS point — then reads it back at line 1769 via `requestApiJson('/v1/admin/cases/${caseId}')`. `requestApiJson` (lines 151-165) throws on any non-2xx, so the helper dies inside `openVisualBriefLinkCase()` **before a single UI assertion or screenshot**. Line 489 (`seedVisualDispatchWorkspace`'s `GET /v1/admin/cases/live`) has the same exposure, and its own seed at line 503 creates an address-source, GPS-less case. No skip or env gate protects these paths.

It is a **required gate**: `.github/workflows/ci.yml` job `admin-visual-regression` runs `npm run admin:visual` on every pull_request over the whole spec.

**Production code is correct**, which is why this is major and not blocker: `apps/admin/app/_shared/adminUpstreamPath.ts` routes `GET admin/cases/live` and `GET admin/cases/:id` to `/v2`, leaving action POSTs on v1. No operator- or member-facing read is broken, and the guard itself is right.

**Fix.** Migrate the two GET helper reads (and their response parsing) in the visual spec to v2, keeping the existing behavioural assertions and the truthful no-location fixtures. **Do not remove the compatibility guard and do not blind-refresh screenshot baselines.** The spec's other `/v1/admin/cases` calls (1755, 1792, 1945) are POST override/reissue and are unaffected.

---

## 4. Codex-reported, not independently verified

Both are minor. They were reported by Codex after its own refutation pass but were **not** put through independent Opus verification; treat the claims below as Codex's, not confirmed.

1. **Contact-reveal operation omits bearer security declaration** — `openapi/v1.yaml:11` (minor, Codex-only).
   Claim: `revealGeneralHelpContact` requires an admin bearer session with permission and fresh MFA in prose and in server enforcement, but the OpenAPI spec declares no operation-level or root security requirement and no bearer scheme, so generated clients cannot infer the credential requirement. The baseline mirrors the omission. **No auth bypass was established.** Suggested fix: define the bearer scheme, reference it on this operation, and update the contract baseline deliberately.
2. **New DSAR truth tests require fulfilment the safety contract forbids** — `apps/api/src/dsar-fulfilment-truth.integration.test.ts:89` (minor, Codex-only).
   Claim: the new export/delete truth subtests expect HTTP 200 and a fulfilled request, but the uncomposed durable DSAR repository deliberately returns 503 before any erasure (as the neighbouring safety test asserts), making the new suite internally contradictory and reliably red. Observed as `503 !== 200` in the fresh serial PostgreSQL run. Suggested fix: reconcile the tests with the unavailable-fulfilment contract; keep and strengthen the assertions that the request and deadline stay unchanged and that no erasure starts; move actual-count assertions to a real composed fulfilment service when one exists. **Do not "fix" this by weakening the 503 guard.**

---

## 5. Refuted findings

**None.** Codex's refutation pass eliminated everything it eliminated before reporting; all six surviving findings were reported, and all four majors survived a second, independent Codex CLI re-review and then my own verification. The residual corrections are refinements inside confirmed findings, recorded above:

- Major 1: closure is not reachable the instant firm B accepts — the case must re-reach `verification_pending`. Narrower window, defect stands.
- Major 2: "no recovery path" is overstated (ops `close_case` works; a fresh member-end request overwrites the stale clock), and this is a pre-rollout defect rather than a live production outage.
- Major 3 / Major 4: three of the cited log files are absent from the working tree; both findings were re-established from source instead.

---

## 6. Migrations and the deploy order they imply

**34 added SQL files across 33 directories. No existing migration is edited** (verified via `git diff main...HEAD -- db`). Every file is tracked at HEAD.

Two hard facts to hold onto:

- **There is no checked-in automatic migration runner or registration list that proves production application.** Deploy scripts do not run this sequence; `ela_schema_migrations` belongs to the external deployment process. The commit label "unapplied" says nothing about a remote database. **Staging/production status is unknown for all 34 files.**
- **No file supplies a production rollback procedure.** Migration `dispatch-transaction-013/001` retention can irreversibly purge eligible rows. A lexicographic directory sweep is unsafe.

Transaction rules worth pinning to the runbook:
- `issued-policy-terms-001/001` and `verification-durable-005/001` have no BEGIN/COMMIT of their own — wrap each file in a single transaction.
- **Do not** wrap `dispatch-transaction-004` and `005` in one transaction: new enum labels must commit before use.
- `verification-durable-001/001` (the `set_updated_at` helper) must run **before** historical `ela-064` on a fresh database — this is not the directory sort order.
- `dispatch-transaction-010` must follow `009` immediately.
- `dispatch-transaction-012` requires duplicate live arms to be resolved before apply.

**Implied deploy sequence:**

1. Inventory actual schema, roles, old writers and holds. Take a verified backup. Stop affected ingress and workers.
2. Install historical foundations, with `verification-durable-001` **before** `ela-064`.
3. Apply `dispatch-transaction-001..013` in dependency order — commit 004 before 005; apply both 013 files only with their full prerequisites (`013/002` also needs the complete `ela-095b` retention schema, and installs no cron schedule).
4. Apply `verification-durable-002..010` with a coordinated API grace-worker handoff (005 renames the old grace function and no-ops the old SQL writer — schema-first without the matching worker can stall progress while cron still returns zero).
5. Install the independent Help / config / issued-terms / device-token / member-push / voice-notes prerequisites **before** their new code; apply `dsar-identity-001` only as a still-uncomposed component (current HTTP routes do not use it).
6. Perform checked canonical adoption (`dispatch-transaction-011`) with old writers stopped, after backup and quiescence, reconciling invalid legacy evidence rather than rewriting it.
7. Boot the matching API/worker build. Verify runtime-role writes, provider callbacks, retention and scheduler age/recovery.
8. **Keep ordinary production dispatch and full DSAR fulfilment gated** until their separate acceptance completes.

**Consequence of Major 2:** `verification-durable-002/001` is documented as frozen after local use. The fix must ship as a **new superseding forward migration** appended to step 4 — not as an edit to the frozen file.

`migration-inventory.json` records each file's SHA256 and whether the composition harness includes it; that harness omits DSAR identity and `013/002`. The disposable owner-role migration tests strip historical cron scheduling and scope enum checks — they do **not** establish the unmodified production runner, its grants, or the scheduler.

---

## 7. Conditions that convert NO-GO to GO

1. Fix Major 1 — bind verification to the current accepted engagement; require current-engagement verification for closure; reorder the mobile hero/timeline branches. Add the verified-A → reassign-B → B-pending regression.
2. Fix Major 2 — new forward migration (do not touch the frozen file) + clear `memberEndRequestedAt` in the force-assign displacement branch. Add the member-end → reassign → report regression.
3. Fix Major 3 — make both SQL builders delimiter-safe using the existing `dsarIdentityChallenge.ts` hex-encoding precedent; cover new input *and* adopted legacy payloads; add a test.
4. Fix Major 4 — migrate the two visual-spec GET helper reads to v2. Keep the guard. Keep the truthful no-location fixtures. Do not approve new screenshot baselines to go green.
5. Re-run on the final reviewed commit: `admin:visual`, the affected PostgreSQL integration suites, `contract:test`, and the standard format/lint/typecheck/build gates. **Unit passes do not override red PostgreSQL, browser or fuzz results.**
6. Explicitly disposition every remaining red with reproducible evidence — real defect, harness artefact, or pre-existing gate debt. **Do not weaken assertions or remove the production-dispatch or DSAR guards to obtain a pass.**
7. Decide on the two minors: either fix them on this branch (OpenAPI bearer declaration + deliberate baseline update; reconcile the DSAR truth tests to the 503 contract) or ticket them with an owner. Either is acceptable; leaving the DSAR suite reliably red is not.
8. Merging changes nothing about release. Production dispatch stays gated, DSAR fulfilment stays uncomposed, and the migration sequence in §6 is unrehearsed on the real deployment.

---

## 8. Claims the branch makes that were not provable

Taken from the claim-to-artifact matrix. These are documentation claims, not defects — but they should not be repeated as fact in a merge description.

| Branch claim | Status |
|---|---|
| "Full `npm run ci` passed in 188.20s; API 1457 pass/45 skip" | **Not proven for current HEAD.** It tested a preserved working checkout at earlier harness/fixture commits. The final full-CI log is absent from both the archive path and the backup. Fresh browser CI is red. |
| "Remediation complete / item verified" | **Not proven for the branch as a whole.** The branch's own work register: 35 verified, 15 blocked, 45 in progress, 110 not started, 1 implemented-but-unverified. Verified slices do not establish parent acceptance. |
| "Current release is not self-contained; 31 migrations remain untracked" | **Stale.** Accurate for the earlier snapshot it names; two later commits tracked the migrations and remaining source. Current HEAD has 34 added SQL files in 33 directories. |
| "Cleanup preserves outside-docs product contents" | **Dated proof only.** True at the named cleanup snapshots; 312 paths differ at current HEAD after four more commits. |
| "Durable canonical commands, adoption, verification and recovery pass selected PostgreSQL slices" | **Bounded.** Historical subset claims, not the whole HEAD suite. This review recorded integration failures and cancellations plus two independently reproduced reassignment defects. Runtime-role, scheduler and rollout proof absent. |
| "No applied migration was edited; hashes and rollback limits preserved" | **True for the diff** (34 additions, zero edits). Production application, registration and grants remain unknown, and no safe current-data downgrade is demonstrated. |
| "Visual report reflects the review checkpoint" | **Dated.** It is a `cdd16caa`/failed-CI checkpoint superseded by FINAL-CI; most referenced image bytes are outside the available checkout. |
| "2,095 command records document the run" | **True as a historical register** (1,674 zero exits, 421 non-zero, deliberate red reproductions included). Exit 0 is not an acceptance conclusion, and the register is not attributed to final HEAD. |

**Unverified overall** (no access, not a defect): production/staging migration ledger, grants/ownership/RLS, database-role execution and installed function hashes; scheduler installation, age and recovery; provider reconciliation and real delivery; native iOS/Android/Watch background and offline behaviour; complete DSAR identity/provenance/export/download/deletion/retention/holds; real refund settlement; exact CI container rendering; and a clean full acceptance run.

---

## 9. Ten-minute morning checklist

1. **(1 min)** Read §1. The decision is NO-GO for merge; §7 lists the eight conditions that flip it.
2. **(1 min)** Accept or reject the framing that the four majors are must-fix-before-merge rather than fix-forward. My argument: two of them break the lawyer-reached-the-detainee assurance, one makes a required CI gate red by construction, and none needs redesign.
3. **(2 min)** Assign the four fixes. Majors 1 and 2 are the same area (engagement lifecycle) and should go to one person; Major 3 is a self-contained SQL-builder change with an in-repo precedent to copy; Major 4 is a test-helper migration, not product code.
4. **(1 min)** Confirm the Major 2 fix ships as a **new** forward migration — `verification-durable-002/001` is frozen and must not be edited.
5. **(1 min)** Make the "do not go green the easy way" rule explicit to whoever picks this up: no approved screenshot baselines, no weakened assertions, no removal of the production-dispatch or DSAR 503 guards.
6. **(1 min)** Decide the two minors: fix on branch, or ticket with an owner.
7. **(1 min)** Confirm the release position is unchanged by any merge — production dispatch stays gated, DSAR fulfilment stays uncomposed, migration sequence unrehearsed.
8. **(1 min)** Note the pre-existing gate debt that is *not* this branch's fault and should be ticketed separately: `production:readiness:check` crashing on a missing `HelpContactOpsScreen.tsx`, the visual-state matrix 38-vs-52 mismatch, and 23 npm advisories on an unchanged lockfile.
9. **(1 min)** If you want a second opinion on the merge call rather than the findings, the raw material is in `/Users/stevanl/dev/reports/overnight-2026-09-12/ela/notes.md` — §"Complete command/results table" and §"Claim-to-artifact matrix" are the two worth skimming.
