Skip to content

Phase 16 — Mid-Review

Read 2026-09-17, after the coordinated release. Backbone v0.26.0 (3eaaf04), UI v0.16.0 (1c713e9), deploy-prod run 35244603330 green at 16:07 UTC. Ground truth: GitHub issues and merged PRs in both repos, the trunk gates, prod itself.

Phase frame and design record: phase-16-consistency.md. Session record: lama-plan-phase-16 (planning), lama-phase-build-P16-B1, -B2, -B2', -W, -U3, -B3, lama-ui-phase-build-P15-U1-U4 (the UI waves U1/U2/U4, built under a stale name).

Verdict

Ready for next phase. Every exit criterion is met. The phase did what it said: the nine inconsistencies the owner's 2026-09-10 prod walkthrough surfaced and the five audit points are closed in code, released, and walked on prod by the owner on 2026-09-17.

Two things are worth carrying rather than celebrating. The UI partial rate is 0.81 — 13 of 16 UI builds shipped with an ## Unmet acceptance section, meaning the issues were written larger than one build. And one UI issue (ui#437) was written against a backbone route that did not have the field it assumed, which cost a mid-wave backbone issue (#1439) to unblock.

Release and gates

Thing Value
backbone tag v0.26.0 at 3eaaf04, at its origin tip
UI tag v0.16.0 at 1c713e9, at its origin tip
deploy deploy-prod.yml 35244603330 success, 2026-09-17 16:07 UTC, UI_TAG: v0.16.0
prod /v1/info version 0.26.0, git_sha 3eaaf043, features.geocoding: true, live_mode: true
backbone gate ci.yml 35242713348 green at 3eaaf04
UI gates ci.yml 35242720688 and e2e-smoke.yml 35242724086 green at 1c713e9
prod services api beat climada-sidecar live-worker nominatim postgres redis titiler ui worker all running

Check 2 — the walk

The owner walked all nine points on prod on 2026-09-17 after the release and reported every one verified: Home in one 1440×900 view; the active nav entry highlighted; a notification opening what it names; hazard datasets grouped by country with the country filter narrowing both catalogues; an address resolving on /lookup; the raster in the card's bands fading at street zoom; a run started on /assess surviving a visit to /lookup; the Greece result keeping every asset when zoomed out and shading regions only where boundaries cover them; the cell popup closing on an outside click.

No per-point ids or URLs were pasted, so the record here is the owner's report rather than nine artefacts (F-1). Cyprus addresses stay unresolved on the Greece-only PBF — deliberate, per the plan's Out of scope.

Exit criteria tracking

# Criterion Verdict
1 Build issues at 0 open, both milestones MET — backbone 15 issues, UI 13, all closed; only the trackers remained and they close with this review
2 One owner walk on prod after the release MET — 2026-09-17, all nine points (see above)
3 Home viewport + coverage-sum tests green on trunk, each with a prove-it-fails run MET — tests/e2e/smoke/home.spec.ts "Home fits one view" (prove-it-fails in PR #470: scrollHeight=1102 clientHeight=843 red at RECENT_RUNS_LIMIT = 10); tests/test_api/test_results_admin_coverage.py (prove-it-fails in PR #1401: overlap check disabled)
4 Contracts MET — all seven, see below
5 Prod data MET — GADM 4.1 loaded, features.geocoding = true
6 Guardrails hold, escape rate ≤ 25 % MET — backbone 0.20, UI 0.19
7 Record MET — this file, plus the plan.md banner and status log

Criterion 4, contract by contract

  • GET /v1/hazards rows carry country — api/v1/hazards.py::_dataset_to_dict(…, country=…), fed by HazardRepository.list_datasets_with_country / get_dataset_with_country.
  • Result GeoJSON at an admin level carries admin_coverage — api/v1/results.py:235.
  • The event-lane job_complete payload carries live_event_id — worker/tasks.py:1871.
  • The report_render download_ready payload carries exactly one of result_id / batch_id, never both — worker/tasks.py:3991, pinned by tests/test_worker/test_render_report.py.
  • Coverage cells carry indicator — api/v1/catalog.py:55, core/catalog_coverage.py.
  • deploy-prod.yml carries the grep-gated geocoding profile — line 492, matching ^[[:space:]]*(export[[:space:]]+)?GEOCODER_ADVERTISED[[:space:]]*[=:].
  • tests/e2e/smoke/contract-map.ts covers the new client methods, including the assertion that a ?country= call really filtered server-side rather than locally.

Criterion 5, as read on prod

admin_boundaries, source GADM version 4.1:

iso3 level 0 level 1 level 2
GRC 1 8 14
CYP 1 5 —

Before the phase the table held four rows, which is why the choropleth showed Attica only. Cyprus has no level-2 rows (F-5).

The circuit breaker — rework at close

scripts/rework_rate.py --follow-ups, scoped to each repo's Phase 16 build window:

Repo Window Builds Escape rate Partial rate Coverage
climate-lama 2026-09-13 → 09-17 25 0.20 (5) 0.24 (6) 0.50
climate-lama-ui 2026-09-16 → 09-17 16 0.19 (3) 0.81 (13) 0.67

Both escape rates are under the 25 % breaker, so no feature wave is stopped. The wider window (from 2026-09-11, overlapping Phase 15's R2) reads 0.42 — that number is Phase 15's tail, not this phase's, and the per-phase windows above are the operative ones.

Cost, from ~/.claude/scripts/usage_scan.py --since 2026-09-13 (list-price proxy, per session): P16-B1 $277 · P16-B2 $159 · P16-U3 $66 · P16-O1/O2 $57 · P16-B2′ $54 · P16-B3 under $30. The three UI waves U1/U2/U4 ran inside lama-ui-phase-build-P15-U1-U4 ($806 over both phases) and cannot be attributed to Phase 16 cleanly — a session per wave is what makes this measurable, and a stale session name is what broke it here. Builders remain the spend: n=40, avg $46, against refuter at avg $3.2.

Attributed rework, backbone: #1430 (GADM canonical spaced names, fixing #1402), #1435 (SDK search kwarg parity, fixing #1434), follow-up #1406 (from #1403). UI: PR #478 (vacuous River Flood guards, fixing #470) and follow-up ui#471. Unattributed fix PRs (#1418, #1423, ui#465) are CI and prod-reality work, not build defects.

Findings

F-1 · The walk's evidence is a report, not nine artefacts — [BACKLOG]

1379 asked for ids or URLs per point; the owner confirmed all nine verbally. Nothing contradicts

the report, and the release gates are green, but a future phase's walk record should paste the artefact per point so a reader a month later can re-check one point without re-walking all nine.

F-2 · ui#471's 5-watch Home box is unproven — [ENHANCE-NEXT]

PR #478 fixed the vacuous River Flood guards and the LiveNowCard overflow, but the assertion that Home still fits one viewport with 5 open watches needs a seeded Docker stack, which build agents do not have (build-agents-have-no-docker). ui#471 stays open with no milestone; it does not hold this phase. Carry it into the next split, or prove it in the next walk.

F-3 · UI partial rate 0.81 — the issues were sized larger than one build — [ENHANCE-NEXT]

13 of 16 UI builds carried ## Unmet acceptance. That is a sizing signal, not a defect signal: the escape rate stayed at 0.19, so what shipped held. The lesson for /phase-split is that a UI issue whose acceptance list spans two components is two issues.

F-4 · ui#437's contract named a route that lacked the field — [ENHANCE-NEXT]

ui#437 assumed GET /v1/exposures/catalog could filter by country. It could not, and it returned no country field at all, so the exposure half needed backbone #1439 mid-wave, decided in-run (backbone route, not a UI migration onto /v1/exposure-datasets, which would have dropped pre-#584 uploads). A premise check that greps the named route's code before the issue is written would have caught it at split time — the same class as issue-bodies-cite-unmerged-prs.

F-5 · Cyprus has no level-2 boundaries on prod — [BACKLOG]

The load left CYP at level 1 only (5 rows). Whether GADM 4.1 ships an ADM2 layer for Cyprus at all was not established during this phase, so it is unknown whether this is a load gap or nothing to load. One check against the GADM release answers it; until then Cypriot results aggregate at level 1 only.

F-6 · The Blocker: route on trackers worked — [DROP], keep the practice

1376 and #1377 were closed by agent proof with a Blocker: line naming what executed and the

proof, and their browser-only boxes moved to the phase walk instead of holding the milestone open. That is the 2026-09-17 loop-breaker working as intended, and it is why this phase closed the day its last build landed rather than waiting on two trackers.

Findings summary

Id Finding Tag Lands
F-1 Walk evidence is a report, not artefacts [BACKLOG] next phase's walk record
F-2 ui#471 5-watch Home box unproven [ENHANCE-NEXT] next split or next walk
F-3 UI partial rate 0.81 — sizing [ENHANCE-NEXT] /phase-split for the next UI wave
F-4 ui#437 named a route lacking the field [ENHANCE-NEXT] split-time premise check
F-5 No CYP level-2 boundaries on prod [BACKLOG] one GADM check
F-6 Blocker: route on trackers [DROP] already practice

What the next phase inherits

  • ui#471, open, no milestone — the 5-watch Home proof (F-2).
  • The CYP level-2 question (F-5).
  • UI issue sizing: acceptance lists that span components split (F-3), and issue bodies naming a backbone route get that route grepped first (F-4).
  • Phase 15 — Live Risk is still open beside this phase; Phase 17 (the visual revamp, ui-revamp.md) is defined when Phase 15 closes too.