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/hazardsrows carrycountry—api/v1/hazards.py::_dataset_to_dict(…, country=…), fed byHazardRepository.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_completepayload carrieslive_event_id—worker/tasks.py:1871. - The
report_renderdownload_readypayload carries exactly one ofresult_id/batch_id, never both —worker/tasks.py:3991, pinned bytests/test_worker/test_render_report.py. - Coverage cells carry
indicator—api/v1/catalog.py:55,core/catalog_coverage.py. deploy-prod.ymlcarries the grep-gated geocoding profile — line 492, matching^[[:space:]]*(export[[:space:]]+)?GEOCODER_ADVERTISED[[:space:]]*[=:].tests/e2e/smoke/contract-map.tscovers 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.