Skip to content

Phase 14 — Mid-Review

Date: 2026-09-10 Reviewer: Claude (planning session lama-plan-phase-14, desk pass over the release, gate and issue evidence) and the owner (the five-question walk on prod on 2026-09-09, and the re-walk of the affected steps on 2026-09-10) Status: closed 2026-09-10 — the owner re-walked every failing step green on prod Artifact walked: the live production stack — https://climate-lama.online (UI v0.14.0 on the walk, v0.14.1 on the re-walk) and https://api.climate-lama.online (backbone v0.24.0 → v0.24.1), against the nine sections of the owner's walk runbook (Home · Assess single-cell · Assess multi-cell · Assess engine comparison · Lookup · Portfolios · Datasets · Runs · support surfaces) and Moment 3 steps 1–6 of showcase-script.md. Framework: per ~/.claude/collaboration-framework.md, Cluster 1 ritual. Evidence pack: phase-14-solidify.md § "Final work order", § "Mid-review inputs", § "F-10 walk outcome and the F-11 remediation wave", and § "Exit criteria"; the closing comments on #1322 and #1323.

Verdict

Verdict: Ready for next phase. All six exit criteria are met with evidence; the product the phase set out to solidify is live on prod and was walked by a human twice.

Phase 14 added no functionality. It framed the product as five user questions on five surfaces, deleted the surfaces that answered none (/matrix, /agreement, the /measures nav entry, the wizard), fixed the features that existed but failed at the contract they consumed, and completed the CLIMADA .h5 upload matrix. Twelve backbone build sessions (B1–B12) and nine UI sessions (U1–U9) landed 51 backbone issues and 47 UI issues, released as R2 (v0.24.0 / UI v0.14.0, 2026-09-08) and R2.1 (v0.24.1 / UI v0.14.1, 2026-09-10). Both milestones are closed at 0 open.

The walk itself is the verdict. On 2026-09-09 the owner walked all nine sections on prod and every one of the five questions was answerable; the walk surfaced nine finding clusters, two of them [CRITICAL] (a branding contract that left the tab title undefined and the save button transparent; a portfolio loss panel that computed against the newest, sparsest raster instead of a chosen dataset). Every cluster was root-caused in code before an issue was filed, the fifteen issues were built the same night in two /phase-build waves, shipped as R2.1 at 06:16 UTC, and on 2026-09-10 the owner re-walked steps 1.2, 1.5, 2.8, 2.10–2.11, 3.6, 5.2, 6.5–6.6 and 8: all green, zero [CRITICAL] outstanding.

# Check State
1 Artifact health ✅ prod on v0.24.1 / UI v0.14.1, deploy run 34444290318 verified (identity, bundle hash index-Do85QOvy.js → index-Qxd715a3.js, Alembic 0091)
2 Happy path (the five questions, Moment 3 steps 1–6) ✅ walked 2026-09-09, re-walked 2026-09-10 by the owner; ids below
3 Unhappy paths ✅ criterion 3's two failure specs execute for real since 2026-09-08 (run 34263764435); the cache miss on a coverage-less result recomputes once (#1319, proven on the 2.8 re-walk)
4 UI/UX presentability 🟨 fixed on the walk's evidence; one decision-class item stays (address search off on prod, F-5)
5 User-path completeness ✅ navigation of eight; /matrix redirects to /assess?batch=; a saved batch is one row on Runs
6 Promised-but-unbuilt ✅ everything in the phase's frame shipped; the deferrals are recorded as the Phase 16 stub, not lost

Check 2 — the walk, with ids

Single baseline scenario 91fb2637-9b4e-4bcf-b84f-42fcead9c405 ("walk baseline river flood GR"), result 0ae432bc-…; reopened from Runs with map and stats
Batch (Moment 3 steps 1–4) e0bf0155-5686-4189-b6fd-4235ac3acd5e — Aqueduct river flood Greece 1980 × LitPop Greece × JRC flood Europe buildings, ssp2-4.5 + ssp5-8.5 × 2030 + 2050, four cells completed; saved as scenario 8cfee746-5cb5-4089-b709-35fa2c4be8e4 and reopened from Runs as one row (/assess?batch=)
Engine comparison (step 6) batch 82e38d05-38ae-4266-badb-4f294a333af5, kind = engine_comparison, two engines, completed
Second batch 1f85a23d-3151-4a78-b90a-8b882bcb5ef7 on Aqueduct river flood Cyprus, saved as "test" — the owner's confirmation that the save round-trip holds for a second country
Coverage GET /v1/catalog/coverage places all 44 hazard datasets (GRC: river_flood 8 · coastal_flood 7 · wildfire 7 · heatwave 3 · earthquake 2 · storm_europe 1; CYP: river_flood 7 · coastal_flood 7 · wildfire 1 · earthquake 1); undetermined.hazards = 0
Lookup card carries location {GRC, Greece} in 12 ms; header "Region: Greece"

Exit criteria tracking

# State Evidence
1 — Walk MET 2026-09-10 owner walk 2026-09-09 (nine clusters → 15 issues, two [CRITICAL]), R2.1 the same night, owner re-walk 2026-09-10 all green; ids above
2 — Surfaces MET 2026-09-08 at UI bdc6e4c nav is a literal eight-entry array; /agreement gone; /matrix a redirect; /measures nav entry gone (route retained, D9 named); wizard absent. Exempted 2026-09-10: the /lookup address box, off on prod by the #534 / #1322 decision — explains itself (ui#410), route fully usable
3 — Outcomes MET 2026-09-08 e2e-smoke.yml run 34263764435 at bdc6e4c with stack_control=true: failure.spec.ts:314 (missing staged file → failed + error_code) 4.8 s, :364 (stalled worker → bounded "still running") 10.1 min, both expected. Vacuous for the whole phase before 2026-09-08 — see the central finding
4 — Machine checks MET 2026-09-08 sdk-smoke.yml 34263538542 + ci.yml 34263533400 at bf62f20 (v0.24.0); UI ci.yml 34263760370 + e2e-smoke.yml 34263764435 at bdc6e4c, the .h5 upload leg executing for the first time. Re-proven at R2.1: backbone ci 34443118215 + sdk-smoke 34443120346 at 117477e; UI ci 34443056481 + e2e-smoke 34443058870 at e9d0c21
5 — Prod hygiene MET 2026-09-08 demo residue 0 (owner, API proof on #1193); portfolio_assets = 0 on #1192, unchanged after the deletions
6 — Record MET 2026-09-10 this file; plan banner, status log and phase table flipped in the same PR; both milestones closed

The circuit breaker (D15) — rework at close

scripts/rework_rate.py --since 2026-09-05 --until 2026-09-10 --window-hours 168 --follow-ups, run at close on both repos. The script counts every non-fix PR as a build, docs PRs included, so the denominators are larger than the 2026-09-08 reading (38 / 25), which counted issue builds only.

Repo non-fix PRs fix PRs fixes / build follow-up issues follow-ups / build
backbone 60 12 0.20 20 0.33
UI 34 10 0.29 15 0.44

Per tier, where a tier was stamped: backbone Opus·xhigh 15 builds / 5 fixes (0.33) / 9 follow-ups (0.60); UI Opus·xhigh 9 / 3 (0.33) / 6 (0.67), Opus·high 3 / 1 / 2. The unsized buckets are the largest in both repos (backbone 39 PRs, UI 21) because the U-wave and closeout builders did not stamp a tier — the attribution gap the 2026-09-08 inputs recorded as fact still stands, and /phase-build now writes the tier into every PR body it opens.

The UI ratio tripped the 25 % breaker (36 % on 2026-09-08, 29 % at close). The analysis the framework asks for: of the ten UI fix PRs, five were trunk-red repairs of two classes that are now rules rather than judgment — (a) an isolated vitest run hides cross-file regressions, so every UI build re-runs the touched files and the full suite at the merged SHA; (b) a smoke spec that has never executed is not evidence (ui#387, ui#392). Three were composed-defaults fixes (a builder's local default disagreeing with the deployed default), one was a bounded-wait fix, one a dead-code sweep's regression. None was attributable to the model tier: Sonnet and Opus builds drew fixes at the same rate, and every shipped defect sat in a seam — a compose profile, a harness passing vacuously, a smoke lane another repo drives — that no reasoning depth sees from inside one PR. The breaker therefore did not stop dispatch (U8 and U9 proceeded); it changed the gates. The number to watch in Phase 15 is follow-ups / build, which is where rework hides when it arrives as a new issue rather than a fix PR.

Central finding — eight gates that reported success without doing their work

Every one was found by reading, not by any gate:

  1. The gh merged-PR body search as a "has the fix landed?" precondition fails open — five merged PRs cited #1237, none of them the fix.
  2. #1307 — probe_branch() printed its expect_terminal argument instead of asserting it; a failure-branch upload returning succeeded still recorded PASS. Fixed in #1308.
  3. #1305 — --require-merged spliced its title regex into a jq string, swallowed the parse error and reported "no merged PR". Fails closed, but refused a valid run. Fixed in #1306.
  4. Exit criterion 3 itself — failure.spec.ts skipped on every run for the entire phase because nothing set CL_E2E_STACK_CONTROL. Reported green throughout. Its first honest execution failed, and the failure was in the check, not the product (a 3-asset compute finished inside docker compose stop's SIGTERM grace, so the "stalled worker" premise was false). PR #399 made the job un-completable by construction.
  5. docs.yml green against a 404 — the Pages deploy lands as a preview alias while site_url returns 404 (#1314, on Phase 15).
  6. The UI's publish.yml has no CI gate where ADR-046 would lead you to assume one (ui#396, on Phase 15); and a stable v* UI tag does not move :latest (ui#397).
  7. deploy-prod.yml cannot prove the UI landed — its UI check is a 200 on / (#1315, on Phase 15). R2 and R2.1 therefore recorded the served bundle hash by hand, before and after.
  8. The maplibre test mock — a hand-copied vi.mock('maplibre-gl') whose getCanvas() returns a fresh object on every call, so every cursor assertion in ten UI test files passes against a throwaway. The F-10 walk's crosshair defect (ui#406) could not have been caught by any of them (ui#421 item 1, on Phase 15 as a W2 precondition).

Two more of the same family surfaced during the F-11 reassess and were corrected before a builder saw them: a disjunctive acceptance check that would have been vacuous (B2), and a feature flag (features.geocoding) that would have read true on prod and hidden nothing (#1322's follow-up, corrected to an advertising-only flag).

The rule, now in the phase-doc template and /phase-split: every gate must be shown red by inverting what it claims to check, before it counts. Prove-it-fails applied to gates and workflows, not only to tests. The one line worth quoting to a future planner: criterion 3 was reported met by a check that had never executed; its first execution failed — and the failure was in the check, not the product.

Second finding — issue bodies written from assumed rather than read state

Four instances caught before they cost a build (ui#385, ui#386 cited paths missing a directory; ui#386 targeted a spec step that did not exist; a cross-repo release rule described a coupling absent from the code), and five more in the F-11 wave caught by the fast /reassess (a non-existent schemas/risk.py, a two-of-three theme literal, a compare spec that would have broken, an initial-selection rule missing, a hard-coded peril union). The pattern holds across both repos and both halves of the phase: the orchestrator writes the body from the diff it has read, with the consumer assumed. The reassess step before dispatch is where this is caught, and it paid for itself every time it ran.

Third finding — the walk finds what no gate can

Of the nine F-10 clusters, seven were invisible to every automated check the phase had: a document.title of undefined, a transparent button, a footer that collapsed under scroll, a note gated > 1, a draw mode with no feedback, a members table with 216 unpaged rows, a checkbox missing on rows of one kind. All are rendering-and-affordance defects on real data at real volume. The two that a check could have caught (the coverage undetermined count, the coverage-less cached result) were caught by the pre-walk read-only pass (F-9), not by CI. The phase's exit-criterion design — the owner walks, the machine checks stay machine checks — was right, and Phase 15 keeps it (W4.2).

Findings

F-1 · UI rework ratio over the breaker — [ENHANCE-NEXT]

Analysed above; the two rules are encoded; the remaining action is the tier stamp on every PR so the per-tier number stops being dominated by unsized. Carried by /phase-build's PR template, no issue.

F-2 · Eight vacuous gates — [ENHANCE-NOW] → done / Phase 15

Items 2, 3, 4 fixed in-phase; 5, 6, 7 filed (#1314, ui#396/#397, #1315) and on the Phase 15 milestone; 8 filed (ui#421) and admitted onto UI milestone 6 as a W2 precondition; 1 is a rule (--require-symbol grep on origin/main).

F-3 · scripts/ reaches trunk unverified — [ENHANCE-NEXT]

ci.yml scopes ruff to src/ tests/ sdk/python/, so scripts/smoke_walk_wsl.sh and the packs are inspected by nothing; that is how #1305 and #1307 landed.

1267 carries the lint scope into Phase 15; #1329 (env templates lacking

BRAND_DEFAULT_*, found by the F-11 refuter) rides the same milestone.

F-4 · Builder isolation refused in a long-lived session — [BACKLOG]

The builder agent's isolation: worktree was refused in this session after five days and several compactions, even with git fsck clean; the wave ran on the documented fallback (orchestrator-made worktrees + general-purpose agents pinned to the path). Recorded in the phase doc and in memory; a fresh session does not reproduce it. No issue — a harness behaviour, not a repo one.

F-5 · Address search off on prod — decision, [DROP] for Phase 14

1322 decided pin-drop-only (reaffirming #534) after a fast /reassess: the

premises hold, enabling is a host action plus a three-site deploy-prod.yml change, nothing in the phase requires it, and the docs are reconciled (showcase Moment 1 step 1; criterion 2's exemption). The discoverability gap is filed as #1336 + ui#424 on Phase 15, keyed to D8's features object.

F-6 · Follow-ups / build is the rework number that matters — [ENHANCE-NEXT]

Backbone 20 follow-up issues on 60 PRs, UI 15 on 34. Rework that arrives as a new issue is invisible to the fix-PR count until a later wave builds it, and then it is counted as a build. /phase-build's convergence check reads this ratio per wave since PR #1242; Phase 15's mid-review should quote it first.

Findings summary

ID Finding Tag Where it lives
F-1 UI fix ratio 29 % at close (36 % on 09-08), not tier-attributable [ENHANCE-NEXT] rules encoded; PR tier stamp
F-2 Eight gates green without doing their work [ENHANCE-NOW] 3 fixed in-phase; #1314 #1315 ui#396 ui#397 ui#421 on Phase 15
F-3 scripts/ unlinted, env templates drift [ENHANCE-NEXT] #1267, #1329 on Phase 15
F-4 Builder isolation refused in a long-lived session [BACKLOG] phase doc + memory
F-5 Address search off on prod [DROP] (decision) #1322 closed; #1336 + ui#424 on Phase 15
F-6 Follow-ups / build as the leading rework signal [ENHANCE-NEXT] /phase-build convergence check

What Phase 15 inherits

Recorded in phase-15-live-risk.md § "Inputs from Phase 14 and their disposition": #1240 (W0.7), #1267, #1303, #1314, #1315,

1329, #1336, ui#358, ui#377, ui#396, ui#397, ui#421, ui#424. Phase 15's

backbone W0 was pulled forward on 2026-09-09 by owner instruction and had landed #1282 and #1283 before this close; it opens formally with this PR.