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(UIv0.14.0on the walk,v0.14.1on the re-walk) andhttps://api.climate-lama.online(backbonev0.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:
- The
ghmerged-PR body search as a "has the fix landed?" precondition fails open — five merged PRs cited #1237, none of them the fix. - #1307 —
probe_branch()printed itsexpect_terminalargument instead of asserting it; a failure-branch upload returningsucceededstill recorded PASS. Fixed in #1308. - #1305 —
--require-mergedspliced 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. - Exit criterion 3 itself —
failure.spec.tsskipped on every run for the entire phase because nothing setCL_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 insidedocker compose stop's SIGTERM grace, so the "stalled worker" premise was false). PR #399 made the job un-completable by construction. docs.ymlgreen against a 404 — the Pages deploy lands as a preview alias whilesite_urlreturns 404 (#1314, on Phase 15).- The UI's
publish.ymlhas no CI gate where ADR-046 would lead you to assume one (ui#396, on Phase 15); and a stablev*UI tag does not move:latest(ui#397). deploy-prod.ymlcannot 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.- The maplibre test mock — a hand-copied
vi.mock('maplibre-gl')whosegetCanvas()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.