Skip to content

Phase 16 — Consistency

CLOSED 2026-09-17 — released as backbone v0.26.0 / UI v0.16.0, deployed by run 35244603330, walked on prod by the owner the same day; both milestones at 0 open and all seven exit criteria met. Record: mid-review-phase-16.md — Ready for next phase. State table: the block at the end of this file.

Defined 2026-09-11 by owner instruction (session lama-plan-phase-16), from a quick owner walkthrough of prod (backbone v0.24.1, UI v0.14.1, GET /v1/info → git_sha 117477e, read 2026-09-10 22:01 UTC). A small phase with no epic: it closes the inconsistencies and minor UX gaps the walk surfaced, mostly in climate-lama-ui, plus the backbone contracts those fixes need. It runs beside Phase 15 — Live Risk (open, building) and opens on the day its split lands; the visual revamp, adaptation measures and the other Phase 14 deferrals move from "Phase 16" to the Phase 17 charter stub (D1). Serves Goal C (the showcase UI reads as one product) and, through three contracts, Goal B.

What it is. Nine owner-reported points (O1–O9 below) and five points found by the code audit that followed (A1–A5), each grounded against UI trunk e9d0c21 and backbone trunk 319a7b6 and, where it mattered, against prod. Every item is a fix at the contract or presentation layer of a surface that already exists; none adds a surface.

What it is not. No redesign (that is the Phase 17 stub in ui-revamp.md); no new peril, engine or country; nothing on the /live surface Phase 15 is still building (its patterns land there first). The /measures route, the dark-mode palette and the address-search coverage of Cyprus are named out of scope with pointers.

The frame — the same user questions, answered without friction

Surface Owner finding (walk 2026-09-10) What Phase 16 changes
Assess O1 hazard datasets are one flat list per peril while exposures are grouped by country; O4 a run dies when the user opens another tab; O5 the result map repaints itself into Attica alone a few seconds after rendering; O6 the cell popup has a tiny × and does not close on an outside click hazard datasets grouped by the server's country (backbone country on GET /v1/hazards); runs owned above the routes; the regional tier only when boundaries cover the result; popups that close like popups
Home O2 five tall cards → endless scrolling; the dashboard should be one look a denser grid that fits one 1440×900 viewport at prod's data volume, with a measured check
Header O3 a notification marks itself read and nothing happens; O8 no way to tell which page is open rows navigate to what they name; the active nav entry is highlighted
Lookup O7 address search "unavailable right now"; O9 the hazard raster is fuzz at every zoom and carries no business meaning geocoding enabled on prod (reverses #1322 on new evidence, D8); the raster rendered in the score scheme's own bands with the legend to match, faded out at street zoom

Ground truth at open — archived to archive/phase-16-log.md (consumed 2026-09-11).

Design record (D1–D12 — one line each; full prose in archive/phase-16-log.md)

One line per decision: the decision, then the one constraint that binds a builder. Nothing is dropped or renumbered — the prose each line compresses is in the archive, and the dated amendments below correct D10 twice.

  • D1 — Phase 16 is Consistency; the revamp stub becomes Phase 17; it runs beside Phase 15 — every item on a Phase 15 hotspot carries a fully-qualified Deps:.
  • D2 — A hazard dataset's country comes from the server, with a ?country= filter — new row-dict repository methods, filter applied in SQL, never derived client-side.
  • D3 — Home fits one 1440×900 viewport at prod's data volume — a Playwright assertion behind a volume floor that fails, never skips.
  • D4 — Notification rows navigate, read off payload keys the emitter already stamps — no new NotificationType; the ids ride the wire, not a UI lookup.
  • D5 — A RunProvider above the routes owns the in-flight run ids, plus ?job= — only ids move up; result and GeoJSON stay on Assess.
  • D6 — The regional tier is gated on admin_coverage, and prod gets the full GADM load — ratio < 0.95 or absent ⇒ point tier; polygons merge, never replace.
  • D7 — Popups close on outside click, Escape and a reachable × — one CSS rule and one option set, applied to every maplibregl.Popup in src/.
  • D8 — Geocoding is enabled on prod, reversing #1322 on the owner's re-report — the deploy profile is grep-gated on GEOCODER_ADVERTISED=true and inert until it flips.
  • D9 — The active nav entry is visible via NavLink + aria-current — no mapping table; exactly one aria-current="page" per route.
  • D10 — The hazard raster speaks the score scheme's bands, minted server-side — classed=score is refused together with colormap_name or rescale.
  • D11 — One app-wide ConfirmDialog replaces the native dialogs — zero window.confirm / alert( left outside tests.
  • D12 — CostBenefitPanel takes ui#403's rail parity fix — drop overflow-y-auto on the aside; the footer is shrink-0.

Guardrails — every Phase 16 issue

Phase 14 guardrails 2, 4, 8 and 9 apply verbatim (nothing removed without its walk; contracts tested, not assumed; dependencies flow downward — the UI never derives country, coverage or bands client-side; feat:/refactor: for planned work, fix: only for rework of Phase 16 code, breaker at 25 % via scripts/rework_rate.py --follow-ups). Plus:

  1. No item touches a Phase 15 file it does not depend on. Deps: on the owning Phase 15 issue, fully qualified across repos; /phase-build resolves a cross-milestone dep by merged PR + trunk grep.
  2. A test that asserts on a MapLibre canvas uses a stable canvas mock (A5); a Phase 16 PR that adds a cursor/popup assertion against the copied mock is red-flagged in review.
  3. Layout targets are measured. Home's one-viewport rule is a Playwright assertion at 1440×900 on the smoke stack, and its prove-it-fails is setting RECENT_RUNS_LIMIT back to 10.
  4. The regional tier never hides assets the loaded boundaries do not cover (D6b), on every deployment, including one with zero boundaries.
  5. Copy names the truth: "assets in ", "indicator", "Assessment running", "Regional view covers N %" — never "cell" for a polygon, never "0 computable" for a peril that computes nothing by design.
  6. Every deploy-workflow edit is grep-gated and inert until the operator flips the .env.prod key (D8), and the PR body records the resolved value of every container- or workflow-consumed key.

Scope — waves and items

Dependency line: W0 (backbone contracts) → W1 (UI, independent S items) ∥ W2 (UI, contract consumers) → W3 (ops, owner) → W4 (closeout). W1 has no backbone dependency and starts on day one. Sizes are drafts; /phase-split grounds and re-sizes each item with /analyze.

W0 — Backbone contracts

Id Title D Files (seam / hotspot) Outcome
W0.1 (#1369) feat(api): country on hazard rows + ?country= filter (row-dict repository methods, SQL-level filter) D2 api/v1/hazards.py, db/repositories/hazard_repository.py, tests/test_api/test_hazards_country.py, docs/CLIMATE_LAMA.md (hotspot) ✅ merged (P16-B1)
W0.2 (#1370) feat(api): admin_coverage on admin-level result GeoJSON + the Attica-popup diagnosis D6b/c api/v1/results.py, core/admin_aggregation.py, tests/test_api/test_results_admin_coverage.py, docs/CLIMATE_LAMA.md (hotspot); diagnosis box routed to W3.1 (Refs) when prod is out of the builder's reach ✅ merged (P16-B1); diagnosis numbers posted on #1376
W0.3 (#1371) feat(worker): live_event_id + portfolio_id on the event-lane job_complete; explicit result_id / batch_id on the report_render download_ready D4 worker/tasks.py (hotspot — after #1293), tests/worker/test_compute_event_impact.py, tests/worker/test_report_render_notification.py ✅ merged (P16-B2)
W0.4 (#1372) feat(api): ?classed=score tile rendering from the score scheme + bands on RasterLegend D10 api/v1/tiles.py, core/tile_render.py, core/scoring.py, models/layer_spec.py, api/v1/hazards.py (layers route), tests/test_api/test_tiles_classed.py, docs/CLIMATE_LAMA.md (hotspot) ✅ merged (P16-B2)
W0.5 (#1373) feat(scripts): load_admin_boundaries --geojson accepts a GADM FeatureCollection D6a scripts/load_admin_boundaries.py (seam), tests/scripts/test_load_admin_boundaries.py ✅ merged (P16-B1)
W0.6 (#1374) chore(ci): --profile geocoding in deploy-prod.yml, grep-gated on GEOCODER_ADVERTISED=true D8i .github/workflows/deploy-prod.yml (seam), docs/DEPLOYMENT.md, .env.example.prod (seam, comment only) ✅ merged (P16-B1)
W0.7 (#1375) feat(api): indicator flag on the catalog coverage cell D3 api/v1/catalog.py (or wherever GET /v1/catalog/coverage lives), core/catalog_coverage.py, tests/test_api/test_catalog_coverage.py, docs/CLIMATE_LAMA.md (hotspot) ✅ merged (P16-B1)
W0.8 (#1406) feat(api): ?search= on GET /v1/hazards (filed 2026-09-14, after P16-B2) — api/v1/hazards.py, db/repositories/hazard_repository.py, docs/CLIMATE_LAMA.md (hotspot with W0.1) ✅ merged (P16-B2′)
W0.9 (#1407) feat(api): portfolio_id on JobListItem (filed 2026-09-14, separate from W0.3) — api/v1/jobs.py ✅ merged (P16-B2′, PR #1436)

W1 — UI, independent (no backbone dependency; after ui#378/#379 where App.tsx is touched)

Id Title D Files Outcome
W1.1 (ui#427) feat(ui): active nav entry — NavLink + aria-current D9 src/App.tsx (hotspot — after ui#379), src/test/App.test.tsx (hotspot — ui#379 owns it too) ✅ merged (P16-U2)
W1.2 (ui#428) feat(ui): popups close on outside click, Escape and a reachable × D7 src/index.css, src/components/ResultMap.tsx, src/components/live/LiveEventMap.tsx (option only), src/test/ResultMap.test.tsx, src/test/mapMock.ts (shared stable canvas mock, A5) ✅ merged (P16-U1)
W1.3 (ui#429) refactor(ui): one ConfirmDialog replaces native confirm/alert and two hand-rolled dialogs D11 src/components/ConfirmDialog.tsx, src/admin/ConfirmDialog.tsx (moved), src/components/ConfigPanel.tsx (hotspot), src/components/ImpactFunctionEditor.tsx, src/auth/ApiKeysPage.tsx, src/pages/RunsPage.tsx, tests ✅ merged (P16-U1)
W1.5 (ui#455) feat(ui): Intl.NumberFormat cache in formatNumber (filed 2026-09-14, independent) — src/lib/format.ts ✅ merged (P16-U1)
W1.4 (ui#430) fix(ui): CostBenefitPanel rail keeps its Run button (aside overflow, min-h-0, shrink-0 footer) D12 src/components/CostBenefitPanel.tsx, src/test/CostBenefitPanel.test.tsx ✅ merged (P16-U1)

W2 — UI, contract consumers

Id Title D Deps Files Outcome
W2.1 (ui#431) feat(ui): hazard datasets grouped by country in the Assess rail; peril labels through hazardLabel D2 W0.1 src/components/ConfigPanel.tsx (hotspot), src/components/HazardUploadModal.tsx, src/types/index.ts (hotspot — after ui#378), src/test/ConfigPanel.test.tsx, src/test/hazardLabelNamespaces.test.ts ✅ merged (P16-U2)
W2.2 (ui#432) feat(ui): Home fits one viewport — 3-column grid, 5 runs, error message, indicator chips, volume-floored check D3 W0.7, ui#380 src/pages/HomePage.tsx (hotspot), src/components/home/*.tsx, src/types/index.ts (hotspot), src/i18n/locales/en/home.json (hotspot — ui#380), tests/e2e/smoke/home.spec.ts, src/test/HomePage.test.tsx (hotspot — ui#380) ✅ merged (P16-U2)
W2.3 (ui#433) feat(ui): notification rows navigate; mark all read in the bell D4 W0.3, ui#379 src/lib/notificationText.ts, src/components/NotificationBell.tsx, src/components/home/NotificationsCard.tsx, src/hooks/useNotifications.ts, src/api/client.ts (hotspot — after ui#378), tests ✅ merged (P16-U3)
W2.4 (ui#434) feat(ui): RunProvider — runs survive navigation; header run pill; ?job= re-attach D5 ui#379 src/run/RunProvider.tsx, src/App.tsx (hotspot), src/components/Dashboard.tsx (hotspot), src/components/assess/RunStatusPanel.tsx, src/test/RunProvider.test.tsx, tests/e2e/smoke/compute.spec.ts ✅ merged (P16-U3)
W2.5 (ui#435) feat(ui): regional tier gated on coverage; polygons merge, never replace; admin-unit popup copy D6b W0.2, W1.2 src/components/ResultMap.tsx (hotspot), src/components/Dashboard.tsx (hotspot), src/components/LayerPanel.tsx, src/i18n/locales/en/results.json, src/test/ResultMap.test.tsx ✅ merged (P16-U4)
W2.6 (ui#436) feat(ui): banded hazard legend + zoom fade on Lookup and Datasets D10 W0.4 src/components/lookup/LookupMap.tsx, src/components/datasets/DatasetMap.tsx, src/map/legend.ts, src/api/client.ts (hotspot), src/i18n/locales/en/lookup.json, tests ✅ merged (P16-U4)
W2.7 (ui#437) feat(ui): country filter on the Datasets hazard and exposure catalogues D2 W0.1 src/components/datasets/HazardCatalog.tsx, src/components/datasets/ExposureCatalog.tsx, src/api/client.ts (hotspot), src/i18n/locales/en/datasets.json, src/test/HazardCatalog.test.tsx, src/test/ExposureCatalog.test.tsx ✅ merged (P16-U3)
W2.8 (ui#454) feat(ui): listHazardCatalog passes search + limit (filed 2026-09-14, cross-repo precondition W0.8) — W0.8 src/api/client.ts (hotspot), tests/e2e/smoke/contract-map.ts ✅ merged (P16-U3)

W3 — Operations (trackers; routed by Blocker:, not by label)

Id What Blocker
W3.1 (#1376) — ✅ closed Load GADM 4.1 GRC levels 1–2 and CYP levels 1–2 on prod through W0.5; record SELECT iso3, admin_level, count(*) before/after per level; run W0.2's diagnosis against the owner's Greece run (count_by_centroid.sum(), n_centroids, max(array_index), the job's portfolio scope) and post the numbers on W0.2's issue; re-open that run on /assess and zoom out Blocker: agent — executed 2026-09-16, closed; the diagnosis numbers are posted on the issue
W3.2 (#1377) — ✅ closed Enable geocoding on prod per D8ii; record import wall time, disk and RAM; verify /v1/geocode 200 and features.geocoding = true Blocker: agent — executed 2026-09-16, closed 2026-09-17; the Moment 1 step 1 address-search walk ran on prod (owner, 2026-09-17) — Cyprus addresses stay unresolved (Greece-only PBF); the deploy-log confirmation box moved to the phase-end walk (#1379)

W4 — Closeout

Id Title Files Outcome
W4.1 (#1378) docs: Phase 16 closeout — plan banner, showcase markers, ui-revamp stub → Phase 17 docs/plan/plan.md (hotspot), docs/plan/showcase-script.md (hotspot), docs/plan/ui-revamp.md, docs/plan/phase-16-consistency.md ✅ this PR — plan banner + status log, showcase Moment 1 step 1, phase doc annotations; no CLAUDE.md edit (no address-search sentence on trunk to update)
W4.2 (#1379) Release (one coordinated bump, both repos), then the one phase-end walk (exit criterion 2) and mid-review-phase-16.md owner + orchestrator; docs/plan/mid-review-phase-16.md ✅ done 2026-09-17 — v0.26.0 / UI v0.16.0 deployed (35244603330), owner walk of all nine points verified, mid-review-phase-16.md written

Exit criteria

  1. Build issues at 0 open on the Phase 16 -- Consistency milestone in both repos. Trackers do not count — a Blocker: credential:* tracker leaves the milestone for the standing ops list rather than holding the phase open; a decision is settled in-run by the framework's decision protocol (2026-09-17) and is never a tracker.
  2. One phase-end walk, by the owner on prod, run once after the release, scripted from the showcase moments this phase touched: Moment 1 (an address resolves on /lookup; the hazard raster shows the card's bands and fades at street zoom) and Moment 2 (Assess: hazard datasets grouped by country; a run started on /assess is still running, or finished with a toast, after a visit to /lookup; the Greece result keeps every asset when zoomed out and shows regions only where the boundaries cover them; the cell popup closes on an outside click) — plus the Home and header points every moment enters through (Home in one 1440×900 view, a notification that opens what it names, the open page highlighted). Findings are tagged and either fixed in one closing wave or filed found-in:phase-16 and drained by the next /phase-split.
  3. Measured: the Home viewport assertion and the coverage-sum test are green on trunk and each has a recorded prove-it-fails run.
  4. Contracts: GET /v1/hazards rows carry country; result GeoJSON at an admin level carries admin_coverage; the event-lane job_complete payload carries live_event_id, the report_render download_ready payload an explicit result_id or batch_id; coverage cells carry indicator; deploy-prod.yml carries the grep-gated geocoding profile; tests/e2e/smoke/contract-map.ts covers every new client method.
  5. Prod data: admin_boundaries holds every GADM 4.1 level-1 and level-2 unit of GRC and CYP; GET /v1/info → features.geocoding = true.
  6. Guardrails hold (1–6 above) and the escape rate is ≤ 25 % (fix PRs + behaviour-changing follow-ups + unattributed fix PRs, per build).
  7. Record: docs/plan/mid-review-phase-16.md with a verdict, tagged findings and the two rework counters (escape rate, partial rate) from scripts/rework_rate.py --follow-ups; plan.md banner and status log flipped; the Phase 17 stub named.

Conflict hotspots (files touched by >1 item, or by a Phase 15 item)

UI: src/App.tsx + src/test/App.test.tsx (W1.1, W2.4 — serial; both after ui#379, which owns both files), src/components/Dashboard.tsx (W2.4, W2.5 — serial), src/components/ResultMap.tsx (W1.2 then W2.5), src/components/ConfigPanel.tsx (W1.3, W2.1 — serial), src/api/client.ts + src/types/index.ts (W2.1, W2.2, W2.3, W2.6, W2.7 — after ui#378, whose body says no later UI issue edits them: the split annotates ui#378 with this phase's consumers; one consumer at a time), src/pages/HomePage.tsx + home.json + HomePage.test.tsx (W2.2 after ui#380), src/index.css (W1.2 only), src/components/datasets/*Catalog.tsx (W2.7 only), tests/e2e/smoke/contract-map.ts (every W2 item that adds a client method — serial), src/i18n/locales/en/*.json (per-surface files, one owner each). Backbone: docs/CLIMATE_LAMA.md (W0.1, W0.2, W0.4 — one docs region each), worker/tasks.py (W0.3 after #1293), api/v1/tiles.py + core/tile_render.py (W0.4 only), .github/workflows/deploy-prod.yml (W0.6 only; Phase 15 W0.3 #1281 already landed there).

Orchestration sketch (with its 2026-09-11 wave plan) — archived to archive/phase-16-log.md (consumed 2026-09-15).

Orchestration — the closed record

Every wave plan this phase ran under is archived; the state block below is the record a reader needs, and /phase-analyze reads it.

Amendment 2026-09-14 — folded into the 2026-09-15 amendment and the closed wave plan; archived to archive/phase-16-log.md.

Amendment 2026-09-15 — the phase's last wave plan; every row closed 2026-09-17, so its state lives in the block below and the section is archived to archive/phase-16-log.md.

Phase state — 2026-09-17 · CLOSED (by /phase-analyze, origin/main 5fb5603)

State — P16 · Consistency · read 2026-09-17 · origin/main 5fb5603 · UI origin/main 1c713e9

Wave Status Owner Issues Deps Command Budget Notes
P16-B1 ✅ done — gate 34786761479 green at e8f1be9 agent ✓1369 ✓1370 ✓1373 ✓1374 ✓1375 — /phase-build 1369 1370 1373 1374 1375 --no-pr-ci --max-runtime 270 270/102 lama-phase-build-P16-B1 · $277
P16-B2 ✅ done — gate 34965255088 green at 7ad780c agent ✓1371 ✓1372 — /phase-build 1371 1372 --no-pr-ci --max-runtime 120 120/~30 lama-phase-build-P16-B2 · own closeout ended blocked on the merge classifier
P16-W ✅ done — walk green at 5a280e1 agent (WSL) — — scripts/smoke_walk_wsl.sh --require-merged 1401 1403 1404 —/~20 lama-phase-build-P16-W · ghcr titiler proven
P16-B2′ ✅ done — gate 35198886403 green at 1efb351 agent ✓1406 ✓1407 #1372 ✓ /phase-build 1406 1407 --no-pr-ci --max-runtime 120 120/— lama-phase-build-P16-B2' · SDK parity fixed in-wave (PR #1435)
P16-U1 ✅ done — e2e-smoke 35104975963 green at 5d31ff1 agent (UI) ✓ui428 ✓ui429 ✓ui430 ✓ui455 — cd ../climate-lama-ui && /phase-build 428 429 430 455 --no-pr-ci --max-runtime 150 150/— built in lama-ui-phase-build-P15-U1-U4 (stale name)
P16-U2 ✅ done — e2e-smoke 35104975963 green at 5d31ff1 agent (UI) ✓ui427 ✓ui431 ✓ui432 — cd ../climate-lama-ui && /phase-build 427 431 432 --no-pr-ci --max-runtime 200 200/— same session · residue ui#471 filed
P16-U3 ✅ done — e2e-smoke 35208961985 then 35227516009 green at 1318152 agent (UI) ✓ui433 ✓ui434 ✓ui437 ✓ui454 #1371 ✓ #1406 ✓ #1439 ✓ cd ../climate-lama-ui && /phase-build 433 434 437 454 --no-pr-ci --max-runtime 240 240/— + 120/~75 lama-phase-build-P16-U3 · ui#437's exposure half took backbone #1439 mid-wave
P16-U4 ✅ done — e2e-smoke 35215947303 green at c388fc7 agent (UI) ✓ui435 ✓ui436 ui#428 ✓ ui#434 ✓ cd ../climate-lama-ui && /phase-build 435 436 --no-pr-ci --max-runtime 200 200/— PRs #476 #477
P16-O1 ✅ done — #1376 closed by agent proof owner ✓1376 v0.25.0 on prod ✓ owner-run — GADM 4.1 on prod: GRC 1:8 2:14 · CYP 1:5
P16-O2 ✅ done — #1377 closed, walk verified owner ✓1377 v0.25.0 on prod ✓ owner-run — nominatim healthy at ~26 min · features.geocoding: true
P16-B3 ✅ done — gate 35242713348 green at 3eaaf04 agent ✓1378 every build issue merged ✓ /phase-build 1378 --no-pr-ci —/~25 lama-phase-build-P16-B3 · PR #1442
P16-R ✅ done — v0.26.0 / UI v0.16.0 on prod, walk green owner + orchestrator ✓1379 B3 ✓ /release + owner walk + mid-review-phase-16.md — deploy 35244603330 · all nine points verified 2026-09-17

Next — P16

# Wave Command Session After
— none the phase is closed; its findings live in mid-review-phase-16.md — —
  • closed — both Phase 16 -- Consistency milestones at 0 open; 15 backbone issues, 13 UI issues
  • release — climate-lama v0.26.0 at 3eaaf04 · climate-lama-ui v0.16.0 at 1c713e9 · deploy-prod 35244603330 success
  • gates — backbone ci.yml 35242713348 green at 3eaaf04; UI ci.yml 35242720688 + e2e-smoke.yml 35242724086 green at 1c713e9
  • breaker — escape rate 0.20 backbone (5/25) · 0.19 UI (3/16); partial rate 0.24 · 0.81 — both escapes under the 25 % gate
  • carried — ui#471 (5-watch Home proof, needs a Docker stack, no milestone) · Cyprus level-2 boundaries absent on prod · UI issue sizing · split-time route premise check
  • drift — the three UI waves ran in a session named for Phase 15, so their per-wave cost and runtime are not attributable

Decisions taken autonomously — archived to archive/phase-16-log.md (consumed 2026-09-11).

Reassess record (2026-09-11) — archived to archive/phase-16-log.md (consumed 2026-09-11).

Out of scope (deliberate)

  • guard: the visual redesign of any surface → Phase 17 stub in ui-revamp.md.
  • guard: the /live surface and every components/live/* behaviour → Phase 15 (only W1.2's shared popup option/CSS touches LiveEventMap).
  • defer: Cyprus address coverage (a merged GRC+CYP PBF or a second nominatim instance) → recorded here; revisit when a Cyprus walk needs an address.
  • defer: a download link for a matrix report from its download_ready row → the row links to /assess?batch= and the strip's report button downloads it; a direct object download needs a matrix-report route.
  • defer: a ?job= deep link from a job_failed notification → needs a Runs hub that lists jobs (ui#377 item 2's backbone counterpart names the gap); the row expands inline instead.
  • defer: a ?hazard=<id> / ?exposure=<id> selection on /datasets so a dataset_available notification can open its row → recorded here; W2.3 links to /datasets until then.
  • defer: search/limit on GET /v1/hazards → ui#421 item 6 (needs a backbone issue when the catalogue grows past one transfer).
  • defer: level-2 choropleth between z8 and z10 → after D6a proves level-1 coverage on prod.
  • guard: dark-mode palette (RAG colours inert in dark mode) → Phase 17.
  • guard: the /measures route's deletion → Phase 14 D9 owner.
  • guard: no new NotificationType, no push, no e-mail (ADR-037, Phase 15 guardrail 9).

Inputs from open residue and their disposition — archived to archive/phase-16-log.md (consumed 2026-09-11).

Split readiness (with Contracts pinned and Split cards) — archived to archive/phase-16-log.md (consumed 2026-09-11).