Skip to content

Phase 17b · Revamp, corrected from the owner walk

OPEN · defined 2026-10-01, extended 2026-10-02, split 2026-10-03, fine-tuned 2026-10-05, delta split 2026-10-07 (33 new issues), delta split 2026-10-12 after walk stop 2 (11 new issues), delta split 2026-10-14 after walk stop 3 (10 new issues, 4 admitted). The owner adds findings here as the walk continues (see Walk still to do), and each walk stop's delta /phase-split docs/plan/phase-17b-walk-corrections.md files them. The split filed 249 issues and the delta split of 2026-10-07 filed 33 more, all mapped in Split record. Nothing in this file is implemented yet.

2026-10-02 extension. The owner walked Home, the shell, Explore, Lookup, Adapt, Settings and the assistant and reported 17 findings. They enter this file as SH-17 to SH-24, AS-1 to AS-3, EX-14 to EX-16 and LK-13, each verified by a /reassess fast pass (two refuters and a completeness critic, then one conceptual refuter on the run model). The business logic the owner asked for, how a scenario is run from exposure to report, what Lookup computes and how Adapt applies, is defined in phase-17b-run-model.md. Every EX, AD and AS issue cites that doc.

2026-10-05 fine-tune. On 4 October the owner reviewed the Phase 17b prototype (v2) and raised eight points. On 5 October the prototype was rebuilt with every change, the owner decided each open choice, asked for four more changes on Scope and marked every live entry of the review board "Looks right". The record is phase-17b-finetune.md: decisions FD-1 to FD-17, changes FT-1 to FT-29 and backend contract changes BC-1 to BC-9. The design target is now the v2 snapshot in the UI repo, docs/prototype/v2/, at artifact version 1791210282-291b, with the decided options as its defaults. This file takes the record in as the findings of Fine-tune of 4 and 5 October 2026, decisions K36 to K52 and guardrail 12. K53 and K54 came from the fast /reassess of this amendment. A cell of an older table that the fine-tune changes opens with "Now (2026-10-05)" and what holds today, complete on its own. The text it replaces follows behind "Before 2026-10-05 (superseded)". That part is history and never a build target. The new findings enter the milestone through a delta /phase-split docs/plan/phase-17b-walk-corrections.md, and the issues already filed are audited against guardrail 12 before any of them builds. That delta split ran on 2026-10-07. It amended 246 issues against guardrail 12 and filed 33 new ones, in the backbone issues #1660 to #1670 and in the UI ui#859 to ui#880. It folded four planned backbone items into existing issues, cut 11 new UI issues out of existing ones and cut the new issue #1670 out of the new #1667. The milestone now holds 279 issues, backbone 105, UI 169 and engine 5, of which 270 are build targets and 9 are trackers, and the wave table runs 23 backbone rows, 36 UI rows and E1 (Split record).

Why this phase exists. Phase 17 (phase-17-revamp.md) closed every build issue (39 backbone and 76 UI) and released backbone v0.27.0 and UI v0.17.0 to prod on 2026-10-01. The phase-end owner walk (R2, #1501, exit criterion 2) failed the same day. Lookup does not read as the place report of the prototype. /portfolios crashes. Explore cannot compute a single future scenario. Event replay blinks. And adaptation has no visible way to be applied. The owner stopped the walk part way through Moment 4. This phase holds every change needed to bring the product to (a) the owner's prototype and (b) the Phase 17 goals and the project's demoable slice. The current target of (a) is the v2 snapshot in the UI repo, docs/prototype/v2/ at artifact version 1791210282-291b, as decided on 2026-10-05 (see the fine-tune paragraph above). The v1 artifact the walk was judged against (claude.ai/artifact/1TXwPDAiGY3Pj1tqB9bBN5, snapshot docs/prototype/v1/) is kept as history.

Relation to Phase 17. Phase 17 stays OPEN and closes only when this phase exits. Its design record (D1 to D22), direction lock (V1 to V3) and guardrails 1 to 8 still bind every item here. This phase adds the guardrails in Guardrails added. Its milestone is Phase 17b -- Walk corrections in three repos: CortoMaltese3/climate-lama (backbone), CortoMaltese3/climate-lama-ui (the sibling lane) and CortoMaltese3/climate-lama-engine (wave E1 only, its own /phase-build session from the engine checkout). /phase-split creates labels, never milestones, so before its create step the split orchestrator creates the milestone in each repo whose gh api repos/CortoMaltese3/<repo>/milestones --jq '.[].title' lacks it (gh api repos/CortoMaltese3/<repo>/milestones -f title="Phase 17b -- Walk corrections"), and every gh issue create passes --milestone "Phase 17b -- Walk corrections" and --label milestone:phase-17b.

Split authorised 2026-10-02. The owner authorised /phase-split on this document after the conceptual and drift passes. Reports, Data, Analyses, Admin and the 390 px pass are still unwalked; their findings enter through Walk stop 1's delta split, which is what the walk stops exist for. The first split checks prototype coverage against the UI repo's docs/prototype/mapping.md until SP-3 writes phase-17b-prototype-inventory.md.

How the findings were made. Seven read-only audits on 2026-10-01 checked every screenshot of the walk against the UI code (v0.17.0, 0640a63), the backbone code (v0.27.0, 433e12f5), the prototype's source (js/views/*.js, mapkit.js, core.js, the page CSS) and prod. Prod checks were unauthenticated API reads, the public UI bundle, and read-only host commands for the engine queue. Every finding below names its evidence. A claim the audits could not prove is marked [UNVERIFIED] with what would prove it.

Owner walk record (2026-10-01, prod v0.27.0 / UI v0.17.0)

Step Result Findings
0 · Oasis three-engine agreement (#1499 box 4) Pass. Comparison 9760fef3 exit 0: climada and climate_lama EAD 846,112,504.6155, oasis 849,467,410.8586 (+0.3965 %). A second run timed out after 300 s EN-1 to EN-6
0 · Oasis sidecar under load (#1499 box 3) Pass. Peak 204.5 MiB of 2 GiB (9.98 %), 182 % CPU, healthy, oasislmf 2.5.7 EN-3
A · every view at 1440×900 Owner marked done, then judged the views far from the prototype on the walk below SH-4, SH-7
B · Moment 1 (Lookup) Fail. The scenario cannot be found, the layout does not read like the prototype, and an address-search error shows beside a good result LK-1 to LK-12
B · Moment 2 (insurer) Fail at step 1. /portfolios crashes with RangeError: invalid currency code in NumberFormat(): m3 PF-1 to PF-7
B · Moment 3 (scientist) Fail. No batch can be selected, three Compute buttons compete, and a single future cell answers E_VALIDATION EX-1 to EX-13
B · Moment 4 steps 1 to 8 (Events) Fail. Map does not focus on the watch, replay blinks, the player is too basic, and a near-real-time watch "does nothing" EV-1 to EV-9
B · Moment 4 step 9 (Adapt) Fail. The editor differs from the prototype, how measures are applied is unclear, and the right column cannot be retracted or resized AD-1 to AD-10
C · demo slice through the new surfaces Not walked. Blocked by EX-1 (no future cell computes) EX-1, EX-13
Moment 2 after step 1, Reports, Data, Analyses, Admin Not walked yet. The audits checked them against the prototype ahead of the owner PF, SH
D · 2026-10-02 · shell, Home, Settings Fail. The lens sits in the bar, the search is left-aligned and misnamed, every view carries far more whitespace than the prototype, Home's actions are plain links and its cards are fixed, buttons are hand-styled in 101 files, Settings lacks half the prototype's sections SH-17 to SH-24
D #1689 ui#888 · 2026-10-02 · assistant Fail. The code default is Opus 5.5, there is no admin control over model or effort, and a simple "run this scenario" request cannot complete: no tool finds a portfolio, nothing waits for the job, and the UI sends no view context AS-1 to AS-3
D · 2026-10-02 · Explore and the run flow Fail. Selecting a portfolio draws nothing on the map and never frames it. The Scope, Layers and Analysis tabs do not read as a flow: scope is confused with exposure, Layers has no stated purpose, and nothing in the form speaks of measures, growth or the damage curve. The owner asked for a conceptually correct, intuitive run flow EX-14 to EX-16
D · 2026-10-02 · Lookup and Adapt Judged against the prototype. Lookup needs the under-the-hood story and the wider panel (LK-8). Adapt's application was unclear (AD-1) LK-13, AD-1

Why 115 closed issues still missed the design

This section is the input for the mid-review and for how this phase is run.

  • The rework breaker tripped and nothing stopped. scripts/rework_rate.py --since 2026-09-28 --follow-ups --escapes-only reads an escape rate of 0.67 in the UI (50 of 75 builds) and 0.18 in the backbone (6 of 34), with partial rates 0.12 and 0.26. The framework stops new feature waves above 0.25. The UI ran 28 waves past it.
  • Acceptance tested components, not the experience. Boxes named one component and its behaviour in jsdom. No box compared a view with the prototype. There is no toHaveScreenshot anywhere in tests/e2e (SH-7).
  • The layout proofs could not fail. assertNoPageScroll measures documentElement and body only, while the shell is h-screen overflow-hidden, so an overflowing view is clipped and passes. Its prove-it-fails case injects outside the shell (SH-7).
  • Fixtures did not look like prod. Every fixture currency is EUR, USD or null, and the volume tests seed empty books. Prod holds GHSL m3 and census persons exposures, so the crash in PF-1 was invisible to every test (PF-1, SH-1).
  • The future-cell path never ran on a stack. tests/e2e/smoke/compute.spec.ts computes only the default Today cell and skips when no hazard is seeded (EX-1).
  • Deferrals pointed at issues that never covered them. Lookup's Watch and Place report went to ui#529 and ui#561, which never mention Lookup (LK-3). About 20 items were deferred to a "Phase 18 charter stub" that does not list them (SH-10). The assistant draft in Reports was promised and got no issue at all (PF-6).
  • Prototype scope that had no item. The frame promised "apply to asset, portfolio and area" for Adapt and an Explore Adapt mode. The split created no item for either (AD-1, EX-12).
  • Regressions during re-hosting. Moving Live into Explore dropped the asset layer coloured by loss and the raster stack that kept replay smooth (EV-2, EV-3).
  • The walk came only at the end. No owner walk ran between wave A and wave E.
  • The run had no business-logic document. Explore's dock grew one component at a time (ScopePicker, LayerCatalogue, AnalysisDock), each correct in isolation, and nobody wrote down what a run is: which inputs, in which order, resolved from where. The owner could not read the flow on 2026-10-02 because there was none to read (EX-15, EX-16).

Each cause has a guardrail below: the breaker (5), component-only acceptance (1), layout proofs (4), fixtures (2), the future-cell path (2 and 3), deferrals (7), prototype scope with no item (10), re-hosting regressions (9) and the late walk (6). The missing business logic is fixed by phase-17b-run-model.md, which every EX, AD and AS issue cites (guardrail 11).

Guardrails added by this phase

These bind every Phase 17b issue on top of Phase 17's guardrails 1 to 8.

Each guardrail is enforced by a skill, not by this doc. Phase 17 had eight guardrails and a framework breaker, and nothing executed them. On 2026-10-02 the rules were wired into ~/.claude/collaboration-framework.md (amendment 2026-10-02), /phase-split (guardrails become issue boxes, hollow deferrals fail, every finding id and inventory row must map, walk stops become walk:mid trackers) and /phase-build (escape breaker at run start and between waves, walk walls no flag passes). Two /reassess fast passes chose the designs below. Each guardrail names its enforcer.

  1. Prototype parity is an acceptance box (enforcer: /phase-split "Guardrails carried" and "Design named", and the wave gate's stack-pending modes with their reference comparison in /phase-build references/trunk-gate.md). Now (2026-10-05, guardrail 12 point 3, K53): every UI issue that changes a view carries this box: <registry ids> design: the wave gate's captures of <view> and of the states in tests/e2e/parity/states/<issue>.json show no design difference from the prototype captures of those registry ids (v2 snapshot 1791210282-291b), the difference list is posted on the issue, and parity.spec.ts <view> is green in the visual project (stack: local-e2e-gate e2e-run.sh visual). Two kinds of file exist and they are never confused. The prototype captures are the design reference. SP-2 (a) renders them from the v2 snapshot source, UI repo docs/prototype/v2/source/ at artifact version 1791210282-291b, with the snapshot's defaults: the decided options of js/finetune17b.js, and a preview state (fresh, adminAs) switched only for the capture whose registry entry needs it. There is one capture per view, tests/e2e/parity/prototype/<view>.png, and one per distinct go hash of the live registry under tests/e2e/parity/prototype/states/, with an index.json that names the capture of every live registry id. They hold mock data and need no compute. A builder opens the capture of its registry ids as the picture of what to build. The product baselines tests/e2e/parity/baselines/<view>.png are the product's own renders. They guard against drift between waves and prove nothing about the design. The design is proved at the wave gate, by the orchestrator, because builders cannot render. For every issue of the wave that carries the box, the gate captures the product in the pinned image at the view and at each state the issue's state file names. That file is tests/e2e/parity/states/<issue number or finding id>-<slug>.json, a list of entries {id, route, prepare?: "run", account?: "org_admin" | "fresh_org", hash?} keyed by id, account and hash (SP-2 (d), ui#859). It reads each product capture beside the prototype capture of the same registry id and posts the difference list as a comment on the issue, one sentence per difference, never a sample. A difference caused only by data values (a number, a name, a count) is listed once per capture as data: <element>. Where the layout depends on how much data there is (the length of a list, an empty state), the seed holds what the snapshot's mock shows. An element on the "prototype only" list of guardrail 10 is listed as prototype only: <element>. A difference in a part of the capture that the issue's registry ids do not describe is listed as elsewhere: <element> with the open issue whose box owns it, because a view is built over several waves and a capture taken in one wave lacks what a later issue adds. An elsewhere: element that no open issue owns is a coverage hole, and the gate files it onto the next wave's list. Every other difference, a difference in what the issue's own registry ids describe, is a design difference. The box ticks only when the list holds no design difference. A design difference makes the box unmet, and it goes to the fixer on the same issue. A difference the owner accepts becomes a deviation with a K line (guardrail 10). A view's product baseline is re-rendered only in a wave whose list for that view holds no design difference. Rendering happens only in one pinned Linux image (mcr.microsoft.com/playwright:v<the @playwright/test version in package-lock>-jammy), locally through WSL Docker or in CI. Windows Playwright never renders a baseline. The visual project runs at 1440×900, and also at 390×844 for Home, Lookup and Events (D12). The smoke config's 1280×800 stays for the other specs. It masks the map canvas and runs with animations disabled and the offline basemap. The orchestrator's detached stack gate runs it once per row, after the row has closed and while the next row builds, lagging at most one row (K72, 2026-10-08; until then the wave gate ran it and held the row: U3 sat 4.5 h in five stack rounds). Builders mark the box stackPending and no PR is billed for it, but a builder Reads the prototype captures of its registry ids before its edit plan and names them in the PR body's ## Design read (K72), and the lane's view PRs get a pre-merge render check from the first design difference a gate posts. SP-2 builds this harness in W0a, before any view issue. Before 2026-10-05 (superseded): the box read parity.spec.ts <view> green in the visual project (stack: local-e2e-gate e2e-run.sh visual), PR body embeds the screenshot beside tests/e2e/parity/prototype/<view>.png with one sentence per difference, and the per-view toHaveScreenshot baselines were taken as the proof of parity.
  2. Prod-shaped data (enforcer: the issue bar's prod-shaped data rule). Unit fixtures, the e2e fixtures and the smoke seed (scripts/smoke_e2e.py in the backbone, with a matching UI seed) hold an m3 book, a persons book, a USD book beside an EUR book, all non-empty, and one region-tagged SSP river flood dataset (region "Greece", as prod holds it) so a future cell computes. A vitest table runs the money formatter over EUR, eur, USD, m3, persons, €, "USD ", null, "" and asserts no throw and a non-empty string.
  3. A skip is a failure (enforcer: the issue bar's skip rule). compute.spec.ts and scenario-matrix.spec.ts fail instead of skipping when the seed lacks a hazard, and they compute one future cell end to end (EX-1). Every stack box pastes passed, failed and skipped counts. A skip of a named test fails the box unless the box lists it as expected-skip with the mode that runs it (for example stackcontrol).
  4. Layout proofs measure the view root inside <main> (enforcer: the issue bar's prove-it-fails placement rule and the "Guardrails carried" rubric item), and the prove-it-fails injects inside main > * (SH-7). The issue bar now requires every inversion to sit where the real defect would sit.
  5. The escape breaker runs per wave (enforcer: /phase-build step 6 "Escape breaker"). At run start and between waves it reads scripts/rework_rate.py --repo <repo> <the last Rework window line> --follow-ups --json for both repos, and trips on the upper bound (escapes plus unattributed follow-ups, over builds) above 0.25. harness fix PRs (e2e, workflow, CI and gate-script repairs) are printed, never counted (K72). A trip lets only the repair set build: the issues attributed to the window's builds plus open fix issues. When they land, the run appends a fresh Rework window: line to this doc through a docs PR, starting at the last repair PR's mergedAt (--since-ts, strictly after, K72; until 2026-10-08 the day after, which hid every same-day build and fix). A second breaker (phase): line from the milestone's first issue never trips and keeps the whole-phase trend visible. Only --override-breaker "<reason>" passes a trip, and the reason is written here. Rework window: --since-ts 2026-10-08T10:39:26Z (trip 2026-10-08: climate-lama 0.40 then 0.8, climate-lama-ui 1.00 then 1.75 read on --since 2026-10-02; repaired be#1677 via PR #1683, be#1679 via PRs #1698 #1699, ui#898 via ui PRs #900 #901 #902, and U3's ui#716 #717 #720 via ui PRs #907 to #916, the last at 10:39:26Z; the earlier --since 2026-10-09 line hid those ten; deferred: be#1688 gated on walk #1631 by its Deps, K11 anti-deadlock) Rework window: --since-ts 2026-10-09T16:41:42Z (trip 2026-10-09: climate-lama 0.27, 6/22 on --since-ts 2026-10-08T10:39:26Z, the sixth escape being follow-up be#1735 against B7's PR #1733; repaired be#1735 via PR #1737, a guard test, after CI e2e-smoke 37949874504 passed the showcase moment 2 walk on backbone 53cb3f32, so be#1735 closed not reproduced) Rework window: --since-ts 2026-10-09T17:34:11Z (trip 2026-10-09: climate-lama-ui 0.35, 8/23 on --since-ts 2026-10-08T10:39:26Z, harness 4 not counted; repaired ui#733 via ui PR #953 and ui#737 via ui PR #956, the last at 17:34:11Z; U7 close-out) Rework window: --since-ts 2026-10-10T00:06:52Z (trip 2026-10-10: climate-lama 0.30, 3/10 on --since-ts 2026-10-09T17:34:11Z, two escapes fixed in-wave by PRs #1745 #1753 plus unattributed follow-up be#1750; repaired be#1750 via PR #1754 at 00:06:52Z, its smoke-stack box stack-pending; B9-residue session) Rework window: --since-ts 2026-10-10T05:40:36Z (trip 2026-10-10: climate-lama 0.27, 3/11 on --since-ts 2026-10-10T00:06:52Z, two composed-wave escapes fixed in-wave by PRs #1766 #1769 plus follow-up be#1767 from B11's PR #1764; repaired be#1767 via PR #1770 at 05:40:36Z, its prove-it-fails box unmet at merge; B11 session) Rework window: --since-ts 2026-10-10T14:13:10Z (trip 2026-10-10: climate-lama-ui 0.57, 4/7 on --since-ts 2026-10-10T05:40:36Z, harness 5 not counted: in-wave fixes ui PRs #980 #987 plus follow-ups ui#983 and ui#984 from U12's PRs #981 and #978; repaired ui#983 via PR #991 and ui#984 via PR #992, explore baseline PR #993 at 14:13:10Z; U12-residue session) Rework window: --since-ts 2026-10-10T22:12:58Z (trip 2026-10-10: climate-lama-ui 0.30, 3/10 on --since-ts 2026-10-10T14:13:10Z, harness 3 not counted: follow-ups ui#996 from U13's PR #995 and ui#1010 from U14's PR #1007 plus unattributed fix ui PR #1006; ui#996 built as PR #1004, repaired ui#1010 via PR #1011 at 22:12:58Z; U14 session) Rework window: --since-ts 2026-10-11T17:49:09Z (trip 2026-10-11: climate-lama-ui 0.29, 9/31 on --since-ts 2026-10-10T22:12:58Z, harness 3 not counted: row U20's in-wave fix PRs #1056 #1057 #1058; repaired ui#770 and ui#872 via ui PRs #1059 #1060, and ui#887 via ui PRs #1061 #1063 #1064 with be#1791 via PR #1792, the last at 17:49:09Z; the same window read 0.35, 11/31, harness 6 with those repairs in it; U20b session) Rework window: --since-ts 2026-10-12T14:27:02Z (trip 2026-10-12: climate-lama-ui 1.00, 4/4 on --since-ts 2026-10-11T17:49:09Z, harness 4 not counted; repaired ui#1081 via ui PR #1086 and ui#1083 via ui PR #1087, the last at 14:27:02Z; the same window read 1.25, 5/4, harness 5 with those repairs in it; deferred: ui#1070 and ui#1073 gated on walk be#1631 by their Deps, K11 anti-deadlock; U21d session) Rework window: --since-ts 2026-10-12T23:11:14Z (trip 2026-10-12: climate-lama-ui 0.29, 2/7 on --since-ts 2026-10-12T14:27:02Z, harness 1 not counted: follow-ups ui#1094 and ui#1104, both attributed to row U21's ui PR #1090; repaired ui#1094 via ui PRs #1098 #1102 and ui#1104 via ui PRs #1106 #1107, the last at 23:11:14Z; deferred: ui#1070 and ui#1073 gated on walk be#1631 by their Deps, K11 anti-deadlock; U23 session) Rework window: --since-ts 2026-10-14T19:36:28Z (trip 2026-10-14: climate-lama 0.60, 3/5 on --since-ts 2026-10-12T23:11:14Z, harness 0; repair set #1836 #1838 #1839 #1840 #1842 #1854 via PRs #1855 #1857 #1860 #1856 #1859 #1858, the last at 19:36:28Z; climate-lama-ui read 0.20, 6/30 in the same window, untripped; deferred: #1853 gated on #1688 #1666 #1687 by its Deps, K11 anti-deadlock; B18 repair session) Rework window: --since-ts 2026-10-16T07:46:51Z (trip 2026-10-16: climate-lama-ui 0.29, 2/7 on --since-ts 2026-10-14T19:36:28Z, harness 0: unattributed fix PRs ui#1159 ui#1163; repair set ui#1141 ui#1142 ui#1143 ui#1144 via ui PRs #1169 #1170 #1171 #1172, the last at 07:46:51Z; climate-lama read 0.00, 0/14 in the same window, untripped; deferred: ui#1070 ui#1073 (open fix issues outside the run set the owner named); U31 session) Rework window: --since-ts 2026-10-16T15:35:55Z (trip 2026-10-16: climate-lama 0.33, 2/6 on --since-ts 2026-10-16T07:46:51Z, harness 1 not counted (#1885); escapes: #1882, a fixture gap #1880's outline guard exposed, and #1881, the planned phase fix #1853 (PF-14) that the read counts as an unattributed fix, kept at the upper bound; repair set #1882 #1885, the last at 15:35:55Z; no open phase fix issue; B22 session) Rework window: --since-ts 2026-10-17T10:48:59Z (trip 2026-10-17: climate-lama 0.33, 2/6 on --since-ts 2026-10-16T15:35:55Z, harness 0; escapes: #1891, a follow-up issue of #1889 (#1669), and fix PR #1896 on #1894; repair set #1891 #1793 via PRs #1897 #1898, the last at 10:48:59Z; no open phase fix issue; B24r session) Rework window: --since-ts 2026-10-17T23:20:23Z (trip 2026-10-17: climate-lama-ui 0.80, 4/5 on --since-ts 2026-10-17T10:48:59Z, harness 3 not counted (#1196 #1197 #1199); escapes: fix PRs ui#1201 (on #1198) and ui#1204 (on #1202), follow-ups ui#1200 (#1198) and ui#1205 (#1202; red at be48751 before #1202, kept at the upper bound); repair set ui#1205 ui#1200 via ui PRs #1206 #1207, the last at 23:20:23Z; no other open phase fix issue; U30c session) Rework window: --since-ts 2026-10-18T17:42:00Z (trip 2026-10-18: climate-lama-ui 0.50, 2/4 on --since-ts 2026-10-17T23:20:23Z, harness 0; escapes: fix PR ui#1210 (on #1208) and follow-up ui#1213 (on #1211); repair set ui#1213 via ui PR #1216 at 17:42:00Z (Refs; its stack boxes ride the U33 render); no open phase fix issue; U33 session) Rework window: --since-ts 2026-10-19T02:55:58Z (trip 2026-10-18: climate-lama-ui 1.00, 4/4 on --since-ts 2026-10-18T17:42:00Z, harness 0; escapes: fix PR ui#1222 (on #1219) and follow-up ui#1224 (on #1223), unattributed ui#1217 ui#1218; repair set ui#1224 via ui PR #1225 at 02:55:58Z (Refs; its stack box rides the U35 render); no other open phase fix issue; U35 session) Rework window: --since-ts 2026-10-19T11:12:33Z (trip 2026-10-19: climate-lama-ui 0.50, 2/4 on --since-ts 2026-10-19T02:55:58Z, harness 3; escape: 1 follow-up plus 1 unattributed; repair set ui#1234 via ui PR #1236 at 11:12:33Z (Resolves); U37 session; ui#832 ui#843 waited)
  6. Owner walks are walls (enforcer: walk:mid trackers in Deps:, /phase-build step 2). Walk stops come after W0, after W2 and W3, and after W4 and W5, each on prod after a release (the framework now allows one release per declared walk stop). The phase-end walk (W7) follows W6. The owner closes the tracker as completed. Until then no issue of the next wave dispatches, and no flag passes the wall. Findings from a walk are filed onto the next wave.
  7. A deferral must hold the item (enforcer: /phase-split "No hollow deferral"). defer: → #N is valid only when #N is open and its body has a - [ ] box with the finding id. A comment does not count. A pointer to a charter stub is valid only when the stub lists the finding id.
  8. Money has one path (enforcer: a vitest AST guard, K34). formatAmount treats a unit as a currency only when Intl.supportedValuesOf('currency') holds it, and otherwise prints the unit as text ("16.8M m3"). It never throws. A vitest AST guard (tests/money_one_path.test.ts, run by npm test) bans style: 'currency' and new Intl.NumberFormat outside src/i18n/format.ts, and eventAssetColumns.tsx:30 moves onto formatAmount (PF-1).
  9. Moving a surface keeps an inventory (enforcer: the issue bar's move rule). EX-12 and AD-1 (Adapt into an Explore mode), AD-3 and SH-12 (docks onto a drawer) and EV-6 (Events tab into Events mode) each list every feature of the source view as a kept box or an explicit drop line. The source's e2e specs pass unchanged on the new host. This guards against a repeat of EV-2 and EV-3.
  10. Prototype coverage is checked, not assumed (enforcer: /phase-split coverage). SP-3 writes a per-view inventory of the prototype. A delta /phase-split maps every row to an issue box or an Out-of-scope line and prints unmapped: empty before any W1 view issue dispatches. Now (2026-10-05, guardrail 12 point 7, K36): the inventory's rows are the change registry ids of the v2 snapshot (js/changes17b*.js: 221 entries, 9 titled "Superseded:", 212 live). Each live id maps to at least one acceptance box of one issue, or to the "prototype only" list of phase-17b-finetune.md § 4: the review board (shell-7), the Options button and board (shell-8 and the Options entry of shell-10), the two preview switches, the error line (shell-9), the retired layout variants B and C (explore-27), the group outline of the "Where, then what" variant (explore-44), the pin control (adapt-19), the scale test and the "Show an example" button of the notifications section (settings-9, a demo run that computes nothing, #1673 item 16). unmapped: is empty over those 212 ids. The inventory also lists every owner-approved deviation from the prototype with its finding id. None is recorded at 2026-10-05, because v2 itself shows the three that were listed before: the lens outside the top bar (shell-1, settings-7), the Home section in Settings (settings-1, settings-2, home-1) and the dock as two steps (explore-28, K36). Those views are proved against the prototype captures like any other (guardrail 1). A difference the owner accepts later enters the inventory as deviation: <finding id> with a K line. On 2026-10-07 the owner accepted the deviations of K57 to K66 (#1673), which the inventory records on 12 rows, each still naming the issue that builds the accepted variant. On 2026-10-13 the owner accepted the deviation of K73 (ui#806), which the inventory records on the lookup-6 row. The check is python scripts/prototype_registry_coverage.py, run from this repo with the UI checkout beside it. It reads the registry ids from the snapshot, the line below, and every milestone issue's acceptance boxes, prints unmapped: and exits 1 when an id has no owner. shell-10 is not on the line, because its other half (the top bar never overlaps at 1366 or 1600 px) is a target and needs a box. explore-27 (the retired layout variants B and C) is on the line, because it describes the prototype's own switches and no product behaviour (fast /reassess of 2026-10-05). The entries that show a state through a preview switch (home-10, home-11, admin-10, admin-12) are targets: the state is proved on an organisation with no data and on an organisation admin's account, and only the switch itself is prototype only.

    Prototype only (registry ids): shell-7, shell-8, shell-9, explore-27, explore-44, adapt-19

    Before 2026-10-05 (superseded): The inventory also lists every owner-approved deviation from the prototype with its finding id (SH-17 lens out of the bar, SH-23 and SH-24 Home section in Settings, EX-16 the three tabs with redefined content), so a parity baseline for those views is the product's own, accepted by the owner under exit criterion 2.

  11. The two logic docs are the reference for every input, field and figure (enforcer: /phase-split premise check and the issue bar). An issue that adds, removes or re-orders a run input, a compute request field, a result field, a rollup field, a measure field or a live-lane field cites the section of phase-17b-run-model.md, phase-17b-domain-model.md or the finding row it implements, and its boxes use these names: exposure_dataset_ids, scope (with scope.admin_boundary_ids for a list of boundaries of one level and one country, scope.admin_boundary_id as its one-element alias and scope.bbox, BC-1, K50), exposure_growth_rate, measure_ids, adapted_from_result_id, cells, batch (the address key batch=<id> that holds a matrix run's batch id, and the same id on a saved run, EX-20 to EX-22), default_dataset_id on the exposure dataset list (EX-18), created_by_user_id on the batch rows and the job row (EX-20), state, reason, money and money_reason per hazard on the hazard options read (EX-19, #1673 item 17), that read's route GET /v1/compute/hazard-options with its scope_total (EX-19, EX-23, #1661), impact_workers on GET /v1/jobs and cached and hazard_dataset_id on a batch cell (EX-20, #1662), compare, live_event_id and event_key on a saved Scenario with the live_event_id and mine filters of GET /v1/scenarios (EX-11, EX-21, EV-19, #1663), design_return_period and value_weighted_average_band on a result (EX-10, #1595), the stored results read GET /v1/results with its indicator_score (EX-10, #1664), indicator_score on a portfolio's indicator hazard (PF-11, #1665), location.containing_units on the Lookup card (LK-14, #1668), default_effort on GET /v1/assistant/options (AS-1, #1602), window_start in a feed's last_poll_summary (EV-1, #1614), latest_insured_event_loss on a watch (EV-14, #1611), the watch_update payload keys event_loss, insured_event_loss and unit (EV-14, #1617), intensity on an event result's asset row (EV-3, #1612), owner, category and geocoded_precise_share on a portfolio (PF-7, #1666), supersedes_dataset_id and portfolio_id on an uploaded dataset and step on an upload job (DT-7, PF-12, #1667), the upload routes POST /v1/hazards/footprints and POST /v1/admin-boundaries/uploads (DT-7, #1670), the admin reads GET /v1/admin/queues and GET /v1/admin/failures (SH-26, #1669), the platform reads GET /v1/admin/feeds and GET /v1/admin/jobs (SH-26, #1680), and the fields the #1673 rulings of 2026-10-07 added: ingest_job_id and cancellable on a job row (#1654), scenario and time_horizon on an ead_by_hazard entry (#1571), event_metadata.via and source=api on the audit log and persona on an invitation (#1652), GET /v1/admin/usage/daily (#1653), GET /v1/admin/engine-stats and data_region on GET /v1/info (#1669), checksum on a dataset version (#1634), single_measures_avoided_loss on a plan (#1624), impact_by_exposure_type and count_high_or_severe on a result (#1595), geometry=simplified on the admin cells read (#1636), and from the owner's picks: GET /v1/risk/admin-units/{boundary_id}/impact with buildings_in_data (LK-15, #1684), band_exp, band, band_max, band_value_weighted, GET /v1/results/assets/{exposure_id} and GET /v1/results/admin-units/{boundary_id} (EX-14, #1685), layer=admin on the exposure and portfolio tiles (EX-14, #1686), GET /v1/live-events/{id}/snapshots/{snapshot_id}/portfolios (SH-16, #1687), POST /v1/portfolios/{id}/members/remove and excluded_row_ids (PF-12, #1688), POST and GET /v1/orgs/{id}/branding/logo, branding.report_footer and formats on the user's preferences (SH-24, #1689), provenance.severity_levels and latest_severity_level on a watch (EV-17, #1605, #1608), created_by_email (EX-20, #1662) and checksum on a result (SH-16, #1636), and from walk stop 2: scope on a matrix batch's params (EX-28, #1806), GET /v1/live-events/replay-series and feed on POST /v1/live-events (EV-20, #1807) and value_unit on the assistant's get_result_summary (AS-5, #1650), value_growth_rate on cost-benefit (K20), cost_basis, unit_count, applies_to, hazard_inten_shift, RP_protect, benefit_years, combined, per_year_benefit, event_key, cumulative, insured_event_loss, band_distribution, share_high_or_severe, ead_by_hazard, exposure_type on impact_functions, bbox on hazard datasets, frequency_type, dropped_columns, rp_protect, climate_driven on coverage, provenance.hazard_conditioning, provenance.exposure_growth, provenance.vulnerability, result.metadata.scope, effect, cobenefits, value_weighted.unit, deductible and cover on live_events, latest_event_loss_unit. A field neither doc nor a row names is Out-of-scope until the doc is amended in the same PR.

  12. An issue is compliant with the decided design before it builds (added 2026-10-05, enforcer: the /phase-split premise check and rubric, and the registry coverage check of SP-3's inventory, scripts/prototype_registry_coverage.py, which prints unmapped: empty). This is the compliance bar of phase-17b-finetune.md § 6. Phase 17 closed 115 issues and failed its walk because the built views did not match the design. An issue is ready for /phase-build only when all seven points hold.
    1. It names its design. A "Design" block gives the snapshot version (1791210282-291b), the hash to open in the snapshot, and the registry ids it implements.
    2. It builds the decided variant only, named in words (K36 to K52), and never offers a variant that was not chosen.
    3. Its acceptance proves the design. It carries guardrail 1's design box. The wave gate captures the product at the view and at every state the issue's state file names, reads each capture beside the prototype capture of the same registry id and posts the difference list on the issue. The box ticks only when that list holds no design difference, which is a difference in what the issue's own registry ids describe. A data:, prototype only: or elsewhere: line does not block it. Behaviour is proved by checks in the framework's command → expected output form, at least one per registry id the issue implements.
    4. Its copy is the snapshot's copy, quoted where it matters: labels, empty states, refusals, the honest-limit lines.
    5. It contradicts neither the record nor the run model: no "Analysis" tab, no Layers tab, no chip menus, no "Advanced" curve override, no "book", no list named Analyses.
    6. Its Files: list is complete and its Deps: reflect the new order. A UI box that needs a backend change names that issue.
    7. Every live registry entry is owned. Each of the 212 live entries maps to at least one acceptance box of one issue, or to the "prototype only" list of guardrail 10. SP-3's inventory (#1565) is the place that prints unmapped: empty over the registry ids.

Findings

Severity follows the framework's tags. [CRITICAL] blocks a showcase moment or the demo slice. Source owner means the owner reported it on the walk. self means an audit found it. Repo ui, be (backbone) or both. File references are to the tagged code.

Cross-cutting (shell, theme, map, data honesty)

Id Sev Source Repo Finding Change needed Acceptance
PF-1 CRITICAL owner ui /portfolios crashes. formatAmount (src/i18n/format.ts:33-37) passes any value unit to Intl.NumberFormat as a currency. The unit comes from the rollup's _dominant_unit() (core/rollup_service.py:1341,1933), which is the members' raw value_unit. Prod data holds m3 (GHSL built volume) and persons (census). The same throw reaches Portfolio detail (PortfolioSynopsis.tsx:283), Home (SH-1) and Events (EventLossHeadline.tsx, DeltaPanel.tsx, eventAssetColumns.tsx:30). Adapt alone guards it (AdaptAnalysis.tsx:68) One shared money formatter that never throws. A unit is a currency only when it is a well-formed ISO 4217 code, otherwise it prints as text ("16.8M m3"). Route every caller through it A vitest table over units EUR, eur, USD, m3, persons, €, "USD ", null never throws. e2e: upload a CSV with value_unit=m3, open /portfolios, the card shows "m3". Prod walk: /portfolios and that book render with no RangeError
SH-1 CRITICAL self ui Home can crash the whole app the same way. HomeKpiRow.tsx:65,80 formats the top book's value_unit, and Home picks the book with the largest exposed_value (src/home/useLensScope.ts:56). Ranking books by value across different units is also meaningless. [UNVERIFIED] whether prod's top book is non-ISO Covered by PF-1's formatter. Rank books only within one money unit Smoke stack with an m3 book as the largest: / renders its key figures
PF-5 ENHANCE-NEXT owner ui One bad book takes the whole app down. The only error boundary wraps the shell (App.tsx:338) Error boundaries per card and per synopsis section that read "this figure could not be shown". The shell boundary stays the last resort A test card that throws leaves the other cards and the nav working
PF-2 ENHANCE-NOW self both The value unit is free text at upload (ExposureUploadModal.tsx:405-413) and in the API (api/v1/exposures.py:172), while the code calls it a currency Upload offers an ISO 4217 select plus an explicit list of non-money units. The backbone answers 422 for anything else. Rollup and summary say whether the unit is money POST /v1/exposures with value_unit=€ is 422, with EUR or m3 is 202
PF-3 ENHANCE-NOW self both Every EAD shows "€" even on USD books. formatEur hard-codes it in cards, synopsis, Portfolio detail, Analyses, Reports previews, ResultMap and LayerPanel. GET /v1/portfolios drops the currency it already computes (core/portfolio_service.py:408-417), and results carry no unit List rows carry value_unit and unit_is_money (#1578), results carry their value unit, every money figure goes through PF-1's formatter, and formatEur is removed Now (2026-10-05, FT-11, K43): A LitPop (USD) portfolio reads "$" on its card, its synopsis and its History card (the saved-work list at /analyses). grep -r "formatEur(" src/ returns nothing. Before 2026-10-05 (superseded): A LitPop (USD) book reads "$" on card, synopsis and Analyses card
SH-8 ENHANCE-NOW self ui Analyses labels every figure in euros (RunCard.tsx:134,192, RunSummaryDrawer.tsx:130,252) Covered by PF-3 A USD run's card never shows €
PF-4 ENHANCE-NOW self both Money losses are computed and shown for m3 and persons books. Opening such a book in Explore auto-runs a matrix compute (PortfolioScope.tsx:206-236) Refuse the money loss with a stated reason (K19, 2026-10-02): the run model's vulnerability rule answers 422 "no vulnerability curve for exposure type X" for a non-money book, and Explore shows that reason in place of a Compute. Lands in W2 inside EX-15 and EX-16. Each refusal at submission is recorded as the audit action compute.refused, and Admin lists it beside failed jobs through GET /v1/admin/failures?days=7, whose rows carry kind job_failed or run_refused (registry admin-6, #1669, 2026-10-07) A GHSL m3 book in Explore reads "score-only exposure: no money loss can be computed" and dispatches no compute
SH-4 ENHANCE-NOW self ui The shell is not the prototype's. The rail opens icon-only with labels sr-only (IconRail.tsx:24-31,89). The prototype shows each icon with a caption and pins Direction, Settings and Admin to the bottom. The top bar has no page title or breadcrumb (TopBar.tsx:126-139), its search is left-aligned while D3 says centred, there is no theme toggle, and Ask is not the primary button Now (2026-10-05, FT-2, FT-12, FT-25, K42, K44): Rail shows icon and caption by default, Settings and Admin pinned at the bottom. The rail and the phone bottom tabs sit on the strip (--strip) in both themes (#1673 item 18). A title or breadcrumb slot with the scope chip on Explore, Adapt and Reports. Centred search, theme toggle, Ask as primary. The scope chip shows the resolved scope line of EX-18 and offers places and portfolios (registry shell-15). The top bar also holds the pill of a run in flight (EX-20), and nothing in it overlaps at 1366 px with the pill showing (shell-10). The rail's mark is the shield (SH-28), and its saved-work entry reads History (SH-25) Now (2026-10-05, FT-2, FT-12): At 1440×900 a fresh user sees captions under every icon, the top bar reads "Explore /" followed by the resolved scope line, the same line as the head of Scope (EX-18), and the search centre sits within 40 px of the bar's centre. At 1366×768 with a run in flight no control of the bar overlaps another. Before 2026-10-05 (superseded): the top bar reads "Explore / Greece"
SH-2 ENHANCE-NOW owner+self ui Prod maps are light OSM raster inside a dark UI, with labels in many scripts. publish.yml:126-128 bakes OSM tiles in and defaultBasemap() (basemaps.ts:115-117) picks them whatever the theme. The offline dark basemap has no streets, labels or GADM lines, so D15's "Natural Earth plus GADM boundaries" shipped only in part Open every map on a basemap that follows the theme. Ship a self-hosted street-level vector basemap in light and dark with Latin labels and GADM admin lines. OSM raster stays an opt-in entry. Provider and licence are decided by spike SP-1 With the dark theme and no saved basemap, /lookup?lat=37.99&lon=23.73 shows a dark map with streets and labels, and /explore shows dark admin-1 lines
SH-3 ENHANCE-NOW self both Score maps paint green to red by default. useMapPalette returns null until a user picks a palette (appearance.ts:178-182), so the backbone's traffic-light classes (core/score_classes.py:54-58) show. D13 makes the colour-blind-safe Ember the default Ember is the default for rasters and classed tiles when no palette is picked A fresh user's Lookup legend uses Ember swatches and no green
SH-7 ENHANCE-NOW self ui The layout proofs are vacuous and nothing compares a view with the prototype (see "Why 115 closed issues") Assert on each view root inside <main>, prove-it-fails inside <main>, per-view screenshot baselines, prod-shaped seed data Injecting a 200vh block inside main > * on /lookup turns the Lookup layout test red
SH-5 ENHANCE-NOW self ui The Ask panel is live on prod but hides the query-tool steps that source its figures (useAssistantLoop.ts:274-286 draws calls only, not append) and sends no view context, while the backbone accepts context (schemas/assistant.py:136) Draw query-tool steps in the trace. Send view, scope, hazard, scenario, horizon and lens on every turn, as the prototype does Asking "what is the expected annual impact here?" on an Explore result shows a get_result_summary step whose result holds the quoted figure
SH-6 ENHANCE-NOW self ui Ctrl K searches views and commands only. The prototype also finds places, municipalities, portfolios, datasets, analyses and events. ui#500 deferred these to issues with no box for them Global search providers for places (admin units and geocoder), portfolios, datasets, analyses and events Typing "Larissa" or a portfolio name in Ctrl K from /settings lists a match that opens on Enter
SH-11 ENHANCE-NEXT self be The bell's items are job and dataset events only. No live or watch code calls emit_notification, while the prototype's bell leads with watch updates (its copy says "alerts"). 2026-10-02: ADR-078 item 6 reads "No push, no subscription, no email, and the word alert never appears in event-lane copy", so a loss threshold or "tell me when" rule is a subscription and needs an ADR amendment first (Phase 18 stub); the prototype's "Alert me when…" and "+12 %" are design-only Now (2026-10-05, FT-12): One in-app bell item "Watch update: scored a new observation" per scored snapshot, with the life-safety line, linking to its event. Job noise is grouped. No threshold rule. Inside the Jobs group the viewer's own finished runs come first, each going back to its run, and the bell counts the unseen ones (EX-20, registry shell-14) Replaying an event that hits a watch adds one "Watch update" item that opens /explore?mode=events&event=; grep -ri "alert" src/components/live src/i18n/locales/en/live.json stays empty
SH-12 ENHANCE-NEXT self ui 724 legacy colour classes remain in 64 files, mostly Admin, API keys, Analyses and the bell. Three docks are not the shared resizable Drawer: Adapt's right column, AssetDetailPanel.tsx:88 and Events' AssetsDrawer. The sheet breakpoint is 640 px while D12 says 768 Move those docks onto the Drawer primitive, set the sheet breakpoint to 768, lower the colour-guard baseline The colour-guard total falls, and every dock drag-resizes with its width kept after reload
SH-14 BACKLOG owner ui /brand.json 404 in the console, harmless and by design (#121) Fetch it only when a build flag says a self-hosted brand exists No 404 for /brand.json on a default build
SH-17 ENHANCE-NOW owner ui The role lens sits in the top bar (TopBar.tsx:134 renders RoleLensMenu). The owner wants it only in Settings. The prototype draws #tb-persona in the bar (app.js:45), so this is an owner deviation, recorded in SP-3's inventory Now (2026-10-05, guardrail 10): This is what the v2 snapshot shows (registry shell-1, settings-7), so the row is the target and not a deviation. Remove RoleLensMenu from the bar and delete src/shell/RoleLensMenu.tsx with its test. The lens is set in Settings → Role lens (RoleLensSection.tsx:93-107, admin org default :168-185). TopBar.test.tsx drops the mock at :23-27 and asserts no role-lens-button at desktop and compact widths. RoleLensSection.test.tsx:139-148 and tests/e2e/smoke/settings.spec.ts:201-269 assert the lens through the Settings radiogroup and the rail order, never through the bar. Before 2026-10-05 (superseded): the row was recorded as an owner deviation from the v1 prototype, which drew the lens in the bar page.getByTestId('role-lens-button') has count 0 on every view; picking Scientist in /settings/lens reorders the rail; the smoke settings.spec.ts is green on the wave gate
SH-18 ENHANCE-NOW owner ui The search trigger reads "Go to or do anything", sits left-aligned, and the palette says "Search views and commands" (common.json:285-290, TopBar.tsx:126-131). The prototype centres a 36 px search in a minmax(180px,1fr) minmax(280px,560px) minmax(180px,1fr) grid (index.html:308-323) reading "Search places, assets, datasets, analyses…" with a Ctrl K key cap (app.js:43), and the palette reads "Search places, portfolios, datasets, analyses — or type a command" (core.js:328) The grid and the 36 px trigger land inside SH-4's item (SH-4 owns the centring). The copy lands inside SH-6's item, with its providers, so the bar never promises places and portfolios it cannot find. Below 760 px the trigger collapses to an icon (already the case, TopBar.tsx:98) The trigger and the input read the prototype strings, the trigger's centre sits within 40 px of the bar's centre (SH-4), and typing "Larissa" lists a place (SH-6)
SH-19 ENHANCE-NOW owner ui Satellite and 3D terrain are stubs. basemaps.ts:106 registers terrain with enabled: false and no build key, and nothing in src/ creates a raster-dem source or calls setTerrain. satellite is wired behind VITE_SATELLITE_TILES (basemaps.ts:61-63,105, styles/base.ts:67-98) but publish.yml:123-128 passes no imagery key, so prod shows the disabled entry. The prototype shows both as "connect a provider" entries (mapkit.js:523-531), which D15 requires with no provider set, and the owner wants them working on prod. Offline stays the no-provider default and is not a priority Terrain: build keys VITE_TERRAIN_DEM_TILES and VITE_TERRAIN_DEM_ATTRIBUTION → a raster-dem source (encoding: terrarium), a hillshade layer and map.setTerrain toggled by the existing 3D control (MapKitControls.tsx:212), the disabled entry naming its key. Prod passes the AWS Terrain Tiles template https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png once SP-1 verifies its licence and attribution. Satellite: SP-1 chooses the imagery provider under the owner's rule that a keyed provider is acceptable (MapTiler free tier or Esri World Imagery under its terms; EOX Sentinel-2 cloudless excluded, CC-BY-NC-SA), publish.yml passes the template, the key is a credential: ops item. Lookup gains the Street / Satellite switch of lookup.js:195 once satellite is configured On prod, the basemap menu offers Satellite and Terrain as enabled entries; Terrain tilts the map with hillshade at Larissa; with both keys unset a local build shows them disabled with "connect a provider" (D15), and the parity visual run still renders on the offline basemap
SH-20 ENHANCE-NOW owner ui Every view carries more whitespace than the prototype. The UI pads pages and cards 16 px (p-4, sm:p-8 on Portfolios and Settings, gap-4), body copy is 14 px on a 16 px base, while the prototype runs 8 px page padding and grid gaps on --gap (6 px compact), cards at 10px 12px 6px head and 4px 12px 12px body, KPI tiles at 12px 14px, 13 px / 1.4 body, 32 px buttons (index.html:85-89,217,226-227,343-365,400-411). The --gap token exists (index.css:9-14, tailwind.config.js:124-128) and is used 0 times, so the density preference changes nothing Adopt the prototype's tokens: page and grid spacing on p-gap/gap-gap, Card.tsx:10 head and body paddings as above, KPI tile padding, base font 13 px / 1.4, button heights through SH-21's primitive. Add tests/density_guard.test.ts with a per-file density_guard.baseline.json, cloned from tests/colour_guard.test.ts: it counts \b(?:sm:)?(?:p-4\|p-6\|p-8\|gap-4\|gap-6\|space-y-6\|space-y-8)\b per file under src/pages/**, src/components/home/** and src/ui/Card.tsx; a file's count may only fall and migrated files listed in tests/phase17_dirs.ts hold at zero The guard is green at the baseline and red when one migrated page regains p-4; Home at 1440×900 shows 8 px gutters and 12 px card padding beside the prototype capture (guardrail 1)
SH-21 ENHANCE-NOW owner ui No shared Button, Input, Select or Modal exists. src/ui/ holds Card, Chip, Drawer, KpiCard, Pill, ScoreBadge, Segmented, Sheet, Tabs and Toast only; 254 <button elements in 101 files carry hand-written classes, and two primary styles compete (Settings.tsx:176 bg-brand-primary px-4 py-2 vs HomePage.tsx:143 bg-accent px-3 py-1.5). ICON_BUTTON and FOCUS_RING in src/shell/NavItemLink.tsx:7,11 act as a de-facto icon-button primitive in 10 files. docs/plan/forge-145-dialog-keyboard.md:236-238 chose a dialog-keyboard hook over a generic Modal src/ui/Button.tsx (variants primary, secondary, ghost, danger, icon; sizes sm 26 px and md 32 px; leading-icon slot; renders a <button> or a router Link), absorbing ICON_BUTTON and FOCUS_RING (NavItemLink re-exports them until its 10 importers migrate). src/ui/Modal.tsx wraps forge-145's hook, and ConfirmDialog is rebuilt on it. src/ui/Input.tsx, Select.tsx. tests/button_guard.test.ts with a per-file baseline counts /<button\b/ outside src/ui and src/test (baseline 254 in 101 files) and may only fall. docs/COMPONENTS.md lists the primitives and the tokens they use. Every view issue in this phase migrates the buttons it touches (a carried box) grep -c "<button" src --include=*.tsx outside src/ui and src/test is below 254 after W1 and keeps falling each wave; the guard is red when a migrated file regains a raw <button; Home, Settings and Portfolios render one primary style
SH-22 ENHANCE-NOW owner ui Home's quick actions are plain bordered text links (HomePage.tsx:29,125-147). The prototype shows .btn.sm buttons with Lucide upload, map-pin and plus icons, "New analysis" as the primary, right-aligned in the head (home.js:12,112, index.html:400-416) Now (2026-10-05, FT-26): Render the three actions through SH-21's Button (sm, leading icon, primary on New analysis), ms-auto in the head. "New analysis" starts clean: it clears the run form (exposure, clip, measures, growth and the curve override) and opens Explore on step 1 Scope, and the same named action serves Ctrl K and the assistant (registry home-12). A hazard that is not computed for the portfolio reads "Not computed for this portfolio" with "Run it", which opens Explore on that portfolio and hazard with Run open. The impact card, the per-hazard list and the hazard mix all carry it, and each hazard keeps its own figure, never summed (home-13, with home-4 superseded, PF-8). Every link from Home into Explore carries an exposure: the map card's "Open in Explore", a top municipality and a map popup set the portfolio, or the place's default exposure layer with its clip (home-14, EX-18). With no EUR portfolio Home reads the largest portfolio of another currency, and with no portfolio it reads the covered countries, and it never throws (SH-1). Work in flight is named in words, "cell k of N" with a time estimate and no percentage (home-15, EX-20) Now (2026-10-05, FT-26): At 1440×900 the three actions show icons and sit at the right of the head beside the prototype capture. From a run with a clip, growth and a measure, "New analysis" on Home lands on /explore with none of exposure, clip, growth, measures or impf in the address and Scope open. "Run it" on a hazard with no run lands on /explore?exposure=portfolio:<id>&hazard=<that hazard> with Run open. No link on Home opens Explore with a legacy scope= key
SH-23 ENHANCE-NEXT owner both Home's cards are fixed per lens (src/home/lens.ts:62-65). The owner wants the user to choose the cards, up to a limit. Preferences are server-side JSONB with extra="allow" (schemas/preferences.py:126-146), so no migration is needed Now (2026-10-05, guardrail 10): Settings → Home: pick up to 4 key figures and up to 5 blocks. The figure catalogue holds only figures an endpoint serves today (headline, eai, assets, events, alerts, runs, datasets, engines, search); the figures lens.ts:57-58 lists as unserved (top municipalities, adaptation plan, engine agreement, cached cells) join it only when their SH-16 backbone item lands, so no pick is permanently "not available". Blocks are the rendered components (map, events and alerts, recent activity), plus top municipalities, hazard mix and key insights as SH-16 builds them. Typed home_cards?: {figures: KpiFigure[]; blocks: HomeBlock[]} on UserPreferences (src/api/preferences.ts:33); the lens default applies when unset; the caps are held client-side on PUT. The v2 snapshot holds this section (registry settings-1, settings-2, home-1), so it is the target and not a deviation. Before 2026-10-05 (superseded): This section is an owner deviation from the prototype's SECTIONS (settings.js:168-177), recorded in SP-3 Picking eai, events, runs and map, recent in /settings/home reloads Home with those three figures and two blocks; clearing the pick restores the lens default; a fifth figure cannot be picked
SH-24 ENHANCE-NOW owner ui Settings has appearance, lens, preferences, notifications, api-keys, organization (Settings.tsx:40, default appearance). The prototype has lens, appearance, units, alerts, places, assistant, keys, org in that order with lens first (settings.js:168-177,184). Missing or partial: Units and formats (currency display, number, area and date formats with live preview, settings.js:306-313), a per-type alerts table (:316-330, e-mail column drawn disabled "later"), Watched places (:340), Assistant (:347), System theme and Reduce motion (:288-289), palette preview, org name, members link, logo upload, accent swatches and report footer (:365-370). SH-9 and SH-16's Settings line are superseded by this row Now (2026-10-05, FT-27, FT-16, K43, K49): SECTIONS = ['lens','appearance','units','alerts','places','assistant','home','api-keys','organization'], default lens. Today's preferences content moves into units and /settings/preferences redirects to /settings/units through settingsPath. Units and formats drive PF-2's unit list and PF-3's money formatter. The alerts table has an in-app column and an e-mail column disabled "later" until a mail item exists. Watched places lists the user's watches with their current place scores. Assistant: user switch, "what it can do" from the manifest, audit link, and AS-1's admin card, already landed in W2b (SH-24 moves it into the nav, it does not rebuild it). Home: SH-23. Organisation: name, members link, logo upload, accent swatches, report footer, default lens. Units and formats shows "Language: English" with "more languages are planned"; translations enter the Phase 18 stub. Settings.test.tsx:78 asserts the new order. The notifications table gains two rows that say where they show and switch it (registry settings-9): "An analysis finishes" with "A toast with Open, and an item in the bell under Jobs. Your own runs only." and "An analysis fails" with "A toast with Open, and an item in the bell under Jobs. The run names the reason and the fix." Each switch turns both the toast and the bell item of EX-20 on or off. The snapshot's "Show an example" button is a demo and is not built. Every line of Settings says "portfolio" for the user's own assets and "exposure layer" for reference data, and the lens cards name the saved-work list History (settings-10). The Home section is the target of v2, not a deviation (guardrail 10) Now (2026-10-05, FT-27): /settings opens on Role lens; the nav shows the nine sections in order; /settings/preferences lands on Units and formats; changing the currency display re-renders a portfolio card's EAI; parity screenshot of each section at 1440×900. With "An analysis finishes" switched off a completed run of the viewer's raises no toast and adds no bell item, and with it on both appear. The same holds for "An analysis fails" on a failed run

The data we hold (audit of 2026-10-02, domain model § 1)

Id Sev Source Repo Finding Change needed Acceptance
DT-1 ENHANCE-NOW self be frequency_type reads "occurrence" on storm, tropical-cyclone and wildfire datasets (core/ingest/storm_europe.py:14, tropical_cyclone.py:14, wildfire.py:67) while every return-period ingest stores and the engine reads marginal band widths (core/ingest/base.py:1838-1880, worker/tasks.py:914); the adapter defaults a missing tag to "occurrence" (engine_adapter.py:224) and the model to "marginal" (models/hazard.py:132) Keep the column (the engine contract needs the tag). The return-period commit writes "marginal" whenever _compute_marginal_freqs drives the weights; the adapter default becomes "marginal"; live snapshots keep "occurrence". Migration: UPDATE hazard_datasets SET frequency_type = 'marginal' WHERE return_periods IS NOT NULL AND cardinality(return_periods) > 0 After the migration no dataset with return periods reads "occurrence"; a fresh storm ingest commits "marginal"; a live snapshot commits "occurrence"
DT-2 ENHANCE-NOW self be No hazard ingest writes bbox, so precedence's locality falls back to the region label and its extent term is None (core/dataset_precedence.py:207-240; base.py:1843-1868 writes footprint only) The commit step writes bbox from the footprint envelope; a migration backfills it from ST_XMin/YMin/XMax/YMax(footprint) Every hazard dataset with a footprint has a bbox; extent_fraction is a number for all of them; the Greek JRC raster ranks "local" by bbox, not by label
DT-3 ENHANCE-NOW self be A single unlabelled field (ADR-068 shape, the EWDS historical FWI) never becomes a band input in Lookup (core/lookup_service.py:1003-1007, 1248-1272) Lookup treats a one-field unlabelled dataset as its only slice and bands it /lookup on a point covered only by the EWDS FWI dataset returns a wildfire band, not horizon_not_in_dataset
DT-4 ENHANCE-NEXT self both The CLIMADA HDF5 exposure upload drops deductible, cover and category_id silently (core/ingest/climada_hdf5.py:120-121, 354-358) The ingest summary lists dropped_columns; the Data drawer shows them with "not used by the platform this phase" Uploading an HDF5 with deductible returns dropped_columns: ["deductible"] and the drawer prints it
DT-5 ENHANCE-NOW self be The curve-to-exposure-type pairing is a name-keyed constant (core/impact_function_seeder.py:822-844) and the one-default index is on (org_id, haz_type) (models/impact_function.py:78-86), so a population curve could never be a second default Migration: impact_functions.exposure_type VARCHAR(32) NOT NULL DEFAULT 'buildings'; drop and recreate uq_impact_functions_haz_type_one_default on (org_id, haz_type, exposure_type) WHERE is_default; the model's ONE_DEFAULT_PER_ORG_HAZ_TYPE_INDEX follows; EX-15's resolution queries (org_id, haz_type, exposure_type, is_default) Seeding a second default for (river_flood, population) succeeds; a second default for (river_flood, buildings) is refused by the index; migrations run from scratch
DT-6 BACKLOG self be docs/CLIMATE_LAMA.md §5 lists n_centroids (:2121), centroid_idx and centroid_dataset_id (:2170-2171) that no model has, says events map by insertion order (:2139) while event_index exists, and lists an FWI curve (:2180) that was never seeded Rewrite §5 from models/*.py Every column named in §5 exists in a model; grep -n "n_centroids\|centroid_idx" docs/CLIMATE_LAMA.md is empty

Lookup (Moment 1, direction V3 = layout A "place report")

Id Sev Source Repo Finding Change needed Acceptance
LK-1 CRITICAL owner ui The scenario can be changed but nobody can see how. Two small native dropdowns both read "Baseline" (RiskLookupPage.tsx:733-738, seed at :49-52). Under Baseline the horizon ruler asks for 2030 to 2080 with scenario=baseline. An undated dataset then repeats today's score at every year (JRC Greece 7/10 four times, hazard_repository.py:204-235), and a dated one shows n/a (Aqueduct Cyprus), although Cyprus has SSP river-flood data. The prototype opens on SSP2-4.5 and prints "SSP2-4.5 · 2050" on every hazard Open the place report on a projection (SSP2-4.5 for the future stops, Today always baseline). A visible segmented scenario picker in the panel. "Today" in place of the second "Baseline". Scenario and year on every hazard header. A future stop never shows a baseline value: it reads "no projection for this hazard here" /lookup?lat=34.89&lon=33.61&haz_type=river_flood with no scenario params names SSP2-4.5, the 2030 stop shows a score, and the chances read "probabilities follow SSP2-4.5". No future stop on the Greek pin repeats a baseline value
LK-2 ENHANCE-NOW owner ui "Address search didn't work just now" shows beside a good result. Picking a match writes its full display name back into the box, and the search effect keyed on the value fires a second geocode (AddressSearch.tsx:90-153). That second call failed. The 195-character Greek name is close to the 200 limit (api/v1/geocode.py:37). [UNVERIFIED] exact status A pick never starts a search. The error clears on pick Search "Τήνου 53 Αθήνα", pick the first match: no further /v1/geocode request and no banner
LK-3 ENHANCE-NOW self ui Watch and Place report are missing. ui#541 deferred them to ui#529 and ui#561, which never mention Lookup, and both closed. C-U3 promised the place report. The plumbing exists: add_to_report reads the place from the URL, the basket accepts place, the backbone has a place template Add Watch (create-watch dialog over the place or its admin unit) and Place report (place widget, then open /reports) On /lookup?lat=40.6&lon=22.8, Place report puts a "place 40.6000, 22.8000" widget in the basket, and Watch creates a watch listed in Explore Events
LK-4 ENHANCE-NOW self ui The map raster does not follow the hazard tab being read. It follows the overlay chip, else the worst hazard (RiskLookupPage.tsx:586-591). An earthquake raster at 70 % tints all of Athens yellow. ui#540 promised "the expanded hazard's raster" The selected tab drives the raster. The chip can still override Clicking the River flood tab on the Cyprus pin switches the overlay to river flood
LK-5 ENHANCE-NOW owner ui Light basemap in dark theme and green-to-red palette on Lookup Covered by SH-2 and SH-3. Until SP-1 lands, darken the streets raster in dark theme with raster brightness and saturation paint With the dark theme, the 40.6, 22.8 map is dark with an Ember legend
LK-6 ENHANCE-NOW owner ui Hazard tabs are plain text, wrap onto two lines, and list hazards that are not covered (HazardTabs.tsx:67-72). The prototype's tabs show icon, short name and score, and only covered hazards. The headline group cards have no icons either. D3 promises one hazard icon set and hazardGlyph exists Each tab shows icon, short name and score badge. Hazards not covered are listed only in the "not covered" line Every tab shows an icon and a score, and no uncovered hazard is a tab
LK-7 ENHANCE-NOW self ui Flood chances read as exact when they are lower bounds. When the shortest served return period is already wet, the chance is set to 1/RP (chances.ts:145-147), so Greece and Cyprus both read exactly 10 %, 79 %, 96 % Read "at least 10 %" and "at least" on the 15 and 30-year figures A chances.test.ts case with a wet RP10 returns a lower bound, and both pins read "at least 10 %"
LK-8 ENHANCE-NOW owner ui The place report is narrow and dense. The panel is capped at 560 px, while the prototype's right column is about 44 % of the width. Technical blocks (EAI grade paragraph, exact-value tables, scoring scheme, dataset picker) sit open, where the prototype keeps them in one collapsed "Why this score?". The context header repeats the headline. The headline lacks "Worst of n hazards · place" Default panel about 40 % wide, resizable to about 55 %. One collapsed "Why this score?" for the technical blocks. One headline line with the place name At 1440×900 the panel opens at about 40 % width, resizes to about 55 %, and shows one headline and one collapsed details block
LK-9 ENHANCE-NEXT self both Earthquake reads "n/a" at 2030 to 2080. "Fire & heat: no score" in Greece where heat may exist only as SSP data [UNVERIFIED] Label hazards that do not depend on climate "not climate-driven, same as today". Hazards with projection-only data read "projection only, pick a scenario" Athens earthquake ruler reads "same as today" at 2030
LK-10 ENHANCE-NEXT self ui No "What you can do" with measures linking to Adapt, as in the prototype Hazard-matched measures from the seeded catalogue, each with "Model the benefit" opening Adapt pre-scoped on the place (needs AD-1) On a flood pin, "Model the benefit" opens Adapt with the place as scope
LK-11 BACKLOG self ui The coordinate inputs stay empty after an address pick (CoordinateEntry.tsx:37-38) Reflect the pin in the inputs Picking an address fills both inputs
LK-12 BACKLOG self ui Prototype elements left out on purpose: 3D buildings, street photo and Edit home (Phase 18, D18), the illustrative damage line (ADR-077), critical infrastructure (Phase 18), the layout A/B toggle (a comparison device). Quick-place chips were never promised and are cheap with real admin-unit picks Quick-place chips from the org's covered admin units only Chips list real places and set the pin
LK-13 ENHANCE-NOW owner ui The owner cannot tell what the score is made of: which hazard, which dataset, which return period, which exposure. The answer is in phase-17b-run-model.md § What Lookup computes: the score is hazard-only, the intensity at the design return period (100 years, 476 for earthquake) banded through the versioned score scheme, no exposure or value enters, and the chances are derived in the UI from the dataset's return periods. The card already serialises every input (lookup_service.py:629-671: intensity.band_return_period, band_value, by_return_period, slice, provenance.{dataset, scheme, surface}) and the UI shows them scattered across open technical blocks (LK-8) Now (2026-10-05, FT-16, K49): Inside LK-8's one collapsed "Why this score?", a "How this score is made" block that reads, in order: hazard and dataset (name, provider, licence), scenario and year answered, the design return period, the intensity read at the pin with its unit, the scheme (id, version, citation) and the band it falls in, and one line "No exposure or value enters this score. For a portfolio’s loss, open it in Explore." The pin's place card carries the same line (LK-14). The owner validated the hazard-only flow on 2026-10-02 (K15). Before 2026-10-05 (superseded): the line read "No exposure or value enters this score. For a book's loss, open it in Explore." On /lookup?lat=40.6&lon=22.8, "Why this score?" opens to a block naming the river-flood dataset, "Design return period." with "1-in-100 years", the depth in metres, the scheme version and the band, and the no-exposure line, and no technical block sits open by default

Portfolios and Reports (Moment 2)

PF-1 to PF-5 are in the cross-cutting table. Once PF-1 is fixed, Moment 2 steps 1 to 9 can be walked: book creation and CSV upload with template and mapping assist work from the book's detail page, the rollup and scenario toggle work, the loss runs through "Analyse in Explore", and the replay books' event loss reads in USD.

Id Sev Source Repo Finding Change needed Acceptance
RP-1 ENHANCE-NOW self both ORSA is marked available (core/reports/composer.py:123-125) while all ten of its checklist items are to_verify paraphrases (checklist.py:494-514), against guardrail 7; the Studio's template card shows no to-verify state (domain model § 6) TEMPLATE_INFO["orsa"].availability = "wording_to_verify" with a test that a template whose items are all to_verify cannot be available; the Studio's template card shows the "Wording to verify" badge whenever availability == "wording_to_verify" (rendered reports already print the banner and the "(to verify)" suffix) GET /v1/reports/templates lists ORSA as wording_to_verify; the card shows the badge; the test is red when ORSA is flipped back to available
PF-6 ENHANCE-NEXT self both Reports. The assistant draft promised in the frame got no issue. A new document sends no scope, so its scope reads "all" (ReportsStudio.tsx:606). There is no portfolio-rollup widget, and Add to report exists only in Explore's insight panel A Report action on portfolio cards and detail that opens a portfolio report scoped to the book. A rollup widget. A commentary widget with "Draft" when the assistant is enabled "Report" on a portfolio card opens a portfolio report scoped to that book, and its commentary widget offers "Draft"
PF-7 ENHANCE-NEXT self ui Prototype gaps on the list: map thumbnail, geocoding-quality meter, band distribution bar, Adapt and Report actions, an "Add a portfolio" card with three ways in, filters by kind, a hazard and scenario context chip, sort by risk or value, Template and Upload on the list. On detail: clusters and areas map modes, a hazard-layer toggle, band donut, geographic spread, 1-in-200 tile, EAI per hazard, top-3 hazard score columns, bulk select, type filter. On upload: currency select (PF-2), owner, geocode-quality pills. The ruler scenario is fixed to SSP2-4.5 (PortfoliosPage.tsx:18) Build the list and detail elements that real fields can feed, each with a parity screenshot. Elements needing new fields carry their backbone item: the band distribution bar and donut read PF-9's fields Parity screenshots of list and detail at 1440×900 beside the prototype, differences listed
PF-8 CRITICAL self both The portfolio list's total_ead adds a dataset's whole EAD once per member row (db/repositories/portfolio_repository.py:580, pinned by tests/test_api/test_portfolios.py:461-502: EAD 1 000 over a two-row dataset reports 2 000) and takes the latest result whatever its hazard (:110-128), so the card's EAD is wrong by the row count and names whichever peril ran last (domain model § 3) Now (2026-10-05, FT-16, FT-26, K49): EAD per (portfolio, haz_type): the latest impact result whose exposure selector is this portfolio, for that hazard, scenario and horizon (AND hd.haz_type = :haz_type in the lateral). Only a dataset attached with selection all may use a dataset-level result, weighted once per dataset (GROUP BY dataset). A bbox or rows portfolio with no portfolio-scoped result reads "not computed for this portfolio", and Home offers "Run it" beside it (SH-22). The list row carries ead_by_hazard; the card shows one hazard's EAD with its label, never a cross-hazard sum. The test asserts 1 000 for the two-row case. Showcase Moment 2 step 6 reads "(exposure count, EAD per hazard)" in the same PR. Before 2026-10-05 (superseded): A bbox or rows book with no book-scoped result reads "not computed for this book" test_portfolios.py two-row case asserts 1 000; a book with a river-flood run and an earthquake run shows two labelled EADs and no total; prod walk: the LitPop Greece book's card EAD equals its latest river-flood result's ead
PF-9 ENHANCE-NOW self both The rollup's headline score is a value-weighted mean of ordinal bands (core/rollup_service.py:1317-1343), shown unlabelled. Portfolio grades are a market norm and Moment 2 step 3 is live, so the figure stays, labelled, and the sound companion figures are missing: no band distribution of exposed value (the prototype draws one, portfolios.js:341) and no "share of value in high or severe" Now (2026-10-05, FT-16, K49): Backbone: value_weighted.band_distribution = [{band, exposed_value, share}] over the ten bands and value_weighted.share_high_or_severe, over the same banded members and weights; combined_score keeps its value with the label "value-weighted average band (ordinal, screening)"; the per-hazard mean-intensity band is labelled "typical intensity across the portfolio" with the worst band beside it. UI: card headline = worst band, then "X % of value in high or severe", the distribution bar; synopsis shows the labelled average and the donut (PF-7). Showcase Moment 2 step 3 reads "headline band, per-hazard chips, band distribution of value with the share in high or severe, value-weighted average band (screening)" in the same PR. Before 2026-10-05 (superseded): the per-hazard mean-intensity band is labelled "typical intensity across the book" GET /v1/risk/portfolios/{id} returns the distribution summing to the exposed value and the share; the card shows "X % of value in high or severe" and the labelled average; a vitest asserts the label string
PF-10 ENHANCE-NOW self both exposed_value sums members of different units and the rollup weights by persons or m3 (rollup_service.py:1338, 1924-1934); the 100-member cap makes the card's exposed value the scored subset while the list covers all members (config.py:826); indicator perils have no run-written cells so they never band in the rollup Now (2026-10-05, FT-16, K49): One unit or none: exposed_value and the value-weighted figures are returned only when every member shares one unit (money or not); a single non-money unit prints as text through PF-1's formatter ("16.8M m3"); mixed units → exposed_value = null, value_weighted.unit = null, reason "mixed units", band figures weighted by member count, the card shows the member count and "mixed units". The card reads "scored on the N largest members" whenever scored < members, and "n hazards not scored for portfolios" from coverage. Before 2026-10-05 (superseded): the last line read "n hazards not scored for books" Now (2026-10-05, FT-16, K49): A portfolio of one USD dataset and one m3 dataset shows run model § 11's "Mixed units: members, no single exposed value." and no exposed value; a 150-member book shows "scored on the 100 largest members". A Greek portfolio shows "3 hazards not scored for portfolios". Before 2026-10-05 (superseded): a Greek book shows "3 hazards not scored for books"
PF-11 ENHANCE-NOW self be The rollup picks the wildfire brightness-temperature (K) scheme by the metric-name tie-break (_active_schemes in core/rollup_service.py) while prod wildfire data is FWI. Heatwave's two ladders are chosen the same way Corrected 2026-10-09 (#1581), verification only, no change to scheme selection. The unit gate sits at write: every surface writer files a cell metric only through rollup_metric_key_for_run (core/scoring.py), which returns no key when the dataset's intensity_unit differs from the selected scheme's unit. FWI wildfire and heatwave datasets are indicator datasets that write no cells (ADR-075), so no FWI value is banded on the K ladder and a portfolio over FWI data reads wildfire unavailable. Per-unit rows ("wildfire (FWI)", "wildfire (K)") are not built while no FWI cell can exist. Reverse when an FWI or heatwave damage curve makes those datasets computable tests/test_core/test_rollup_scheme_gate.py: through the real writer path, a Kelvin-unit wildfire surface bands a portfolio under wildfire/brightness_temperature_k@v1, and an FWI-unit surface files no wildfire metric, so the rollup reads wildfire available: false with no band

Explore, Analyse and Compare (Moment 3 and the demo slice)

Id Sev Source Repo Finding Change needed Acceptance
EX-1 CRITICAL owner both Any future cell (SSP × year) answers 422 E_VALIDATION, so one scenario never runs. The UI posts haz_type, scenario and horizon_year with no region and refuses to send a hazard_dataset_id for a future cell (analysis.ts:194, api/compute.ts:44-66). The backbone resolves the scenario dataset within (haz_type, region), and region=None matches only rows whose region is null (core/compute_service.py:244-294, hazard_repository.py:914). Every river-flood SSP dataset is ingested with region "Greece" (scripts/ingest_scenario_hazards.py:424). The scope is never sent to compute. Return period is not in the body, so D8 holds. [UNVERIFIED] that every prod SSP row has a region Explore addresses a future cell inside a hazard scope: send the scope's dataset as hazard_dataset_id with scenario and horizon_year (the backbone supports it) and drop the client rule, or send region from the scope or the exposure's country Prod, as owner: psql -U "$POSTGRES_USER" -c "select scenario, supported_years, region, provider from hazard_datasets where haz_type='river_flood' and scenario is not null" shows a region on every row. Then river flood · SSP5-8.5 · 2050 · LitPop Greece · Compute gives 202 and figures render. A future-cell walk runs in tests/e2e/smoke/compute.spec.ts
EX-3 CRITICAL owner ui No multi-select batch, so Moment 3 step 1 regressed. Scenario and horizon are single-select (AnalysisDock.tsx:275-324). The only batch is a secondary "Compute every cell" with no choice of cells, scoped to the newest dataset of the peril regardless of scope or exposure (hazardAvailability.ts:145-154). ui#517 replaced the old multi-select Now (2026-10-05, FT-5, K51): Scenario and horizon are multi-select in a scenario by year matrix, a "Today" row and one row per scenario with its years, in which selecting and showing are two controls (registry explore-32). Each cell has a tick, in or out of this run, and a body. The tick only toggles the selection. Clicking the body of a computed cell shows it on the map and never changes the selection. Clicking the body of a cell that is not computed adds it to the run. A key line under the matrix reads "Tick: in this run" and "Outline: shown on the map", with the marks for computed, not computed and not available. One primary "Run N cells" (EX-16) sends one POST /v1/compute/impact/matrix and shows a per-cell progress grid, and a completed tile of the grid is a button that shows its cell. The grid's scope comes from the scope and exposure (as EX-1). The run in flight is EX-20. Before 2026-10-05 (superseded): Scenario and horizon chips become multi-select. One primary "Compute N cells" sends one POST /v1/compute/impact/matrix and shows a per-cell progress grid Now (2026-10-05, FT-5, K51): Tick SSP2-4.5 and SSP5-8.5 × 2030 and 2050 and press "Run 4 cells": one matrix POST, four tiles settle completed, and /explore?batch= reloads them. Clicking the body of a completed cell changes the map and the strip to that cell and leaves the four ticks as they were. Clicking the body of a cell that is not computed ticks it. Clicking a completed progress tile shows its cell. Before 2026-10-05 (superseded): Select SSP2-4.5 and SSP5-8.5 × 2030 and 2050, press Compute 4 cells: one matrix POST, four tiles settle completed, and /explore?batch= reloads them
EX-4 ENHANCE-NOW owner ui Three Compute buttons with hidden state. All three call the same compute, but the cell exists only after the Analysis tab was opened once (ExplorePage.tsx:376). Before that, two of them only switch tabs. The exposure lives in component state, not the URL (AnalysisDock.tsx:89), against D11 One Compute per state, in the strip or the panel, as the prototype's "Compute it". Exposure in the URL. The cell is built outside the dock Opening /explore?…&exposure=<id> and pressing the strip's Compute dispatches at once, with no tab opened
EX-2 ENHANCE-NOW self ui The 422's reason and candidate list are thrown away (AnalysisDock.tsx:392-400), although parseHazardResolutionFailure exists and Adapt uses it Show the reason and "what this scope holds", in the strip and the panel as well as the dock An unresolvable cell shows the reason sentence and at least one candidate
EX-5 ENHANCE-NOW owner ui Explore opens on all of Europe and North Africa with "No scope yet" (MapKit.tsx:60-61). The prototype opens framed on the covered countries Default scope = the org's covered countries from coverage, fitted. The scope restricts hazard resolution and exposure A bare /explore frames Greece and Cyprus. A Cyprus scope with a Greek exposure warns
EX-6 ENHANCE-NOW owner ui The horizon player is the live-event player squeezed into w-80 (ExplorePage.tsx:920-944). Text is clipped, ticks are unlabelled, there is no Today stop, and "Follow the latest horizon" does nothing A horizon player with play, scenario name and labelled Today, 2030, 2050 and 2080 ticks that fits its content At 1366×768 nothing overlaps, and clicking 2080 sets the horizon
EX-7 ENHANCE-NOW owner ui Light OSM basemap with labels in many scripts Covered by SH-2 As SH-2
EX-8 ENHANCE-NOW owner ui A loaded result has no legend. The legend follows hazard layers only and reads "Turn a layer on to see its legend" (ExplorePage.tsx:231-257) While a result tier is drawn, the legend shows its ramp, metric and level A loaded result with no layers on shows the EAI ramp
EX-9 ENHANCE-NEXT owner ui The context chips over the map are static (ExplorePage.tsx:688-700). The prototype's are hazard, scenario, year and engine menus, with "Compare engines side by side" Now (2026-10-05, FT-4, K37): One caption sits over the map in place of the context chips (registry explore-31, with explore-19 superseded). It reads the word "Showing", then the hazard as icon and name in plain text, then scenario and year as one small menu button, then the engine in plain text, then a status word: "not computed yet", "running", "not available", "score only", "one country only", "not covered", "engine not available", "nothing to compute", "hazard only, nothing to assess yet" when no exposure is chosen, or nothing when the viewed cell is computed. Only the scenario and year button opens a menu. The menu lists the computed cells for the current inputs (labels such as "Today" and "SSP2-4.5 · 2050"), changes only which cell the map, the strip and the panel show, never the run's cells, and ends with "Choose the cells of the run", which opens Run. With nothing computed it reads "No computed cell yet". With the dock collapsed the caption carries an "Edit run" button that reopens the dock on Run. The caption is hidden while Scope is open (EX-14). "Compare engines side by side" is reached from the Compare button of the Engine row in Run (EX-16). Before 2026-10-05 (superseded): Chips become menus that run set_analysis Now (2026-10-05, FT-4, K37): Picking a cell from the caption's menu changes the viewed cell on the map, the strip and the panel and leaves cells in the address unchanged. The menu lists computed cells only. No hazard, year or engine control over the map opens a menu. With the dock collapsed, "Edit run" opens Run. Before 2026-10-05 (superseded): Picking 2080 from the chip sets horizon=2080
EX-10 ENHANCE-NEXT owner both Strip and panel are reduced. No total value and no "high or severe" (deferred by ui#520 to a stub that does not list them). No Selection tab, donut or by-hazard bars (ui#519). The Risk score toggle always reads "not available" (InsightPanel.tsx:215). The strip's 1-in-200 ignores rp Now (2026-10-05, FT-9, K52): Backbone result fields for total value and band counts. Hide Risk score until a score is served. The strip follows rp. Add the Selection tab. The strip's third label reads "Expected annual impact". Each of the five cells (Locations, Exposed value, Expected annual impact, 1-in-N loss, High or severe) has a small info button that opens a popover titled "How this is made" with three lines, What, How and Use, and, where the result holds them, the hazard dataset's name, its return periods and the damage curve with its version, from the provenance fields of #1595 (registry explore-36). The copy ships as written. Locations. What: "The exposure rows inside your scope, after the clip." How: "Counted when the run starts." Use: "The size of what you assess. A clip that leaves no rows has nothing to compute." Exposed value. What: "The sum of the values of those locations, in one unit." How: "Each row’s value from the exposure layer or your upload. With growth, values are scaled from the layer’s reference year." Use: "The denominator. Every loss can be read as a share of it." Expected annual impact. What: "The average loss per year over a long time, for this hazard." How: "For each location, the damage share from the damage curve at each event’s intensity, times the value, times the event’s yearly frequency, summed. With return-period maps this is the area under the loss curve." Use: "The basis of a technical premium or a yearly budget. It is not a typical year. Hazards are not summed." 1-in-N loss. What: "The loss exceeded with a 1-in-N chance in any year." How: "Read from the loss curve at N years. With return-period maps every location takes its own 1-in-N intensity at once, so it is an upper bound for a spread-out portfolio. Only this dataset’s return periods are offered." Use: "Sizing capital and reinsurance." High or severe. What: "The share of exposed value in the two worst score bands." How: "Each location’s hazard intensity at the design return period is banded from 1 to 10, and bands 7 to 10 count." Use: "Concentration. It works even when no money loss can be computed." The issue checks the band edges that last text names against the served definition of share_high_or_severe (PF-9) before it ships the sentence Now (2026-10-05, FT-9, K52): Five strip cells from served fields, and no dead Risk score toggle. The third strip label reads "Expected annual impact". Each of the five cells has an info button whose popover is titled "How this is made" and holds the What, How and Use lines above. On a loaded result the popover of Expected annual impact names the hazard dataset, its return periods and the damage curve with its version, each equal to the result's provenance
EX-11 ENHANCE-NEXT self ui Compare cannot show a future cell. A side renders only when already computed, its Compute leaves Compare, and engine compare takes only a Today cell and needs the Analysis tab opened first Now (2026-10-05, FT-21): After EX-1, compute missing sides in place. Engine compare on future cells. Default Today versus SSP5-8.5 · 2080, as the prototype. Compare gains five things (registry explore-40). "Save analysis" in Compare stores a Comparison with its two sides (BC-5) and raises "Saved the comparison. Listed in History." An engine comparison links its group through the declared comparison_id. A comparison of two cells has no declared key for its sides, so the issue names the keys and declares them in ScenarioInputs in the same PR (run model § 6, "Saved work"). It is off with its reason until there is something to keep: "Run the missing side first. A saved comparison holds both sides.", and for engines "Wait until at least two engines have answered. A saved comparison holds their figures." The address carries a, b and engines, so "Copy link" reopens the comparison with both sides. A side that is running says so and cannot be started twice. The dock shows one compact "Comparing" card in place of the run form: the scope line, the hazard and the engine, side A and side B each with its cell picker and its figure or "Run here", the line "Change, B against A", "Swap sides", "Compare engines", the inputs both sides share (damage curve, growth, measures) and "Edit the run in Analyse". With no exposure the card reads "Nothing to compare yet." with the button "Choose what to assess", which opens Scope Now (2026-10-05, FT-21): Today versus SSP5-8.5 · 2050 renders both sides with the change %. /explore?mode=compare with a=baseline@today and b=ssp5-8.5@2050 beside the run keys reloads both sides. While side B computes its button is disabled and a second click sends no second request. "Save analysis" stores one Comparison that History opens with "Open in Compare" (EX-21). With no exposure the dock shows the button to Scope
EX-12 ENHANCE-NEXT owner ui No Adapt mode (the frame promises Analyse · Compare · Events · Adapt, and ExplorePage.tsx:129 says "Adapt joins with its item", which never happened). No Copy link. Dock tabs have no icons Now (2026-10-05, FT-1, K36): A fourth mode, Adapt, over the current scope (AD-1). Copy link and Add to report in the header. The dock's two steps carry their numbers, and the collapsed dock shows the two step icons (EX-16). Before 2026-10-05 (superseded): Icons on the dock tabs The mode bar shows four modes, and Adapt keeps the scope
EX-13 BACKLOG self ui Save analysis and CSV and PDF export are wired but unreachable until a result loads, and the strip's e2e was never run on a stack None beyond EX-1. Re-walk slice step 6 After EX-1, a saved analysis appears in /analyses, and CSV and PDF download
EX-14 CRITICAL owner both Selecting a portfolio does nothing on the map. No layer draws the scope's exposure before a compute (the MapKit children at ExplorePage.tsx:463-487 are ScopeArea, PinDrops, SelectedArea, AssetFocus, MapCentre, ResultTiles and rasters), the "Analyse in Explore" link carries no bbox (PortfolioDetailPage.tsx:139-148) and useScopeExtent.ts:27-30 returns null for a portfolio, so the map stays at the default centre. Result tiles serve assets only for a computed result (core/vector_tiles.py:22-32). The level switch hides until a tier is drawn (ExplorePage.tsx:307-309) and no result or level legend exists (EX-8). The prototype draws the scope's assets at every zoom before and after a compute, aggregated to regions, then hexagons, then clustered points, with a legend and a popup card per level (mapkit.js:12-20,176-240,465-508). The backbone has no portfolio extent and no portfolio tiles (portfolios.py routes :121-558; tiles.py has exposure tiles per dataset only, :393-421, and a portfolio's members are a selection over datasets, so the dataset tile is not the book) Now (2026-10-05, FT-3): Backbone: bbox on GET /v1/portfolios/{id} and on list rows, from the members; GET /v1/tiles/portfolios/{id}/{z}/{x}/{y}.mvt serving bins (hex count, value_sum) below POINTS_MIN_ZOOM and points from it, the exposure-tile shape (D4). UI: generalise src/map/layers/exposures.ts into a MapKit ScopeAssetsLayer that takes a tile URL: an exposure-dataset scope uses the existing /v1/tiles/exposures/{id}, a portfolio scope the new route. It draws admin tiers by value below z 9 (layer=admin on the same tiles, #1673 item 37, K71), the server's hex bins coloured by value below the points zoom and points from it, with no client-side clustering (MapLibre clusters GeoJSON only, never MVT). useScopeExtent returns the bbox from GET /v1/portfolios/{id} for a portfolio and from the dataset's bbox for a dataset, and MapKit fits it on entry. The level switch shows before a compute, its tier taken from the zoom table. Per-tier popup cards through src/actions (D11): admin unit → name, assets, EAI, band, top hazards, "Analyse here"; hex → n assets, value, EAI, "Zoom to assets"; asset → value, EAI, hazard bars, "Make this the scope", "Adapt". After a compute the result tiers draw above the scope layer, with EX-8's legend. 3D extrusion twins (mapkit.js:107-174) are out of scope this phase (see Out of scope). While step 1 Scope is open in Analyse mode the map speaks about exposure only (registry explore-30). It draws the scope's assets and value tiers fitted to the scope, and hides the hazard surface, the result tiers, the caption over the map (EX-9) and the horizon player (EX-6). The legend reads the exposed-value ramp, titled "Exposed value" in the exposure's unit ("Mixed units" when mixed), with the note "Exposure only while Scope is open". Leaving Scope restores what was shown, because the Layers switches keep their values. The Layers popover (EX-16) says "While Scope is open the map shows exposure only. These switches apply again when you leave Scope." Now (2026-10-05, FT-3): Opening /explore?exposure=portfolio:<book> (the legacy scope=portfolio&id= address is rewritten to it, run model § 7) animates the map to the book's extent and shows its value by admin unit, then its hex bins, zooming past z11 shows its points, with no compute; after Compute, the result tiers draw over them with a legend; clicking a hex at z9 opens the card with n assets and value; a vitest on the shared map mock asserts the layer uses a vector source and never cluster: true. With a computed result on screen, opening Scope hides the result tiers, the hazard surface, the caption and the horizon player, and the legend reads "Exposed value" in the exposure's unit. Opening Run brings all four back with the same cell shown. A vitest asserts the Layers switches hold their values across the two steps
EX-15 CRITICAL owner be A run is not defined. POST /v1/compute/impact takes one exposure selector and a hazard cell; country, region and drawn-area scopes never reach compute; measures and value growth exist only on cost-benefit; the damage curve resolves on hazard type alone and ignores the typed exposure_type (compute.py:252-325, impact_function_service.py:175-185, models/exposure.py:48,154). The owner asked what scope, layers, hazards and events mean and in what order a scientist runs a scenario. The answer is phase-17b-run-model.md Now (2026-10-05, FT-29, K50, BC-1): Implement § The backbone contract in one ADR: exposure_dataset_ids (a union with one value_unit, else 422), scope ({"admin_boundary_ids": [UUID, ...]} for units of one level and one country, clipped to their union, or the existing {"admin_boundary_id": UUID} as a one-element alias, or {"bbox": [...]}, applied inside compute_order_sql so scope_total counts the clipped members, persisted in result.metadata.scope and read by every join consumer: /per-exposure, result asset tiles, the surface writer), exposure_growth_rate (applied to value, deductible and cover before the engine as (1+g)^(horizon_year − reference_year), the year from properties.reference_year, else temporal_coverage_end, else the run year, recorded in provenance.exposure_growth, K18), measure_ids (AD-1's contract and AD-5's hazard-mismatch 422, cited not re-specified), the same fields plus cell addressing on /matrix and /compare (EX-11), the cache key extended only when set, cost-benefit accepting only a Today base with no measures and no growth and inheriting its scope (K20), vulnerability resolved on (haz_type, exposure_type) with the non-money 422 that resolves PF-4, the frequency curve served only inside the dataset's [RP_min, RP_max], and the Python SDK's request model updated. In the same PR: docs/CLIMATE_LAMA.md § Impact Calculation Flow (:3045), § Scenario x horizon on impact compute (:3105), § Exposure selector on impact compute (:3309) and the compute endpoint table gain the new request fields, refusal texts, worker order, cache key and provenance; the run-contract ADR in docs/DECISIONS.md states that it supersedes in part ADR-019's unimplemented "Job submission body gains measure_ids" (DECISIONS.md:657) and extends #1008 and #1185. The list form of scope brings three refusals, each a 422 E_VALIDATION with the reason text run model § 6 gives it: boundaries of mixed levels, boundaries of mixed countries, an unknown id. The clipped set, scope_total, the persisted result.metadata.scope, the cache key and the provenance all take the list, so the alias is read as a one-element list before any of them is written. The same scope holds on /matrix and /compare, in cost-benefit's inherited scope and in the SDK's request model. The address is clip=admin:<id>,<id> (EX-23). Before 2026-10-05 (superseded): scope was {admin_boundary_id} or {bbox}, one unit only Now (2026-10-05, BC-1): The contract tests in § Acceptance pass: a clipped run returns scope_total equal to the members inside the clip and its /per-exposure rows all lie inside it; a 2050 run with exposure_growth_rate=0.02 on a dataset with properties.reference_year=2018 has EAI 1.02^32 times the ungrown run's on a stationary hazard; cost-benefit over that grown result answers 422; a persons book answers 422 naming the exposure type; /compare accepts the same body as /impact with a (scenario, horizon_year) cell. A run whose scope.admin_boundary_ids names two regions of one country returns scope_total equal to the members inside either region, and its /per-exposure rows all lie inside their union. Ids of two levels answer 422, ids of two countries answer 422, and an unknown id answers 422. The one-element alias and a one-element list return the same scope_total, the same persisted result.metadata.scope and the same cache key
EX-17 CRITICAL self be The result cache serves wrong figures today. The key omits deductible and cover (api/v1/compute.py:293-294, 653-677; core/result_cache_service.py:79-94, 166-188), so an insured run and a ground-up run over the same inputs return each other's result as a 200 cache hit. A portfolio-addressed run is keyed on portfolio_id alone (result_cache_service.py:180-181) while membership is mutable (api/v1/portfolios.py:252-345) and the only invalidation purges orphans (worker/tasks.py:3824-3826), so attaching, removing or re-ingesting a member keeps serving the old book's loss (domain model § 1.4) Append deductible and cover (stringified, only when not null, the engine_name pattern) and a membership digest = SHA-256 of the sorted (exposure_dataset_id, dataset_sha256, selection kind, canonical selection JSON, weight) tuples resolved at request time, for portfolio_id (this W0 issue) and, when EX-15 adds the field, for exposure_dataset_ids (EX-15 cites this rule). Existing keys stay byte-identical when none of these is present Two keys from the same request with deductible None and 1 000 differ and the None key equals today's; attaching a second dataset to a book changes the key for an otherwise identical request; re-ingesting a member dataset changes it
EX-16 CRITICAL owner ui The Scope, Layers and Analysis tabs do not read as a flow. Scope mixes geography with the portfolio, the exposure picker sits in the Analysis tab as component state (AnalysisDock.tsx:89,212-240), Layers lists every dataset switched off (useHazardLayers.ts:20-26), nothing in the form speaks of measures, growth or the damage curve, and the Events tab competes with the mode bar (EV-6). The owner chose to keep the three prototype tabs with their content redefined (K13) Now (2026-10-05, FT-1, FT-6, FT-7, FT-8, K36, K38, K39, K40, K47, K48): Implement § The Explore dock as amended on 2026-10-05. The exposure-first order of K13 and the field order of K23 stand. The dock is two numbered steps, 1 Scope then 2 Run, with a chevron between them, a tick in place of the 1 once an exposure is chosen and Run is in front, and the two step icons when the dock is collapsed (registry explore-28, with explore-1 superseded). On phones the sheet shows the same two steps (D12). Scope is EX-18 (two doors, A place and My assets), EX-19 (several countries) and EX-23 (narrowing). Layers is a button in the map's control group, outside the dock. It opens a popover titled "Layers" with the line "Map display only. Nothing here changes the run." The popover holds the hazard surface switch (the hazard chosen in Run), the return period select (the dataset's own, the design one marked), surface opacity, and the switches "Results", "The scope’s assets" and "Historical event footprints", then "Add a layer from Data". The palette stays in the map's own palette menu and is not repeated. The snapshot's scale test is a demo and is not built. Run shows the resolved scope line with a pencil back to Scope, then seven numbered sections. 1 "Hazard": one row with icon, name, status pill and chevron that opens a list grouped "Computable", "Score only" and "Not available here", with the reason beside each hazard that cannot be picked (explore-33). The picker reads each hazard's state and its reason for the run's exposure from the read route of EX-19. It never derives them from coverage counts, which cannot tell one dataset that covers two countries from one dataset per country, so ui#747 is rewritten to read that route. 2 "Scenario × horizon": the matrix of EX-3, in which the future cells of a non-climate hazard read "same as today". 3 "Damage curve": a visible select whose first option is the resolved default, "Default · ", followed by the other curves for this hazard and exposure type, and under it a small preview of damage share against intensity (the default dashed when another curve is chosen), the source and version, the line "Resolved on " and a "not the default" pill when another curve is chosen (explore-35, with explore-7 superseded). The select is the only curve control. A non-money exposure keeps the score-only refusal and shows no select. 4 "Exposure growth": a number input in % per year, default 0, with the hint "Uniform growth from (), existing locations only. A Today cell is never grown." 5 "Adaptation measures": the chosen measures as chips with the before and after sketch and a remove button, or "No measures in this run", then the buttons "Add measures" and "Use a saved plan" (explore-34, with explore-9 superseded). Add measures opens a dialog over the map titled "Add measures to the run": a search box, filter chips by measure kind, rows with a checkbox, the name, the effect line, the sketch and the cost line, only the measures of the run's hazard, a footer saying how many measures for other hazards are not offered, and a button that counts the change ("Add n measures"). Use a saved plan lists the saved plans of this hazard and loads the plan's measures. Its box is on the AD-8 issue, with "No saved plan for yet" and "Build a plan in Adapt" until a plan exists. The order note and the line "Money appraisal (avoided loss, BCR) is in Adapt." stay. No pinned shortlist is built (K40). 6 "Engine", with a "Compare" button. 7 one "Run N cells" with the progress grid, and EX-20 for the run in flight. With nothing chosen Run reads "Start with what you want to assess." with "Open Scope". Events never appears in the form. Exposure, clip, hazard, cells, impf, growth, measures and engine live in the URL (run model § 7 keys, with clip taking a list, EX-23, and batch joining the address, EX-20) and in the set_scope, set_analysis and compute_cell arg schemas. The manifest and the URL bridge round-trip them (D11), with a registry vitest dispatching each. Before 2026-10-05 (superseded): Implement § The Explore dock: Scope = Exposure first (one portfolio, or one or more catalogue exposure datasets of one unit with country pills from coverage, or saved pins, or one asset), then Where as an optional clip (country, region, municipality, drawn area). Layers = map display only: the selected hazard's surface on by default at the design return period, results, event footprints, the scope's assets, palette. Analysis = Hazard (gated by coverage for the exposure's countries) → Scenario × horizon multi-select (EX-3) → Vulnerability (resolved curve, read-only, Advanced override) → Future exposure growth (default 0, hint "uniform growth from , existing locations only") → Adaptation measures (library filtered by hazard, each shown as before/after of the curve and hazard) → Engine and Compare engines → one "Run N cells" with the progress grid (order fixed by K23). Non-climate hazards show "same as today" for future cells. Exposure, clip, hazard, cells, impf, growth, measures and engine live in the URL (run model § 7 keys) and in the set_scope, set_analysis and compute_cell arg schemas, the manifest and the URL bridge round-trip them (D11), with a registry vitest dispatching each. Events never appears in the form Now (2026-10-05, FT-1, FT-2, FT-6, FT-7, FT-8, K36, K38, K39, K40, K47, K53): A fresh /explore reads exactly two numbered steps, 1 Scope and 2 Run, and Scope opens on "What do you want to assess?" with the A place door (EX-18). The Layers button on the map opens the popover, and no control in it changes a run key of the address. The hazard row opens the grouped list. The damage curve is a select, picking another curve writes impf= and shows the "not the default" pill, and the form holds no other curve control. With no measure chosen the form reads "No measures in this run", and "Add measures" opens the dialog with the measures of the run's hazard only. Picking LitPop Greece narrowed to one region, river flood, SSP2-4.5 × 2050 and Today, growth 2 % and one measure, then pressing "Run 2 cells", sends one /matrix POST whose body holds exposure_dataset_ids, scope, exposure_growth_rate, measure_ids, haz_type and cells with two entries, and no hazard_dataset_id. An address that holds exposure, clip, hazard, cells, growth=0.02 and measures restores the form, and a legacy address with scope, id, sc and horizon is rewritten to those keys. The earthquake 2050 cell reads "hazard same as today" (coverage climate_driven: false). The design box of guardrail 1 holds for the dock: the wave gate's captures show no design difference from the prototype captures of explore-28, explore-33, explore-34 and explore-35. Before 2026-10-05 (superseded): A fresh /explore reads the three tabs in order and the Scope tab opens on "What is at risk"; picking LitPop Greece, river flood, SSP2-4.5 × 2050, growth 2 %, one measure and pressing Run sends one /matrix POST whose body holds exposure_dataset_ids, scope, exposure_growth_rate, measure_ids, haz_type and cells with two entries (no hazard_dataset_id); /explore?exposure=…&clip=…&hazard=…&cells=…&growth=0.02&measures=… restores the form and a legacy scope=…&id=…&sc=…&horizon=… address is rewritten to those keys; the earthquake 2050 cell reads "hazard same as today" (coverage climate_driven: false); parity screenshot of the dock beside the prototype with the redefined labels listed as differences

Explore, Events (Moment 4 steps 1 to 8)

Id Sev Source Repo Finding Change needed Acceptance
EV-1 CRITICAL owner both A near-real-time watch "does nothing". Compute is disabled with no reason when no snapshot exists (EventsMode.tsx:534), the copy speaks of "this snapshot" when there is none, and Poll now only toasts "Poll queued". The USGS watch polls fine: its bounding box has had no earthquake at or above M4.5 since 2026-09-01 (public USGS query, 0 results). [UNVERIFIED] the FIRMS Evia watch, settled by EV-9 A "waiting for the first observation" state: what the watch looks for, last poll, events found in the window, next poll. No Compute until a snapshot exists. Poll now reports its outcome. List cards read "Polling · nothing qualifying yet". Backbone keeps a per-poll summary on the feed and returns it Open the USGS watch aafa30f7: last and next poll and "0 events ≥ M4.5 in window", no Compute. Poll now reports within one tick. GET /v1/live-events/{id} returns feed.last_poll_summary
EV-2 CRITICAL owner ui Replay blinks. Each step removes and re-adds the raster source or reloads its tiles (FootprintLayer.tsx:132-145), so the old frame disappears before the new one loads. Every frame has its own colour stretch. Playback is a fixed 1 s per frame and does not wait for tiles. The right panel blanks to "Not yet computed" on every step. The retired Live map kept a window of mounted rasters switched by opacity, so this is a regression from #657 Preload snapshots as raster layers kept at opacity 0, cross-fade over about 600 ms, advance only when the next frame has loaded, one stretch for the whole event, speeds 0.5×, 1×, 2× and 4×, figures kept on screen and tweened while new ones load, and the observed-at of the frame on screen Unit test on the shared map mock: stepping frames 0 to 5 calls removeSource and setTiles zero times and changes only raster-opacity. Walk: the North Evia replay at 1× never shows a blank map or "Not yet computed" for a computed snapshot
EV-3 CRITICAL self ui The Events map lost the book's assets coloured by loss, walked live on 2026-09-17 (Moment 4 step 2). Re-hosting kept the footprints only (EventsMode.tsx:594-597) Draw the result's assets as loss-graded points with the shared popup. A row in the Assets drawer highlights its point North Evia on 6 Aug shows loss-coloured points inside the footprint, as many as "Assets (N)"
EV-4 ENHANCE-NOW owner both Selecting a watch does not focus the map. The camera fits only to the watch AOI and jumps without animation (MapKit.tsx:300). The AOI column was added in migration 0092 with no backfill, so the Phase 13 replays likely have none [UNVERIFIED] Fall back to the union of the snapshots' dataset bounding boxes, then to the book's extent. Animate the fit over about 800 ms. Backfill or derive the AOI for older watches Selecting North Evia from Analyse animates the map to Evia, and every watch returns a non-null aoi
EV-5 ENHANCE-NOW owner ui The player is too basic for precise navigation. Ticks are unlabelled dots spaced by index (TimelinePlayer.tsx:160-175), with no speed, loop, hover preview, jump to time or keyboard Ticks spaced by time and labelled at day boundaries. Hover shows observed-at, loss and computed state. A loss sparkline under the track. Step ±1, jump to date, speed, loop, keyboard A vitest with irregular valid_at places ticks in proportion, and hover shows the loss. The walk jumps straight to 6 Aug 2021
EV-6 ENHANCE-NOW owner ui The left "Events" tab and the top "Events" mode disagree. Opening the tab keeps Analyse mode, its chips and "Run or open an analysis", and the analysis header shows in every mode Now (2026-10-05, FT-1, K36): Events is entered from the mode bar only. The dock holds two steps, Scope and Run, and no entry for Events (registry events-1, explore-28). The analysis header hides in Events. Before 2026-10-05 (superseded): The Events tab enters Events mode, and leaving it returns to Scope (or one of the two entries goes) Now (2026-10-05, FT-1, K36): Clicking Events in the mode bar from Analyse sets mode=events and the right panel reads "Pick a watch". The dock shows no entry for Events in any mode. Before 2026-10-05 (superseded): Clicking the Events tab from Analyse sets mode=events
EV-7 ENHANCE-NEXT owner+self both Watch list hygiene. The two Phase 13 replays are still open, North Evia appears twice, open and closed watches share one list, replays read "Observed 5 years ago", and there are no hazard icons or filters Group as Live, Replays and Closed with hazard icons and filters, and show replays by event dates. Owner data call: close the Phase 13 replays on prod The list shows its groups and no duplicate open Evia watch
EV-8 ENHANCE-NEXT self both The impact panel is the old Live panel, re-hosted unchanged by #526. The prototype shows four tiles (assets in footprint, value exposed, estimated loss, change since last update) and "most exposed municipalities". A lone "‹" shows where the inspector should be [UNVERIFIED] cause. 2026-10-02: no per-admin-unit aggregation of an event result exists, so the chart needs a backbone half Backbone: per-admin-unit sums of the event result through sum_points_by_admin_unit, cached on the result and served on GET /v1/live-events/{id}/results/{job}. UI: a tile row and the admin-unit top-movers chart from those fields The panel shows four tiles and the municipalities chart for North Evia, whose bars sum to the event loss
EV-9 BACKLOG self be Phase 15 tracker #1300 is still open. The 24 h poll-health read was never pasted, so Moment 4 step 8 stays target Run the psql and log-count command written in #1300, paste it, close it #1300 closed with about 288 firms_nrt.polled per 24 h
EV-10 CRITICAL self both A FIRMS snapshot holds only each overpass's new detections (ingest/feeds/firms_nrt.py:23-34), so "latest event loss" on the rail, the Home card and the headline is the loss of one overpass, not the fire to date (domain model § 5.2) Backbone: each poll also writes a cumulative snapshot, the cell-wise maximum brightness temperature over the watch's observations to date (the quantity Lüthi 2021's curve expects, monotone non-decreasing), flagged cumulative = true; latest_event_loss and the headline result are read from the newest cumulative snapshot; per-overpass snapshots remain the replay frames. UI: none beyond EV-14's basis line, the fields keep their names On the North Evia replay the headline loss never falls between frames and equals the loss of the cumulative snapshot; GET /v1/live-events/{id} returns latest_event_loss from a cumulative snapshot
EV-11 ENHANCE-NOW self both A ShakeMap revision shares the origin-time valid_at and is deduplicated on (watch, instant), so revisions are never scored (ingest/feeds/usgs_shakemap.py:721-726, db/repositories/live_event_repository.py:567-611); one USGS watch interleaves different earthquakes on one timeline and the delta compares two events (DeltaPanel.tsx:39-42) Backbone: snapshots carry event_key (the USGS event id, the FIRMS watch id, the replay manifest's event); GET /v1/live-events/{id}/snapshots?event_key= filters, the watch payload gains events: [{event_key, first_valid_at, last_valid_at, snapshot_count}]; ShakeMap dedupes on (watch, event_key, version). UI (Deps: the backbone half): timeline, delta and headline operate within one event key, the rail lists a watch's events A watch that receives v12 and v13 of one ShakeMap scores both and the delta compares them; two earthquakes in one watch list as two events
EV-12 ENHANCE-NOW self both Compute sends only portfolio_id, so an exposure-dataset watch computes against every org exposure (EventsMode.tsx:291-296, api/v1/live_events.py:446-449); both selectors are ON DELETE SET NULL (models/live_event.py:44-49, 121-125), so a deleted book silently widens an open watch to the whole org Compute always carries the watch's own selector; deleting a portfolio or exposure dataset referenced by an open watch is refused with 409 naming the watch; the auto-compute path refuses a watch with no selector Computing an exposure-dataset watch posts exposure_dataset_id; DELETE /v1/portfolios/{id} with an open watch is 409
EV-13 ENHANCE-NOW self both Create watch offers all eight hazards with auto_compute on by default (CreateWatchDialog.tsx:219, 416), so heatwave and drought watches are created whose snapshots are never scored (core/live_feeds.py:1610-1621) The dialog offers a money loss only for hazards whose default curve consumes the selector's exposure type (coverage computability); otherwise the watch is "exposure count only" with auto_compute off and the dialog says so; drought is not offered Creating a heatwave watch shows "exposure count only, no loss curve" and sends auto_compute: false; drought is absent from the list
EV-14 ENHANCE-NOW self both The event loss ignores the deductible and cover the same book carries in its annual run (compute_event_impact reads neither, worker/tasks.py:1514-1543), and no UI copy says the figure is not annual (only the server's grade note, EventLossHeadline.tsx:108-113) Backbone: deductible and cover on POST and PATCH /v1/live-events, passed to compute_event_impact, and insured_event_loss stored beside event_loss through the engine's per-point insurance transform (one event, weight 1, no engine change). UI: every surface labels the figure "ground-up loss of one observed footprint, not annual" (run model § 11), and CreateWatchDialog gains optional deductible and cover inputs Now (2026-10-05, FT-11, K43): The headline, the Home card and the History row show the basis line, and a saved replay's card shows the same line (EV-19). A watch with a deductible returns event_loss and insured_event_loss. Before 2026-10-05 (superseded): The headline, Home card and Analyses row show the basis line
EV-16 ENHANCE-NOW self both latest_event_loss carries no unit (schemas/live_event.py:258-260; WatchCard.tsx:78-80, LiveNowCard.tsx:144-147), and the footprint raster draws with no legend or unit although each snapshot carries intensity_unit (EventsMode.tsx:594-597) Backbone: latest_event_loss_unit on the watch summary. UI (Deps: the backbone half): every figure goes through PF-1's formatter; the footprint layer gets a legend with the snapshot's intensity_unit (K, g) The rail shows "€2.1M" or "16.8M m3", never a bare number; the Evia footprint shows a legend in K
EV-17 ENHANCE-NEXT self be The post-event report is not offered on close, has no footprint map, and its checklist says "Estimated loss" where the template says "Event loss" (core/reports/checklist.py:399-419) Closing a watch offers "Post-event report"; the template carries the footprint map and the cumulative loss (EV-10); the checklist item reads "Event loss" Closing the Evia watch offers the report; its PDF shows the footprint; the checklist line reads "Event loss"
EV-18 BACKLOG self ui The assets drawer counts one API page (EventsMode.tsx:247, AssetsDrawer.tsx:89), the timeline loads the newest 100 snapshots while a feed holds 500 (EventsMode.tsx:180), the Compute button shows on a closed watch and fails 422 (:529-539), and the Version field reads the empty watch provenance instead of the snapshot's source (EventHeader.tsx:21-24) Drawer shows total_assets; the timeline loads every snapshot of the event; Compute hides on a closed watch; Version reads the snapshot's source A 250-asset result reads "Assets (250)"; a closed watch shows no Compute; the Evia header reads the ShakeMap version

Adapt (Moment 4 step 9)

The adaptation model today: Appraise posts POST /v1/compute/cost-benefit against one saved result. The worker applies every measure to the whole hazard and every exposure, ignores the measure's region, overwrites its hazard type, and returns portfolio totals per measure. No impact compute accepts measures, so there is no adapted run, adapted map, adapted portfolio, area or event. /adapt is reached only from the nav rail.

2026-10-02. The owner asked how a measure is applied, against CLIMADA's entity model where measures ride inside the scenario run. The answer is AD-1 as written, and the run model makes it explicit: measures are a run input (measure_ids on /impact, /matrix and /compare, applied as Measure.apply(hazard, impfset) before ImpactCalc), and Adapt stays the appraisal layer on top. AD-1's backbone contract, AD-5's hazard-mismatch 422 and the cache-key change now land in W2 inside EX-15, in one ADR with the scope and growth fields (phase-17b-run-model.md § How Adapt applies). W4 keeps AD-1's UI half.

Id Sev Source Repo Finding Change needed Acceptance
AD-1 CRITICAL owner both Nobody can tell how or where a measure applies, and the frame's "apply to asset, portfolio and area" and Explore's Adapt mode never got an item. The prototype enters Adapt from Explore's mode tab and "Model a measure", from portfolio cards, detail and asset rows, from Lookup's "Model the benefit" and from Home, each carrying its scope Now (2026-10-05, FT-7, FT-14, K40): Adapt runs on a scope, not on a dropdown of saved results. Backbone contract: POST /v1/compute/impact accepts measure_ids and adapted_from_result_id. The worker applies the measures before the impact and stores a normal result with per-location EAI and tiles. Cost-benefit stays the money layer on top. UI: Explore mode 4 "Adapt" over scope, scenario and horizon, and Adapt actions on portfolio, asset and Lookup that open it pre-scoped, while a saved analysis reaches it through Open in Explore and the Adapt mode (#1673 item 40), with scope, scenario and horizon chips in its head. In Explore's run form measures are picked, never listed whole: chips for the chosen ones, "Add measures" (a searchable dialog over the map with the measures of the run's hazard only) and "Use a saved plan" (EX-16, AD-8). Adapt's library stays the place where measures are browsed and edited. No pin control and no pinned shortlist is built (registry adapt-19, Out of scope). Adapt opened with no scope is AD-17's landing page, never an empty view From /portfolios/:id, Adapt lands with the portfolio as scope. Toggling a measure recomputes the adapted figures with no saved-result picker. An adapted result id draws tiles
AD-2 CRITICAL self be The seeded "1-in-100-year" flood protections protect only to about 1-in-20. The seed sets a frequency cutoff of 1/100 (core/measure_seeder.py:63-66), the engine drops events whose frequency is above it, and event frequencies are band widths, not exceedance (core/ingest/base.py:132-143): RP 10, 20, 50, 100, 200, 500 carry 0.05, 0.03, 0.01, 0.005, 0.003, 0.002. Only RP10 and RP20 are removed. Amended 2026-10-02: band widths are not monotone in return period for irregular lists (RPs 10, 11, 100 give 0.0091, 0.0809, 0.0100), so no frequency threshold can express a protection standard Migration in this W0 issue: measures.rp_protect INTEGER NULL, the two seeded flood protections set to 100 and their hazard_freq_cutoff cleared. The worker selects the event rows with rp <= rp_protect (rows carry rp, base.py:1878) and removes them before the engine, never a frequency threshold on marginal weights. Cards read "Prevents floods up to 1-in-100 years" A unit test over RPs 10, 20, 50, 100, 200 removes 10, 20, 50 and 100 and keeps 200; over RPs 10, 11, 100 with RP_protect = 10 it removes RP10 only
AD-3 ENHANCE-NOW owner ui The right column is a fixed sm:w-[26rem] div (AdaptPage.tsx:128-131), not the shared Drawer that every other end dock uses. The map shares it with the whole appraisal form, and the centre holds only the selected measure's detail. The prototype puts money cards, the evolution chart and loss by hazard in the centre, and location map over selected measures on the right Centre: money cards, evolution chart, loss by hazard. Right: a retractable, resizable Drawer with the map over the plan list. Run settings in a compact head. Measure detail in the editor At 1440×900 there is no page scroll, the right dock collapses and drag-resizes with its width kept after reload, and the money cards sit in the centre
AD-4 ENHANCE-NOW owner both The Edit measure modal differs from the prototype: one narrow column, a free-text region, an advanced section, four attribution fields, and the picture behind a button. The prototype shows two columns with "applies to buildings or region", slider hints, an effect line with coverage, cost per unit, "what it does", co-benefits, an icon and an open sketch gallery. The backbone measure has no applies-to, per-unit cost, effect or co-benefit fields Match the prototype's layout and fields. One migration adds applies_to JSONB NOT NULL DEFAULT '{"kind": "scope"}' with kinds scope (the whole run scope), admin ({"kind": "admin", "admin_boundary_id"}) and assets ({"kind": "assets", "asset_ids"}), the prototype's "buildings" mapping to scope; cost_basis and unit_count (AD-11); hazard_inten_shift (AD-15); effect and cobenefits (rp_protect landed in AD-2's W0 migration). AD-14 consumes applies_to. Keep attribution, collapsed The editor shows the prototype's fields in two columns, and an admin measure round-trips through PATCH with its boundary id
AD-5 ENHANCE-NOW self both "Lowers damage by 20 % where applied" is wrong. A −20 % intensity change goes through the nonlinear damage curve (climate_lama_engine/measure.py:98-101), so damage can fall by any amount. "Where applied" means nothing because the region is never used. A coastal measure is accepted against a river-flood result with its hazard silently replaced (compute_service.py:830-865) Copy: "Lowers the hazard by 20 %. Damage falls by an amount that depends on the damage curve, shown after appraisal." The backbone refuses a measure whose hazard differs from the result's (422), and the UI never sends one. A measure of another hazard stays in the plan as " plan" and is appraised only under its own hazard (#1673 item 42) No "20 % damage" claim for an intensity change, and a coastal measure against a river-flood result answers 422
AD-6 ENHANCE-NOW self ui The defaults land in the degraded state. The horizon defaults to Baseline while SSP2-4.5 shows, so the first Appraise has no future cell and no BCR, payback or evolution. A second "Horizon (years)" means the appraisal period, and future figures are labelled with the present year + 30 instead of the hazard year Default to the first available future cell. Rename "Appraisal period (years)". Label future figures with the hazard year. Disable the scenario at Baseline A fresh /adapt?result=… → Appraise shows the BCR card with no more input when an SSP cell exists
AD-7 ENHANCE-NEXT self both The Adapted map tab is a placeholder note (AdaptMap.tsx:117-125) Draw adapted tiles and an avoided-loss difference map from AD-1's adapted result The Adapted tab draws tiles from an adapted result id
AD-8 ENHANCE-NEXT self both No Save plan or Add to report. The plan lives only in the URL, and the backbone has no plan entity Now (2026-10-05, FT-7, FT-14, K40): A persisted adaptation plan (scope, measures, cell, settings) and a report widget for it. "Save plan" opens a short dialog that names the plan and shows its scope, hazard, measures and avoided loss, with the line "A saved plan keeps the measures of one hazard. It is listed under Saved plans in Adapt, and the run form in Explore can load it." (registry adapt-18, with adapt-14 superseded). The plan then shows under "Saved plans" on Adapt's landing page (AD-17, adapt-17), and a folder button beside Save plan reaches the same list from any plan. The run form's "Use a saved plan" (EX-16 section 5, explore-34) lists the saved plans of the run's hazard and loads the plan's measures, with "No saved plan for yet" and "Build a plan in Adapt" when there is none. That box belongs on this row's issue with a Deps: on the run form's measures issue, because the plans land in a later wave than the form. The issue checks that the plan entity holds or links the avoided loss of its combined run (AD-13) before a plan card shows that figure Now (2026-10-05, FT-7, FT-14): A saved plan reopens with its measures and cell, and its widget renders in a report. A saved plan is listed under "Saved plans" on /adapt with its name, scope, hazard and number of measures, and "Open" restores the scope, the hazard and the measures. In Explore's Run step, "Use a saved plan" on a run of that plan's hazard lists it, and loading it sets measures= to the plan's ids
AD-9 BACKLOG self both No adaptation on an event footprint (event hazards carry no annual frequency, so cost-benefit refuses them) and no Lookup "Model the benefit" "Avoided loss for this event" with no BCR through AD-1's contract, and the Lookup link (LK-10) An event watch shows avoided loss for a chosen measure
AD-10 BACKLOG self ui Dead legacy code still holds measure selection (Dashboard.tsx, ConfigPanel.tsx, mounted nowhere) Delete it grep -r "ConfigPanel" src/ returns only its removal
AD-11 ENHANCE-NOW self both The reference catalogue's two measures have cost = NULL by design (core/measure_seeder.py:12-16, 104), cost_unit is a currency with no per-unit basis, and BCR divides a benefit in the exposure's value_unit by a cost in cost_unit with no unit check (engine cost_benefit.py:136, 149; the adapter defaults the unit to USD, engine_adapter.py:366) (domain model § 4.1) Inside AD-4's migration: cost_basis (lump_sum \| per_building \| per_km \| per_ha) and unit_count, total cost = unit cost × count resolved backbone-side before _build_measure; the Adapt form asks for both when a reference measure is copied with a cost; cost-benefit answers 422 "measure cost unit differs from the exposure unit " and the adapter stops defaulting cost_unit Copying the river-flood reference with 42 M per km × 3 km yields cost = 126 M in the appraisal; a EUR measure against a USD book is 422
AD-12 ENHANCE-NOW self both lifetime_years is stored and never read, and the appraisal end year is now + time_horizon_years, not the hazard horizon (compute.py:1683-1684, AD-6). Truncating the ramp would overstate benefit (engine cost_benefit.py:93, 108-119) (domain model § 4.3) Engine (EN-8): benefit_years: int \| None zeroes the per-year benefit for i > benefit_years, n stays horizon_year − present_year. Backbone passes the measure's lifetime and sets future_year to the hazard horizon year when a future cell is given; payback is the first year the cumulative per-year benefit reaches the cost, from the same series, exposed as per_year_benefit. UI: "Appraisal period" is the hazard horizon, lifetime shows on the card and caps the benefit years A 30-year measure appraised to 2080 counts 30 benefit years; payback and NPV come from one series (a unit test asserts the series sums to npv_benefit)
AD-13 ENHANCE-NOW self both Plan totals are independent sums (api/v1/results.py:611-613), over-counting overlapping measures on one hazard; the engine has no combined mode (cost_benefit.py:122-127) and the composition order matters once a depth shift exists (max(1.0 − 0.6, 0) × 0.5 = 0.2 versus max(0.5 − 0.6, 0) = 0) Engine (EN-8): combined: bool applies the measures in list order to one hazard and curve set and returns one result named "plan" with cost = Σ costs (one unit). Backbone: the plan's avoided loss, BCR, payback and residual risk (baseline EAD − combined avoided) come from the combined run; per-measure rows stay for ranking; the canonical order is hazard-side shifts, hazard-side factors, protection standards, then curve-side factors, unless the user reorders the persisted plan (AD-8). UI (AD-3, extended): the centre's first card ranks measures by BCR as the ECA cost curve (x = cumulative avoided loss, bar width = the measure's avoided loss, height = BCR) and shows residual risk per hazard; the form notes "order matters when a measure lowers depth" With two overlapping flood measures the plan's avoided loss is below the sum of the per-measure rows; the bars sort by BCR; residual risk equals baseline minus the combined run's avoided loss
AD-14 ENHANCE-NEXT self be A measure acts on every exposure of the run (Measure.apply has no exposure argument); the frame promises "apply to asset, portfolio and area" and region is a label nothing reads (AD-5) applies_to = the whole run scope (default) or an exposure mask (admin boundary id or asset ids). The worker runs ImpactCalc on the masked and unmasked subsets with and without the measure and combines: eai_exp concatenated in point order, aai_agg summed, at_event aligned by event id (events a protection standard removed from the masked half filled with 0) then added, the loss curve from the summed at_event, never a sum of curves. Cost-benefit on a masked measure uses EN-8's npv_of_avoided helper on the combined avoided loss A measure applied to one municipality lowers only that municipality's per-location EAI; aai_agg equals the sum of the two halves; a unit test with a deductible asserts exactness against a whole-set run with the measure masked to all
AD-15 ENHANCE-NOW self both The engine supports an additive intensity shift (haz_inten_b) and the adapter hard-codes 0 (engine_adapter.py:371), so "raise the floor by 0.6 m" cannot be modelled; the editor's "Lowers damage by N % where applied" is wrong (AD-5) hazard_inten_shift on the measure (AD-4's migration) → haz_inten_b, labelled "Raise the asset or floor by h (m)" with copy "depth above the raised floor = max(depth − h, 0)", allowed only when the measure's intensity unit equals the hazard's. Barriers and levees never subtract depth: they use the protection standard (AD-2) A measure with shift 0.6 on a 1.0 m depth cell computes against 0.4 m; a shift on an earthquake measure is refused
AD-16 ENHANCE-NEXT self both A measure on an indicator hazard (heatwave, wildfire-FWI) has no money appraisal, and a curve-side measure on such a hazard changes nothing a score can see A hazard-side measure (hazard_inten_imp, hazard_inten_shift, a protection standard at the design return period) appraises as a score delta: the band of the transformed intensity at the design return period versus the baseline band, no BCR. A curve-side-only measure on an indicator hazard is refused in the form with "no loss curve for this hazard, this measure cannot be appraised; record it as a qualitative action" A heat measure with intensity factor 0.8 shows "band 7 → 6" and no BCR; a heat measure with only a damage-curve factor is refused with that copy

Assistant (D11, ADR-086)

The assistant today: POST /v1/assistant/messages returns proposals only; the UI executes named actions visibly; two read-only query tools exist (get_result_summary, get_coverage); the model and effort come from env settings; the Anthropic adapter is the only provider. Facts verified 2026-10-02 in config.py:976-1010, core/assistant/{service.py,tools.py,anthropic_adapter.py}, useAssistantLoop.ts. The end-to-end contract is phase-17b-run-model.md § The assistant runs a scenario.

Id Sev Source Repo Finding Change needed Acceptance
AS-1 ENHANCE-NOW owner both The code default is claude-opus-5-5 · medium (config.py:986-989, ADR-086 Consequences). The owner wants Sonnet 5.5 at medium effort, and an admin way to change model and effort without editing .env on prod. No runtime override exists (build_adapter(), service.py:127-136, reads env only, and organization.py:24-37 has no assistant column). The adapter sends model, max_tokens and output_config.effort and never thinking or tool_choice (anthropic_adapter.py:127-132), which is valid for Sonnet 5.5 unchanged (a) Default assistant_model = "claude-sonnet-5-5", effort medium. ADR-086's Consequences are amended, and .env.example, .env.example.prod and the settings table in docs/CLIMATE_LAMA.md (the assistant_model row) follow in the same PR. Owner check on prod, read-only: grep '^ASSISTANT_' .env.prod, an unset key takes the new default at the next deploy. (b) A deployment allowlist setting ASSISTANT_MODEL_CHOICES (default claude-sonnet-5-5,claude-opus-5-5) and efforts low,medium,high, advertised by GET /v1/assistant/options. An org admin picks model and effort in Settings → Assistant (SH-24), stored at org_settings.settings.assistant = {model, effort} (no migration, OrgSettings is extra="allow"). build_adapter() takes the org's override per request, env default otherwise, and a value outside the allowlist is 422. Keys never leave env. GET /v1/assistant/options also serves default_effort, the deployment's default effort, unchanged by an organisation override, so the Settings card can mark the default beside the organisation's choice (#1602, 2026-10-07) GET /v1/info on a fresh deploy reports model claude-sonnet-5-5. PUT /v1/orgs/{id}/settings {"assistant": {"model": "claude-opus-5-5", "effort": "high"}} makes the next POST /v1/assistant/messages audit row carry that model. A model outside the allowlist answers 422, and the Settings card lists exactly the allowlist
AS-2 CRITICAL owner both A simple "run this scenario" cannot complete. The model has no way to learn a portfolio id or a dataset id (tools.py:37-68 returns counts, never ids), nothing waits for a compute job (compute_cell resolves at dispatch, analysis.ts:258-261), the UI never sends context (useAssistantLoop.ts:270-273), so a result_id reaches the model only on a cache hit, and no test drives a compute through the assistant (the smoke stack keeps it 503, contract-map.ts:2367-2372) Now (2026-10-05, FT-19, BC-3, BC-4): Backbone query tools, read-only and org-bound: find_portfolios(q) → [{id, name, asset_count}]; list_hazard_options({portfolio_id \| exposure_dataset_ids}) → hazard types with climate_driven, their computable scenario × year cells and dataset ids (the run model's resolution rule over catalog_coverage and resolve_cells_within_scope); get_job(job_id) → {status, result_id, error}. UI: post context on every turn as {view, exposure, clip, hazard, cells, growth, measures, impf, engine, job_id, result_id, lens}, the same names as the run model's URL keys (the second half of SH-5). after a startsCompute action the loop waits for the job's terminal state through RunProvider (bounded, 180 s) and posts it as the tool result so get_result_summary can follow in the same turn; open_view explore precedes the compute so the job shows in Explore. No fake provider and no change to ADR-086 §5 (off unless flag and key): backbone tests inject a scripted AssistantAdapter through core/assistant/adapter.py and assert the sequence find_portfolios → open_view → set_scope → list_hazard_options → set_analysis → compute_cell → get_job → get_result_summary (run model § 10); tests/e2e/smoke/assistant-run.spec.ts intercepts only POST /v1/assistant/messages with page.route() returning scripted {reply, calls, append, stop} envelopes while compute, jobs and /v1/results/{id} hit the real smoke backbone, and asserts the reply's figure equals GET /v1/results/{id}'s. A live-provider e2e against the real adapter is a credential: ops item (a CI secret) that fails, never skips, on 503 (guardrail 3). A place joins the portfolio as what the assistant can run. AS-4 adds the read-only tool find_exposure_datasets({country, region}), set_scope with exposure in the datasets: form and a clip list, and the place sequence find_exposure_datasets → open_view → set_scope → list_hazard_options → set_analysis → compute_cell → get_job → get_result_summary, in which list_hazard_options is called on exposure_dataset_ids. This row's portfolio sequence stands beside it. For an exposure set of several countries list_hazard_options (#1604) returns each hazard with its state and reason, the same answer as the read route of EX-19, and never refuses the whole call, because AS-4 calls it on a place Now (2026-10-05, FT-19, BC-4): On the wave gate, assistant-run.spec.ts passes the portfolio sentence: "Run river flood today for and tell me its expected annual impact" ends with a reply quoting the served EAI and a trace of eight steps. The backbone test asserts the eight-call sequence. The sentence the owner runs on prod against the live adapter, within the 60 s deadline, is the place sentence of exit criterion 9, "Run river flood today for Greece and tell me its expected annual impact" (AS-4). Before 2026-10-05 (superseded): On the wave gate, assistant-run.spec.ts passes: "Run river flood today for and tell me its expected annual impact" ends with a reply quoting the served EAI and a trace of eight steps; the backbone test asserts the eight-call sequence; on prod, as the owner, the same request against the live adapter completes within the 60 s deadline
AS-3 ENHANCE-NEXT owner be Only Anthropic is implemented (anthropic_adapter.py is the sole adapter; build_adapter() hard-imports it). The owner wants to choose between Anthropic and OpenAI (K16: this phase, W6) ASSISTANT_PROVIDER setting (anthropic default, openai), build_adapter() resolving by provider with a provider-neutral missing-module guard, core/assistant/openai_adapter.py as the only file importing openai behind an import-ban test like the Anthropic one, OPENAI_API_KEY secret, the provider and its model allowlist in GET /v1/assistant/options, and the admin card (AS-1) showing the provider when its key is configured. The key is a credential: ops item the owner fulfils before W6's release With ASSISTANT_PROVIDER=openai and a key, POST /v1/assistant/messages audit rows carry the OpenAI model; with the provider set and no key, 503 no_key; the import-ban test fails when any file outside the adapter imports openai

Engines and the compute lane (step 0)

The second agreement run did not hang. It waited behind six cells of two comparisons the owner had interrupted (Ctrl-C at about 13:08Z). Every comparison cell runs on one Celery worker with --concurrency=1. Each cell holds that slot for about 60 s, of which the engine takes 0.4 to 17 s and the admin result-cells write about 57 s. Run 2's three cells ran 13:14 to 13:17Z with the same EADs as run 1, after the script's 300 s timeout. Evidence: worker and API logs, docker inspect, Redis queue lengths and the comparison rows, all read-only.

Id Sev Source Repo Finding Change needed Acceptance
EN-1 ENHANCE-NOW owner be One compute slot serialises every comparison cell and every user (--concurrency=1, marked stale in docker-compose.hetzner.yml:38-40 since the CX43 rescale). Ingest and impact share the lane Now (2026-10-05, FT-12, BC-6): Route compute_impact to its own impact queue and worker at concurrency 3 or more, after a memory check. This row also carries the proof that a run blocks nothing. The API is one process and prod runs one compute worker until this lands, so a matrix run in flight must leave every other view usable (EX-20). The worker count is the setting impact_worker_concurrency (env IMPACT_WORKER_CONCURRENCY), which the impact worker's --concurrency also reads, and Admin's System health reads both queues from GET /v1/admin/queues, {impact: {waiting, running, workers, oldest_wait_seconds}, ingest: {waiting, running}}, whose workers is that setting (registry admin-2, #1662, #1669, 2026-10-07) Now (2026-10-05, FT-12, BC-6): Two parallel verify_engine_agreement.py … --require-engine climada --require-engine oasis runs both exit 0 within 300 s. A prod box is added. During a matrix run a Lookup request answers within its normal time, where normal is the same request timed in the same session with no run in flight, and both timings are pasted
EN-2 ENHANCE-NOW self be The admin result-cells join takes about 57 s of each 60 s task. sum_points_by_admin_unit tests each point against whole GADM polygons of up to 29,909 vertices. A read-only probe on 2,000 points took 23.8 s A subdivided admin geometry table (ST_Subdivide, migration) for the join, or a cached exposure-to-admin-unit map, or the write moved to a follow-up task so the slot frees on commit For hazard a085c0b9 and exposure 69e03607, the run-save to result-cells gap in the worker log is under 5 s, and climada and climate_lama tasks succeed in under 10 s
EN-3 ENHANCE-NEXT self be Oasis takes 16 to 17 s against about 1 s for the others. [UNVERIFIED] cause: each request rebuilds the model and starts five tool processes. Memory is fine: peak 204.5 MiB of 2 GiB Log per-tool durations in the sidecar, then cache the static model files by hazard and impact-function hash. Record 204.5 MiB in docker-compose.hetzner.yml in place of the [ASSUMPTION] Sidecar logs show per-tool ms, and an Oasis cell answers in under 5 s for this pair
EN-4 ENHANCE-NEXT self both Abandoned or deleted comparisons keep using the slot. DELETE /v1/compute/impact/compare/{id} deletes the group only, and callers get no queue position The script deletes its group on timeout or Ctrl-C. DELETE revokes pending cells and marks them cancelled. The compare group reports how many cells are queued ahead (queued_ahead, #1640) to API callers, and the compare view shows pending only, as the snapshot does (#1673 item 27) Ctrl-C a run and start another: the first group's cells end cancelled and the new run starts within 60 s
EN-5 ENHANCE-NEXT self ui Explore Compare never shows a timeout. The poller's timeout phase is dropped (src/actions/explore/compare.ts:350) and "pending engines" shows forever Carry the poll status through and show the timeout state A vitest where the poller times out shows the timeout state
EN-6 BACKLOG self be A comparison group stays running in the database when nobody polls it, although its jobs completed Roll the group status up on job completion or in sweep_stale_jobs No group with all-terminal jobs has a status other than completed
EN-8 ENHANCE-NOW self be The engine's cost-benefit has no lifetime parameter and no combined mode, its NPV helper is a closure the backbone cannot reuse for masked measures (climate_lama_engine/cost_benefit.py:105-127), and §7 of its spec documents the point-in-time formula while the code defaults to the linear ramp (CLIMATE_LAMA_ENGINE.md:371-389 vs cost_benefit.py:42) (domain model § 4.3) Engine repo items, one release and a pin bump in uv.lock before W4: calc_cost_benefit(..., benefit_years: int \| None = None, combined: bool = False), an npv_of_avoided(avoided_present, avoided_future, discount_rate, present_year, future_year, value_growth_rate, benefit_years) helper, per_year_benefit on CostBenefitResult, and §7 rewritten to the ramp. The backbone's AD-12, AD-13 and AD-14 consume them. Admin's Engines tab shows the tagged release: every GET /v1/engines row serves version and runtime (in_process or http_sidecar), and an http_sidecar engine's version is the engine_version its newest completed result recorded (registry admin-7, #1669, 2026-10-07) The engine tag is on PyPI or the index the backbone pins. uv lock --check passes with the new pin, the oasis-conformance gate is green, and the engine's own tests cover benefit_years, combined and the helper
EN-7 ENHANCE-NOW self both The engine specification reads the loss curve below the smallest modelled exceedance two ways (CLIMATE_LAMA_ENGINE.md:259-260 extends with losses[0], :460 says 0), and the UI's return-period control offers 10 to 1000 regardless of the dataset's return periods (AnalysisDock.tsx:337-347). On return-period hazards the 1-in-N loss is the whole footprint at the RP-N intensity at once, an upper bound for a dispersed book, and nothing says so (found by the conceptual /reassess, 2026-10-02) Now (2026-10-05, FT-1, FT-16, K36, K49): Engine half (wave E1, built in CortoMaltese3/climate-lama-engine, in the same tag as EN-8): settle §4 versus §9 and fix the code to the chosen reading. Backbone freq-curve 422 (GET /v1/results/{id}/freq-curve inside [RP_min, RP_max]): owned by EX-15, not repeated here. UI half (W2, issue "EN-7 ui", Deps: EX-15): the return-period control sits in the strip and in the Layers popover on the map (EX-16), and in both it offers only the dataset's return periods. The loss curve carries the basis label of phase-17b-run-model.md § 11, which says "portfolio". Before 2026-10-05 (superseded): the strip's and the dock's return-period control offers only the dataset's return periods A JRC Greece result offers 10, 20, 50, 100, 200, 500 only; GET …/freq-curve?rp=5 is 422 (EX-15's box); the chart legend reads the return-period-map basis line; the engine tag's spec §9 and §4 agree and its test covers the chosen reading

Home, Data, Analyses, Settings, Admin (audited ahead of the owner's walk)

Id Sev Source Repo Finding Change needed Acceptance
SH-9 ENHANCE-NEXT self ui Settings has no Assistant section, which C-U12's title promises. ui#530 deferred it to ui#565, which has no box for it. Superseded by SH-24 (2026-10-02), which carries the section with AS-1's admin card Covered by SH-24 As SH-24
SH-13 ENHANCE-NEXT self ui Small dead ends. Analyses table rows skip the run summary (AnalysesPage.tsx:902). Data's title reads "Datasets" while the nav says "Data". An empty org sees "No datasets match the selected filters" with no upload prompt Rows open the summary drawer. Title "Data". An empty org gets an upload call to action A row click opens the summary, and an empty org sees the upload prompt
SH-15 BACKLOG self ui Admin has no Invite although POST /v1/admin/users exists, the audit view has no source filter or CSV export, and Engines has no conformance run button Now (2026-10-05, FT-15, K45): Add them inside SH-26's two groups. Invite sits under Organisation, in Users and roles. The source filter and the CSV export sit under Organisation, in Audit log. The conformance run sits under Platform, in Engines Now (2026-10-05, FT-15, K45): Invite creates a user, and the audit view filters by source and exports CSV. Each is reached through its group's tab, and an organisation admin does not see the Engines tab
SH-16 ENHANCE-NEXT self ui Prototype gaps per view. Home: map hazard chips, insight actions, the adaptation-plan and engine-agreement figures, top municipalities. Data: free-text search, status chips and counts, vulnerability, boundaries, measures and events tabs, download, used-by and versions in the drawer, GeoTIFF guide, drag and drop. Analyses: progress and ETA, cancel, re-run, fix-it, add to report, KPI strip, map and treemap in the drawer, kind and status filters, New analysis. Settings: watched places, units and formats, alerts per type, reduce motion, palette preview (the Settings line is superseded by SH-24, 2026-10-02). Admin: live-feeds tab, thin health, usage and audit Now (2026-10-05, FT-11, FT-12, FT-15, K43, K45, K53): Each view gets one parity item that carries the design box of guardrail 1. Items that need backbone fields carry their backbone item. What the owner moves to Phase 18 is listed in the stub (SH-10). Home's item composes SH-20, SH-22, SH-23 and SH-1. The Admin line of the Finding is superseded by SH-26: Admin is two groups of tabs by level, with System health and Live feeds under Platform and Usage and Audit log under Organisation. The saved-work view is named History (SH-25). Its progress and cancel items read the shared jobs list of EX-20 under "Running now", with a time estimate and no percentage, and its cards and their links follow EX-21. Data's upload and "Use in Explore" follow DT-7, and Home's empty state follows SH-27. Before 2026-10-05 (superseded): Each view gets one parity item with a screenshot box Now (2026-10-05, K53): For each view at 1440×900 the wave gate's difference list against the prototype captures of the v2 snapshot holds no design difference (guardrail 1). Admin is captured once per level (SH-26). Before 2026-10-05 (superseded): Parity screenshots per view at 1440×900 beside the prototype, differences listed
SH-10 ENHANCE-NEXT self be About 20 items were deferred "to the Phase 18 charter stub" and the stub (plan.md) lists none of them: Data tabs (ui#551), download (ui#552), coverage side cards (ui#553), checksum and reproduction bundle (ui#555), cancel (ui#556), decision-maker figures (ui#538), watched places, units and per-alert toggles (ui#530), lens column (ui#531), org-wide map providers (ui#532), live-feeds tab and latency tiles (ui#533), tile and API usage (ui#536), ingest cancel (ui#535), streamed replies and report commentary (ui#565), and three Home figures that point at closed issues (ui#559, ui#524, ui#565) Each one lands either in this phase (through SH-16) or in the Phase 18 stub with its source issue The stub lists every deferral not taken into this phase, each with its source issue

Fine-tune of 4 and 5 October 2026

The owner reviewed the Phase 17b prototype (v2) on 4 October. The prototype was rebuilt with every change, and on 5 October he decided each open choice and marked every live entry of its review board "Looks right" (phase-17b-finetune.md). The rows below are the findings that review adds. Each names its FT change and the registry ids that show it in the v2 snapshot (UI repo docs/prototype/v2/, artifact version 1791210282-291b). BC-n is a backend contract change of the record § 5. Copy in quotes is the snapshot's and ships as written. Where an older row says "parity box" or "parity screenshot" it means the design box of guardrail 1 as it reads now. Every figure in the snapshot is illustrative and none is copied here (ADR-077). Where a row says "the issue checks", the record left that point open and the issue must settle it before it builds. A cell of the tables above that the fine-tune changes opens with "Now (2026-10-05)" and what holds, and the text it replaces sits behind "Before 2026-10-05 (superseded)" as history. In an older row that carries no such marker, "Analyses" as the name of the saved-work list reads History (K43), the "Analysis" tab reads the Run step (K48), and "book" reads portfolio (K49).

Id Sev Source Repo Finding Change needed Acceptance
EX-18 CRITICAL owner both Scope read as insurer-only. The reviewed prototype opened Scope on "What is at risk" with the portfolio list first, and the owner found no visible way to run a country, a region or several countries. A reference dataset for a place was one option among four, while a place is the first question of a scientist and of a public body (FT-2, registry explore-29, explore-43, shell-15, with explore-2 and explore-3 superseded) Scope asks "What do you want to assess?" through two doors, a two-part switch "A place" and "My assets" (K38). The open door is the one that matches the chosen exposure, and with nothing chosen it is A place, for every lens. A place. Step 1 "Where": one chip per country from GET /v1/catalog/coverage, and several may be on. Step 2 "Exposure layer" fills in by itself, one card per country with the layer's name and under it its unit, its number of locations and its reference year, read from GET /v1/exposure-datasets?country=<ISO3> (BC-2, no new route). The default layer is computed once, in the backbone. One core function default_exposure_dataset(repo, org_id, country_iso3) picks it: money unit first, then exposure type buildings, then newest created_at, over all non-superseded datasets of the organisation whose derived country is that ISO3, independent of search, type filter, limit and offset. The list route serves it as the envelope key default_dataset_id beside total, and the key is null with no country parameter or when no row qualifies. The UI reads that key and computes nothing. find_exposure_datasets (AS-4) calls the same function and marks the same row. The SDK's list model gains the optional key in the same PR. "Change" opens the layers of that country inside the card as radio rows, each with the name, then unit, locations and reference year, and the tags "default" and "score only". Picking a row selects it and closes the list, the last row is "Upload your own exposure", and while the list is open the button reads "Close". With two countries each card opens on its own, and no floating menu is used (explore-43). Before a country is picked the step reads "It fills in by itself once you pick a country." A country with no layer reads "No exposure layer for yet" with "Add one". Layers of different units across countries show EX-15's mixed-unit refusal. "Upload your own exposure" and "Add one" open Data's upload on the exposure layer kind and offer the way back (DT-7). The address holds exposure=datasets:<id> with one id per country. Narrowing is EX-23 and the list of hazards for several countries is EX-19. My assets. A choice of "Portfolio", "Pins" and "One asset", the button "Upload a portfolio" to the portfolio upload (PF-12), and "Narrow to a place (optional)" with EX-23's control. Both doors. The head shows the resolved line: the place or the portfolio, the narrowing, the exposure layer with its unit and the number of locations, or "Nothing chosen yet". The scope chip in the top bar of Explore, Adapt and Reports shows the same line, and its menu offers "A place" (each covered country and the covered countries together, each on its default exposure layer) and "My assets" (the portfolios) (shell-15). While the place door is open a click on a country on the map sets the place. The primary button "Next: choose the hazard" opens Run and is off until an exposure resolves. Every control writes through set_scope (D11). The doors that open by role lens and the one path "Where, then what" are not built. A dataset with no bbox has no derived country, so it never lists under a country and is never a default. "Upload your own exposure" therefore needs the upload to record its extent: the issue checks whether the exposure ingest writes a bbox, and adds it if not A fresh /explore opens on step 1 Scope with "What do you want to assess?", the A place door open and "Next: choose the hazard" off. Ticking Greece fills the Greece card with the layer that default_dataset_id names, with no other click, enables the button and writes exposure=datasets:<id>. "Change" lists that country's layers as radio rows inside the card, and picking one closes the list and rewrites the id. Ticking a second country adds a second card and the address holds two ids. A country with no exposure layer reads "No exposure layer for yet" with "Add one". My assets lists the portfolios and picking one writes exposure=portfolio:<id>. The scope chip in the top bar reads the same line as the head of Scope. A backbone test of default_exposure_dataset, over a fixture holding a non-money layer, a money layer of type population, an older and a newer money layer of type buildings and a superseded layer, returns the newer buildings layer, whatever search, exposure_type, limit and offset the request carries. GET /v1/exposure-datasets with no country returns default_dataset_id: null. A dataset with no bbox is in no country's list. The UI holds no copy of the rule: a vitest feeds the place door a list whose default_dataset_id is not the newest row, and the card shows the row the key names. An exposure layer uploaded through "Upload your own exposure" lists under its country once its ingest ends. The design box of guardrail 1 covers both doors of Scope (prototype captures of explore-29 and explore-43)
EX-19 ENHANCE-NOW owner both With two or more countries chosen nothing said which hazards can run for them together. A hazard held as one dataset per country was refused only at Run, by the 422 of run model § 6 ("no single hazard dataset covers …"), after the form had been filled (FT-10, registry explore-37) (K41) In the A place door with two or more countries on, a list "Hazards for together" sits under the exposure cards, one line per hazard type, reading "One dataset covers both", "No single dataset covers both" or "Not covered" (with three or more countries the count takes the place of "both"), and under it the line "A hazard with no single dataset is off in Run. Run one country to use it." In Run such a hazard is listed under "Not available here" with its reason. When it is the run's hazard the form reads "No single dataset covers . One run never stitches per-country hazard files." with a "Run one country" menu that lists each country as " only" and drops the scope to it. Section 7 then reads " cannot run for together. Run one country, in step 1." and shows no Run button. The strip reads "One country per run" with the same menu, and the caption's status word is "one country only" (EX-9). Backbone (BC-3): before Run the UI must know, for an exposure selector, which hazard types resolve to one dataset. It is one rule with one implementation, shared with the hazard resolution of run model § 6. The record asks whether the list_hazard_options work of #1604 is an HTTP route the UI can call. It is not: #1604 files it as an assistant query tool, query tools run inside POST /v1/assistant/messages, and coverage serves counts and names no dataset (run model § 6, "Hazard options for an exposure selector"). So this finding is two issues: a new UI issue, and a backbone read route over the core function that #1590 builds. The route takes the exposure selector and an optional scope and returns, per hazard type, a state (covered, no_single_dataset or not_covered) and a reason, which is null for covered and otherwise the text the compute refusal gives for that hazard. The backbone issue names the route's path, and the UI issue lists it in Deps:. ui#747 holds a rule that is wrong: "with several exposure countries a hazard is enabled only when every country's coverage cell has computable_count > 0". Coverage counts cannot tell one dataset that covers both countries from one dataset per country. ui#747 is rewritten to read the new route's per-hazard state, with the route's issue in Deps:. #1604's list_hazard_options returns the same per-hazard state and reason for a set of several countries and never refuses the whole call, because AS-4 calls it on a place. The per-country batch is not built (Out of scope, Phase 18 stub) On fixtures listed in Files: that hold a river-flood dataset per country and another hazard that one dataset covers for both, with Greece and Cyprus ticked: Scope lists river flood as "No single dataset covers both" and the other hazard as "One dataset covers both". In Run, river flood sits under "Not available here", no Run button shows, no /matrix POST is sent, and "Run one country" offers "Greece only" and "Cyprus only". Picking "Greece only" rewrites exposure to the Greek layer alone and the Run button returns. A backbone test asserts the read route gives the same answer as the run's own resolution for the same exposure set, in both cases. On the same fixtures list_hazard_options returns river flood with state: no_single_dataset and its reason, and does not refuse the call. The hazard list of the UI changes when the route's answer changes and the coverage counts stay the same, which proves ui#747 reads the route
EX-20 ENHANCE-NOW owner both Nothing showed a run in flight, which the owner raised on 4 October. The progress grid was lost when the view redrew or the user left Explore, and no view said that a run of the user's was still going (FT-12, registry explore-38, shell-13, shell-14, analyses-15, home-15) (K42) A run keeps going when the user leaves Explore. Coming back to Explore, or opening the run's address with batch=<id>, shows its progress grid or its results without pressing Run again. While the run of this form is in flight the Run button reads "Running · cell k of N" and cannot be pressed twice, and the line under it ends "The run keeps going if you leave Explore." A "Stop" button sits beside the Run button. It cancels the cells that have not started and keeps the completed ones, and it lands in W6, apart from the rest of this row (K54, see the end of this cell). Until W6 no Stop control is shown, in the Run row or in any list of running runs. The top bar shows a pill only while a run of the viewer's own is in flight, with a spinning icon, the count and a word: "running", "queued" when none has started, "in flight" when some have (shell-13). It opens a list headed "Yours" with each run's title, "cell k of N", a time estimate that says it is an estimate, and "Open", then one line "n more in your organisation", or "Nothing else is running in your organisation", that goes to History. The backend serves no progress fraction, so no percentage and no progress bar is drawn for a run anywhere. One shared poll serves the pill and History's "Running now", which lists the runs and ingests in flight, the viewer's first with Open and Cancel, then the organisation's (analyses-15, on ui#844 in W6, reading the hook this row builds in W2). Home names a batch the same way (home-15). A run of the viewer's that ends raises a toast "Finished: " with "Open", or "Failed: ", and an item in the bell's Jobs group, which lists the viewer's finished runs first and counts the unseen ones (shell-14). A run that completes raises no toast while the viewer is looking at that run. SH-24's two notification rows switch the toast and the bell item. Nothing blocks navigation while a run is going. Backbone, one new W2 issue (BC-7): the batch list row and the batch detail gain created_by_user_id, the job row gains created_by_user_id for a run of one cell with no batch, and GET /v1/jobs accepts a repeated status parameter, so one call serves the shared poll. "Cell k of N" is read from the batch detail, which serves every cell with its status, so a batch_id on the job row is optional. BC-6: a stack box on the Explore end-to-end issue and a prod box on EN-1. Where each part lands. W2: the pill, the one shared jobs poll, the run that keeps going when the user leaves Explore, the batch= reload, the toast and the bell item, and the new backbone issue. W6: Stop, as a box on ui#844 with #1654 in Deps: (K54). Stop sends one POST /v1/jobs/{id}/cancel per cell that has not started. A cell that is already running finishes and keeps its result, and the batch stays, so batch=<id> still opens the run. The W2 UI issue carries defer: Stop → ui#844. The History line about busy workers is drawn only when the issue names a served source for the worker total, which the snapshot hard-codes Stack, the box BC-6 adds to the Explore end-to-end issue: while a matrix run of four cells is in flight the test opens Data, Portfolios and Lookup, gets a Lookup answer, returns to Explore and sees the run's progress grid, with one /matrix POST in all. Opening the run's address with batch=<id> in a new tab shows the same grid, and after the run ended it shows the results. The pill is absent before the run, reads "1 running" during it and is absent after it. Its list shows "cell k of N" and no percentage. A vitest asserts one jobs poll serves every reader of the shared hook. The finished toast carries "Open", and the bell lists "Finished: " first under Jobs. Backbone, W2: GET /v1/jobs?status=pending&status=running answers both statuses in one call, and the job rows, the batch list rows and the batch detail carry created_by_user_id. Prod: EN-1's box. W6, the box on ui#844: Stop sends one POST /v1/jobs/{id}/cancel per cell that has not started, the completed tiles stay completed, the cell that was running completes and keeps its result, no cell stays pending, and batch=<id> still opens the run
EX-21 ENHANCE-NOW owner both Saved work did not bring its run back. In the reviewed prototype "Open in Explore" on a saved item did not restore the exposure, the clip, growth, the measures, the damage curve and the engine, a save made in Explore did not show in the list, a replay and a comparison opened like a plain run, and "Add to report" sent a generic scope (FT-18, registry analyses-10 to analyses-14, analyses-17, reports-14) A saved item carries its whole run: every key of run model § 7 (exposure, clip with its list of units, hazard, cells, growth, measures, impf, engine) and batch. On the History page (the list at /analyses, SH-25) "Open in Explore" writes them all into the run form, and its cells read as computed (analyses-10). "Re-run" does the same and starts the run. An Event replay card has "Open in Events" on its watch (a box of the History issue in W6 that names the EV-19 issue in Deps:), and a Comparison card has "Open in Compare" with its two sides (a and b, or engines), with both cells on the card and in its summary (analyses-13). A card of a run of several cells reads " of cells · failed", and its summary lists each stored cell with its figure and each failed cell with its reason (analyses-12). The list reads the saved work itself and redraws when something is saved or deleted, newest first (analyses-11). "Add to report" sends the item's own scope, hazard, scenario and result id, and the report shows the figures of that result (analyses-14, reports-14). A card shows the same figure Explore shows when the run is reopened, in the unit of its exposure (analyses-17). Backbone (BC-5): a saved run carries every run key, the batch id and which cells completed, a saved comparison its two sides, a saved event replay its watch. The record's check of the Scenario schema is made in run model § 6, "Saved work": a saved item is a Scenario, its inputs store the run under the contract's own names, batch_id is declared already, the saved cells list the completed cells only, and the run's total and failed cells are read from the batch through batch_id. No migration is needed. The read view ScenarioInputs declares the run keys it does not know yet, and the issues of EX-11 and EV-19 name and declare the keys for a comparison's two sides and a replay's watch. The History page's link is W6 work on the existing History issue. The URL keys it writes and the saved fields land in W2 with EX-16 Save a run with a clip of two regions, growth 0.02, one measure and two cells, open History and press "Open in Explore": the address holds exposure, clip=admin:<id>,<id>, hazard, cells, growth, measures, engine and batch (and impf when the saved run overrode the curve), every control of the form shows the saved value, and both cells read as computed with no /matrix POST. "Re-run" sends one /matrix POST with the same body. A saved comparison opens Compare with both sides. A replay saved through EV-19 shows as an Event replay card, and its "Open in Events" opens Events on its watch. A save made in Explore is listed first in History with no reload. GET /v1/scenarios/{id} returns the run keys, the batch id and the completed cells
EX-22 ENHANCE-NOW owner both "Save analysis" on a run of several cells did not say what it stored. The review found that nothing said which cells a save keeps when one has failed or not finished, and that Save was offered before anything was computed (FT-20, registry explore-39, analyses-12) "Save analysis" on a run of several cells stores the batch id and the completed cells only. The toast says how many and names the list: "Saved of cells ( failed). Listed in History.", where the bracket names what was left out (failed, still running, not computed), "Saved cells. Listed in History." when every cell completed, and "Saved. Listed in History." for a run of one cell. Save is off, with its reason on hover and on click, until at least one cell is completed: "Run at least one cell first. A saved analysis holds computed cells only.", and with nothing chosen "Choose what to assess and run it first. A saved analysis holds computed cells only." Backbone (BC-5): the saved Scenario carries the batch id, and its cells list the completed cells only. The History card reads the run's total and its failed cells, each with its reason, from the batch through batch_id, so no field is added for them (run model § 6, "Saved work", EX-21) On a run of four cells of which one fails (a fixture cell listed in Files: that the seed cannot compute), "Save analysis" raises "Saved 3 of 4 cells (1 failed). Listed in History." and the stored Scenario holds the batch id and the three completed cells. Before any cell completes the button is aria-disabled, and a click raises the reason and stores nothing. The History card of the saved run reads "3 of 4 cells · 1 failed"
EX-23 ENHANCE-NOW owner both Narrowing a place took one unit only and its modes did not exclude each other. After an area was drawn, choosing Whole country, Region or Municipality left the dashed rectangle on the map although the clip was gone. A chosen region or municipality could not be removed, and two regions of one country could not be run together (FT-29, round 2 of the fine-tune, registry explore-41, explore-42) (K50) In the A place door with exactly one country on, "Narrow it (optional)" offers "Whole country", "Region", "Municipality" and "Draw area", and they exclude each other. Choosing any of the first three clears a drawn area, both the clip and its rectangle on the map. Choosing "Draw area" clears the chosen regions or municipalities. Changing the country chips clears every narrowing. With two or more countries on, narrowing is off with "Pick one country to narrow to a region or municipality." The rectangle is on the map only while the clip is that area, on every path that changes the clip: the narrowing switch, the country chips, a map click on a unit, "Clear", the address (back, forward, a pasted link), the scope chip's menu, "New analysis" and a switch between the doors. A chosen unit shows as a chip with a remove button. Several regions, or several municipalities, can be chosen, one level and one country at a time: the Region list ticks several, the Municipality search adds several, "Clear all" shows from two, and the line "Regions or municipalities of one country, one level at a time." states the rule. On the map a click on a unit of the level being picked adds it and a click on a chosen unit removes it, and the map's hint line names what is chosen and how to change it. Switching between Region and Municipality starts that level empty. A unit of another country is refused with a toast that starts "One country at a time." The address carries clip=admin:<id>,<id>, and one id keeps today's form. Everything that reads the clip follows the list: the count of locations, the exposed value, the figures, the hazard coverage, the outline of every chosen unit, the map fit over their union, the resolved line, the run request (scope.admin_boundary_ids), saving and reopening a run (EX-21) and the unit popup's action, "Add to the place" or "Remove from the place". The same control serves My assets under "Narrow to a place (optional)", where the kinds are "No clip", "Country", "Region", "Municipality" and "Draw area". A link that mixes countries or levels keeps what one run can take and says "Some places in this link were left out. A run takes regions or municipalities of one country, one level at a time." Backbone: BC-1, on EX-15's scope Draw an area, then choose "Region": the address loses its clip=bbox: value and the map holds no rectangle layer. Tick two regions in the dock, add a third by a click on the map and remove one by its chip: the address reads clip=admin:<id>,<id> with the two that are left, the map outlines both, the resolved line names both, and the location count equals the members inside either. Reloading that address restores both chips. Run sends a /matrix POST whose scope is {"admin_boundary_ids": [<id>, <id>]}. A region of a second country is refused with the one-country line. With two countries ticked the narrowing switch is absent and the one-country sentence shows
SH-25 ENHANCE-NOW owner ui "Analyse" (the mode), "Analysis" (the dock tab) and "Analyses" (the saved-work list) were three words for three things, and the owner found that the last two collided (FT-11 with FD-13, registry shell-12, analyses-16, explore-28, settings-10) (K43, K48) The saved-work list is named "History" in the rail, the top bar title, the search group, the page itself (its count, its search box, its links from Data and Reports) and every sentence that names the list. The route stays /analyses and the i18n key changes. The buttons "Save analysis" and "New analysis" keep their words, and a saved item is still an analysis. The dock step that was "Analysis" is named "Run" everywhere (EX-16). The mode keeps the name "Analyse". Every UI issue whose copy or acceptance names the list "Analyses" or the tab "Analysis" is amended (guardrail 12 point 5) At 1440×900 the rail entry and the top bar of /analyses read "History", Ctrl K groups saved analyses under "History", and a save's toast ends "Listed in History." grep -rn '"Analyses"' src/i18n/locales/en returns nothing, and no dock step or tab is labelled "Analysis". /analyses still resolves and /runs still redirects to it. The header button of Explore still reads "Save analysis"
SH-26 ENHANCE-NEXT owner both Admin was one list of eight tabs, shown alike to an organisation admin and to a platform admin, who are two different people. In the reviewed prototype Admin also drew empty once Adapt had been opened, because the two views shared style names (FT-15, registry admin-8 to admin-13) (K45) Admin shows two labelled groups of tabs gated by level. Organisation, for an organisation admin: "Users and roles", "Audit log", "Usage" and "Data requests", a new tab that lists the uploads and catalogue downloads this organisation asked for, each with its status, where an organisation admin accepts licence terms or withdraws a request (admin-11). Platform, for a platform admin: "System health", "Organisations", "Ingest approvals", which names the organisation that asked, "Engines" and "Live feeds". The header names the level of the tab being read: "Organisation admin · " or "Platform admin · all organisations". An organisation admin sees only the Organisation group, with the line "Platform tools are shown to platform admins only.", and lands on Users and roles. A platform admin sees both groups and lands on System health. In Usage the table by organisation is a platform-only block (admin-12). The feeds tile of System health counts from the same rows as the Live feeds tab (admin-13). Admin's styles share no name with Adapt's, so opening one after the other draws both (admin-8). Each tab's gate names the route dependency it relies on (BC-9): the Organisation tabs rely on require_role(Role.ORG_ADMIN) routes (api/v1/admin/usage.py, catalog.py, downloads.py, the user routes of management.py), the Platform tabs on the platform-admin checks (pending_ingests.py, the organisation routes of management.py). Engines and Live feeds have no admin route, so the issue names their gate before it places them. SH-15's Invite, audit filter and export and conformance run land inside these tabs. The snapshot's "View Admin as" switch is a preview and is not built (admin-10 shows the state, the switch is not a target). Backbone (#1669, 2026-10-07): Data requests reads GET /v1/admin/data-requests, whose rows are {id, name, kind, how, source, requested_by_user_id, requested_by_name, size_bytes, status, detail, created_at} with how upload or download and status licence, waiting, ingesting, rejected or ready, and it acts through POST /v1/admin/data-requests/{id}/accept-licence, /withdraw and /dismiss. Ingest approvals names the asking organisation through org_id and org_name on every GET /v1/admin/pending-ingests row As an organisation admin /admin shows the Organisation group only with the platform line and opens on Users and roles, and a call to a route behind a Platform tab answers 403. As a platform admin both groups show and the page opens on System health. The by-organisation usage table shows to the platform admin only. A viewer or an analyst gets the 403 or redirect of today (the demo slice's admin-gating side-check). Opening Adapt and then Admin draws Admin's layout unchanged. Design box of guardrail 1: both levels, against the prototype captures of admin-9 to admin-13, taken at the snapshot's #admin with its adminAs preview on org and on platform
SH-27 ENHANCE-NOW owner ui A new organisation has no data and no order of operations. Home had no state for it, and nothing said what to do first (FT-17, registry home-10, home-11) (K46) To an organisation with no data Home shows a "Get started" card on top, with the line "Four steps take a new organisation to its first result. Each step ticks itself when it is done.", a count of the steps done, and four steps, the next one outlined. 1 "Add hazard data": "Upload a hazard dataset, for example flood depth by return period.", action "Upload hazard data", which opens Data's upload on the hazard kind (DT-7). 2 "Add what is at risk": "An exposure layer for a place, or a portfolio of your own assets.", actions "Add an exposure layer" to Data and "Upload a portfolio" to the portfolio upload (PF-12). 3 "Run your first analysis": "Pick what is at risk, a hazard and the scenarios, then run.", action "Open Explore". 4 "Save it or add it to a report": "Save the run to History, or add its figures to a report.", action "Open the last run". Each step ticks itself from served state, never from a click. The record names three sources: coverage, the jobs list and saved work. The issue names the read behind each of the four ticks, a portfolio (step 2) and a report (step 4) included. In the same state every figure card and block of Home reads "Nothing to show yet. It fills in after your first run." in place of a figure. It is an empty state, not the guided tour of #273. The "Reset the steps" link and the "Preview: a new organisation" pill of the snapshot are prototype controls and are not built. The issue states when the card leaves Home: the record shows it to an organisation with no data, and the snapshot's all-done line is preview copy On a freshly bootstrapped stack (one organisation, one admin, no data, the demo slice's step 1) / shows "Get started" with no step ticked and step 1 outlined, and every figure card reads the empty line with no number. After a hazard ingest completes, step 1 is ticked on reload with no click on the card. After the demo slice's steps 3 to 6 all four steps are ticked. An organisation that holds data and saved work never sees the card. Design box of guardrail 1: against the prototype capture of home-10 and home-11, the snapshot's #home with its fresh preview on
SH-28 ENHANCE-NOW owner ui The rail shows the wave mark while the browser tab shows the shield, so the product carries two marks (FT-25, registry shell-11) (K44) The rail brand is the shield, the same mark as the browser tab, at the size the wave mark has today, from the assets ui#857 added under the UI repo's public/brand/. It still links to Home. The wave mark leaves the rail. The issue checks how a self-hosted brand (#121, SH-14) sets the rail mark and keeps that path working At 1440×900 the first element of the rail is the shield image served from public/brand/, on every view and in both themes, and it opens Home. The wave mark appears in no view. The difference list of any view's design box (guardrail 1) holds no line for the rail mark
LK-14 ENHANCE-NEXT owner both The Lookup pin could not be read. A click on it moved it, and the place's actions sat only in the panel's head (FT-13, registry lookup-12, lookup-13, lookup-14) A click on the pin opens a small card anchored to it and never moves it. The card shows the place name, its admin unit and coordinates, "Worst of n hazards: /10" (or "No hazard answers this scenario and year."), "Scenario and year:" followed by the stop being read, with "Today, the baseline" for today, the limit line "No exposure or value enters this score. For a portfolio’s loss, open it in Explore.", and three actions: "Place report" and "Watch", which are LK-3's, and "Open in Explore". Open in Explore opens the place door for the pin's country on its default exposure layer (EX-18), clipped to the municipality that contains the pin, with the hazard being read. The card sits above the pin when there is room, else below it, always inside the map. Escape closes it and returns focus to the pin. A drag pans the map and the card follows. A click elsewhere on the map moves the pin and closes the card, and raises no error. The pin is a button that the keyboard reaches, and Enter or Space opens the card. Backbone (BC-8): Open in Explore needs the municipality that contains a point. The record asks whether Lookup's response already names it. It does not: the card's location block names the country only (run model § 8, "From a pin to a run"). The rule there adds no route: the location block gains the admin units that contain the pin, one per loaded level, each with the boundary_id and name that GET /v1/admin-boundaries serves. That backbone half is a box named in Deps:. Where the block names no municipality for the pin, the link opens the country without a clip On /lookup?lat=40.6&lon=22.8 a click on the pin opens the card, and lat and lon in the address do not change. The card names the worst hazard with its score, the scenario and year the panel reads, and the limit line. "Open in Explore" lands on /explore with exposure=datasets:<the country's default layer>, clip=admin:<the pin's municipality> and hazard=<the hazard tab being read>, with the A place door open. A click on the map away from the pin moves it and closes the card, with no console error. Escape closes the card and focus returns to the pin. GET /v1/risk/lookup for that pin returns the containing admin units in its location block, and a pin with no municipality there opens Explore on the country with no clip
LK-15 ENHANCE-NOW owner both "This area" shows only the score rollup of answer-plane cells, while the prototype shows the municipality's expected annual impact per hazard from saved runs, with "Buildings in data" and three actions (registry lookup-11, K15, run model § 8, #1673 item 5) Now (2026-10-07, K71): a backbone read serves, per hazard, the unit's figures from the newest completed Today result of the organisation that covers the unit (no measures, no growth, preferring the country's default exposure layer), never summed (K26), with buildings_in_data. This area draws the snapshot's head, tiles, note and the actions "Analyse this municipality", "Model a measure here" and "Municipality report" On a pin in a municipality a saved river-flood Today run covers, This area shows "River flood a year" equal to that result's figure for the unit, and a hazard no saved run covers shows no tile
AD-17 ENHANCE-NOW owner both Adapt drew empty when opened with nothing chosen, which is how the rail opens it. Nothing asked what to adapt, and saved plans had no list (FT-14, registry adapt-16, adapt-17, adapt-18, adapt-20) With no scope Adapt is a landing page headed "What do you want to adapt?", with the line "Adapt puts measures on what is at risk and weighs their cost against the loss they avoid. Pick a place or your own assets, or open a saved plan." and the two doors of Scope (EX-18). "A place", "A country, with its reference exposure layer": one chip per covered country, each with its default exposure layer's name, unit, locations and reference year, or "No exposure layer for yet." with "Add one". Picking a country opens Adapt on exposure=datasets:<default>, and a note says where narrowing lives: "Pick a country and Adapt opens on every location of its exposure layer. To narrow it to a region or a municipality, use Scope in Explore." "My assets", "One of your portfolios": the portfolio list with locations and unit, the button "Upload a portfolio", and the line "An amber unit means score only: no money loss can be computed." "Saved plans" lists the plans of AD-8, each with its name, scope, hazard, number of measures, its avoided loss a year (or "Score only, a band change") and "Open", which sets the scope, the hazard and the measures. With none it reads "No saved plans yet. Open a scope, switch measures on and use Save plan." The measure library stays beside the doors, with no pin control (K40, registry adapt-19). In a plan the head holds together at 1366 px: the future-cell chips wrap inside the head and the actions stay together on the right (adapt-20). The saved plans list needs AD-8's plan entity, so that box sits on the AD-8 issue or names it in Deps: /adapt with no scope shows "What do you want to adapt?", both doors and "Saved plans", never an empty page. Picking Greece opens the plan view with exposure=datasets:<id> of the Greek default layer in the address. Picking a portfolio opens it on exposure=portfolio:<id>. Once AD-8 has landed, a saved plan is listed with its scope, hazard and number of measures, and "Open" restores all three. At 1366×768 the plan head does not overflow. Design box of guardrail 1: against the prototype captures taken at the snapshot's #adapt.start
PF-12 ENHANCE-NOW owner ui "Book", "portfolio", "exposure" and "dataset" named two things between them, so a portfolio could not be told from a reference dataset, and an upload did not say which of the two it creates or where the result shows up (FT-16, registry portfolios-11 to portfolios-15, settings-10, data-15) (K49) Two nouns in all copy. "Portfolio" is the user's own assets. "Exposure layer" is a reference dataset for a place. "Book" appears in no UI string of any view: cards, synopsis, notes, Settings, the lens cards and the limit lines of run model § 11. A portfolio's own name is data and is never rewritten. A portfolio built from catalogue data reads "Built from exposure layer: " with a link to that layer in Data, every card carries a kind chip, and these portfolios have their own filter (portfolios-11). The upload dialog opens with "This creates a portfolio: your own assets, listed under Portfolios and under My assets in Explore.", then "Reference data for a place is an exposure layer." with the link "Upload that in Data." (portfolios-12). No polygon upload is promised. "Upload a new version" opens the same dialog on that portfolio with "This uploads a new version of ." and "Its assets are replaced for new runs. Results already computed keep the version they used." Import runs as an ingest job the user can follow from any view (the pill and History's "Running now", EX-20), and when it ends the new portfolio is listed with "Analyse in Explore" and "Open" beside it (portfolios-13). "Open Explore to drop pins" opens Explore on My assets with Pins chosen. The bulk "Add to analysis" says what it scopes: one asset, or the portfolio clipped to the box around the selection (portfolios-14). A portfolio that came from a file links to that file in Data, and the file's row in Data says it is a portfolio and links back (portfolios-15, data-15). The value unit select is PF-2's. The upload dialog has an address (ui#861, 2026-10-07): /portfolios?upload=portfolio&back=<path and query> for a new portfolio and /portfolios/<id>?upload=version&back=<path and query> for a new version, in the grammar of Data's /data?upload=<kind>&back=<path and query> (ui#822), so Home's "Upload portfolio", the "Get started" card (SH-27) and Explore's My assets open it and offer the way back grep -rniwE "books?" src/i18n/locales/en returns nothing. The portfolio upload dialog opens on the "This creates a portfolio" sentence and names no polygon upload. Importing a CSV shows the pill, and the new portfolio is listed when the ingest ends with no reload. A portfolio made of an attached catalogue dataset reads "Built from exposure layer: ", and the link opens that dataset in Data. "Open Explore to drop pins" lands on Explore's Scope with My assets and Pins chosen. Design box of guardrail 1: against the prototype captures taken at the snapshot's #portfolios and #portfolios.upload
DT-7 ENHANCE-NOW owner both Data's upload did not say what each kind creates or where it shows up, nor that a hazard file needs its hazard type, country, scenario, horizon years and return periods to serve a run. HDF5 from CLIMADA was not named, and a dataset had no way into a run (FT-16 with FT-24 folded in, registry data-10 to data-17) The upload dialog offers its kinds as cards, and each says what it creates and where it shows up (data-10). "Hazard layer: intensity maps of one hazard for a place. It appears in Data and in the hazard list of a run in Explore." "Exposure layer: reference data for a place. It appears in Data and under A place in Explore." "Damage curve: the share of value lost at each hazard intensity. It appears in Data and in the Damage curve list of a run." "Event footprint: the observed extent of one past event. It appears in Data and in Explore, under Events." "Adaptation measures: one row per measure, with its effect and cost. They appear in Data and in the measure library of Adapt." "Boundaries: administrative areas to clip a run and to group results. An administrator reviews the file before it is loaded." A hazard layer asks for hazard type, country, scenario, horizon years (with "Today has no horizon year." for the historical scenario) and return periods in years, with the hint "One band of the file per return period. Leave it empty for an indicator: it is scored, never priced." An exposure layer asks for country, value unit (PF-2's list), exposure type and the reference year of the values. The formats line names HDF5 from CLIMADA for a hazard layer and for an exposure layer. The Vulnerability tab uploads a damage curve and the Events tab an event footprint, each with its own fields (data-11). An upload runs as an ingest job the user can follow: the pill, History's "Running now" and a bar over the table that follows its steps with a time estimate. When it ends the dataset is listed and the bar offers the next step (data-12, EX-20). "Upload a new version" opens the dialog on that dataset and stores the version when its ingest ends (data-17). When Explore or Home sent the user here (EX-18, SH-27), the dialog opens on that kind and the bar offers "Back to ", and an exposure layer goes back to Explore already chosen (data-13). "Use in Explore" is on the table row, the drawer and the full page (data-14): for an exposure layer it opens /explore?exposure=datasets:<id>, for a hazard layer it sets hazard only and leaves the rest of the run as it is, and for a portfolio's own file it runs that portfolio. "Post-event report" on an event opens the event template for that event with its id (data-16, RP-2). The issue names the route behind each kind before it offers it. The snapshot names POST /v1/hazards/upload, POST /v1/exposures, POST /v1/impact-functions and POST /v1/measures, and none for an event footprint or for boundaries. The hazard form maps onto the form fields of upload_hazard (haz_type, region, scenario, supported_years, return_periods, indicator, and single_event for one observed event). A kind with no route gets a backbone box or goes back to the owner, and is never drawn as a dead control. The formats line names only what the route accepts: the snapshot's hazard line also names NetCDF, and upload_hazard documents GeoTIFF and CLIMADA HDF5, so the issue checks that before it ships the line The upload dialog shows one card per kind with its sentence. Uploading a hazard GeoTIFF with hazard type, country, scenario, horizon years and return periods sends one POST /v1/hazards/upload whose form holds those values, shows the pill, and lists the dataset when the ingest ends. A CLIMADA HDF5 file is accepted for a hazard layer and for an exposure layer. Opened from Explore's "Upload your own exposure", the dialog is on the exposure layer kind, and after the ingest "Back to Explore" lands on Scope with the new layer chosen. "Use in Explore" on an exposure layer's row opens /explore?exposure=datasets:<id>, and on a hazard layer's row it changes hazard and nothing else in the address. Design box of guardrail 1: against the prototype captures taken at the snapshot's #data
AS-4 ENHANCE-NOW owner both The assistant could run a portfolio only. "Run river flood today for Greece" had no path: no tool finds the exposure layer of a place, and set_scope took no dataset exposure with a clip (FT-19, registry assistant-6) Backbone (BC-4): a read-only, org-bound query tool find_exposure_datasets({country, region}) that returns the exposure layers assigned to the country with id, name, unit, type and the default flag. The default flag marks the row that EX-18's core function default_exposure_dataset returns, the same row the list route serves as default_dataset_id, so the tool and the place door cannot disagree. set_scope takes exposure in the datasets: form and a clip list (admin:<id>,<id>, EX-23). The sequence for "Run river flood today for Greece and tell me its expected annual impact" is find_exposure_datasets → open_view → set_scope → list_hazard_options → set_analysis → compute_cell → get_job → get_result_summary, with list_hazard_options called on exposure_dataset_ids. The same sentence for a region runs the same sequence with the region passed as region, never as country, and its clip set in set_scope. The clip's boundary id comes from the tool: with region it also returns the clip of that unit, as the snapshot's tool does (run model § 10), although BC-4's field list names only id, name, unit, type and the default flag. One country at a time. For a set of several countries list_hazard_options returns each hazard with its state and reason and never refuses the whole call (EX-19, #1604), so a hazard with one dataset per country comes back as having no single dataset, the assistant runs nothing for the two countries together, and the reply gives that reason. With no exposure layer for the place nothing runs, and the reply says why. UI: two suggestions, "Run river flood today for Greece and tell me its expected annual impact" and the same for a region, the manifest and Settings → Assistant list the new tool, and the trace shows the eight steps (SH-5). Tests as AS-2: the backbone sequence test with the injected adapter asserts the eight calls for the place sentence, and assistant-run.spec.ts runs it with only POST /v1/assistant/messages intercepted A backbone test of the tool: find_exposure_datasets({country: "Greece"}) returns the Greek exposure layers with exactly one flagged as the default, the row default_exposure_dataset returns for GRC, and never a dataset of another organisation. list_hazard_options on a Greece plus Cyprus set returns river flood with state: no_single_dataset and its reason, and does not refuse the call. The sequence test prints find_exposure_datasets → open_view → set_scope → list_hazard_options → set_analysis → compute_cell → get_job → get_result_summary for the Greece sentence. On the wave gate assistant-run.spec.ts passes the Greece sentence: the reply's figure equals that of GET /v1/results/{id} and the address holds exposure=datasets:<id>. The region sentence ends with a clip=admin:<id> in the address. On prod, as the owner, the Greece sentence completes against the live provider (exit criterion 9)
EV-19 ENHANCE-NEXT owner both Events had no way to keep a replay or to put an event in a report. Save and Add to report existed in Analyse only (FT-22, registry events-17, with analyses-13 and reports-13 for where they land) The Events header gains "Save replay" and "Add to report", beside "Create a watch" and "Copy link". "Save replay" stores one Scenario for the open watch whose inputs name the watch, and raises "Saved the replay of . Listed in History." No declared key of the Scenario names a watch, and result_id cannot link an event loss, so the issue names the key that holds the watch and declares it in ScenarioInputs in the same PR (BC-5, run model § 6, "Saved work", guardrail 11). One replay is kept per watch and user, so a second save replaces the first, as in the snapshot. "Add to report" adds the event summary to the report basket as an Event item that names its watch, its event key and its hazard, so a report can open the event again. Both are off, with the reason on hover and on click, while no watch with an observation is open: "Pick a watch first. Its event summary is what is saved or added.", and on a watch still waiting (EV-1) "This watch has no observation yet, so there is no event to save or add." The saved figures follow EV-10, EV-11 and EV-14: the cumulative loss, inside one event key, with its basis line. Two boxes that show this work elsewhere are not on this W3 issue, because they need W6 work. The History card's "Open in Events" sits on the EX-21 History issue (W6), and "the report shows the event's id" sits on the RP-2 issue (W6). Each of the two names the EV-19 issue in Deps: With the North Evia replay open, "Save replay" stores one Scenario whose inputs name the watch under the key the issue declared in ScenarioInputs, and GET /v1/scenarios/{id} returns it. Saving again leaves one such Scenario for that watch and user. "Add to report" puts one Event item in the report basket. With no watch open both buttons are aria-disabled and a click raises the first reason. On a watch waiting for its first observation a click raises the second
RP-2 ENHANCE-NOW owner both A report could show a figure nothing had computed. In the reviewed prototype a data widget drew a figure and a result id for a scope with no stored result, the share link named no report, an empty report gave no way to start, and the event report did not say which event it was (FT-23, registry reports-9 to reports-14, shell-16, data-16, with reports-5 superseded) A data widget (key figures, map, loss curve, horizon, table, hazards, rollup) reads a stored result or says that there is none (reports-9). With no stored result for its scope it reads "Not computed yet." with what is missing, "No stored result for . A report reads results and never makes a figure up.", and the button "Run it in Explore", which opens that run's address. A result that was deleted reads "This result is no longer stored." with the same button. A result id shows only where a result exists, and the checklist counts only the widgets that show one. A place report with no run for its municipality is a screening: Hazards and Over time show the hazard score at the place and say that no exposure enters it (reports-10). A new report starts with nothing in it, and its canvas reads "This report has nothing in it yet" with three ways to fill it: "Add a widget", "Insert from the basket" and "Start from the template" (reports-11). The empty basket names where items come from: Add to report in Explore, Events, Lookup, Portfolios, Adapt or History. An item sent from a saved analysis arrives with that analysis's own figures and result id (reports-14, EX-21), and an event summary arrives as an Event widget that shows the event's id (a box of this row's issue that names the EV-19 issue in Deps:). "Share link" copies an address that names this report, and a report opens from its link (reports-12). The post-event report shows the id of its event in the header and on the event widget (reports-13). The toast after Add to report reads "Added to the report basket" with "Open the report", which goes to Reports (shell-16). The record places this on the Reports issues under SH-16 and on #1637 A new portfolio report scoped to a portfolio with no run shows "Not computed yet." in every data widget, with no number and no result id, and "Run it in Explore" opens Explore on that portfolio. After that run is saved the same widget shows the result's figure and its id, equal to GET /v1/results/{id}. A test fails when a data widget renders a figure without a result id. "Share link" copies a URL that opens this report in another session of the same organisation. The post-event report of the North Evia watch shows its event id, and a report filled from the Event basket item of EV-19 shows that event's id. "Add to report" raises the toast with "Open the report"

Walk stop 1 (owner walk of 8 October 2026, prod v0.28.0 / UI v0.18.0, #1572)

The owner walked steps 1 to 9 of #1572 on prod. The scope of this stop is what W0 promised: Portfolios no longer crashes, a single future cell computes, Lookup and Events are walkable. Distance from the prototype is W1 to W6's work and is not a finding here. Data gaps of prod's catalogue (the superseded Aqueduct copies, unmapped hazards) are out of this phase's scope, by the owner's ruling of the same day.

What passed. Home renders its figures with units and no NaN (SH-1). Lookup answers Τήνου 53 with the score card, plain-language bands and the risk-over-time chart (LK-2's second search is gone). The Portfolios list renders with no RangeError, and the m3 portfolio prints its unit (PF-1). Analyses lists the saved batches. Events lists the watches. Adapt opens and shows a measure. Reports, Data, Settings (Sonnet 5.5 default, AS-1) and Admin open. The 390 px pass of Home, Lookup and Events holds.

EX-1 on prod (read by the orchestrator through the API, 2026-10-08 18:36Z): the JRC Greece baseline id with ssp5-8.5 and 2050 resolved to Aqueduct river flood Greece - RCP8.5/SSP5-8.5-eq 2050 (f06458c4, the active copy, not the superseded one) and job a25df496 computed 7,896 of 7,896 LitPop Greece rows in 1.1 s, EAD 425,033,644. The owner's Explore runs failed for another reason, EX-24 below. "Run 2 cells" is ui#748 (W2), not built yet, so it is not a finding.

Id Sev Source Repo Finding Change needed Acceptance
EX-24 CRITICAL owner ui With no scope picked, Explore paired river flood's Cyprus baseline (Aqueduct river flood Cyprus - historical baseline 1980, first by name) with the LitPop Greece exposure. The backbone resolved SSP5-8.5 2050 inside Cyprus, as it must, and the worker failed six runs with "Every assigned exposure row sits outside this hazard dataset's grid" The hazard row offers only datasets whose region covers the scope's exposure (run model, exposure first) Box on ui#747 (W2)
EX-25 CRITICAL owner ui A failed run left "Computing…" in the dock and "Assessment running" in the top bar, and no failure was shown A failed job ends the in-flight state, and the toast and bell read the served error_message Box on ui#866 (W2)
PF-13 CRITICAL owner ui The two USD portfolios' cards (Evia 2021, Samos 2020) read "—" for total value and EAD and stay on "Loading..." for the headline, while GET /v1/portfolios/summary serves Evia's exposed_value 1,730,416,453 USD and ead 2,099,159 and each rollup answers in under 2 s. The detail page prints that EAD as "€2.10M" for a USD portfolio and the m3 portfolio's EAD as "€15.3K". PF-8 therefore does not hold on prod The card prints the served figures in the served unit, and the detail page's latest-run EAD carries the portfolio's unit Box on ui#736 (W1)
SH-29 ENHANCE-NEXT owner ui History's "Recently failed" rows read the catalogue line "The request payload failed validation." instead of the job's sentence The row reads the job's served error_message Box on ui#845 (W6)
LK-16 ENHANCE-NEXT owner ui After a match is picked, Lookup still shows "None of these matches look reliable" under the box The low-confidence note clears once a match is picked Box on ui#807 (W5)
LK-17 CRITICAL self be A Lookup with no scenario answers from a projection. core/lookup_service.py:1510 leaves the scenario unset, so SSP rows compete and recency picks one. On prod on 2026-10-08, heatwave Today at Athens, Piraeus and Larnaca read heatwave-euro-cordex-europe-ssp5-8.5, while scenario=baseline read heatwave-ecde-europe. The smoke stack's river flood Today read the ssp2-4.5 2050 slice. Found by #1564's stack read An unqualified request resolves as baseline (rows labelled baseline or unlabelled) Box on #1665 (W1, B4)

Walk stop 2 (owner walk of 12 October 2026, prod v0.29.0 / UI v0.19.0, #1618)

The owner walked the script of #1618 on prod after release R2 (deploy run 38175655511). No core path is dead: a place run, a portfolio run, an assistant run and a replay's reading all complete. The tracker closed the same day by the owner's ruling, with the findings below filed by the delta /phase-split after the close, so B15 and U21 start without waiting for it.

What passed. Moment 3 through "A place": Greece with its exposure layer filled in, narrowed to Attica and Aegean (729 locations), river flood offered with no Cyprus dataset (EX-24), Today and SSP2-4.5 2030 with one measure, "Run 2 cells", the top bar pill and the batch= address, the caption over the map, the five-figure strip with "How this is made", the hexagon, asset and unit cards, and "Save analysis". Moment 3 through "My assets" on the Evia 2021 book. Compare's card with a side that runs in place. Moment 2: the list, and the USD, EUR and m3 cards in their own units (PF-13, PF-9). Events: the grouped watch list, a selected replay with its footprint and legend, and the FIRMS and USGS feed strips. The demo slice's ingest. The assistant's two sentences, with the right figure. A failed run ends its in-flight state (EX-25).

Read on prod by the agent (read-only, 2026-10-12). PF-8 holds: the Evia 2021 book's card reads $1.4M and the plain portfolio run of the walk (job 577b9602, result dbe56bf6) stores ead 1,423,594. Batch 4a87a651, which the screen showed under the Evia book's name, is a run of the whole LitPop Greece dataset with one measure and no portfolio_id (EX-27). The replay series on the worker volume is /data/live_series/evia_2021_daily (seven files, manifest present).

Not walked, carried to walk stop 3 (#1631): saving a comparison and "Compare engines" (script steps C2 and C3), Moment 4 steps 4 to 8 on a newly created replay (E4 to E8, stopped by EV-20), and the agent's EX-17 and EV-10 reads of #1618's second box.

The last column is where the delta split of 2026-10-12 filed each finding, after checking it against Files: (Split record). The corrections that split made are under the table.

Id Sev Source Repo Finding Change needed Acceptance
EX-26 CRITICAL owner be Changing the map level on a whole-country result made every other call answer 503 E_SERVICE_BUSY. Session lama-fix-prod-queries found 15 queries of /v1/tiles/results/32bf40d4… (whole-Greece river flood Today, 7,896 rows) running 4 to 6 minutes each and holding every DB connection, and ended them A result tile answers within a stated bound on a whole-country result, a tile query carries a statement timeout, and concurrent tile queries are capped below the pool #1804 (the guard, row B15b) and #1805 (the speed fix, row B15c), after the prod plan read #1803
EX-27 CRITICAL owner ui With a run on screen, changing the scope or the form makes the address add and drop batch= in a loop and the loader flip between "not computed yet" and "running". The old run's figures stay on screen under the new scope's name: the Evia 2021 book read 7,896 locations and $260.7M, the whole-Greece run's figures. useSwitchContract.ts:435 drops a stale batch; the writer that restores it is not traced. 1,300 matrix polls in 20 minutes were counted on prod A change of inputs drops the batch once, no figure of another scope is shown under the new one, and the batch poll is bounded ui#1068 (row U21b)
EX-28 ENHANCE-NOW owner both A run reopened from the pill, the bell or its batch= address shows its result with an empty dock: "Nothing at risk chosen yet", no hazard, "Run 0 cells". The address carries batch= and result= only (RunProgressGrid.tsx:142, notificationText.ts:78), and the batch on the server holds the exposure, hazard, cells and measures A batch= address restores the scope and the form from the batch ui#1069 (row U23b), on scope in the batch's params from #1806 (row B15b)
AS-5 ENHANCE-NOW owner both The assistant's run from a portfolio page does not open Explore: the run's keys land on the portfolio page's address (/portfolios/<id>?cells=…&batch=…), because compute_cell writes to the bound page and never changes the path (analysis.ts:535). The reply prints raw markdown and no currency. The figure is right A run the assistant starts outside Explore opens Explore on it, and the reply renders its markdown and names the unit Boxes on ui#852 (row U29) and, for the unit, a box on #1650 (row B22)
EX-29 ENHANCE-NOW owner ui Compare puts SSP1-2.6 2030 on side B, a scenario the Run form says is not ingested, and running it fails with "Input data failed validation". Compare's figures carry no unit Compare offers only cells the hazard holds, and prints the result's unit ui#1070 (row U37)
EV-20 ENHANCE-NEXT owner both Creating a replay watch asks for a directory on the worker as free text. A value the backbone refuses (schemas/live_feed.py:126: absolute and under the data root) leaves the watch created with no feed, and the reason reads "Input data failed validation". Two feedless watches are on prod The dialog offers the series the deployment holds, creates nothing when the feed is refused, and prints the served reason ui#1071 (row U37), on the series list and the one-call create of #1807 (row B23b)
EX-30 ENHANCE-NEXT owner ui In Scope, the chosen regions are not outlined: every region of the country is filled alike while the address carries clip=admin: with two ids The chosen units are outlined and the rest dimmed ui#1072 (row U37)
EX-31 ENHANCE-NEXT owner ui "High or severe" reads "not available" on every place run: share_high_or_severe is written by the portfolio rollup only (rollup_service.py:2122) The strip shows the tile only where the result serves the share, or the backbone serves it for a dataset run ui#1073 (row U37)

Corrections from the delta split of 2026-10-12, read on UI de628e1 and backbone 0265ac7a. EX-27: the writer that restores the batch is RunProvider.tsx:265-270, which re-attaches a remembered run that nothing on Explore clears; the polls come from the poller restarting on every flip. EX-29: the default side B is ssp5-8.5 at the last horizon, and SSP1-2.6 2030 is the first future option of a static list; both ignore the hazard's cells. EX-31: the share is not written by the rollup only. The worker writes it on every impact result (worker/tasks.py:1279-1288) and leaves it null for a run with measures (:1948), and the walk's place runs each carried one measure. ui#1073 keeps the tile and names that reason; hiding the tile would deviate from explore-11 and K52, and banding a run with measures is a product decision this phase does not take. EX-28, AS-5 and EV-20 each need a backbone change, so their Repo reads both. EX-26: the source does not explain a tile of minutes (the result route aggregates nothing at query time), so #1803 reads the plans on prod before #1805 builds.

Correction from the prod plan read #1803 of 2026-10-12, on prod v0.29.0. EX-26: the 15 queries ended at 07:59 UTC were not result tiles. Every one ran _PORTFOLIO_BINS_SQL, the bins layer of /v1/tiles/portfolios/dbe2a065…, and the tiles of result 32bf40d4… answered 200 throughout. At tiles 6/36/24 and 8/145/98 the four result statements take at most 0.43 s as climate_lama_app. The bins statement takes 0.7 s as the owner and is cancelled at 30 s as climate_lama_app: under each of a tile's 449 hexagons the app role's plan scans idx_exposures_dataset_id and filters, where the owner's uses idx_exposures_geometry. The same loss of the spatial index shows on result_h3_cells, harmlessly at its size. Greece holds 29,909, 32,051 and 33,681 points at levels 0, 1 and 2, so prod does not hold the full-resolution archive. #1805's boxes bound the result statements, so row B15c reads them against this before it builds. The scripts, plans and logs are on #1803, the summary on #1805.

Walk stop 3 (owner walk of 13 and 14 October 2026, prod v0.30.0 / UI v0.20.0, #1631)

The owner walked the script of #1631 on prod after release R3 (deploy run 38398954001), from 13:45 UTC on 13 October to the morning of 14 October, while an agent read prod read-only beside the walk. One core path is dead on prod: Adapt's money appraisal (AD-18). Its fix is on trunk (PR #1835), and script steps E5 and E6 are walked again once a release carries it. Release R3.1 (v0.30.1, deploy run 38528649564) carried it, and on 14 October the owner saw the appraisal on prod: benefit–cost ratio 3.61, payback 1.4 years, with the cost curve and the evolution chart (cost-benefit job 3453a21f).

What passed. Moment 1 on 40.6, 22.8, on Athens and on the Cyprus pin: the place report with its hazard tabs, the coordinates filled from the pin, the low-confidence note cleared once a match is picked (LK-16), the stops labelled under SSP2-4.5 with earthquake "same as today" (#1629), river flood's chances "at least 10 %" with the lower-bound line, the raster following the tab being read, "How this score is made", "What you can do" with "Model the benefit", the pin's place card and its "Open in Explore" clipped to the pin's unit with the tab's hazard (LK-14), and This area's "River flood a year" $201,071,350 for Attica, equal to Attica's figure in the whole-Greece Today run (LK-15). Moment 4 step 9 as far as the appraisal: Adapt opened from the rail on "What do you want to adapt?" with "A place" and "My assets" (AD-17), the EUR measure refused against the USD book with its reason (#1621), "Copy and price", the adapted and avoided maps, heatwave's three score-only cards and Import. Compare: a saved comparison whose link reopens both sides, and "Compare engines" on Attica at SSP2-4.5 2030 (climada 218,936,267, climate_lama 218,936,267, oasis 209,843,531, −4.2 %). EX-27 and EX-28 seen fixed. Moment 4 steps 1 to 8 on a replay watch over /data/live_series/evia_2021_daily, saved to History.

Read on prod by the agent (read-only, 13 and 14 October). EV-10 holds: two replay watches over the series (7a586e29, created at 13:45 UTC on 13 October, and 76f6cf4d, at 06:06 UTC on 14 October) each received the six frames one tick apart and scored each twice, the overpass and the fire to date. The fire to date in USD reads 926,127, 21,614,851, 28,823,433, 33,639,074, 33,639,074 and 48,698,552, while single overpasses fall (21,451,558, 15,095,527, 13,491,984, 5,470,912). The insured figure beside it (deductible 50,000 and cover 500,000 per asset) ends at 10,913,425. EX-26: the tile concurrency cap refused 24 portfolio tiles and 2 exposure tiles of the Evia book (503 E_SERVICE_BUSY, 13:42 and 13:56 to 13:57 UTC), no tile ran into the statement timeout and no other route answered 5xx. The whole-Greece Today run of step B1 was a cache hit on a result of 10 September that holds no tile cells (EX-32), so #1805's bound on a whole-country result was not exercised by it. EX-17: the stored pairs read on 12 October stand. LK-17: the UI no longer sends an unqualified lookup (the report seeds SSP2-4.5 · 2050 and asks Today as scenario=baseline, API log at 22:08 UTC), so the unqualified path rests on #1665's tests, and a live API read needs a prod login.

Id Sev Source Repo Finding Change needed Acceptance
AD-18 CRITICAL self be Adapt's money appraisal never completed on prod. The one cost-benefit job prod has run (394020a0, 22:20 UTC on 13 October, the Evia book with a USD-priced protection standard at SSP2-4.5 2030) failed in _check_same_centroid_grid (worker/tasks.py:3653): the Aqueduct Greece historical baseline keeps 24,390 cells of its raster and the RCP4.5/SSP2-4.5 2030 slice keeps 23,533. The six cards, the cost curve and the evolution chart never drew. The adapted and avoided maps did A future slice of the same grid is aligned onto the baseline's cells Fixed on trunk by PR #1835 (f5008e3a), not released at this writing. Its residue, cells wet only in the future slice adding no risk, is #1836. Seen on prod after the next release: the Evia book with only a USD-priced measure on shows the six cards, the cost curve and the evolution chart (script steps E5 and E6) Filed: the fix is #1835, seen on prod on 2026-10-14 on v0.30.1 (job 3453a21f); its residue is #1836 (row B23c).
EX-32 ENHANCE-NOW self be A cache hit serves a result older than the tile aggregates. Step B1's whole-Greece river flood Today run reused result c2ee567a of 10 September, which has no rows in result_h3_cells, so every result tile answered 404 and the strip read "not available" for exposed value and for high or severe A cached result that lacks what the current code writes is completed or recomputed, never served as it is On prod, a whole-Greece river flood Today run draws every map level from tiles (200) and fills all five figures of the strip Filed: #1838 (row B23c).
LK-18 ENHANCE-NOW self be The raster tile proxy answers 500 on an upstream timeout. Heat at Athens at zoom 14 (/v1/tiles/cog/{id}/14/…png?bidx=65&classed=score) failed 13 times (17:45 and 22:03 UTC), each an unhandled httpcore.ReadTimeout from the tile server A tile server timeout is answered as a retryable 503 or 504 with Retry-After, and the slow tile is traced A Lookup raster at zoom 14 leaves no unhandled exception in the API log Filed: #1839 (row B23c).
SH-30 ENHANCE-NEXT owner ui Leaving Explore or Lookup through the rail and coming back resets the view: Explore loses the scope and the form, and Lookup loses the pin, for example after "Model the benefit" opens Adapt The rail reopens a view on its last address, and a reset control clears it Leaving Explore with a scope and a ticked cell, and Lookup with a pin, then coming back through the rail shows them, and the reset returns the empty view Filed: ui#1140 (row U38).
EV-21 ENHANCE-NEXT owner ui Explore → Events is slow to draw its map: most of the basemap's range requests to grc-cyp.pmtiles are aborted and sent again while the map moves to the watch The basemap is not re-requested while the map flies to a watch Selecting a watch draws the basemap with no aborted range request Filed: ui#1141 (row U38).
PF-14 ENHANCE-NEXT owner both Portfolios take too long to load (owner, 14 October). During the walk the Evia book's map hit the tile concurrency cap of 8: 24 of its tiles were refused with 503 The Portfolios list, the detail page and the portfolio map's first paint are measured, and the cap and the map's retry of a refused tile are set from that The portfolio map draws every tile of its first paint, none left refused Filed: #1841, an agent tracker that reads prod and files the fix (Ops row after walk stop 3). #1841's read found the cause is not the cap: every member read scanned all of exposures and JIT compiled it. Fix: #1853 (each book's members are reached by key from its attachments, and the API's connections run with jit = off); its prod proof runs at the release that carries it.
LK-19 ENHANCE-NEXT owner ui A Lookup address that names no stop opens on SSP2-4.5 · 2050 (the seed LOOKUP_AXIS_SEED, LookupAxis.tsx:28, K4) and does not write it into the address. SSP2-4.5 stays pressed at Today, where no scenario applies, and a pressed scenario or year cannot be released The address names the stop shown, and Scenario reads as not applying at Today. The seed itself is K4's and stays /lookup?lat=…&lon=… is rewritten to name scenario=ssp2-4.5&horizon=2050, and at Today no scenario is pressed Filed: ui#1142 (row U38).
LK-20 ENHANCE-NEXT self both Heat reads "n/a" at Today on Athens (37.998, 23.737) and on 40.6, 22.8, while 2030 to 2080 score 3 to 6/10. Today is asked as scenario=baseline, so the baseline heat dataset gives no band there Today's heat answers from the baseline dataset with a band, or its tab names why it has none Heat at Today on both pins shows a score or a named reason Filed: #1840 (row B23c).
LK-21 ENHANCE-NEXT self ui This area on Athens reads the heat ruler "n/a" at all four stops beside a 6/10 badge. It names Attica, a region (GADM level 2 in Greece), "Municipality", and counts the LitPop grid as "Buildings in data 216" The ruler and the badge read one source, and the level and the count are named as the data holds them On Athens, This area's badge and ruler agree, and the unit and count read as the data holds them Filed: ui#1143 (row U38), the ruler and the badge. The level and count nouns stay, decided on ui#1143: no GADM type is served.
LK-22 BACKLOG self ui A key insight compares two lower bounds: "River flood chance here goes from at least 10 % to at least 10 % a year by 2080 under SSP2-4.5" No trend line is drawn between two lower bounds On 40.6, 22.8, no insight compares two "at least" figures Filed: ui#1144 (row U38).

Considered and not filed: one empty "Engine comparison" card in History per engine run, the heatwave plan's pill "no future cell for this hazard" beside a card labelled SSP2-4.5 · 2030, the basemap's glyph ranges 512–767 and 768–1023 answering 404, and the Events address keeping Compare's a= and b=.

Decisions taken while writing this phase

Each is a recommendation the owner can reverse. A reversal is one line in this file.

  • K1. A separate Phase 17b doc and milestone over amending Phase 17. Phase 17's doc is already 80 KB, and a clean milestone lets the rework rate be read for the corrections alone. Phase 17 stays open until this phase exits.
  • K2. Blockers ship first as a mid-phase release (W0). The framework allows one mid-phase release for a user-visible fix, plus one per declared walk stop (amendment 2026-10-02). PF-1, SH-1, EX-1, LK-2, EV-1 (UI half), AD-2, EN-1 and EN-2 make the demo slice and Moments 1 to 4 walkable again.
  • K3. Adaptation is a scoped adapted run (AD-1) over keeping the saved-result appraisal as the entry. The prototype and the frame both apply measures to a scope. Cost-benefit stays as the money layer on top.
  • K4. Lookup opens on SSP2-4.5 for future stops (LK-1) over Baseline. It matches the prototype and Moment 1's "what about 2050?" question. Today always reads the baseline.
  • K5. Batch through one matrix POST (EX-3) over a separate batch page. Now (2026-10-05, K51): the cells of the batch are picked in a matrix in which a tick puts a cell in the run and a click on the cell shows it, and one matrix POST runs them. Moment 3 step 1 needs it and the matrix endpoint exists. Before 2026-10-05 (superseded): the title read "Batch through multi-select chips and one matrix POST (EX-3) over a separate batch page".
  • K6. A theme-following basemap now, a self-hosted dark street basemap after spike SP-1. Provider, licence and hosting need a verified choice before any build.
  • K7. Prototype parity becomes an acceptance box (guardrail 1) over owner review at the end. The end-only review is what this phase is correcting.
  • K8. Guardrails live in the skills over living in this doc (2026-10-02, /reassess fast twice). Phase 17's guardrails 1 to 8 and the framework breaker were all written down, and none was executed.
  • K9. Mid-phase walks on prod after a release over walks on a local stack. A local stack holds seeded data, which is the blind spot behind PF-1. Reversed by naming a prod data snapshot the local gate can load.
  • K10. Parity screenshots render in one pinned Linux image, once per wave, over a billed smoke run on every view PR. Builders have no Docker, Actions minutes are scarce, and Windows renders differ from Linux. Reversed by adding the smoke label to view PRs.
  • K11. The breaker trips into a repair round, then a fresh window, over "stop until the rate falls". Fix PRs never count as builds in rework_rate.py, so a tripped rate could never fall and the phase would deadlock. Every trip and its repair is recorded here, and the phase-end read still covers the whole phase.
  • K12. The prototype inventory (SP-3) runs beside W0 and gates W1 view issues over blocking the split on it. W0's blockers must ship first. The delta split after the W0 walk checks coverage before W1.
  • K13. The run model is exposure first, in the prototype's three tabs (2026-10-02, owner, after a conceptual /reassess). Now (2026-10-05, K36): the exposure-first run model of this line stands, and its tabs are reversed. A run = exposure × optional clip × hazard × scenario × horizon cells × growth × measures × resolved vulnerability × engine, the standard catastrophe-model pipeline, not a CLIMADA artefact (phase-17b-run-model.md). The dock that holds it is two numbered steps, Scope then Run, and Layers is a popover on the map. Before 2026-10-05 (superseded): The owner chose to keep Scope, Layers and Analysis with redefined content over a single stepped Run panel, for prototype parity. Reversed by one line here and a new dock design.
  • K14. The role lens leaves the top bar (2026-10-02, owner). Now (2026-10-05): Settings only. The v2 snapshot draws no lens in the bar (registry shell-1), so this is the target and not a deviation (SH-17, guardrail 10). Before 2026-10-05 (superseded): A recorded deviation from the prototype's #tb-persona (SH-17).
  • K15. Lookup stays hazard-only (2026-10-02, owner). A place score is a hazard screening. Exposure-based figures come through "This area" and Portfolios. A home-value input and a damage line wait for Phase 18's Edit home (D18). Reversed by adding the input under LK-13.
  • K16. The OpenAI adapter ships in this phase, W6 (2026-10-02, owner), over the Phase 18 stub. It needs OPENAI_API_KEY, a credential: ops item the owner fulfils before W6's release (AS-3).
  • K17. A keyed imagery provider is acceptable for satellite (2026-10-02, owner). SP-1 picks between MapTiler and Esri World Imagery under their terms; the key is a credential: ops item (SH-19).
  • K18. Exposure growth compounds from the dataset's recorded value epoch (2026-10-02, conceptual /reassess). The LitPop pack writes properties.reference_year (2018) and the dataset model has temporal_coverage_end, so no migration is needed: exposure_growth_rate applies as (1+g)^(horizon_year − reference_year) with reference_year = properties.reference_year, else temporal_coverage_end, else the run year, the source recorded in provenance and shown in the dock. Value, deductible and cover scale together, before the engine's insurance transform. Reversed by a reference_year column if a pack ever needs one.
  • K19. A non-money book is score-only (2026-10-02, /reassess). PF-4's "refuse with a stated reason" option, in two refusals that name their cause: an exposure type with no curve (population, infrastructure) answers 422 details.reason = "no vulnerability curve for exposure type <type>", and a non-money value_unit (m3, persons, PF-2's list) answers 422 details.reason = "no money loss for value unit <unit>" with details.value_unit, until population or volume curves exist (Phase 18 stub). PF-4 moves from W6 to W2.
  • K20. Growth is counted exactly once (2026-10-02, /reassess). The engine's cost-benefit applies value_growth_rate to the future stream itself, so a cost-benefit base result must be a Today cell with no measures and no growth applied (else 422 "appraise from an unadapted, ungrown present run"), and the base result's scope is inherited. Adapt defaults its value_growth_rate to the Explore form's exposure_growth_rate. One growth field name per surface: exposure_growth_rate on impact, matrix and compare, value_growth_rate on cost-benefit as the engine names it, growth_rate in the saved Scenario as schemas/scenario.py:67 already types it.
  • K21. No fake assistant provider (2026-10-02, /reassess). A scripted provider selectable by env would break ADR-086 §5 and ADR-077. Deterministic coverage comes from an injected adapter in backbone tests and a page.route() intercept of the assistant endpoint alone in the e2e (AS-2).
  • K22. Home is a Settings section (2026-10-02, owner finding 8). Now (2026-10-05): The v2 snapshot holds the Home section (registry settings-1, settings-2), so this is the target and not a deviation (SH-23, SH-24, guardrail 10). Before 2026-10-05 (superseded): A recorded deviation from the prototype's SECTIONS (SH-23, SH-24), listed in SP-3's inventory.
  • K23. The run form resolves vulnerability before measures (2026-10-02, conceptual /reassess). Now (2026-10-05, K48): A measure transforms the damage curve and the hazard (engine §6), so the Run step reads hazard → cells → vulnerability → growth → measures → engine → Run. Growth and measures commute only without insurance terms, so the order is fixed, not a preference. Before 2026-10-05 (superseded): the step was named the Analysis tab.
  • K24. Two stages, named honestly (2026-10-02, conceptual /reassess). The loss run (exposure → hazard → cells → vulnerability → ground-up loss → financial terms) is the catastrophe-model standard Oasis implements. Growth, measures and BCR are the economics-of-climate-adaptation appraisal that CLIMADA, Climate X and Jupiter implement and Oasis does not. Copy and docs never claim Oasis does the second.
  • K25. The value-weighted portfolio score stays, labelled, beside a band distribution (2026-10-02, domain /reassess, owner confirmed). Portfolio grades are a market norm and Moment 2 step 3 is live, so combined_score is kept as "value-weighted average band (ordinal, screening)" and the sound figures join it: the band distribution of exposed value and the share in high or severe (PF-9). Reversed by removing the label and the figure together.
  • K26. EAD is per hazard, from book-scoped runs (2026-10-02, domain /reassess). No cross-hazard total anywhere (the engine has no aggregator); a dataset-level result stands in only for an all selection, weighted once per dataset (PF-8).
  • K27. One unit or none (2026-10-02, domain /reassess). A book's exposed value and weighted figures exist only when every member shares one unit, money or not; mixed units show the member count (PF-10). Consistent with K19 and PF-1.
  • K28. No subscriptions and no thresholds in the live lane this phase (2026-10-02, domain /reassess, owner confirmed). ADR-078 item 6 bars them. The bell carries plain "Watch update" items (SH-11). A loss-threshold rule needs an ADR amendment, filed in the Phase 18 stub. Reversed by that amendment.
  • K29. A plan is one combined run, in a declared order (2026-10-02, domain /reassess). Plan totals come from the engine's combined mode (EN-8), never from per-measure sums; the canonical order is hazard-side shifts, hazard-side factors, protection standards, curve-side factors (AD-13).
  • K30. A depth shift raises the asset, a barrier is a protection standard (2026-10-02, domain /reassess). hazard_inten_shift means "raise the floor by h" (max(depth − h, 0)); levees and walls remove the events up to RP_protect (AD-2 as amended, AD-15). A barrier is never a depth subtraction.
  • K31. frequency_type stays and is derived from storage (2026-10-02, domain /reassess). The engine contract needs the tag; return-period commits write marginal, live snapshots occurrence (DT-1).
  • K32. Engine changes land in this phase, before W4 (2026-10-02, owner confirmed). benefit_years, combined and the NPV helper are small, additive and needed for correct plan totals and lifetimes (EN-8). Admin's Engines tab shows that release through version and runtime on every GET /v1/engines row (registry admin-7, #1669, 2026-10-07). Reversed by moving AD-12 and AD-13 to the Phase 18 stub.
  • K33. The parity CI job runs the pinned image through docker run --rm --network host in a dispatch-only e2e-visual.yml over a container: job (2026-10-03, split, refuter corrected). GitHub's container.options cannot pass --network host, which the stack-backed screenshots need. SP-2 (c) (ui#710) builds it.
  • K34. Guardrail 8's enforcer is a vitest AST guard over an ESLint rule (2026-10-03, split, refuter corrected). The UI repo has no ESLint (package.json lint is tsc --noEmit), so tests/money_one_path.test.ts runs under npm test and bans style: 'currency' and new Intl.NumberFormat outside src/i18n/format.ts (ui#711).
  • K35. One concurrent lane per repo, each running its rows back to back, over one mixed session at a time (2026-10-03, owner asked for a faster build, refuter corrected). At the sizing table's step times the build span falls from 119 h to 88 h, and each repo still runs one session at a time. The refuter's corrections are rules in Lanes: the backbone lane never opens the sibling lane, the gates of the two lanes never overlap, and the UI gate reads backbone fixtures pinned to the gated SHA (SP-2 (b), ui#709). Reversed by running the rows one at a time in table order.
  • K36. The Explore dock is two numbered steps, 1 Scope then 2 Run, and Layers is a button in the map's control group that opens a popover (2026-10-05, owner, FD-1). Not chosen: three tabs in the order Scope, Run, Layers. Nothing in Layers changes the run, which is why it left the dock. It reverses K13's three tabs and amends EX-16, EX-12's tab icons, EN-7's return-period control, the phone sheet of D12 (two steps) and guardrail 10's deviation list. K13's exposure-first run model stands. Reversed by one line here and a dock with Layers as its last tab (snapshot option dock = three).
  • K37. One caption over the map instead of four menu chips (2026-10-05, owner, FD-3). The caption reads "Showing", the hazard, the viewed scenario and year, the engine and a status word. Only scenario and year open a menu, which lists computed cells and never changes the run. Not chosen: four menu chips that edit the run form. It reverses EX-9, whose issue is rewritten. Reversed by restoring EX-9's first sentence (snapshot option chips = menus).
  • K38. Scope asks "What do you want to assess?" through two doors, A place (which opens first) and My assets (2026-10-05, owner, FD-2). Not chosen: the same doors opening by role lens, and one path "Where, then what". It amends EX-16's Scope paragraph and run model § 3.1, § 7 and § 12 (the line that Scope opens on "What is at risk"). EX-18 specifies it. Reversed by one line here and EX-18 rewritten for the other variant (snapshot option scope).
  • K39. The hazard picker is one row that opens a grouped list with reasons (2026-10-05, owner, FD-4). The groups are Computable, Score only and Not available here, with the reason beside each hazard that cannot be picked. Not chosen: a row of seven icons. It amends EX-16's hazard grid. Reversed by the icon row (snapshot option hazard = strip).
  • K40. Measures in the run form are chips plus "Add measures" and "Use a saved plan", with no pinned shortlist (2026-10-05, owner, FD-5). Not chosen: a shortlist pinned in Adapt. It amends EX-16's measure list, AD-1 and AD-8. The pin control of registry entry adapt-19 is not built, and the shortlist is in the Phase 18 stub. Reversed by taking it from the stub (snapshot option measures = shortlist).
  • K41. Several countries with no single hazard dataset: the hazard is off with its reason and "Run one country" (2026-10-05, owner, FD-6). Not chosen: a per-country batch, one run per country with the expected annual impact summed and the 1-in-N loss read per country. It reverses nothing: one run still never stitches per-country hazard datasets (run model § 3.3). EX-19 specifies it, and the batch is in the Phase 18 stub. Reversed by taking the batch from the stub into a phase (snapshot option multi = batch).
  • K42. A run in flight shows as a pill in the top bar that opens a short list (2026-10-05, owner, FD-7). Not chosen: a count badge on the rail entry of the saved-work list. It reverses nothing. EX-20 specifies it. Reversed by the badge (snapshot option indicator = rail).
  • K43. The saved-work list is named History in the rail, the top bar and every sentence (2026-10-05, owner, FD-8). The route stays /analyses, the i18n key changes, and the button "Save analysis" keeps its words. Not chosen: Analyses, Results, Library, Runs. It reverses Phase 17's label "Analyses". SH-25 specifies it, and older rows of this doc that name the Analyses view mean History. Reversed by one i18n string.
  • K44. The rail logo is the shield, the same mark as the browser tab (2026-10-05, owner, FD-9). The assets are in the UI repo's public/brand/ (ui#857). Not chosen: the wave mark. It reverses nothing. SH-28 specifies it. Reversed by putting the wave mark back.
  • K45. Admin shows two groups of tabs gated by level, Organisation and Platform (2026-10-05, owner, FD-10). Organisation, for an organisation admin: Users and roles, Audit log, Usage, Data requests. Platform, for a platform admin: System health, Organisations, Ingest approvals, Engines, Live feeds. Not chosen: one list of eight tabs. It amends SH-15 and SH-16's Admin line. SH-26 specifies it. Reversed by the flat list (snapshot option admin = flat).
  • K46. Home shows the four-step "Get started" card to an organisation with no data, in this phase (2026-10-05, owner, FD-11). Not chosen: Phase 18. It is an empty state, not the guided tour of #273, which stays outside the demo slice. It reverses nothing. SH-27 specifies it. Reversed by moving SH-27 to the Phase 18 stub.
  • K47. The damage curve is a visible select with the resolved default first and a curve preview (2026-10-05, owner, FD-12). Not chosen: a read-only line with an "Advanced" override. It amends EX-16 and run model § 3.5, § 5 and § 7. A non-money exposure keeps the score-only refusal and shows no select. Reversed by the read-only line.
  • K48. The dock step that was "Analysis" is named "Run" (2026-10-05, owner, FD-13). Not chosen: Analysis, which sat between the Analyse mode and the Analyses list. It amends EX-16, K23's wording and run model § 7. The order K23 fixed stands. Reversed by one i18n string.
  • K49. Two nouns in all copy: "portfolio" for the user's own assets and "exposure layer" for a reference dataset (2026-10-05, owner, FD-14). "Book" is gone from the copy. Not chosen: book, catalogue. It amends three limit lines of run model § 11 ("An upper bound for a dispersed portfolio", "For a portfolio’s loss, open it in Explore", "typical intensity across the portfolio") and the quoted copy of LK-13, PF-8, PF-9 and PF-10. PF-12 and DT-7 specify it, and older rows of this doc that say "book" mean a portfolio. Reversed by the i18n strings.
  • K50. A clip may hold several regions or several municipalities of one country and one level (2026-10-05, owner, FD-15). Not chosen: one unit only. It amends EX-15's scope (BC-1: {"admin_boundary_ids": [UUID, ...]}, the one-element alias {"admin_boundary_id": UUID}, or {"bbox": [...]}) and run model § 3.2, § 6 and § 7 (clip=admin:<id>,<id>). EX-23 specifies the control. Reversed by refusing a list longer than one.
  • K51. In the scenario matrix a tick puts a cell in the run and a click on the cell shows it (2026-10-05, owner, FD-16). Not chosen: one click doing both. It amends EX-3 and K5's multi-select chips. Reversed by the single click.
  • K52. The strip's third figure is "Expected annual impact" and every strip figure opens "How this is made" (2026-10-05, owner, FD-17). Not chosen: "Annual impact" with no explanation. It amends EX-10, whose row holds the five popover texts. Reversed by removing the info buttons.
  • K53. The design is proved against the prototype at the wave gate, per state, over a per-view product baseline alone (2026-10-05, fast /reassess, refuter and critic corrected). The harness of ui#710 compares a view with the product's own earlier render, which approves whatever was built. Builders cannot render, so nothing produced the difference list the parity box asked for, and a capture of a view's default state says nothing about the Run step, the second door of Scope or a drawer. SP-2 (a) now also renders one prototype capture per distinct registry state, a new harness item SP-2 (d) captures the product at the same states, the wave gate reads both and posts the difference list on each issue, and the box ticks only when no design difference is left (guardrail 1). The rule is wired into /phase-build references/trunk-gate.md (reference comparison) and /phase-split (the design line in its rubric), as K8 asks. Reversed by removing the state captures from SP-2 and the comparison from the gate, which leaves the per-view drift guard.
  • K54. Stop lands in W6 with the cancel route and the cancelled job state, over moving #1654 and #1639 to W2 (2026-10-05, fast /reassess, corrected, then settled by the migration chain). EX-20's Stop cancels the cells of a run that have not started through #1654's POST /v1/jobs/{id}/cancel, which needs the cancelled job state of #1639. #1639 owns migration 0114_job_status_cancelled, and the issues number one migration chain from 0099 to 0114 (W0 0099 and 0100, W1 0101 to 0103, W3 0104 to 0110, W4 0111, W6 0112 to 0114). Moving #1639 into W2 would renumber 0104 to 0113 in ten issues. So Stop is a W6 box on ui#844 with #1654 in Deps:, and the rest of EX-20 lands in W2: the top bar pill, the one shared jobs poll, the run that keeps going when the user leaves Explore, the batch= reload, the toast and the bell item, and the new backbone issue. The W2 issue carries defer: Stop → ui#844. At walk stop 2 a run in flight can be followed and reopened, and it cannot be stopped yet. Reversed by renumbering migrations 0104 to 0113 and moving the two issues to W2.
  • K55. Five auto decisions that the delta split settled (2026-10-07, /phase-split under the decision protocol). A matrix cell reuses a stored result through its cache key and computes nothing on a hit, over cached staying false and every cell computing again (#1662), as the snapshot draws it (registry explore-6 "cache hit or miss", explore.js:418, app.js:442, #1673 item 52). The hazard options read is GET /v1/compute/hazard-options with the selector and the clip as query parameters, over a POST body or a catalogue path (#1661). A saved comparison keeps its sides in the one key compare and a saved replay its watch in live_event_id, over two flat side keys and keys the read view reports as unknown (#1663). The exposure upload answers 202 with a data_ingest job, over a 201 after the file is parsed inside the request (#1667). A state entry is {id, route, prepare?, account?, hash?} and nothing else, over a free list of steps (#859). Each is reversed by one line here.
  • K56. The prototype wins over agent decisions unless a reason stands (2026-10-07, owner, #1673). The owner ruled on #1673 that the target is to be as close to the prototype designs as possible, unless there's a reason not to. A Decision (auto, ...) line on an issue that departs from the v2 snapshot yields to the snapshot when no binding rule stands against it: an owner K line or FD, ADR-077, ADR-078, a run model section, or the migration chain of K54. The lines ruled under it on 2026-10-07 are listed on #1673 with the issue that now holds each box, and every issue amended for them carries a comment headed "Body amended on 2026-10-07 under #1673". A difference from the snapshot that a binding rule keeps is still a deviation, and it enters SP-3's inventory only when the owner accepts it (guardrail 10). Reversed by one line here.
  • K57. Users and roles has no "Two-step sign-in" column (2026-10-07, owner, #1673 item 13). The snapshot draws the column (registry admin-5, admin.js:249), and the product has no second factor, so no row could say whether it is on. Not chosen: a second factor built in this phase, and a column reading "Off" for everyone. Two-step sign-in joins the Phase 18 charter stub. An owner-accepted deviation (guardrail 10). Reversed by a backbone item that adds a second factor and the column on ui#840.
  • K58. Engine cards show no time of the last conformance run and no peak memory (2026-10-07, owner, #1673 item 13). The snapshot's conformance line ends "· today 06:00" and the Oasis card reads "205 MiB, peak of 2 GiB" (registry admin-5, admin.js:268 and :281). The conformance proof is the path-scoped CI gate oasis-conformance.yml (core/engines.py:64-79), which no route can time, and no route measures an engine's memory, so ADR-077 bars both figures. The line names the gate instead of a time, and the three stats per engine come from #1669. Not chosen: a scheduled conformance job and a memory probe of the sidecar, both in the Phase 18 charter stub. An owner-accepted deviation (guardrail 10). Reversed by a scheduled conformance job that stores its time and a served memory figure.
  • K59. System health has no Storage tile and no tile sparklines (2026-10-07, owner, #1673 item 13). The snapshot draws "Storage, 412 GB of 1 TB, +3.1 GB this week" and a sparkline under every tile (registry admin-9, admin.js:222-238). No route serves the store's size, its capacity or any tile's history, and the request latency, tile and geocoder tiles of ui#533 already wait in the Phase 18 charter stub (#1651). Storage and the sparklines join them there. Not chosen: a storage read on MinIO built in this phase. An owner-accepted deviation (guardrail 10). Reversed by a backbone metrics item that serves the size, the capacity and a 24-hour series per tile.
  • K60. Notifications lists four rows, without "A report is shared with you" and "Daily summary of assistant actions" (2026-10-07, owner, #1673 item 16). The snapshot draws six rows (registry settings-6, settings.js:342-350). NotificationType holds six values locked by ADR-037, none for a shared report or an assistant digest, and the product shares no report with a person and writes no digest. Not chosen: two new types with a migration and the two features behind them. Both join the Phase 18 charter stub. An owner-accepted deviation (guardrail 10). Reversed by an ADR-037 amendment that adds the types and the features that emit them.
  • K61. Explore's admin tiers stop at GADM level 2, with no building tier and no unit band after a result (2026-10-07, owner, #1673 item 26). The snapshot's level switch reads Regions, Regional units and Municipalities over GADM levels 1 to 3 (registry explore-16, mapkit.js:10-15), its Selection line names a building (registry explore-22), and its admin unit card reads a result band after a run (registry explore-15). The product's tiers are "Regions" (GADM 1) and "Municipalities" (GADM 2), the noun K50 gives level 2, because result cells and tiles serve GADM levels 0 to 2 only (api/v1/tiles.py:346-347). The building tier is Phase 18 (D18), and a per-unit result band needs a column on result_admin_cells, which has no JSONB field (a migration outside K54's chain), so the unit card keeps the band it reads before a result. Not chosen: a tier, a line or a band the product cannot fill. An owner-accepted deviation (guardrail 10). Reversed by a GADM level 3 result tier, D18's building tier and a unit band column, all in the Phase 18 charter stub.
  • K62. The life-safety line stays on every event-lane surface (2026-10-07, owner, #1673 item 45). The snapshot prints the ADR-078 item 6 line only as the event panel's last line and at the end of the bell (registry events-12, shell-2). The product also keeps it under the watch list, in the Inspector beside observed-at, age and source version, and on Home's events card (registry home-6). ADR-078 item 6 asks every surface that renders an event-lane result to carry the line, and those three render event losses. Where the Inspector sits inside the panel, the line prints once. Not chosen: dropping the three to match the snapshot. An owner-accepted deviation (guardrail 10). Reversed by a superseding ADR that narrows item 6.
  • K63. The Events chart keeps an "Outside loaded boundaries" bar when assets lie outside every loaded boundary (2026-10-07, owner, #1673 item 46). The snapshot draws no such bar (registry events-8) because every mock asset lies in a municipality. Its own line "Bars sum to the event loss." holds only with that remainder, and #1612 sums each level's units plus the assets outside every loaded boundary to the event loss. The bar draws only in that state, so the Evia capture matches. Not chosen: dropping the remainder, which breaks the sum the chart states. An owner-accepted deviation (guardrail 10). Reversed by loaded boundaries that cover every asset, after which the bar never draws.
  • K64. Cancel copy follows run model § 6, not the snapshot (2026-10-07, owner, #1673 item 49). The snapshot says "The cells that have not finished are dropped.", "Cancelling discards the partial results." and, on a cancelled run, "Partial results were discarded" (analyses.js:1363, :1353, :856, :1116, registry analyses-15). Stop cancels only the cells that have not started, and a running cell finishes and keeps its result, because the backbone never terminates a running task (K54). ui#844 quotes copy that says so. Not chosen: copy that promises a discard the backbone does not do. An owner-accepted deviation (guardrail 10). Reversed by a cancel that terminates running tasks and discards their results.
  • K65. The portfolio name's placeholder reads "For example: Hellenic Property, Q4" (2026-10-07, owner, #1673 item 51). The snapshot's upload dialog suggests "For example: Hellenic Property Book — Q4" (registry portfolios-12, js/views/portfolios.js:656). K49 removed the noun book from every UI string. Not chosen: the snapshot's example. An owner-accepted deviation (guardrail 10). Reversed by one i18n string and an exception to K49.
  • K66. Two Home lens figures join later (2026-10-07, owner, #1673 item 48). The snapshot's lens rows show the cached scenario cells and "Municipalities at high risk" (registry home-1, home.js:147 and :150), and the snapshot itself marks both as joining the catalogue when their SH-16 backbone item lands (home.js:136, registry settings-2). No read serves either count. Home shows "Events" in their place and Settings → Home lists both as joining later. Not chosen: a new read for each in this phase. Both reads join the Phase 18 charter stub. An owner-accepted deviation (guardrail 10). Reversed by a backbone issue that serves either count.
  • K67. On a phone the Events player stays on the map with no fold (2026-10-07, owner, #1673 item 35). The snapshot draws it at 390 px with the track on its own row and hides Speed, the loop and "Jump to…" (explore17b-style.js:140-151, registry events-7), which ui#775 builds. It amends D12's owner note of 2026-09-27, that the map chrome collapses on phones so it never blocks the map, for the Events player only. Not chosen: today's fold behind a toggle chip. Reversed by restoring the fold in ui#775.
  • K68. On Reports the top bar's scope chip reads and writes the open report's scope (2026-10-07, owner, #1673 item 20). The snapshot's chip shows the run's scope on Explore, Adapt and Reports (registry shell-15), and Reports holds no run. On Reports the chip shows the report's scope in the same resolved-line form and opens the report's scope menu (ui#833). Not chosen: the last run scope of Explore or Adapt shown on Reports. Reversed by reading the shell's run scope on Reports.
  • K69. An event's severity level shows only where its feed publishes one (2026-10-07, owner, #1673 item 1). The snapshot shows a level on every event (registry reports-4, data-16). The USGS ShakeMap feed publishes the PAGER level, which #1605 keeps as severity_level, while FIRMS and replays publish none, and ADR-077 bars an invented one. The checklist reads "Event, dates and severity level" with " level" or "no severity level published by ", the word "alert" never shows (ADR-078 item 6), and a missing level is a data line at the wave gate. Not chosen: "Event and dates" everywhere. Reversed by dropping severity_level from #1605 and #1608.
  • K70. Owner copy for two states the snapshot does not draw (2026-10-07, owner, #1673 items 9 and 30). A saved plan's figure slot reads "Appraising, the figure follows", "Appraisal failed. Open runs it again." or "Not appraised yet. Open runs it." (ui#834). Lookup's Watch dialog, bound to the country's default exposure layer, says "This place has no value of its own, so the watch computes the event loss of inside it. Each scored observation adds one “Watch update” to the bell. There are no thresholds." when that layer carries money, and the snapshot's count-only sentence without "and no alerts" when it does not (ui#810). Reversed by the i18n strings.
  • K71. The designs the owner chose to build on 2026-10-07 (#1673). This area's money tiles from saved runs (LK-15, item 5, #1684 and ui#886), Portfolios in the footprint (item 24, #1687 and ui#823), the per-asset band, per-hazard bars, legend and the hexagon's worst asset band (item 25, #1685 and ui#887, with band_max in the JSONB result_h3_cells.metrics), Remove from a portfolio (item 34, #1688 and ui#829), admin value tiers before a compute (item 37, #1686 and ui#754, run model § 7), the logo upload, report footer and cover preview (item 16, #1689 and ui#888), the starter's e-mail in Running now (item 48, #1662 and ui#844), and a result checksum with the Reproduce dialog (item 48, the reproduction bundle stays in the Phase 18 charter stub). None needs a migration. Reversed per item by closing its issue and writing the deviation it leaves.
  • K72. Design at build time, gates off the critical path (2026-10-08, owner-directed, /reassess fast: 2 refuters, 1 critic). Read from the first two days of this phase: 13 code builds and 23 closed issues in 26 h, 14 UI fix PRs against 8 builds, U3's builds merged in 75 min and the row then held 4.5 h for five stack rounds, no view builder had opened a prototype capture, two usage-limit pauses cost 5 h. Adopted, each wired into a skill or script (framework amendment 2026-10-08): (1) a builder Reads the prototype captures of its registry ids before its edit plan and names them under ## Design read; the 6b refuter reads them too; the lane's view PRs get a pre-merge render check (local visual at the PR sha, or e2e-visual.yml --ref) from the first design difference a gate posts. (2) A row closes on merged plus the whole-suite gate green, and the next row starts at once; the row's stack modes run detached in a gate-runner agent on the row's final sha, lag at most one row, and no /release runs while an earlier row's stack boxes are open (amends guardrail 1 and § Lanes). (3) Rows hold up to 8 build targets (U1 ran 6 and U2 7 already). A 3rd live builder per lane only while no stack or gate step is live and the other lane holds at most 2. A live ledger of any name halts a second session in the lane. (4) WSL 16 GB and 8 processors, one visual worker, memory diagnostics in every visual log, stack images tagged per their repo's sha. (5) No acceptance box deletes through an API; a red-without-X proof seeds without X (smoke_e2e.py --legs). A classifier denial is stackPending: classifier on a builder box and trunk: deferred on a gate step. (6) The breaker window is an instant (--since-ts), harness fix PRs are printed and never counted, and a breaker (phase): line shows the whole-phase trend. (7) Guard tests ride every scoped run; the UI pins one Node major (.nvmrc, CI reads it). Not chosen: E1 released from walk stop 1 (B9 is its first consumer, and the stop-1 delta split may rewrite EN-7); a builder-side fixture-routed render harness (a new harness, where e2e-visual.yml --ref exists); the stack gate once per walk stop (twelve rows of design and seed debt would land on the owner at /release). Targets at walk stop 2: UI escape rate without harness fixes at or under 0.25, no design difference posted for a view whose PR carried ## Design read, a U row's wall-clock at most twice its slowest build, no stack-gate kill. Reversed by a line here and the skills' 2026-10-08 markers.
  • K73. Lookup's map legend lists the score classes as swatches, not one ramp (2026-10-13, owner, ui#806). The snapshot's legend draws one palette ramp over "Low", the unit and "High" (registry lookup-6, js/views/lookup.js:249). The product draws Lookup's raster in the dataset's score classes whenever the backbone publishes them (?classed=score, #1372, ui#436), recoloured in the picked palette with Ember as the default (D13, #1475, SH-3). One value drives the tiles and the legend (RiskLookupPage.tsx:695-701), so the legend lists the class swatches with their served labels, and it draws a ramp with the measured range only for a raster that publishes no classes. The deviation covers the legend's body only. The rest of the lookup-6 state is proved against the prototype capture like any other (guardrail 1). Not chosen: a ramp legend over tiles drawn in classes, which would describe a map the tiles do not draw, and unclassed tiles on Lookup. An owner-accepted deviation (guardrail 10). Reversed by a build that passes Lookup no classes, after which the tiles and the legend draw the ramp.

Spikes

Id Question Blocker Feeds
SP-1 Which self-hosted street-level vector basemap (light and dark, Latin labels, GADM lines) for an offline-capable product: data source, licence (ODbL attribution terms), tile build and size, and whether prod may keep using tile.openstreetmap.org under the OSM tile-usage policy. Extended 2026-10-02 (SH-19): which satellite imagery provider under K17 (MapTiler free tier with a key, or Esri World Imagery under its attribution terms; EOX Sentinel-2 cloudless excluded, CC-BY-NC-SA) with the exact tile template, attribution string and the credential: item; and whether the AWS Terrain Tiles (Terrarium) at s3.amazonaws.com/elevation-tiles-prod may serve prod's DEM under their data licences, with the attribution line. Written to docs/plan/exploration/basemap.md. Rides W1 agent SH-2b, LK-5, EX-7, SH-19
SP-2 Now (2026-10-05, guardrail 1, K53): The prototype parity harness, built in W0a before any view issue, as four items (K10: builders have no Docker). (a) A spike tracker, Blocker: agent: the orchestrator session copies the UI repo's docs/prototype/v2/source/ snapshot at artifact version 1791210282-291b (checked against docs/prototype/v2/SHA256SUMS), commits it under tests/e2e/parity/prototype-src/, and renders the prototype captures in the pinned Playwright image through WSL Docker, landed by a PR. There is one capture per view with the snapshot's defaults, tests/e2e/parity/prototype/<view>.png (1440×900, and 390×844 for Home, Lookup and Events), and one per distinct go hash of the live registry in tests/e2e/parity/prototype/states/, with an index.json that names the capture of every live registry id. The defaults are the decided options of js/finetune17b.js, and a preview state (fresh for Home's first-run card, adminAs for the organisation admin) is switched only for the capture whose registry entry needs it. An entry's capture hash is its registry go unless that hash renders another state, and index.json records each override (2026-10-07, ui#708): adapt-2 to adapt-13, whose go #adapt renders the landing page, map to the plan view #adapt.plan?exposure=portfolio:hpb&hazard=river_flood, except adapt-12, which maps to its heatwave plan #adapt.plan?exposure=portfolio:hpb&hazard=heatwave with a capture of its own. #settings.places and #settings.organization, which no live entry names as its go, are captured too and listed under extra, so the states folder holds 82 PNG files. (b) An ops tracker, Blocker: agent, for the e2e-run.sh … visual mode in ~/.claude/scripts/local-e2e-gate/, outside every repo. (c) One buildable UI issue (type test, /analyze-sized), Deps: (a) and (b): the harness below in the UI repo, its red/green proof a stackPending box the W0a wave gate runs. The harness adds the visual project to playwright.config.ts with snapshotPathTemplate free of the platform suffix, parity.spec.ts with one toHaveScreenshot per view (map canvas masked, animations disabled), an e2e-run.sh … visual mode in ~/.claude/scripts/local-e2e-gate/ that runs the image through wsl docker run --network host, and the same image through docker run --rm --network host in the dispatch-only e2e-visual.yml (K33). Proof of (c): the visual project goes red when one view's padding changes by 8 px, and green on the baseline. (d) One buildable UI issue (ui#859), type test, W0a, Deps: (c): tests/e2e/parity/states.spec.ts in the visual project reads tests/e2e/parity/states/*.json, one file per issue at tests/e2e/parity/states/<issue number or finding id>-<slug>.json. Each entry is {id, route, prepare?, account?, hash?}, with prepare only "run" and account only "org_admin" or "fresh_org": the registry id, the product route with its seeded ids named by placeholders, prepare set to run when a run must complete first, account for an organisation admin without the platform flag or for an organisation with no data, and hash when the prototype capture is not the entry's go. Entries are keyed by id, account and hash. It captures the 14 view defaults and every entry to test-results/parity/states/<id>[@<account>][~<hash slug>].png with the same masks as (c), and its prove-it-fails is a state file that names an unknown route, which fails the run. A design that shows only after a click (a dialog, a popover, an open list) has no entry and is proved by its issue's behaviour box with the copy quoted (2026-10-07, ui#859). If a prototype view cannot render offline, the owner captures that one view by hand and the tracker names it. Before 2026-10-05 (superseded): the harness had three items, (a) read the live artifact with its Artifact tool or copied the v1 snapshot docs/prototype/source/, and it rendered one capture per view only agent Guardrail 1, exit 2
SP-3 Now (2026-10-05, guardrails 10 and 12): The prototype inventory, beside W0. From the v2 prototype snapshot in the UI repo (docs/prototype/v2/source/ at artifact version 1791210282-291b), write docs/plan/phase-17b-prototype-inventory.md from the snapshot's change registry (js/changes17b*.js): one table per view whose rows are the registry ids of that view, 221 in all. Each row gives the title, the hash that shows the entry, and one of: the issue and acceptance box that owns it, prototype-only for an entry on the line of guardrail 10, superseded: <id> for the 9 entries a later one replaced, or deviation: <finding id> for an owner-approved departure (none at 2026-10-05). An element of a view that no registry entry names gets a row with the element, the prototype file and line, and the finding id that covers it or out-of-scope: <reason>. mapping.md was written against v1 and is a first draft for those rows only. Every W1 and later view issue lists SP-3 in Deps:. The delta /phase-split after the W0 walk prints unmapped: empty over the 212 live ids before W1, through python scripts/prototype_registry_coverage.py. Before 2026-10-05 (superseded): From the prototype snapshot in the UI repo (docs/prototype/source/, mapping.md is the first draft of the per-view table), write docs/plan/phase-17b-prototype-inventory.md: one table per view with element, prototype file and line, and the finding id that covers it, out-of-scope: <reason>, or deviation: <finding id> for an owner-approved departure (SH-17 lens, SH-23 and SH-24 Home section, EX-16's redefined tab content) agent Guardrail 10, guardrail 12, exit 6

Item groups (draft for /phase-split)

/phase-split cuts these into issues, sizes them and writes the wave table with full commands. The groups are ordered. A group may start when the one before it has landed, except where marked. Each walk stop is a walk:mid tracker (guardrail 6). Every build issue in every group after a stop lists the latest preceding stop's tracker in Deps:, not only the next group: W1, W2, W2b, W3 and the engine pin bump list stop 1, W4 and W5 list stop 2, W6 lists stop 3, and E1's engine issues list CortoMaltese3/climate-lama#<stop 1>. "Beside" means one lane session per repo: the groups' issues of one repo go in one command and the orchestrator's independent set runs them in parallel. Two 17b sessions never run in the same repo at once (/phase-build's lane check counts every builder worktree as a lane). One lane per repo runs concurrently, backbone, UI and engine, and the wave table's After column orders the rows within and across the lanes (Lanes, K35). Titles: every planned 17b item is titled by what it builds (feat, refactor, perf, ci, test, docs), never fix(...). fix is reserved for a defect found in a 17b build, so rework_rate.py's unattributed-fix count measures 17b rework and not the planned corrections of Phase 17 (the breaker's default window is repo-wide from the first 17b issue date, guardrail 5). No issue body opens a line with "Found by", "Split out of", "Deferred from" or "Follow-up": it cites the finding id ("Finding: PF-8") instead.

Group Covers Notes
W0a · foundations SP-2 (parity harness, four items: (a) a spike tracker, Blocker: agent, in which the orchestrator copies the UI repo's docs/prototype/v2/source/ snapshot at artifact version 1791210282-291b, commits tests/e2e/parity/prototype-src/** and renders the prototype captures in the pinned image through WSL Docker, one per view with the snapshot's defaults as tests/e2e/parity/prototype/<view>.png and one per distinct go hash of the live registry under tests/e2e/parity/prototype/states/ with an index.json, landed by a PR; (b) an ops tracker, Blocker: agent, for the e2e-run.sh visual mode in ~/.claude/scripts/local-e2e-gate/, which lives outside every repo; (c) one buildable UI issue, type test, sized by /analyze, for the visual project in playwright.config.ts, parity.spec.ts and the dispatch-only e2e-visual.yml job that runs the pinned image through docker run --network host (K33), Deps: (a) and (b), its prove-it-fails box (8 px padding red, baseline green) a stackPending box the W0a wave gate runs, and (d) one buildable UI issue, type test, Deps: (c), for tests/e2e/parity/states.spec.ts, which captures the 14 view defaults and every entry of tests/e2e/parity/states/*.json in the visual project, its prove-it-fails a state file that names an unknown route (K53)), SH-7 (layout proofs inside <main>), guardrail 2 (the prod-shaped seed: the m3, persons, USD and EUR books and one region-tagged SSP river-flood dataset, region "Greece", one SSP scenario, one future horizon, as small synthetic fixtures committed under tests/fixtures/ and listed in Files:, loaded by the backbone scripts/smoke_e2e.py and the UI seed; no ingest pack and no network fetch runs in the smoke or the wave gate), guardrail 3 (compute and matrix specs fail instead of skipping), guardrail 8 (never-throwing formatAmount, the vitest AST guard (K34), the vitest unit table) Runs first, in one session with W0 (the independent set runs in parallel). The W0 wave gate and the release wait for both. SP-3 runs in the orchestrator beside them (a spike tracker, Blocker: agent, landed by a docs PR). W1's UI view issues list SP-2 (c) and SP-2 (d) in Deps:
W0 · blockers PF-1 (callers onto formatAmount, Deps: W0a's guardrail-8 issue, src/i18n/format.ts (hotspot)), SH-1, EX-1, EX-2, EX-17 (cache key: terms and membership digest), PF-8 (EAD per hazard, the row-count defect), LK-2, EV-1 (UI state), AD-2 (as amended: select by rp <= RP_protect), EN-1, EN-2, AS-1 (a) (the one-line model default and the ADR-086 amendment) Release R1
Walk stop 1 (walk:mid) Moments 1 to 4, demo slice steps 3 to 6, and everything under Walk still to do, the 390 px phone pass included Findings and SP-3's coverage check go through one delta /phase-split before W1
W1 · shell, map, density, money honesty and the data we hold SH-4 (with SH-18's grid and 36 px trigger), SH-17, SH-20, SH-21, SH-22, SH-2 (theme-following default), SH-3, PF-2, PF-3, SH-8, PF-5, PF-9, PF-10, PF-11, DT-1, DT-2, DT-5, LK-5 (interim dark raster paint), SP-1 (extended: satellite and DEM providers), SH-25 (the names History and Run), SH-27 (the "Get started" card on Home), SH-28 (the shield in the rail) SP-1 rides this group in the orchestrator. SH-21's Button lands before any other W1 UI issue, which then migrate the buttons they touch. SH-28 and SH-25 land early in the group, because the rail mark and the list's name show in every view's parity box. SH-27's steps link into the uploads of DT-7 and PF-12 (W6) and into Explore's Scope (EX-18, W2), so the delta split orders its boxes so that no step opens a dead control. Rollup chain, serial: PF-10 (unit gate and member-count weighting) → PF-9 (band distribution over PF-10's weights) → PF-11 (the scheme's unit gate, verified); all three list core/rollup_service.py (hotspot). Ingest and migration chain, serial: DT-1 → DT-2 → DT-5, each revision's down_revision the previous one; DT-1 and DT-2 list core/ingest/base.py (hotspot). DT-5's migration lands before EX-15 (W2)
E1 · engine EN-7 (engine half: the frequency-curve reading below RP_min) and EN-8 (benefit_years, combined, npv_of_avoided, per_year_benefit, §7 rewrite) as issues in CortoMaltese3/climate-lama-engine, built there by its own /phase-build session started in the engine checkout (bare #N targets, not a sibling lane of the backbone run), one tag, then one backbone issue "engine pin bump" that updates uv.lock Its own /phase-build session from the engine checkout, the engine lane of Lanes. It reads the breaker from the backbone checkout: python ../climate-lama/scripts/rework_rate.py --repo CortoMaltese3/climate-lama-engine --since <the engine milestone's earliest createdAt> --follow-ups --json, and the phase doc it reads is ../climate-lama/docs/plan/phase-17b-walk-corrections.md; the wave-table command says so. The pin-bump PR body cites CortoMaltese3/climate-lama-engine#<EN-7>, #<EN-8> and the tag, trips the oasis-conformance gate (engine pin change) and lands before W4. The engine issues list CortoMaltese3/climate-lama#<stop 1> in Deps:; AD-12, AD-13 and AD-14 name the pin-bump issue
W2 · Explore, the run model and the street basemap EX-15 (the run contract ADR: exposure_dataset_ids, scope, exposure_growth_rate, measure_ids with AD-1's backbone half and AD-5's 422, compare cell addressing, cache key, vulnerability resolution with PF-4's 422, freq-curve range), EX-14 (portfolio bbox and tiles, the scope assets layer, per-tier popups, and the exposure-only map while Scope is open), EX-16 (the two-step dock of K36: Scope and Run, Layers as a popover on the map, the hazard row, the damage curve select, measures as chips with a picker, URL and actions), EX-18 (Scope through two doors, with the backbone's default_exposure_dataset function and the default_dataset_id key), EX-19 (several countries: one new UI issue, one backbone read route over the core function #1590 builds, and ui#747 rewritten to read that route), EX-23 (narrowing a place, with BC-1's boundary list on EX-15's scope), EX-20 (a run in flight, without Stop: the top bar pill, the one shared jobs poll, the run that keeps going when the user leaves Explore, the batch= reload, the toast and the bell item, BC-6's stack box, and one new backbone issue for created_by_user_id on the batch list row, the batch detail and the job row and for a repeated status on GET /v1/jobs), EX-21 (the saved run's keys and fields, BC-5), EX-22 (saving a run of several cells), EX-3 (the matrix: a tick selects, a click shows), EX-4, EX-5, EX-6, EX-8, EX-9 (reversed: one caption over the map, K37), EX-10 (with "How this is made"), EX-11 (with the saved comparison and the Comparing card), EX-12 (mode tab, Copy link), EX-13 re-walk, EN-5, EN-7 (UI half), PF-4 (UI reason), SH-19 (terrain, and satellite once SP-1 names the provider), SH-2b (the self-hosted light and dark street basemap SP-1 chose, which closes SH-2's acceptance, EX-7 and LK-5) EX-15 lands first, then EX-14 and EX-16 beside the rest. SH-2b and SH-19 need SP-1. Added 2026-10-05: EX-18 and EX-23 land with or after the EX-16 dock issue and before EX-19. EX-21's "Open in Explore" on the History page is W6 work on the existing History issue, and only its URL keys and saved fields land here. EX-20's W2 issue carries defer: Stop → ui#844. Stop is a W6 box on ui#844 with #1654 in Deps:, because #1639 and #1654 stay in W6 with their migration (K54). EX-19's UI issue and the rewritten ui#747 name the read route's issue in Deps:. FT-7's "Use a saved plan" box is not in this group: it sits on the AD-8 issue (W6) with a Deps: on the run form's measures issue
W2b · Assistant AS-2 (query tools, context, wait for the job, the e2e), AS-4 (the place run of BC-4: find_exposure_datasets, set_scope with a dataset exposure and a clip list, the place sequence and its tests), AS-1 (b) (GET /v1/assistant/options, the org override, and Settings → Assistant as a minimal section holding only the admin model and effort card) In one session with W3 (the independent set runs them in parallel). Deps: the EX-16 dock issue (URL state and actions the assistant drives) and stop 1. SH-24 (W6) adopts the minimal section into the nine-section nav, it does not rebuild the card. AS-4 also names the EX-18 issue (the place door and the backbone's default rule) and the EX-23 issue (the clip list) in Deps:. #1604's list_hazard_options returns each hazard's state and reason for a set of several countries, as EX-19's read route does, and never refuses the whole call
W3 · Events EV-2, EV-3, EV-4, EV-5, EV-6, EV-1 (backbone poll summary), EV-7, EV-8 (both halves), EV-9, EV-10, EV-11, EV-12, EV-13, EV-14, EV-16, EV-17, EV-18, EV-19 ("Save replay" and "Add to report" in the Events header), SH-11 (as amended) In one session with W2 and W2b (the files do not overlap, except SH-2b's basemap files, which serialise with any Events map change), or the session after W2 when the budget says so. Every issue lists stop 1. Migration chain, serial in one revision line: EV-11 (hazard_datasets.event_key VARCHAR(128) NULL, backfilled from the replay manifest's event, the ShakeMap event id in properties, or the watch id for FIRMS; index (live_event_id, event_key, valid_at)) → EV-10 (hazard_datasets.cumulative BOOLEAN NOT NULL DEFAULT false; the North Evia replay is re-run to mint cumulative snapshots, a stack box) → EV-12 (live_events selector FKs to RESTRICT, with the 409 in the delete routes) → EV-14 (live_events.deductible, live_events.cover NUMERIC NULL, insured_event_loss on the event result). Each lists src/climate_lama/models/hazard.py (hotspot) or models/live_event.py (hotspot). The panel rows follow. EV-19 follows EV-10, EV-11 and EV-14, whose figures it saves. It is proved in this group by the saved Scenario and the Event item in the report basket. The History card's "Open in Events" box sits on the EX-21 History issue (W6), and "the report shows the event's id" sits on the RP-2 issue (W6). Each of the two names the EV-19 issue in Deps:
Walk stop 2 (walk:mid) Moment 3 through the two-step dock (through A place: a place with its exposure layer filled in, narrowed to one or more regions, a future cell and a measure, and once through My assets), a run left in flight and found again (EX-20, without Stop, which lands in W6, K54), Moment 4 steps 1 to 8, demo slice, the assistant's scenario for a portfolio and for a place (AS-2, AS-4). Folded from walk stop 1 on 2026-10-11 (Walk still to do): Compare, the findings fixed since (EX-24, EX-25, PF-13, LK-17), the PF-8 read, Moment 2 as far as W1 built it (PF-9, PF-10), EX-17 and EV-10 Release R2 first. The rows B14b, U20b and U20c land before it
W4 · Adapt AD-1 (Explore Adapt mode and entry points, the contract landed in W2), AD-3 (with AD-13's cost curve and residual risk), AD-4 (with AD-11's, AD-14's and AD-15's columns in its one migration; AD-2's rp_protect landed in W0), AD-5 (UI: the corrected copy and the greyed-out mismatched measure, its 422 landed in W2), AD-6, AD-7, AD-11, AD-12, AD-13, AD-14, AD-15, AD-16, AD-17 (the landing page: the two doors and the saved plans list) Deps: E1's pin bump (AD-12, AD-13, AD-14 call the new engine API). AD-4's migration lands first. AD-17's two doors land here. Its saved plans list reads AD-8's plans (W6), so that box sits on the AD-8 issue or names it in Deps:. FT-7's "Use a saved plan" box also belongs on the AD-8 issue, with a Deps: on the run form's measures issue of W2
W5 · Lookup LK-1, LK-3, LK-4, LK-6, LK-7, LK-8, LK-9, LK-10, LK-11, LK-13, LK-14 (the pin's place card, with BC-8's check), DT-3 In one session with W4 or the one after it. Every issue lists stop 2. LK-14 names the LK-3 issue (Place report and Watch) and the EX-18 issue (the place door) in Deps:. LK-10 alone names the W4 AD-1 UI issue in Deps: (the pre-scoped Adapt entry point), because AD-1's W2 contract gives Lookup no entry point
Walk stop 3 (walk:mid) Moment 1 (with the pin's place card, LK-14), Moment 4 step 9 (with Adapt opened from the rail on its landing page, AD-17). Carried from walk stop 2 on 2026-10-12: a saved comparison and "Compare engines", Moment 4 steps 4 to 8 on a newly created replay, the EX-17 and EV-10 reads, and EX-26 to EX-28 seen fixed Release R3 first
W6 · Portfolios, Reports, Settings and the rest PF-6, PF-7, RP-1, DT-4, DT-6, SH-5 (query steps in the trace), SH-6 (with SH-18's copy), SH-12, SH-13, SH-15, SH-16, SH-23, SH-24, AS-3 (with its credential: OPENAI_API_KEY item fulfilled before R4), AD-8 (with the saved plans list of AD-17 and FT-7's "Use a saved plan" box), AD-9, AD-10, EN-3, EN-4, EN-6, LK-12 chips, SH-14, SH-10 bookkeeping, SH-26 (Admin by level, with BC-9's gates), PF-12 (two nouns and the portfolio upload), DT-7 (Data's upload dialog and "Use in Explore"), RP-2 (reports invent nothing, with the box "the report shows the event's id" of EV-19), EX-21 (the History page: "Open in Explore", "Re-run", "Open in Events", "Open in Compare"), EX-20's Stop (a box on ui#844 with #1654 in Deps:, K54) Items the owner moves to Phase 18 leave this group and enter the stub, with a grep that the stub lists each id (guardrail 7). Added 2026-10-05: EX-21's History link is work on the existing History issue, and its URL keys and saved fields landed in W2. FT-7's "Use a saved plan" box sits on the AD-8 issue with a Deps: on the run form's measures issue. SH-26 composes SH-15's items into its tabs. #1639 (the cancelled job state, migration 0114) and #1654 (the cancel of a queued impact job) stay in this group, and EX-20's Stop lands here on ui#844 (K54). Migration chain of this group, serial in one revision line (2026-10-07): 0114 #1639, 0115 #1666, 0116 #1667 (which also widens ck_admin_level_range to levels 0 to 3 for the boundary review of #1670), 0117 #1669. The two EV-19 boxes that sit on the History issue and on the RP-2 issue name the EV-19 issue (W3) in Deps:. PF-12's rule that no UI string says "book" binds every earlier wave too (guardrail 12 point 5), so W6 only removes what is left
W7 · close The phase-end owner walk (exit 1) on prod after the final release, mid-review-phase-17.md covering 17 and 17b Closes Phase 17 and this phase together

Ops and owner items (no build)

  • 1499: run 1's evidence satisfies boxes 3 and 4 (peak 204.5 MiB of 2 GiB, comparison 9760fef3 exit 0). The owner ticks them and closes it, or a session with issue-write permission does.

  • 1501: box 1 (released from gated SHAs) is proven by its own release comment and still unticked.

  • Owner data call: close the two Phase 13 replay watches on prod (EV-7).
  • 1300: paste the 24 h poll-health read and close it (EV-9).

  • Owner prod check (AS-1): grep '^ASSISTANT_' .env.prod on the host, read-only. An explicit ASSISTANT_MODEL=claude-opus-5-5 there overrides the new default and must be removed or changed at the R1 deploy.
  • credential: OPENAI_API_KEY (AS-3, K16): the owner adds the key to prod's .env.prod before W6's release. Until then the provider stays anthropic.
  • credential:VITE_SATELLITE_TILES (SH-19, K17): the tile template with the key SP-1's chosen provider needs, added to the UI publish.yml secrets before W2's release. Terrain needs no key.
  • Engine wave E1 is a build wave in the sibling repo, not an ops item: /phase-split files EN-7 and EN-8 in CortoMaltese3/climate-lama-engine quoting docs/CLIMATE_LAMA_ENGINE.md:259-260 and :460 (EN-7) and :371-389 with climate_lama_engine/cost_benefit.py:42, 105-127 (EN-8, paths in the engine checkout), and files the backbone "engine pin bump" issue with Deps: on both.
  • ADR-078 item 6 amendment (loss-threshold notifications): a Phase 18 stub item, never built in this phase (K28).
  • SH-9: superseded by SH-24 (W6), no build; closed by SH-24's PR.
  • Owner data call (PF-8): after R2, read a USD portfolio's card EAD against ead of GET /v1/results/{latest_result_id} on prod and record the match in #1501. The read of 2026-10-08 after R1 failed on PF-13, so it moved to walk stop 2 (wave table, reworded 2026-10-11).

Lanes

Added 2026-10-03 (K35). The wave table runs as three lanes at once, one /phase-build session per repo.

  • Backbone lane (B rows): started in the backbone checkout with backbone numbers only. It never opens the sibling lane, so its run set holds no owner/repo#N target (check with --plan once).
  • UI lane (U rows): started in ../climate-lama-ui with bare UI numbers. It reads this doc and the breaker script from ../climate-lama.
  • Engine lane (E1): started in ../climate-lama-engine, as the E1 row says.
  • Back to back. A lane runs its rows top to bottom in one session. When a row's PRs are merged and its whole-suite gate is green (K72: its stack modes run detached and never hold it) and the next row's After holds, the same session starts the next row's command. Row N+2 waits until row N's detached stack gate has returned and its boxes are disposed, so at most one row is unverified by the stack at any time. A row whose After names another lane's row waits until that row's issues read CLOSED or their code is merged on the trunk (/phase-build step 2's merged-via-Refs rule), polling gh issue list every 10 minutes for up to 2 hours, then stops with next: set to its own command. One session per lane: a second /phase-build session in the same repo halts on the first one's live ledger, whatever its row name (U3 was opened beside U2 on 2026-10-08 and polled for ten hours).
  • A lane stops at a walk stop, a tripped breaker in either repo, a red gate it cannot clear, or the owner's engine tag.
  • Load. Each lane keeps 2 live builders, and a 3rd only while gate-lock.sh status shows no stack or heavy gate step on the machine and the other lane holds at most 2 (K72), so the machine runs at most 5 builders, or 4 beside a stack, where Phase 17 ran
  • Every heavy gate step takes the machine-wide lock first (gate-lock.sh acquire <lane> in ~/.claude/scripts/local-e2e-gate/, added by SP-2 (b), ui#709), so two gates never run side by side. Until ui#709 is closed the lock does not exist, so the first backbone row's wave gate waits for that tracker, and ui#709 edits the gate scripts only while no other lane's wave-gate-* worktree is live. Only the UI lane brings up the local e2e stack.
  • Pinned fixtures. The UI lane's stack e2e reads backbone fixtures from a detached backbone worktree at the gated backbone SHA (CL_BACKBONE_DIR), never from the shared checkout the backbone lane fast-forwards.
  • Docs PRs. A breaker line or override from either lane lands through the docs PR rule. A conflict between the two lanes' docs PRs goes to land-wait's docs-conflict branch.

Split record

Split 2026-10-03 by /phase-split, and delta split 2026-10-07 after the fine-tune of 4 and 5 October (docs/plan/phase-17b-finetune.md), which amended 246 issues and filed 33: backbone #1660 to #1670 and UI ui#859 to ui#880. Milestone Phase 17b -- Walk corrections in all three repos (backbone #18, UI #9, engine #2). It holds 279 issues: backbone 105, UI 169 and engine 5, of which 270 are build targets and 9 are trackers (the walk stops #1572, #1618, #1631 and #1655, the agent trackers #1565, #1573, ui#708 and ui#709 of SP-1 to SP-3, and the engine tag engine#49). The credential items #1656, ui#855 and ui#856 are open ops issues outside the milestone. Sizing: 244 at tier 2, 8 at tier 1 and 18 at tier 3 (builder-max, 13 of them new), and none is a Fable candidate. Drained found-in:*: none admitted, none closed.

#1673 rulings, 2026-10-07. The owner's directive on #1673 (K56) amended open issues in place, each with a comment headed "Body amended on 2026-10-07 under #1673". It filed #1680 (SH-26, item 3) into the new row B20b, ahead of ui#841's row U31, and closed ui#740 as superseded by ui#832 (item 12), so PF-3's formatEur check lands in W6. The owner's picks of the same day (K57 to K71) filed #1684 to #1689 and ui#886 to ui#888 into the rows B9b, B14, U20, B17, U26, B18b, B20b and U36, and the walk stop trackers #1618, #1631 and #1655 list them and walk stop 2's Deps: on ui#740 is met by the close. At 14:50Z the milestone holds 275 open issues: backbone 106, UI 164 and engine 5, the U1 and B1 builds having closed others the same day. The estimate below predates the row B20b.

Rows B14b and U20b, 2026-10-11. Three issues filed by the U14 and U18 sessions sat in no row and in no Deps: of walk stop 2: #1783 (the result hexagon tile's value and the nearest admin level, which ui#887 reads), #1785 (the seed's Greek river-flood baseline does not cover the default Greek exposure layer, so ui#770's place sentence fails on the stack) and ui#1037 (the assistant's action host hands compute_cell no address to read, a product defect that would fail the assistant sentences of the walk). They are the rows B14b (backbone, beside U19 and U20) and U20b (UI, after U20 and B14b). ui#887 moved from U20 to U20b, because its Deps: name #1783 and an unmerged dep halts a /phase-build run at step 2. ui#770 rides U20b for its one open box. #1783 was sized L · Opus · high · tier 2 the same day. #1618's Deps: gained #1783, #1785 and ui#1037, and its walk boxes gained the agent reads on prod and the lines folded from walk stop 1 (Walk still to do). The wave table's Ops rows beside them hold the e2e-smoke.yml canary, the K72 targets and the PF-8 read as reworded. /reassess fast, 2 refuters and 1 critic: 5 of 10 claims confirmed, 5 corrected, 3 added.

U18's residue before walk stop 2, 2026-10-11. Two issues of row U18 stayed open with one box each after B14b and U20b landed, and both are in walk stop 2's Deps:. ui#766 closed on its red-on-mutation proof: a UI image built from a local-only commit that makes compute_cell send hazard_dataset_id failed the body check of explore-run-form.spec.ts (5 passed, 1 failed), and UI main add9694 passed 6 of 6, on the local stack at backbone b573647 (logs under C:/tmp/gate-766). ui#788 closed on the owner's ruling: its one line left at 390×844, the watch list open under the map on a phone, moved to ui#835 (W6, row U32) with the tests it amends and the baseline it re-renders, so EV-6 now names ui#835 too and U32's budget grew by 20 minutes. Walk stop 2 walks no phone, and the 390 px pass is the phase-end walk's.

Row U20c, 2026-10-11. One issue stood between the landed rows and R2 with no row of its own: ui#1052, filed from the canary run 38140385954 that row B14b dispatched. contract-map.ts declares getMatrixBatch's cells with horizon_year: num, the backbone documents null as Today, and contract.spec.ts goes red on it. The UI release gate needs e2e-smoke.yml green at the tag sha, so R2 waits for it. It is row U20c, sized S · tier 1, and walk stop 2's Deps: and After name it. The live job of ui#855 (its key was set on 2026-10-10) and the K72 targets ride the same session as Ops rows, in that order after the row's own smoke run, because the workflow cancels a run in flight on the same ref. Left out on purpose: #1793 and ui#914, both found-in:phase-17b with no milestone and no exit criterion behind them. #1793's first half can show on prod as a Today cell that reads "not computed" on re-entry after it cache-hit an older bare run, which is a finding for the walk to record and not a reason to hold R2.

Walk stop 2 closed, 2026-10-12. The owner walked prod on v0.29.0 / UI v0.19.0 and #1618 closed the same day. Eight findings are in the Walk stop 2 table under Findings (EX-26 to EX-31, AS-5, EV-20). By the owner's ruling the tracker closed before the delta split, so B15 and U21 dispatch at once and the split runs beside them: none of the eight touches Adapt or Lookup, which is all of W4 and W5. Placement the split starts from: EX-26 in a backbone row of its own after B15, EX-27 and EX-28 in a UI row of its own after U21, so walk stop 3 sees the three fixed on prod, and the other five on the W6 issues whose Files: own them. Script steps C2, C3 and E4 to E8 and the EX-17 and EV-10 reads were not walked and moved to walk stop 3 (#1631).

Delta split of walk stop 2, 2026-10-12. The eight findings became 11 issues, and boxes on two open ones. Backbone: #1803 (an agent tracker, the prod plan read), #1804, #1805, #1806 and #1807. UI: ui#1068 to ui#1073. AS-5 is four boxes on ui#852, whose Files: own the assistant's loop and panel, and one on #1650. Only AS-5 had an open owner by Files:, so the starting placements of EX-29, EV-20, EX-30 and EX-31 on the issues of rows U32 and U35 did not hold and each got its own issue. The new rows are B15b (#1803, #1804, #1806), B15c (#1805, alone because its Deps: name the tracker), U21b (ui#1068), U23b (ui#1069, alone because its Deps: name #1806), B23b (#1807) and U37 (ui#1070 to ui#1073). B16, U22, U24 and W7 now wait for the row before them, and U29's budget grew by 20 minutes for ui#852, now size M. Walk stop 3 (#1631) lists #1804, #1805, #1806, ui#1068 and ui#1069 in Deps:, and #1655 lists #1807 and ui#1070 to ui#1073. Sizing: #1804 and #1805 at tier 3, #1806 and ui#1073 at tier 1, the other six and ui#852 at tier 2, and none is a Fable candidate. Eight choices were settled by the decision protocol (/reassess, one refuter: six confirmed, two corrected), each recorded as a comment on its issue. The registry coverage check prints unmapped: empty over 314 issues. Drained found-in:*: no label of an earlier phase holds an open issue; #1793 and ui#914 (found-in:phase-17b) stay out, as recorded on 2026-10-11. The milestone holds 316 issues: backbone 125, UI 186 and engine 5.

Walk stop 3 walked, 2026-10-14. The owner walked prod on v0.30.0 / UI v0.20.0 (R3, deploy run 38398954001). Ten findings are in the Walk stop 3 table under Findings (AD-18, EX-32, LK-18 to LK-22, SH-30, EV-21, PF-14). AD-18 is fixed on trunk by PR #1835. #1631 stays open on its second box until a release carries #1835 and the owner sees Adapt's appraisal on prod (script steps E5 and E6). The fix's residue #1836 has no milestone yet. The delta split files the other nine, and #1836, onto the open W6 issues whose Files: own them, else onto issues of their own.

Delta split of walk stop 3, 2026-10-14. The ten findings became ten issues and four admissions. Backbone: #1838 (EX-32), #1839 (LK-18), #1840 (LK-20), #1841 (an agent tracker: the prod read of PF-14, which files its fix) and #1842 (cut from #1833). UI: ui#1140 (SH-30), ui#1141 (EV-21), ui#1142 (LK-19), ui#1143 (LK-21) and ui#1144 (LK-22). No open W6 issue's Files: owned a finding, so each has its own issue. AD-18 itself is fixed by #1835 and stays on #1631's second box, which now names it: the owner runs /release from the primary checkout, then walks script steps E5 and E6. The drain admitted AD-18's residue #1836 and the found-in:phase-17b issues #1833, ui#1135 and ui#1137; #1793 and ui#914 stay out, as the walk stop 2 split left them. #1833 sized L, so its precision finding moved to #1842, and its Deps: dropped ui#1135, which consumes its tile route (ui#1135 now names #1833). New rows: B23c after B23b, U38 after U37, U38b after U38 and B23c, and an Ops row for #1841 once walk stop 3 closes. W7 now waits on B23c and U38b, and #1655's Deps: gained the 13 build issues. Sizing: 11 at tier 2 and 2 at tier 1 (ui#1143, ui#1144), none above. Sizers added src/shell/BottomTabs.tsx to ui#1140 and LookupInsights.tsx with its test to ui#1144.

Walk stop 3 closed, 2026-10-14. Release R3.1 shipped backbone v0.30.1 on 8b833155 (PR #1844, ci.yml 38526939322, deploy 38528649564), carrying #1835. The owner then saw Adapt's appraisal on prod (script steps E5 and E6) and #1631 closed as completed with its four boxes ticked. The walk proved the prod proofs of #1805, #1629, #1608, ui#806, ui#812 and ui#814, whose labels came off. #1630, ui#805 and ui#811 keep theirs: the Cyprus pin and wildfire at Today were not read. W6 dispatches from B18 and U27, with the Ops row of #1841 beside them.

Estimate. At the sizing table's p75 step times the build span is 110 h with the lanes, against 194 h for one session at a time. The span is the longest path through the table's After order at each row's budget, and the serial figure is every build row's budget in turn. A row's budget is its serial chains (issues joined by a dep or a shared non-docs file) by Size: on the lane's two live builders, plus the 20 minute reserve and 80 minutes for each heavy spike it carries. Read the same way, the 2026-10-03 table gives 87 h and 152 h, and 101 h and 174 h with its rows recounted by this budget rule. So the recount explains 14 h of the span's rise, and the 33 new issues with the amended deps explain the other 9 h. Phase 17's waves ran at about 0.78 of their budget, which puts the span near 86 h. The owner's release and walk at each of the three stops and the engine tag come on top.

Conflict hotspots (files most issues touch, serialised by /phase-build):

  • Backbone: worker/tasks.py (27), api/v1/compute.py (18), core/compute_service.py (17), worker/models/engine_adapter.py (10), tests/test_api/test_compute_run_contract.py (10).
  • UI: src/types/index.ts (55), src/i18n/locales/en/explore.json (30), src/explore/EventsMode.tsx (20), src/pages/ExplorePage.tsx (20), src/i18n/locales/en/settings.json (20), src/i18n/locales/en/live.json (19).
  • Engine: climate_lama_engine/cost_benefit.py (2), tests/test_cost_benefit.py (2).

docs/CLIMATE_LAMA.md is in 65 backbone Files: lines. It is shared as docs only, which /phase-build never serialises.

Issue map. #N is the backbone, ui#N is CortoMaltese3/climate-lama-ui and engine#N is CortoMaltese3/climate-lama-engine. An issue sits under every finding its Finding: line names. Under the 17 findings of the fine-tune it also sits where it owns a registry entry the finding's row names.

  • AD: AD-1 #1594 ui#751 ui#790 ui#791 ui#792 ui#793 · AD-2 #1566 ui#797 · AD-3 ui#794 ui#796 ui#799 · AD-4 #1619 #1620 ui#795 ui#802 · AD-5 #1594 ui#797 · AD-6 #1623 ui#800 · AD-7 #1628 ui#803 · AD-8 #1632 #1641 #1642 ui#834 ui#875 · AD-9 #1643 ui#836 · AD-10 ui#832 · AD-11 #1619 #1620 #1621 ui#798 · AD-12 #1619 #1623 #1624 ui#800 · AD-13 #1619 #1624 #1632 #1641 ui#799 ui#834 · AD-14 #1619 #1620 #1625 #1626 · AD-15 #1619 #1622 ui#797 · AD-16 #1619 #1627 ui#801 · AD-17 #1632 ui#794 ui#834 ui#874
  • AS: AS-1 #1567 #1602 ui#768 · AS-2 #1603 #1604 ui#769 ui#770 ui#855 · AS-3 #1644 #1656 · AS-4 #1603 #1604 ui#769 ui#770 ui#853 · AS-5 #1650 ui#852
  • DT: DT-1 #1574 ui#823 · DT-2 #1575 ui#823 · DT-3 #1630 ui#823 · DT-4 #1633 ui#825 · DT-5 #1576 ui#823 · DT-6 #1645 · DT-7 #1667 #1670 ui#767 ui#822 ui#823 ui#824 ui#876 ui#877
  • EN: EN-1 #1569 · EN-2 #1570 · EN-3 #1646 #1647 · EN-4 #1639 #1640 #1648 ui#837 · EN-5 ui#752 · EN-6 #1649 · EN-7 #1585 #1596 engine#45 engine#48 engine#49 ui#739 ui#762 · EN-8 #1585 engine#46 engine#47 engine#48 engine#49
  • EV: EV-1 #1614 ui#716 ui#771 · EV-2 ui#773 ui#774 · EV-3 #1612 ui#776 · EV-4 #1613 ui#787 · EV-5 ui#775 · EV-6 ui#788 ui#835 · EV-7 ui#778 · EV-8 #1612 ui#786 · EV-10 #1607 #1608 ui#779 · EV-11 #1605 #1606 ui#777 · EV-12 #1609 #1610 ui#781 · EV-13 #1615 ui#782 · EV-14 #1611 #1617 ui#783 ui#784 ui#848 ui#878 · EV-16 #1608 ui#785 · EV-17 #1616 ui#780 · EV-18 ui#772 · EV-19 ui#868 ui#869 ui#878 · EV-20 #1807 ui#1071
  • EX: EX-1 #1563 ui#713 ui#714 · EX-2 ui#717 · EX-3 ui#748 · EX-4 ui#744 ui#748 · EX-5 ui#746 · EX-6 ui#763 · EX-7 ui#760 · EX-8 ui#758 · EX-9 ui#764 · EX-10 #1595 #1664 ui#755 ui#756 ui#873 · EX-11 #1587 ui#752 ui#872 · EX-12 ui#745 ui#765 ui#790 · EX-13 ui#748 · EX-14 #1597 #1598 #1685 #1686 ui#754 ui#756 ui#757 ui#758 ui#887 · EX-15 #1586 #1587 #1588 #1590 #1591 #1592 #1593 #1594 #1596 #1599 #1600 #1601 ui#879 · EX-16 #1589 ui#744 ui#745 ui#746 ui#747 ui#748 ui#749 ui#750 ui#751 ui#752 ui#753 ui#766 ui#767 ui#879 · EX-17 #1568 · EX-18 #1660 ui#746 ui#766 ui#767 ui#790 · EX-19 #1590 #1604 #1661 ui#747 ui#865 · EX-20 #1639 #1654 #1662 ui#748 ui#766 ui#789 ui#844 ui#866 · EX-21 #1663 ui#744 ui#767 ui#845 ui#878 ui#879 · EX-22 ui#871 ui#878 ui#879 · EX-23 #1588 #1589 ui#744 ui#766 ui#870 · EX-26 #1803 #1804 #1805 · EX-27 ui#1068 · EX-28 #1806 ui#1069 · EX-29 ui#1070 · EX-30 ui#1072 · EX-31 ui#1073
  • LK: LK-1 ui#804 ui#805 · LK-2 ui#718 · LK-3 ui#810 · LK-4 ui#806 · LK-5 ui#725 ui#760 · LK-6 ui#808 · LK-7 ui#811 · LK-8 ui#809 · LK-9 #1629 ui#812 · LK-10 ui#813 · LK-11 ui#807 · LK-12 ui#831 · LK-13 ui#814 · LK-14 #1668 ui#867 · LK-15 #1684 ui#886
  • PF: PF-1 ui#711 ui#719 · PF-2 #1577 #1578 #1579 ui#738 ui#821 · PF-3 #1578 #1583 ui#735 ui#736 ui#739 ui#832 · PF-4 #1591 ui#743 ui#749 ui#766 · PF-5 ui#734 · PF-6 #1638 #1650 ui#830 ui#833 ui#838 · PF-7 #1666 ui#826 ui#828 ui#829 ui#860 · PF-8 #1571 #1599 ui#720 ui#828 ui#848 · PF-9 #1580 ui#741 · PF-10 #1579 #1582 ui#737 · PF-11 #1581 #1665 ui#741 · PF-12 #1667 #1688 ui#823 ui#829 ui#853 ui#861
  • RP: RP-1 #1637 · RP-2 #1637 #1638 ui#823 ui#833 ui#845 ui#869
  • SH: SH-1 ui#721 · SH-2 ui#723 ui#759 ui#760 · SH-3 #1584 ui#724 · SH-4 ui#730 ui#731 ui#732 · SH-5 ui#769 ui#852 · SH-6 ui#854 · SH-7 #1562 #1564 ui#710 ui#712 ui#715 · SH-8 ui#735 · SH-9 ui#853 · SH-10 #1635 #1636 #1651 #1652 #1653 #1654 ui#821 ui#824 ui#827 ui#838 ui#840 ui#841 ui#842 ui#844 ui#847 · SH-11 #1617 ui#789 · SH-12 ui#794 ui#815 ui#819 ui#835 · SH-13 ui#820 · SH-14 ui#839 · SH-15 #1652 ui#840 ui#843 · SH-16 #1634 #1687 #1635 #1636 #1653 #1654 ui#821 ui#822 ui#823 ui#824 ui#827 ui#841 ui#842 ui#844 ui#845 ui#846 ui#847 ui#848 · SH-17 ui#729 · SH-18 ui#731 ui#854 · SH-19 ui#761 ui#856 · SH-20 ui#726 ui#733 · SH-21 ui#722 ui#728 ui#742 · SH-22 ui#727 ui#827 ui#846 · SH-23 ui#816 · SH-24 ui#817 ui#818 ui#849 ui#850 ui#851 ui#853 · SH-25 ui#745 ui#753 ui#853 ui#854 ui#862 · SH-26 #1652 #1669 #1680 ui#840 ui#841 ui#842 ui#843 ui#880 · SH-27 ui#864 · SH-28 ui#863
  • SP: SP-1 #1573 · SP-2 ui#708 ui#709 ui#710 ui#859 · SP-3 #1565

Wave table. One row per session: 23 B rows in the backbone lane, 36 U rows in the UI lane and E1 in the engine lane (Lanes). Walk stop rows are walls for every lane. A row holds at most 8 build targets (K72, 2026-10-08; 5 until then, which U1 and U2 already exceeded), and every issue's Deps: sit in an earlier row its After reaches, or in the same row's chain. A tier 3 issue runs on builder-max, which changes nothing in the command.

Wave Command After Owner Budget (min) Covers
Ops ~~Tick boxes 3 and 4 of #1499 from run 1's evidence (peak 204.5 MiB of 2 GiB, comparison 9760fef3 exit 0), then close it~~ — done 2026-10-07 (session lama-phase-plan-P17): ticked and closed now agent — #1499
Ops ~~Tick box 1 of #1501 (its own release comment proves it)~~ — done 2026-10-07 (session lama-phase-plan-P17): ticked now agent — #1501
Ops ~~grep '^ASSISTANT_' /opt/climate-lama/.env.prod on the prod host, read-only. Remove or change an explicit ASSISTANT_MODEL=claude-opus-5-5 at the R1 deploy~~ — done 2026-10-07 (session lama-phase-plan-P17): prod sets ASSISTANT_MODEL=claude-sonnet-5-5, so R1 changes nothing (#1567 comment) before walk stop 1's /release agent — AS-1
Ops ~~Close the two Phase 13 replay watches on prod (data call)~~ — done 2026-10-07 (session lama-phase-plan-P17): both closed through POST /v1/live-events/{id}/close (CortoMaltese3/climate-lama-ui#778 comment) now agent — EV-7
Ops Paste the 24 h poll-health read into #1300 and close it. The 2026-10-07 read found 164 polls in 24 h against about 288 (feeds skip every other beat), so #1300 stays open as the prod proof of #1691 24 h after the release that carries #1691 (B5) agent — EV-9 (#1300)
B1 · W0a+W0 /phase-build 1562 1563 1565 1568 1571 --no-pr-ci --max-runtime 260 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes now, and its wave gate waits until CortoMaltese3/climate-lama-ui#709 reads CLOSED (the gate lock exists) agent · backbone lane 260 EX-1, EX-17, PF-8, SH-7, SP-3
U1 · W0a+W0 /phase-build 709 708 710 711 715 718 --no-pr-ci --max-runtime 260 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · the orchestrator runs the tracker ui#709 first, so the gate lock exists before any heavy gate step in either lane now agent · UI lane 260 LK-2, PF-1, SH-7, SP-2
B2 · W0a+W0 /phase-build 1564 1566 1567 1569 1570 1679 --no-pr-ci --max-runtime 270 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B1 lands agent · backbone lane 270 AD-2, AS-1, EN-1, EN-2, SH-7, SP-3
U2 · W0a+W0 /phase-build 889 715 712 713 859 719 721 --no-pr-ci --max-runtime 280 from ../climate-lama-ui · U1's residue rides here: ui#889 builds and ui#715's box 4 is proven by this row's wave gate · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U1 lands and B1 lands agent · UI lane 280 EX-1, PF-1, SH-1, SH-7, SP-2
U3 · W0a+W0 /phase-build 714 716 717 720 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U2 lands and B1 lands agent · UI lane 120 EV-1, EX-1, EX-2, PF-8
Walk stop 1 /release (R1), then #1572's Run: the owner walks prod per its second box, then the delta /phase-split docs/plan/phase-17b-walk-corrections.md B2 lands and U3 lands owner (walk), agent (release, delta split) — Moments 1 to 4, demo slice steps 3 to 6, Walk still to do, 390 px
B3 · W1 /phase-build 1573 1574 1577 1578 1579 1583 --no-pr-ci --max-runtime 270 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes walk #1572 closed agent · backbone lane 270 DT-1, PF-2, PF-3, PF-10, SP-1
Credential gh secret set VITE_SATELLITE_TILES -R CortoMaltese3/climate-lama-ui with the template from docs/plan/exploration/basemap.md, then close CortoMaltese3/climate-lama-ui#856 B3 lands (SP-1), before walk stop 2's /release owner — SH-19
E1 · engine /phase-build 45 46 47 48 --no-pr-ci --max-runtime 235 from ../climate-lama-engine · breaker: python ../climate-lama/scripts/rework_rate.py --repo CortoMaltese3/climate-lama-engine --since 2026-10-02 --follow-ups --json · phase doc ../climate-lama/docs/plan/phase-17b-walk-corrections.md walk #1572 closed agent · engine lane 235 EN-7, EN-8
E1 tag /engine-tag v0.6.0 from ../climate-lama-engine (owner-typed, the skill has disable-model-invocation), then wait for release.yml, closes CortoMaltese3/climate-lama-engine#49 E1 lands owner — EN-7, EN-8
U4 · W1 /phase-build 722 729 730 731 732 --no-pr-ci --max-runtime 230 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes walk #1572 closed agent · UI lane 230 SH-4, SH-17, SH-18, SH-21
U5 · W1 /phase-build 723 724 725 862 863 --no-pr-ci --max-runtime 190 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U4 lands agent · UI lane 190 LK-5, SH-2, SH-3, SH-25, SH-28
B4 · W1 /phase-build 1575 1576 1580 1581 1665 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B3 lands agent · backbone lane 225 DT-2, DT-5, PF-9, PF-11
U6 · W1 /phase-build 726 727 728 733 735 --no-pr-ci --max-runtime 160 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U5 lands and B3 lands agent · UI lane 160 PF-3, SH-8, SH-20, SH-21, SH-22
B5 · W1 /phase-build 1582 1584 1691 --no-pr-ci --max-runtime 110 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B4 lands agent · backbone lane 110 EV-9, PF-10, SH-3
B6 · W2+W2b+W3 /phase-build 1586 1587 1589 1597 1660 --no-pr-ci --max-runtime 200 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B5 lands agent · backbone lane 200 EX-11, EX-14, EX-15, EX-16, EX-18, EX-23
U7 · W1 /phase-build 734 736 737 738 739 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U6 lands and B5 lands agent · UI lane 150 EN-7, PF-2, PF-3, PF-5, PF-10
U8 · W1 /phase-build 740 741 742 864 --no-pr-ci --max-runtime 140 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U7 lands and B4 lands agent · UI lane 140 PF-3, PF-9, PF-11, SH-21, SH-27
B7 · W2+W2b+W3 /phase-build 1588 1590 1591 1598 1602 --no-pr-ci --max-runtime 260 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B6 lands agent · backbone lane 260 AS-1, EX-14, EX-15, EX-19, EX-23, PF-4
U9 · W2+W2b+W3 /phase-build 759 772 773 774 775 --no-pr-ci --max-runtime 200 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U8 lands and B3 lands agent · UI lane 200 EV-2, EV-5, EV-18, SH-2
B8 · W2+W2b+W3 /phase-build 1592 1593 1594 1603 1663 --no-pr-ci --max-runtime 260 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B7 lands agent · backbone lane 260 AD-1, AD-5, AS-2, AS-4, EX-15, EX-21
U10 · W2+W2b+W3 /phase-build 743 744 745 760 761 --no-pr-ci --max-runtime 290 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U9 lands and B7 lands agent · UI lane 290 EX-4, EX-7, EX-12, EX-16, EX-21, EX-23, LK-5, PF-4, SH-2, SH-19
B9 · W2+W2b+W3 /phase-build 1585 1595 1599 1661 1662 --no-pr-ci --max-runtime 270 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B8 lands and CortoMaltese3/climate-lama-engine#49 closed (engine tag) agent · backbone lane 270 EN-7, EN-8, EX-10, EX-15, EX-19, EX-20, PF-8
B9b · W2+W2b+W3 /phase-build 1686 --no-pr-ci · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B9 lands agent · backbone lane 100 EX-14
U11 · W2+W2b+W3 /phase-build 768 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U10 lands and B7 lands agent · UI lane 70 AS-1
B10 · W2+W2b+W3 /phase-build 1596 1604 1605 1606 1664 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B9b lands agent · backbone lane 225 AS-2, AS-4, EN-7, EV-11, EX-10, EX-15, EX-19
U12 · W2+W2b+W3 /phase-build 746 747 748 749 750 --no-pr-ci --max-runtime 230 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U11 lands and B9 lands agent · UI lane 230 EX-3, EX-4, EX-5, EX-13, EX-16, EX-18, EX-19, EX-20, PF-4
B11 · W2+W2b+W3 /phase-build 1600 1601 1607 1608 1609 --no-pr-ci --max-runtime 270 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B10 lands agent · backbone lane 270 EV-10, EV-12, EV-16, EX-15
U13 · W2+W2b+W3 /phase-build 751 752 753 754 755 --no-pr-ci --max-runtime 270 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U12 lands and B9b lands agent · UI lane 270 AD-1, EN-5, EX-10, EX-11, EX-14, EX-16
B12 · W2+W2b+W3 /phase-build 1610 1611 1612 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B11 lands agent · backbone lane 225 EV-3, EV-8, EV-12, EV-14
U14 · W2+W2b+W3 /phase-build 873 870 756 757 758 --no-pr-ci --max-runtime 230 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U13 lands and B10 lands agent · UI lane 230 EX-8, EX-10, EX-14, EX-23
B13 · W2+W2b+W3 /phase-build 1613 1614 1615 1616 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B12 lands agent · backbone lane 225 EV-1, EV-4, EV-13, EV-17
U15 · W2+W2b+W3 /phase-build 776 777 778 779 780 --no-pr-ci --max-runtime 230 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U14 lands and B12 lands agent · UI lane 230 EV-3, EV-7, EV-10, EV-11, EV-17
B14 · W2+W2b+W3 /phase-build 1617 1685 --no-pr-ci --max-runtime 250 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B13 lands agent · backbone lane 250 EV-14, EX-14, SH-11
U16 · W2+W2b+W3 /phase-build 762 763 781 784 785 --no-pr-ci --max-runtime 130 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U15 lands and B13 lands agent · UI lane 130 EN-7, EV-12, EV-14, EV-16, EX-6
U17 · W2+W2b+W3 /phase-build 764 765 786 787 871 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U16 lands and B12 lands agent · UI lane 110 EV-4, EV-8, EX-9, EX-12, EX-22
U18 · W2+W2b+W3 /phase-build 766 769 770 788 866 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U17 lands and B10 lands agent · UI lane 150 AS-2, AS-4, EV-6, EX-16, EX-18, EX-20, EX-23, PF-4, SH-5
Credential ~~gh secret set ANTHROPIC_API_KEY -R CortoMaltese3/climate-lama-ui~~ — done 2026-10-10 (the owner set it, CortoMaltese3/climate-lama-ui#855's comment). What is left is its second box, one green run of the live job: gh workflow run e2e-smoke.yml -R CortoMaltese3/climate-lama-ui --ref main -f assistant_live=true, then close CortoMaltese3/climate-lama-ui#855 with the run id and its counts. The live spec sends the Greece sentence on the smoke stack, so it needs the seed of #1785. The workflow cancels a run in flight on the same ref, so it is dispatched only when no other e2e-smoke.yml run on main is going (amended 2026-10-11) U20c lands agent — AS-2
U19 · W2+W2b+W3 /phase-build 767 771 782 783 865 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U18 lands and B13 lands agent · UI lane 120 DT-7, EV-1, EV-13, EV-14, EX-16, EX-18, EX-19, EX-21
U20 · W2+W2b+W3 /phase-build 789 868 872 --no-pr-ci --max-runtime 170 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · ui#887 moved to U20b on 2026-10-11: its Deps: name #1783, which is open, and step 2 of /phase-build halts a run on an unmerged dep U19 lands and B14 lands agent · UI lane 170 EV-19, EX-11, SH-11
B14b · W2+W2b+W3 /phase-build 1785 1783 --no-pr-ci --max-runtime 190 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes · runs beside U19 and U20 (another lane, no shared file) · the third box of each issue is a UI stack run: both PRs land with Refs, and U20b's stack run ticks the boxes and closes the issues B14 lands agent · backbone lane 190 AS-4, EX-10, EX-14
Ops gh workflow run e2e-smoke.yml -R CortoMaltese3/climate-lama-ui --ref main, read with gh run list -R CortoMaltese3/climate-lama-ui --workflow e2e-smoke.yml -L 1 --json headSha,conclusion. The workflow last ran on 2026-10-09 (run 37987594535, red at d9e654e) and the UI release gate needs it green at the tag sha (publish.yml's verify-ci). This run is a canary over the commits since. The run that gates R2 is the one /release dispatches at the bump sha. Ran 2026-10-11: run 38140385954 at UI 00ca6ef was red on three tests, the two place sentences of assistant-run.spec.ts (the seed, fixed by #1785 since) and contract.spec.ts (ui#1052, row U20c). Run 38164803210 was dispatched at UI main add9694 after U20b. The next run is ui#1052's second box, in row U20c B14b lands (the workflow builds backbone main, and the place sentence of assistant-run.spec.ts fails on the seed before #1785) agent — R2's gate
U20b · W2+W2b+W3 /phase-build 770 1037 887 --no-pr-ci --max-runtime 170 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · chain ui#770 then ui#1037 (both write tests/e2e/smoke/assistant-run.spec.ts), ui#887 beside it · ui#770 rides for its one open box (assistant-6), which passes on the seed of #1785. That stack run also ticks the third box of #1785, and ui#887's "hexagon value" spec ticks the third box of #1783 U20 lands and B14b lands agent · UI lane 170 AS-2, AS-4, EX-14
Ops Record here the four targets the framework set for walk stop 2 (K72): the UI's escape rate with harness fixes left out (at or below 0.25), the design differences a detached gate posted on a view whose PR carried ## Design read (none), each U row's wall-clock against twice its slowest build, and the stack gate kills (none). Read them with python scripts/rework_rate.py --repo CortoMaltese3/climate-lama-ui --since 2026-10-08 --follow-ups, python ~/.claude/scripts/build_timing.py --since 2026-10-08 and the rows' ledgers. Read 2026-10-12, after U21 and with U21b live, from the ledgers of U12 to U21 on one machine (the rows before U12 ran on the other machine and were not read): one target met, three missed. (1) Escape rate: 0.44 (39/89 builds, 28 harness fix PRs not counted, coverage 0.97, partial rate 0.06) on --since 2026-10-08, against 0.25. Missed. The window open now, --since-ts 2026-10-11T17:49:09Z, reads 0.25 (1/4, the follow-up ui#1077), which does not trip the breaker. (2) Design differences a gate posted after the merge on a view whose PR carried ## Design read: three views, against none. ui#788 (PR #1035, Events at 390 px) and ui#866 (PR #1039, the Recent activity rows) from U18's stack gate, and ui#789 (PR #1051, the bell) from U20's. Missed. U21's one difference (the YEAR label of PR #1067) was sent back before the merge. (3) Row wall-clock against twice the slowest build: no row made it. U18 2.4×, U21 2.7×, U17 3.3×, U16 3.5×, U20 3.7×, U15 4.2×, U19 5.7×, U13 7.0×, U14 7.1× and U20c 8.7× (rows of 105, 150, 106, 189, 165, 213, 170, 296, 340 and 54 min; slowest builds of 43, 56, 32, 54, 45, 51, 30, 42, 48 and 6 min). Missed. U12 has one of its five builds timed on this machine and U20b ran fixers only, so neither is rated. (4) Stack gate kills: none recorded in those ledgers. Met. build_timing.py --since 2026-10-08 on the same machine: 115 builds from 2026-10-09 21:01, mean 24 min, model 47.5 % of builder wall-clock, child agents 19.5 %, checks on the critical path 22.4 % (the framework's target of 2026-09-28 is 15 %) U20b lands agent — K72
U20c · W2+W2b+W3 /phase-build 1052 --no-pr-ci --max-runtime 60 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · the last row before R2 (added 2026-10-11). ui#1052 is the red contract.spec.ts of the e2e-smoke.yml canary: contract-map.ts rejects the Today cell (horizon_year: null) the backbone documents. Its second box is a green e2e-smoke.yml run on UI main after the merge, one run at a time on main. A spec that run shows red is this row's residue, fixed in-run or filed onto this row U20b lands agent · UI lane 60 R2's gate
Ops On prod after the R2 deploy and before the owner walks (#1618's second box). (a) The GET /v1/exposure-datasets list for Greece gives default_dataset_id. GET /v1/compute/hazard-options?exposure_dataset_ids=<id> then reads river flood state: covered with a today cell and a 2050 cell among its cells, and reads the same with admin_boundary_ids=<one level-1 region>. The coverage test is geometric (ST_Covers over every member extent, hazard_repository.py), so a dataset tighter than the layer reads no_single_dataset on prod as it did on the seed (#1785). GET /v1/admin-boundaries lists Greece's level-1 regions and GET /v1/measures lists a measure for the org. (b) Prod's North Evia watch is re-run through a replay feed over its archived frames, so its headline reads cumulative snapshots (the prod proof of #1608). (c) One run that fails is posted through the API (the Cyprus baseline with the LitPop Greece exposure), so the owner can see how a failed run ends (EX-25). (d) An insured and a ground-up run over the same inputs return different results (EX-17), and a Lookup with no scenario answers from the baseline dataset (LK-17). Each read is pasted on #1618 walk stop 2's release (R2) agent — AS-4, EV-10, EX-17, EX-18, EX-23, EX-25, LK-17
Ops On prod after R2: read a USD portfolio's card EAD (Evia 2021, read on 2026-10-08, or any USD portfolio with a plain river-flood run) against ead of GET /v1/results/{latest_result_id}, the id GET /v1/portfolios/summary serves for it, and record the match in #1501. After #1599 only a plain portfolio run stands as a portfolio's latest result, so a clipped or adapted run of this walk is not the figure to compare, and a portfolio with no plain run reads "not computed for this portfolio", which is no mismatch. Reworded on 2026-10-11: the row read "the LitPop Greece book" and waited for walk stop 1's release, and its read of 2026-10-08 failed on PF-13, whose fix (ui#736) first ships in R2 walk stop 2's release (R2) owner — PF-8, PF-13
Walk stop 2 /release (R2), then #1618's Run: the agent reads of its second box, the owner walks prod per its third and fourth boxes, then the delta /phase-split docs/plan/phase-17b-walk-corrections.md B14b lands and U20b lands and U20c lands and E1 lands owner (walk), agent (release, prod reads, delta split) — Moment 3 through the two-step dock (A place narrowed to several regions, and My assets), a run in flight found again (EX-20 without Stop), Moment 4 steps 1 to 8, demo slice, AS-2, AS-4. Folded from walk stop 1 on 2026-10-11: Compare, EX-24, EX-25, PF-13, LK-17, the PF-8 read, Moment 2 as far as W1 built it with PF-9 and PF-10, EX-17 and EV-10
B15 · W4+W5 /phase-build 1619 1620 1621 1628 1629 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes walk #1618 closed agent · backbone lane 225 AD-4, AD-7, AD-11, AD-12, AD-13, AD-14, AD-15, AD-16, LK-9
B15b · W4+W5 /phase-build 1803 1804 1806 --no-pr-ci --max-runtime 130 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes · added 2026-10-12 from walk stop 2. The orchestrator runs the tracker #1803 first (a read-only plan read on prod, pasted on #1805). #1804 and #1806 build beside it, with no shared file B15 lands agent · backbone lane 130 EX-26, EX-28
B15c · W4+W5 /phase-build 1805 --no-pr-ci · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes · a row of its own because its Deps: name the tracker #1803, and step 2 of /phase-build halts a run on an unmerged dep B15b lands and #1803 closed agent · backbone lane 110 EX-26
U21 · W4+W5 /phase-build 790 791 804 805 806 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes walk #1618 closed agent · UI lane 150 AD-1, EX-12, LK-1, LK-4
U21b · W4+W5 /phase-build 1068 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · added 2026-10-12 from walk stop 2 U21 lands agent · UI lane 70 EX-27
U22 · W4+W5 /phase-build 794 796 807 808 809 --no-pr-ci --max-runtime 130 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U21b lands agent · UI lane 130 AD-3, LK-6, LK-8, LK-11, SH-12
B16 · W4+W5 /phase-build 1622 1623 1624 1630 1668 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B15c lands agent · backbone lane 225 AD-6, AD-12, AD-13, AD-15, DT-3, LK-14
U23 · W4+W5 /phase-build 792 795 810 811 874 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U22 lands and B15 lands agent · UI lane 150 AD-1, AD-4, AD-17, LK-3, LK-7
U23b · W4+W5 /phase-build 1069 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · a row of its own because its Deps: name CortoMaltese3/climate-lama#1806 (row B15b) U23 lands and CortoMaltese3/climate-lama#1806 closed agent · UI lane 70 EX-28
U24 · W4+W5 /phase-build 793 812 814 --no-pr-ci from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U23b lands and B15 lands agent · UI lane 80 AD-1, LK-9, LK-13
B17 · W4+W5 /phase-build 1625 1626 1627 1684 --no-pr-ci --max-runtime 250 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B16 lands agent · backbone lane 250 AD-14, AD-16, LK-15
U25 · W4+W5 /phase-build 797 798 799 800 813 --no-pr-ci --max-runtime 250 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U24 lands and B16 lands agent · UI lane 250 AD-2, AD-3, AD-5, AD-6, AD-11, AD-12, AD-13, AD-15, LK-10
U26 · W4+W5 /phase-build 801 802 803 867 886 --no-pr-ci --max-runtime 290 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U25 lands and B17 lands agent · UI lane 290 AD-4, AD-7, AD-16, LK-14, LK-15
Walk stop 3 /release (R3), then #1631's Run: the owner walks prod per its second box, then the delta /phase-split docs/plan/phase-17b-walk-corrections.md B17 lands and U26 lands owner (walk), agent (release, delta split) — Moment 1 with the pin's place card (LK-14), Moment 4 step 9 from Adapt's landing page (AD-17), and EX-26, EX-27 and EX-28 seen fixed
B18 · W6 /phase-build 1632 1633 1636 1639 1652 --no-pr-ci --max-runtime 270 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes walk #1631 closed agent · backbone lane 270 AD-8, AD-13, AD-17, DT-4, EN-4, EX-20, SH-10, SH-15, SH-16, SH-26
B18b · W6 /phase-build 1688 --no-pr-ci · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B18 lands agent · backbone lane 100 PF-12
U27 · W6 /phase-build 815 816 817 818 819 --no-pr-ci --max-runtime 220 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes walk #1631 closed agent · UI lane 220 SH-12, SH-23, SH-24
U28 · W6 /phase-build 820 821 826 849 850 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U27 lands agent · UI lane 150 PF-2, PF-7, SH-10, SH-13, SH-16, SH-24
B19 · W6 /phase-build 1634 1637 1640 1654 1666 --no-pr-ci --max-runtime 190 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B18b lands agent · backbone lane 190 EN-4, EX-20, PF-7, RP-1, RP-2, SH-10, SH-16
U29 · W6 /phase-build 827 828 829 840 852 --no-pr-ci --max-runtime 240 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U28 lands and B18b lands agent · UI lane 240 AS-5, PF-7, PF-8, SH-5, SH-10, SH-15, SH-16, SH-22, SH-26
B20 · W6 /phase-build 1638 1642 1667 1670 1669 --no-pr-ci --max-runtime 280 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B19 lands agent · backbone lane 280 AD-8, DT-7, PF-6, PF-12, RP-2, SH-26
B20b · W6 /phase-build 1680 1687 1689 --no-pr-ci --max-runtime 280 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B20 lands agent · backbone lane 280 SH-16, SH-24, SH-26
U30 · W6 /phase-build 831 837 844 845 851 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U29 lands and B19 lands agent · UI lane 150 EN-4, EX-20, EX-21, LK-12, SH-10, SH-16, SH-24
B21 · W6 /phase-build 1635 1641 1643 1644 1653 --no-pr-ci --max-runtime 190 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B20b lands agent · backbone lane 190 AD-8, AD-9, AD-13, AS-3, SH-10, SH-16
U31 · W6 /phase-build 822 830 833 841 876 --no-pr-ci --max-runtime 170 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U30 lands and B20b lands agent · UI lane 170 DT-7, PF-6, RP-2, SH-10, SH-16, SH-26
U32 · W6 /phase-build 823 835 878 848 853 --no-pr-ci --max-runtime 210 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U31 lands and B20 lands agent · UI lane 210 AS-4, DT-1, DT-2, DT-3, DT-5, DT-7, EV-14, EX-21, EX-22, PF-8, SH-9, SH-12, SH-16, SH-24
B22 · W6 /phase-build 1645 1646 1647 1648 1650 --no-pr-ci --max-runtime 190 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B21 lands agent · backbone lane 190 AS-5, DT-6, EN-3, EN-4, PF-6
U33 · W6 /phase-build 824 825 842 846 854 --no-pr-ci --max-runtime 150 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U32 lands and B21 lands agent · UI lane 150 DT-4, DT-7, SH-6, SH-10, SH-16, SH-18, SH-22, SH-25, SH-26
B23 · W6 /phase-build 1649 1651 --no-pr-ci · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes B22 lands agent · backbone lane 65 EN-6, SH-10
B23b · W6 /phase-build 1807 --no-pr-ci · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes · added 2026-10-12 from walk stop 2 B23 lands agent · backbone lane 100 EV-20
U34 · W6 /phase-build 832 836 838 839 843 --no-pr-ci --max-runtime 130 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U33 lands and B22 lands agent · UI lane 130 AD-9, AD-10, PF-6, SH-14, SH-15, SH-26
U35 · W6 /phase-build 834 847 860 875 879 --no-pr-ci --max-runtime 180 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U34 lands and B21 lands agent · UI lane 180 AD-8, AD-13, AD-17, EX-15, EX-16, EX-22, PF-7, SH-10, SH-16
U36 · W6 /phase-build 861 869 877 880 888 --no-pr-ci --max-runtime 330 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes U35 lands and B20b lands agent · UI lane 330 DT-7, PF-12, RP-2, SH-24, SH-26
U37 · W6 /phase-build 1070 1073 1071 1072 --no-pr-ci --max-runtime 140 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · added 2026-10-12 from walk stop 2 · chain ui#1070 then ui#1073 (both write explore.json), ui#1071 and ui#1072 beside it U36 lands and B23b lands agent · UI lane 140 EV-20, EX-29, EX-30, EX-31
B23c · W6 /phase-build 1838 1839 1840 1836 1842 1833 --no-pr-ci --max-runtime 225 · backbone only, no sibling lane · ≤2 live builders · lane rules: § Lanes · added 2026-10-14 from walk stop 3 · chains #1839 then #1833 (both write api/v1/tiles.py) and #1842 then #1833 (both write core/indicator_sampling.py), #1838, #1840 and #1836 beside them B23b lands agent · backbone lane 225 AD-16, AD-18, EX-32, LK-18, LK-20
Ops agent-run: #1841's Run, the read-only prod read of PF-14, then its fix filed by Files: walk #1631 closed agent — PF-14
U38 · W6 /phase-build 1140 1141 1142 1143 1144 --no-pr-ci --max-runtime 130 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · added 2026-10-14 from walk stop 3 · no shared file, so all five run beside each other U37 lands agent · UI lane 130 EV-21, LK-19, LK-21, LK-22, SH-30
U38b · W6 /phase-build 1137 1135 --no-pr-ci --max-runtime 70 from ../climate-lama-ui · ≤2 live builders · phase doc and lane rules: ../climate-lama/docs/plan/phase-17b-walk-corrections.md § Lanes · added 2026-10-14, found-in residue admitted by the walk stop 3 split · ui#1137 after ui#1143 (lookup.json), ui#1135 on #1833's tile route U38 lands and B23c lands agent · UI lane 70 AD-16, lookup residue of U21
Credential ~~Add OPENAI_API_KEY=<key> to /opt/climate-lama/.env.prod (mode 600), then close #1656~~ — done 2026-10-07 (session lama-phase-plan-P17): the owner added it, presence checked on the host, #1656 closed before W7's /release owner — AS-3
W7 · close #1655's Run: /release (R4) on both repos, deploy per docs/DEPLOYMENT.md, the owner walk per exit criterion 1, then docs/plan/mid-review-phase-17.md B23c lands and U38b lands owner (walk), agent (release, review) — exit 1 to 11

Walk still to do

The owner adds findings here as the walk goes on. Each new finding gets the next id in its surface's series and a row in its table above. Walk stop 1 covers every line below (most need W0's fixes), and its findings enter the milestone through the delta /phase-split before W1, so no wave builds from an incomplete list.

Walked on 2026-10-02: Home, the shell, Explore (Scope, Layers, Analysis, a portfolio scope), Lookup, Adapt, Settings and the assistant (findings SH-17 to SH-24, AS-1 to AS-3, EX-14 to EX-16, LK-13). The prototype review of 4 and 5 October 2026 added the fine-tune findings (EX-18 to EX-23, SH-25 to SH-28, LK-14, AD-17, PF-12, DT-7, AS-4, EV-19 and RP-2). It was a review of the design, not a walk of the product, so Walk stop 1 still covers every line of this list. Still to walk:

  • Moment 2 after step 1 (needs PF-1).
  • Moment 3 steps 2 to 6 and Compare (needs EX-1), and Moment 3 again through the two-step dock after W2 (EX-15, EX-16, EX-18, EX-23).
  • Demo slice steps 3 to 6 through Data and Explore (needs EX-1).
  • Reports, Data, History (the saved-work list at /analyses, K43) and Admin.
  • The 390 px phone layout of Home, Lookup and Events (D12). No audit could run a browser.

Walked on 2026-10-08 at walk stop 1 (#1572): every line above except Moment 3 through the two-step dock, which is walk stop 2's. Findings EX-24, EX-25, PF-13, SH-29 and LK-16 are in the Walk stop 1 table under Findings. Each was filed as a box on the open issue whose Files: own the fix, so the delta split opened no new issue. The same pass gave registry admin-1 an owning box on ui#843.

Folded into walk stop 2 on 2026-10-11 (#1618's fourth box). Walk stop 1 is closed and gates nothing, but four things from it were never seen working on prod, and each first ships in R2:

  • Compare. "Run 2 cells" (ui#748) was not built on 8 October, and the saved comparison is ui#872.
  • A run of the owner's that succeeds in Explore. His six runs failed on EX-24, and EX-1 was read through the API. Moment 3 at walk stop 2 covers it.
  • The findings fixed since: EX-24 (ui#747), EX-25 (ui#866), PF-13 (ui#736) and LK-17 (#1665).
  • The PF-8 read, which failed on PF-13 (#1501's comment of 2026-10-08).

Walk stop 2 also takes what no mid-phase stop covered. W1's portfolio work landed after walk stop 1, and stops 2 and 3 name Moments 3, 4 and 1 only, so Moment 2 would first be walked at W7. It is walked here as far as W1 built it: the list, and the card, synopsis and detail page with their units (PF-13), the band distribution and the labelled average (PF-9), and the member count of a mixed-unit portfolio if prod holds one (PF-10). The detail page's per-hazard list, 1-in-200 figure and map modes are W6 (ui#828) and are not judged. Of exit criterion 11's other figures, EX-17 has been on prod since R1 and EV-10 can be read once the North Evia watch is re-run (the agent row before the walk). SH-29 (ui#845, W6) and LK-16 (ui#807, W5) are not built and stay with W7 and walk stop 3. Exit criterion 11 is read again in full at W7.

Walked on 2026-10-12 at walk stop 2 (#1618): Moment 3 through both doors, Compare's card, Moment 2, Events as far as a selected replay and the two live feeds, the demo slice, the assistant and a failed run. Findings EX-26 to EX-31, AS-5 and EV-20 are in the Walk stop 2 table under Findings. Carried to walk stop 3 (#1631): saving a comparison and "Compare engines", Moment 4 steps 4 to 8 on a newly created replay (the series is /data/live_series/evia_2021_daily), and the EX-17 and EV-10 reads.

Walked on 13 and 14 October 2026 at walk stop 3 (#1631): Moment 1 with the pin's place card and This area, Moment 4 steps 1 to 9 on two replay watches over /data/live_series/evia_2021_daily, Adapt from its landing page, Compare's saved comparison and "Compare engines", and EX-26 to EX-28. Findings AD-18, EX-32, LK-18 to LK-22, SH-30, EV-21 and PF-14 are in the Walk stop 3 table under Findings. Adapt's six cards, cost curve and evolution chart (script steps E5 and E6) were seen on prod on 14 October, after release R3.1 carried #1835, and #1631 closed the same day.

Exit criteria

  1. The owner walks Moments 1 to 4, the CLAUDE.md demo slice, and Reports, Data, History (the saved-work list at /analyses, K43), Home, Settings and Admin on prod after the W7 release, on prod data only, plus Home, Lookup and Events at 390×844, with no [CRITICAL] finding open.
  2. Every view has a Linux baseline from the pinned image at 1440×900 (and Home, Lookup and Events at 390×844) that the owner has accepted against the prototype capture, which is rendered from the v2 snapshot at artifact version 1791210282-291b with its defaults, and the difference list the wave gate posted on every view issue holds no design difference (guardrail 1, K53).
  3. The final wave gate shows the prod-shaped seed loaded, 0 unexpected skips in the smoke suite, and the layout proofs measuring the view root (guardrails 2 to 4).
  4. Every breaker trip is recorded here with its repair set. The last window reads at or below 0.25 in both repos, and the whole-phase read (python scripts/rework_rate.py --repo <repo> --since <17b milestone's first issue date> --follow-ups --json) is reported in the mid-review whatever its value (guardrail 5).
  5. Every walk:mid tracker is closed as completed, with its findings filed onto a wave (guardrail 6).
  6. The prototype inventory prints unmapped: empty over the 212 live registry ids of the v2 snapshot: each maps to an acceptance box of an issue or to the "prototype only" list (guardrails 10 and 12).
  7. Phase 17's exit criteria 2 to 7 are re-checked, and mid-review-phase-17.md records the verdict for 17 and 17b together.
  8. The owner runs Moment 3 on prod through the two-step dock, 1 Scope then 2 Run, and through both doors of Scope. Through A place: a place with its exposure layer filled in, narrowed to one or more regions, a future cell, a measure and a growth rate. And once through My assets, on a portfolio. He reads every limit line of phase-17b-run-model.md § 11 where it applies (EX-15, EX-16, EX-18, EX-23, K36, K38, K50).
  9. The assistant completes "Run river flood today for Greece and tell me its expected annual impact" on prod against the live provider, through the place sequence of BC-4 (AS-4), and assistant-run.spec.ts is green on the final wave gate with 0 skips for the place sentence and for the portfolio sentence (AS-2, AS-4, guardrail 3).
  10. SP-3's inventory lists every owner-approved deviation from the v2 snapshot with its finding id, and the owner has accepted the parity baseline of each. None is recorded at 2026-10-05: the lens outside the top bar (SH-17), the Home section in Settings (SH-23, SH-24) and the two-step dock (EX-16, K36) are what v2 itself shows, so they are the target. The inventory also lists the "prototype only" entries of the record § 4, and no issue builds one of them (guardrails 10 and 12).
  11. The figures the domain model corrects read right on prod: a book's card EAD equals its latest river-flood result's ead (PF-8), the rollup shows the band distribution and the labelled average (PF-9), a mixed-unit book shows the member count (PF-10), an insured and a ground-up run over the same inputs return different results (EX-17), and the North Evia replay's headline never falls between frames (EV-10).

Out of scope

  • Property-level results, 3D buildings, street photos and Edit home (Phase 18, D18).
  • Engines beyond Oasis LMF, OED policy layers, stochastic event sets with AEP (Phase 18, D19, D21).
  • Tiers and billing (D20).
  • Any figure that is illustrative in the prototype. The prototype's numbers never ship (ADR-077).
  • Spatial exposure growth (new locations entering hazard zones), vulnerability curves for population and built-volume exposures, per-asset vulnerability binding, a multi-hazard aggregate figure, an exposure reference_year column and a monetary damage line in Lookup (phase-17b-run-model.md § 13). Each enters the Phase 18 stub with this doc as its source.
  • Translations. English stays the only language; the Units and formats section says so (SH-24) and i18n enters the Phase 18 stub.
  • 3D extrusion twins for result tiers (EX-14) and a fake assistant provider (K21 rejected).
  • A dock of tabs with Layers among them, as K13 kept it. The dock is two numbered steps, Scope then Run, with Layers as a popover on the map (K36).
  • The other variants the owner did not choose on 2026-10-05: the doors that open by role lens, the one path "Where, then what", the four menu chips over the map, the hazard icon row, the read-only curve line with an "Advanced" override, the count badge on the rail, the wave mark and the flat list of Admin tabs. The v2 snapshot keeps its option switches so the reasoning can be seen, and no value but the default is a target (guardrail 12 point 2).
  • The per-country batch for a run over several countries with no single hazard dataset: one run per country in one batch, the expected annual impact summed and the 1-in-N loss read per country. This phase switches the hazard off with its reason and offers "Run one country" (EX-19, K41, FD-6). The batch enters the Phase 18 stub.
  • The pinned measure shortlist: measures pinned in Adapt's library and listed in the run form as switches (K40, FD-5, registry adapt-19). This phase picks measures through chips, "Add measures" and "Use a saved plan". The shortlist enters the Phase 18 stub.
  • The "prototype only" parts of the v2 snapshot, which are never a target (phase-17b-finetune.md § 4): the review board (shell-7), the Options button and board (shell-8 and the Options entry of shell-10), the two preview switches, the error line (shell-9), the retired layout variants B and C (explore-27), the group outline of the "Where, then what" variant (explore-44), the pin control (adapt-19), the scale test and the "Show an example" button of the notifications section (settings-9, a demo run that computes nothing, #1673 item 16).

Phase state — 2026-10-19 (by /phase-analyze, origin/main 0aefffb4, UI 7c0a8ef1)

State — P17b · Walk corrections · read 2026-10-19 · origin/main 0aefffb4

Wave Status Owner Issues Deps Command Budget Notes
Ops ✅ done — doc: done 2026-10-07 agent ✓1499 now — — #1499
Ops ✅ done — doc: done 2026-10-07 agent ⊘1501 now — — #1501 box 1
Ops ✅ done — doc: done 2026-10-07 agent — before walk stop 1's /release — — AS-1
Ops ✅ done — doc: done 2026-10-07 agent — now — — EV-7
Ops ✅ done — #1300 closed agent ✓1300 24 h after the release that carries #1691 (B5) POST /v1/live-events/{id}/close reads, #1300 — EV-9
B1 · W0a+W0 ✅ done — gate 37944500707 at 53cb3f3 agent · backbone lane ✓1562 ✓1563 ⊘1565 ✓1568 ✓1571 now, and its wave gate waits until CortoMaltese3/climate-lama-ui#709 reads CLOSED (the gate lock exists) /phase-build 1562 1563 1565 1568 1571 --no-pr-ci --max-runtime 260 260/— EX-1, EX-17, PF-8, SH-7, SP-3
U1 · W0a+W0 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ⊘ui709 ⊘ui708 ✓ui710 ✓ui711 ✓ui715 +1 now cd ../climate-lama-ui && /phase-build 709 708 710 711 715 718 --no-pr-ci --max-runtime 260 260/— LK-2, PF-1, SH-7, SP-2
B2 · W0a+W0 ✅ done — gate 37883813739 at e336fc9 agent · backbone lane ✓1564 ✓1566 ✓1567 ✓1569 ✓1570 +1 B1 lands /phase-build 1564 1566 1567 1569 1570 1679 --no-pr-ci --max-runtime 270 270/— AD-2, AS-1, EN-1, EN-2, SH-7, SP-3
U2 · W0a+W0 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui889 ✓ui715 ✓ui712 ✓ui713 ✓ui859 +2 U1 lands and B1 lands cd ../climate-lama-ui && /phase-build 889 715 712 713 859 719 721 --no-pr-ci --max-runtime 280 280/— EX-1, PF-1, SH-1, SH-7, SP-2
U3 · W0a+W0 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui714 ✓ui716 ✓ui717 ✓ui720 U2 lands and B1 lands cd ../climate-lama-ui && /phase-build 714 716 717 720 --no-pr-ci 120/— EV-1, EX-1, EX-2, PF-8
Walk stop 1 ✅ done — #1572 closed, R1 deploy 37776281060 owner ⊘1572 B2 lands and U3 lands /release (R1), then #1572 — Moments 1 to 4, demo slice steps 3 to 6, Walk still to do, 390 px
B3 · W1 ✅ done — gate 37848366558 at 5494f43 agent · backbone lane ⊘1573 ✓1574 ✓1577 ✓1578 ✓1579 +1 walk #1572 closed /phase-build 1573 1574 1577 1578 1579 1583 --no-pr-ci --max-runtime 270 270/— DT-1, PF-2, PF-3, PF-10, SP-1
Credential ✅ done — ui856 closed owner ✓ui856 B3 lands (SP-1), before walk stop 2's /release gh secret set VITE_SATELLITE_TILES -R CortoMaltese3/climate-lama-ui — SH-19
E1 · engine ✅ done — gate 37878662432 at 079616a agent · engine lane ✓engine45 ✓engine46 ✓engine47 ✓engine48 walk #1572 closed cd ../climate-lama-engine && /phase-build 45 46 47 48 --no-pr-ci --max-runtime 235 235/— EN-7, EN-8
E1 tag ✅ done — engine49 closed, v0.6.0 owner ✓engine49 E1 lands /engine-tag v0.6.0 from ../climate-lama-engine — EN-7, EN-8
U4 · W1 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui722 ✓ui729 ✓ui730 ✓ui731 ✓ui732 walk #1572 closed cd ../climate-lama-ui && /phase-build 722 729 730 731 732 --no-pr-ci --max-runtime 230 230/— SH-4, SH-17, SH-18, SH-21
U5 · W1 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui723 ✓ui724 ✓ui725 ✓ui862 ✓ui863 U4 lands cd ../climate-lama-ui && /phase-build 723 724 725 862 863 --no-pr-ci --max-runtime 190 190/— LK-5, SH-2, SH-3, SH-25, SH-28
B4 · W1 ✅ done — gate 37883813739 at e336fc9 agent · backbone lane ✓1575 ✓1576 ✓1580 ✓1581 ✓1665 B3 lands /phase-build 1575 1576 1580 1581 1665 --no-pr-ci --max-runtime 225 225/— DT-2, DT-5, PF-9, PF-11
U6 · W1 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui726 ✓ui727 ✓ui728 ✓ui733 ✓ui735 U5 lands and B3 lands cd ../climate-lama-ui && /phase-build 726 727 728 733 735 --no-pr-ci --max-runtime 160 160/— PF-3, SH-8, SH-20, SH-21, SH-22
B5 · W1 ✅ done — gate 37917740388 at ebaad8b agent · backbone lane ✓1582 ✓1584 ✓1691 B4 lands /phase-build 1582 1584 1691 --no-pr-ci --max-runtime 110 110/— EV-9, PF-10, SH-3
B6 · W2+W2b+W3 ✅ done — gate 38027357218 at 4b63a56 agent · backbone lane ✓1586 ✓1587 ✓1589 ✓1597 ✓1660 B5 lands /phase-build 1586 1587 1589 1597 1660 --no-pr-ci --max-runtime 200 200/— EX-11, EX-14, EX-15, EX-16, EX-18, EX-23
U7 · W1 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui734 ✓ui736 ✓ui737 ✓ui738 ✓ui739 U6 lands and B5 lands cd ../climate-lama-ui && /phase-build 734 736 737 738 739 --no-pr-ci --max-runtime 150 150/— EN-7, PF-2, PF-3, PF-5, PF-10
U8 · W1 ✅ done — gate 37995087367 at f0b4f2e agent · UI lane ✓ui740 ✓ui741 ✓ui742 ✓ui864 U7 lands and B4 lands cd ../climate-lama-ui && /phase-build 740 741 742 864 --no-pr-ci --max-runtime 140 140/— PF-3, PF-9, PF-11, SH-21, SH-27
B7 · W2+W2b+W3 ✅ done — gate 37944500707 at 53cb3f3 agent · backbone lane ✓1588 ✓1590 ✓1591 ✓1598 ✓1602 B6 lands /phase-build 1588 1590 1591 1598 1602 --no-pr-ci --max-runtime 260 260/— AS-1, EX-14, EX-15, EX-19, EX-23, PF-4
U9 · W2+W2b+W3 ✅ done — gate 38007735663 at 1cf7d98 agent · UI lane ✓ui759 ✓ui772 ✓ui773 ✓ui774 ✓ui775 U8 lands and B3 lands cd ../climate-lama-ui && /phase-build 759 772 773 774 775 --no-pr-ci --max-runtime 200 200/— EV-2, EV-5, EV-18, SH-2
B8 · W2+W2b+W3 ✅ done — gate 37985306025 at dc3010f agent · backbone lane ✓1592 ✓1593 ✓1594 ✓1603 ✓1663 B7 lands /phase-build 1592 1593 1594 1603 1663 --no-pr-ci --max-runtime 260 260/— AD-1, AD-5, AS-2, AS-4, EX-15, EX-21
U10 · W2+W2b+W3 ✅ done — gate 38034535226 at 87ddb05 agent · UI lane ✓ui743 ✓ui744 ✓ui745 ✓ui760 ✓ui761 U9 lands and B7 lands cd ../climate-lama-ui && /phase-build 743 744 745 760 761 --no-pr-ci --max-runtime 290 290/— EX-4, EX-7, EX-12, EX-16, EX-21, EX-23, LK-5, PF-4, SH-2, SH-19
B9 · W2+W2b+W3 ✅ done — gate 38010034891 at 21b33ee agent · backbone lane ✓1585 ✓1595 ✓1599 ✓1661 ✓1662 B8 lands and CortoMaltese3/climate-lama-engine#49 closed (engine tag) /phase-build 1585 1595 1599 1661 1662 --no-pr-ci --max-runtime 270 270/— EN-7, EN-8, EX-10, EX-15, EX-19, EX-20, PF-8
B9b · W2+W2b+W3 ✅ done — gate 38010034891 at 21b33ee agent · backbone lane ✓1686 B9 lands /phase-build 1686 --no-pr-ci 100/— EX-14
U11 · W2+W2b+W3 ✅ done — gate 38034535226 at 87ddb05 agent · UI lane ✓ui768 U10 lands and B7 lands cd ../climate-lama-ui && /phase-build 768 --no-pr-ci 70/— AS-1
B10 · W2+W2b+W3 ✅ done — gate 38027357218 at 4b63a56 agent · backbone lane ✓1596 ✓1604 ✓1605 ✓1606 ✓1664 B9b lands /phase-build 1596 1604 1605 1606 1664 --no-pr-ci --max-runtime 225 225/— AS-2, AS-4, EN-7, EV-11, EX-10, EX-15, EX-19
U12 · W2+W2b+W3 ✅ done — gate 38054601217 at 8eee3ff agent · UI lane ✓ui746 ✓ui747 ✓ui748 ✓ui749 ✓ui750 U11 lands and B9 lands cd ../climate-lama-ui && /phase-build 746 747 748 749 750 --no-pr-ci --max-runtime 230 230/— EX-3, EX-4, EX-5, EX-13, EX-16, EX-18, EX-19, EX-20, PF-4
B11 · W2+W2b+W3 ✅ done — gate 38027357218 at 4b63a56 agent · backbone lane ✓1600 ✓1601 ✓1607 ✓1608 ✓1609 B10 lands /phase-build 1600 1601 1607 1608 1609 --no-pr-ci --max-runtime 270 270/— EV-10, EV-12, EV-16, EX-15
U13 · W2+W2b+W3 ✅ done — gate 38090201682 at 14ae1e7 agent · UI lane ✓ui751 ✓ui752 ✓ui753 ✓ui754 ✓ui755 U12 lands and B9b lands cd ../climate-lama-ui && /phase-build 751 752 753 754 755 --no-pr-ci --max-runtime 270 270/— lama-ui-phase-build-P17b-U13 · AD-1, EN-5, EX-10, EX-11, EX-14, EX-16
B12 · W2+W2b+W3 ✅ done — gate 38041289415 at 65c7e4c agent · backbone lane ✓1610 ✓1611 ✓1612 B11 lands /phase-build 1610 1611 1612 --no-pr-ci --max-runtime 225 225/— EV-3, EV-8, EV-12, EV-14
U14 · W2+W2b+W3 ✅ done — gate 38096294766 at 58846fe agent · UI lane ✓ui873 ✓ui870 ✓ui756 ✓ui757 ✓ui758 U13 lands and B10 lands cd ../climate-lama-ui && /phase-build 873 870 756 757 758 --no-pr-ci --max-runtime 230 230/— lama-ui-phase-build-P17b-U14 · EX-8, EX-10, EX-14, EX-23
B13 · W2+W2b+W3 ✅ done — gate 38041289415 at 65c7e4c agent · backbone lane ✓1613 ✓1614 ✓1615 ✓1616 B12 lands /phase-build 1613 1614 1615 1616 --no-pr-ci --max-runtime 225 225/— EV-1, EV-4, EV-13, EV-17
U15 · W2+W2b+W3 ✅ done — gate 38108002041 at 04e5a8a agent · UI lane ✓ui776 ✓ui777 ✓ui778 ✓ui779 ✓ui780 U14 lands and B12 lands cd ../climate-lama-ui && /phase-build 776 777 778 779 780 --no-pr-ci --max-runtime 230 230/— EV-3, EV-7, EV-10, EV-11, EV-17
B14 · W2+W2b+W3 ✅ done — gate 38046236551 at d44d10e agent · backbone lane ✓1617 ✓1685 B13 lands /phase-build 1617 1685 --no-pr-ci --max-runtime 250 250/— EV-14, EX-14, SH-11
U16 · W2+W2b+W3 ✅ done — gate 38117590455 at 3aebd91 agent · UI lane ✓ui762 ✓ui763 ✓ui781 ✓ui784 ✓ui785 U15 lands and B13 lands cd ../climate-lama-ui && /phase-build 762 763 781 784 785 --no-pr-ci --max-runtime 130 130/— EN-7, EV-12, EV-14, EV-16, EX-6
U17 · W2+W2b+W3 ✅ done — gate 38124992047 at aa76423 agent · UI lane ✓ui764 ✓ui765 ✓ui786 ✓ui787 ✓ui871 U16 lands and B12 lands cd ../climate-lama-ui && /phase-build 764 765 786 787 871 --no-pr-ci 110/— EV-4, EV-8, EX-9, EX-12, EX-22
U18 · W2+W2b+W3 ✅ done — gate 38159708546 at f93f381 agent · UI lane ✓ui766 ✓ui769 ✓ui770 ✓ui788 ✓ui866 U17 lands and B10 lands cd ../climate-lama-ui && /phase-build 766 769 770 788 866 --no-pr-ci --max-runtime 150 150/— AS-2, AS-4, EV-6, EX-16, EX-18, EX-20, EX-23, PF-4, SH-5
Credential ✅ done — ui855 closed agent ✓ui855 U20c lands gh workflow run e2e-smoke.yml -R CortoMaltese3/climate-lama-ui --ref main -f assistant_live=true — AS-2
U19 · W2+W2b+W3 ✅ done — gate 38140059350 at 00ca6ef agent · UI lane ✓ui767 ✓ui771 ✓ui782 ✓ui783 ✓ui865 U18 lands and B13 lands cd ../climate-lama-ui && /phase-build 767 771 782 783 865 --no-pr-ci 120/— DT-7, EV-1, EV-13, EV-14, EX-16, EX-18, EX-19, EX-21
U20 · W2+W2b+W3 ✅ done — gate 38159708546 at f93f381 agent · UI lane ✓ui789 ✓ui868 ✓ui872 U19 lands and B14 lands cd ../climate-lama-ui && /phase-build 789 868 872 --no-pr-ci --max-runtime 170 170/— EV-19, EX-11, SH-11
B14b · W2+W2b+W3 ✅ done — gate 38140370968 at 14c4a8c agent · backbone lane ✓1785 ✓1783 B14 lands /phase-build 1785 1783 --no-pr-ci --max-runtime 190 190/— AS-4, EX-10, EX-14
Ops ✅ done — e2e-smoke 38172544022 green at de628e1 agent ✓ui1052 B14b lands (the workflow builds backbone main, and the place sentence of assistant-run.spec.ts fails on the seed before #1785) gh workflow run e2e-smoke.yml -R CortoMaltese3/climate-lama-ui --ref main — R2's gate
U20b · W2+W2b+W3 ✅ done — gate 38161233739 at add9694 agent · UI lane ✓ui770 ✓ui1037 ✓ui887 U20 lands and B14b lands cd ../climate-lama-ui && /phase-build 770 1037 887 --no-pr-ci --max-runtime 170 170/— AS-2, AS-4, EX-14
Ops ✅ done — doc: read 2026-10-12, 1 of 4 targets met agent ⊘K72 U20b lands python scripts/rework_rate.py --repo CortoMaltese3/climate-lama-ui --since 2026-10-08 --follow-ups — K72
U20c · W2+W2b+W3 ✅ done — gate 38168835333 at eacb0e0 agent · UI lane ✓ui1052 U20b lands cd ../climate-lama-ui && /phase-build 1052 --no-pr-ci --max-runtime 60 60/— R2's gate
Ops ✅ done — #1618 closed 2026-10-12 agent ⊘1618 walk stop 2's release (R2) agent-run: prod reads of #1618's second box — AS-4, EV-10, EX-17, EX-18, EX-23, EX-25, LK-17
Walk stop 2 ✅ done — #1618 closed 2026-10-12, R2 deploy 38175655511 owner ⊘1618 B14b lands and U20b lands and U20c lands and E1 lands /release (R2) ✓, then #1618 — Moment 3 through the two-step dock (A place narrowed to several regions, and My assets), a run in flight found again (EX-20 without Stop), Moment 4 steps 1 to 8, demo slice, AS-2, AS-4. Folded from walk stop 1 on 2026-10-11: Compare, EX-24, EX-25, PF-13, LK-17, the PF-8 read, Moment 2 as far as W1 built it with PF-9 and PF-10, EX-17 and EV-10
B15 · W4+W5 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1619 ✓1620 ✓1621 ✓1628 ✓1629 walk #1618 closed /phase-build 1619 1620 1621 1628 1629 --no-pr-ci --max-runtime 225 225/— AD-4, AD-7, AD-11, AD-12, AD-13, AD-14, AD-15, AD-16, LK-9
B15b · W4+W5 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ⊘1803 ✓1804 ✓1806 B15 lands /phase-build 1803 1804 1806 --no-pr-ci --max-runtime 130 130/— EX-26, EX-28
B15c · W4+W5 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1805 B15b lands and #1803 closed /phase-build 1805 --no-pr-ci 110/— EX-26
U21 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui790 ✓ui791 ✓ui804 ✓ui805 ✓ui806 walk #1618 closed cd ../climate-lama-ui && /phase-build 790 791 804 805 806 --no-pr-ci --max-runtime 150 150/— AD-1, EX-12, LK-1, LK-4
U21b · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui1068 U21 lands cd ../climate-lama-ui && /phase-build 1068 --no-pr-ci 70/— EX-27
U22 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui794 ✓ui796 ✓ui807 ✓ui808 ✓ui809 U21b lands cd ../climate-lama-ui && /phase-build 794 796 807 808 809 --no-pr-ci --max-runtime 130 130/— AD-3, LK-6, LK-8, LK-11, SH-12
B16 · W4+W5 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1622 ✓1623 ✓1624 ✓1630 ✓1668 B15c lands /phase-build 1622 1623 1624 1630 1668 --no-pr-ci --max-runtime 225 225/— AD-6, AD-12, AD-13, AD-15, DT-3, LK-14
U23 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui792 ✓ui795 ✓ui810 ✓ui811 ✓ui874 U22 lands and B15 lands cd ../climate-lama-ui && /phase-build 792 795 810 811 874 --no-pr-ci --max-runtime 150 150/— AD-1, AD-4, AD-17, LK-3, LK-7
U23b · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui1069 U23 lands and CortoMaltese3/climate-lama#1806 closed cd ../climate-lama-ui && /phase-build 1069 --no-pr-ci 70/— EX-28
U24 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui793 ✓ui812 ✓ui814 U23b lands and B15 lands cd ../climate-lama-ui && /phase-build 793 812 814 --no-pr-ci 80/— AD-1, LK-9, LK-13
B17 · W4+W5 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1625 ✓1626 ✓1627 ✓1684 B16 lands /phase-build 1625 1626 1627 1684 --no-pr-ci --max-runtime 250 250/— AD-14, AD-16, LK-15
U25 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui797 ✓ui798 ✓ui799 ✓ui800 ✓ui813 U24 lands and B16 lands cd ../climate-lama-ui && /phase-build 797 798 799 800 813 --no-pr-ci --max-runtime 250 250/— AD-2, AD-3, AD-5, AD-6, AD-11, AD-12, AD-13, AD-15, LK-10
U26 · W4+W5 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui801 ✓ui802 ✓ui803 ✓ui867 ✓ui886 U25 lands and B17 lands cd ../climate-lama-ui && /phase-build 801 802 803 867 886 --no-pr-ci --max-runtime 290 290/— AD-4, AD-7, AD-16, LK-14, LK-15
Walk stop 3 ✅ done — #1631 closed 2026-10-14, R3 deploy 38398954001, R3.1 38528649564 owner ⊘1631 B17 lands and U26 lands /release (R3) ✓, R3.1 v0.30.1 ✓, then #1631 — Moment 1 with the pin's place card (LK-14), Moment 4 step 9 from Adapt's landing page (AD-17), and EX-26, EX-27 and EX-28 seen fixed
B18 · W6 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1632 ✓1633 ✓1636 ✓1639 ✓1652 walk #1631 closed /phase-build 1632 1633 1636 1639 1652 --no-pr-ci --max-runtime 270 270/— AD-8, AD-13, AD-17, DT-4, EN-4, EX-20, SH-10, SH-15, SH-16, SH-26
B18b · W6 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1688 B18 lands /phase-build 1688 --no-pr-ci 100/— PF-12
U27 · W6 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui815 ✓ui816 ✓ui817 ✓ui818 ✓ui819 walk #1631 closed cd ../climate-lama-ui && /phase-build 815 816 817 818 819 --no-pr-ci --max-runtime 220 220/— SH-12, SH-23, SH-24
U28 · W6 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui820 ✓ui821 ✓ui826 ✓ui849 ✓ui850 U27 lands cd ../climate-lama-ui && /phase-build 820 821 826 849 850 --no-pr-ci --max-runtime 150 150/— PF-2, PF-7, SH-10, SH-13, SH-16, SH-24
U29 · W6 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui827 ✓ui828 ✓ui829 ✓ui840 ✓ui852 U28 lands and B18b lands cd ../climate-lama-ui && /phase-build 827 828 829 840 852 --no-pr-ci --max-runtime 240 240/— AS-5, PF-7, PF-8, SH-5, SH-10, SH-15, SH-16, SH-22, SH-26
B23b · W6 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1807 B23 lands /phase-build 1807 --no-pr-ci 100/— EV-20
B23c · W6 ✅ done — gate 39026389550 at 35cc21bf agent · backbone lane ✓1838 ✓1839 ✓1840 ✓1836 ✓1842 +1 B23b lands /phase-build 1838 1839 1840 1836 1842 1833 --no-pr-ci --max-runtime 225 225/— AD-16, AD-18, EX-32, LK-18, LK-20
Ops ✅ done — #1841 closed agent ⊘1841 walk #1631 closed agent-run: #1841's Run — PF-14
U38 · W6 ✅ done — gate 39031062765 at 1b4ef6b agent · UI lane ✓ui1140 ✓ui1141 ✓ui1142 ✓ui1143 ✓ui1144 U37 lands cd ../climate-lama-ui && /phase-build 1140 1141 1142 1143 1144 --no-pr-ci --max-runtime 130 130/— EV-21, LK-19, LK-21, LK-22, SH-30
Credential ✅ done — doc: done 2026-10-07, #1656 closed owner ✓1656 before W7's /release — — AS-3
B19 · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1637 #1640 stack boxes agent · backbone lane ✓1634 ◐1637 ◐1640 ✓1654 ✓1666 B18b lands /phase-build 1634 1637 1640 1654 1666 --no-pr-ci --max-runtime 190 190/— EN-4, EX-20, PF-7, RP-1, RP-2, SH-10, SH-16
B20 · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1667 smoke-walk box agent · backbone lane ✓1638 ✓1642 ◐1667 ✓1670 ✓1669 B19 lands /phase-build 1638 1642 1667 1670 1669 --no-pr-ci --max-runtime 280 280/— AD-8, DT-7, PF-6, PF-12, RP-2, SH-26
B20b · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1687 Evia read agent · backbone lane ✓1680 ◐1687 ✓1689 B20 lands /phase-build 1680 1687 1689 --no-pr-ci --max-runtime 280 280/— SH-16, SH-24, SH-26
U30 · W6 🔵 merged-unverified: 72723de — gh workflow run ci.yml -R CortoMaltese3/climate-lama-ui --ref main agent · UI lane ✓ui831 ✓ui837 ✓ui844 ✓ui845 ✓ui851 U29 lands and B19 lands cd ../climate-lama-ui && /phase-build 831 837 844 845 851 --no-pr-ci --max-runtime 150 150/170 lama-ui-phase-build-P17b-U30 + -U30c (118/150) · ui#845 closed since the 18 Oct read · UI ci.yml red at its newest run, so PRs #1197 to #1211 stay unverified · EN-4, EX-20, EX-21, LK-12, SH-10, SH-16, SH-24
B21 · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1653 usage-counter read agent · backbone lane ✓1635 ✓1641 ✓1643 ✓1644 ◐1653 B20b lands /phase-build 1635 1641 1643 1644 1653 --no-pr-ci --max-runtime 190 190/— AD-8, AD-9, AD-13, AS-3, SH-10, SH-16
U31 · W6 🔵 merged-unverified: f3fb3c8 — gh workflow run ci.yml -R CortoMaltese3/climate-lama-ui --ref main agent · UI lane ✓ui822 ✓ui830 ✓ui833 ✓ui841 ✓ui876 U30 lands and B20b lands cd ../climate-lama-ui && /phase-build 822 830 833 841 876 --no-pr-ci --max-runtime 170 170/149 lama-ui-phase-build-P17b-U30b (149/170) + -U31 (148/260, live idle) · DT-7, PF-6, RP-2, SH-10, SH-16, SH-26
B22 · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1648 interrupted-run box agent · backbone lane ✓1645 ✓1646 ✓1647 ◐1648 ✓1650 B21 lands /phase-build 1645 1646 1647 1648 1650 --no-pr-ci --max-runtime 190 190/— AS-5, DT-6, EN-3, EN-4, PF-6
B23 · W6 🔵 merged — ci 39026389550 covers all merges, unmet: #1649 sweep box agent · backbone lane ◐1649 ✓1651 B22 lands /phase-build 1649 1651 --no-pr-ci 65/— EN-6, SH-10
U38b · W6 🔵 merged — ci 39031062765 covers all merges, unmet: ui#1135 stack proof agent · UI lane ✓ui1137 ◐ui1135 U38 lands and B23c lands cd ../climate-lama-ui && /phase-build 1137 1135 --no-pr-ci --max-runtime 70 70/— AD-16, lookup residue of U21
B24 · #1793 (no doc row) 🔵 merged-unverified: 414fce8 — gh workflow run ci.yml -R CortoMaltese3/climate-lama --ref main agent · backbone lane ◐1793 PR #1902 merged /phase-build 1793 --no-pr-ci --max-runtime 90 90/85 lama-phase-build-P17b-B24 · unmet: #1793 datasets.spec.ts:512 stack check · PR #1898 35cc21b is inside the last green run, PR #1902 is not · off-milestone found-in:phase-17b
B25 · #1905 (no doc row) 🔵 merged-unverified: b737aef — gh workflow run ci.yml -R CortoMaltese3/climate-lama --ref main agent · backbone lane ✓1905 #1901 ✓ merged (PR #1904) /phase-build 1905 --no-pr-ci --max-runtime 110 110/— PR #1907 Refs · issue closed 18 Oct 18:27Z · off-milestone found-in:phase-17b · ui#845 closed
B26 · #1900 (no doc row) 🔵 merged-unverified: 23ba2d46 — gh workflow run ci.yml -R CortoMaltese3/climate-lama --ref main agent · backbone lane ✓1900 PR #1909 merged /build 1900 — closed 18 Oct 17:48Z · U30 residue (roll a cancelled comparison group up) · off-milestone found-in:phase-17b
U32 · W6 🔵 merged-unverified: 215ba345 — gh workflow run ci.yml -R CortoMaltese3/climate-lama-ui --ref main agent · UI lane ✓ui823 ✓ui835 ✓ui878 ✓ui848 ✓ui853 U31 lands and B20 lands cd ../climate-lama-ui && /phase-build 823 835 878 848 853 --no-pr-ci --max-runtime 210 210/— PRs ui#1212 #1214 #1215 #1220 #1221 merged 18 Oct · all five issues closed · AS-4, DT-1, DT-2, DT-3, DT-5, DT-7, EV-14, EX-21, EX-22, PF-8, SH-9, SH-12, SH-16, SH-24
U33 · W6 🟡 dispatched: stale 6h — cd ../climate-lama-ui && /phase-build 824 825 842 846 854 --no-pr-ci --max-runtime 150 agent · UI lane ◐ui824 ✓ui825 ✓ui842 ✓ui846 ◐ui854 U32 merged, B21 ✓ cd ../climate-lama-ui && /phase-build 824 825 842 846 854 --no-pr-ci --max-runtime 150 150/— open PR ui#1232 (#824 Delete, last push 05:14Z, design-check: pre-merge) · unmet: ui#824 data-14 and design boxes, ui#854 shell-4 design box · PRs ui#1226 to #1235 merged 19 Oct, unverified by CI · DT-4, DT-7, SH-6, SH-10, SH-16, SH-18, SH-22, SH-25, SH-26
B27 · #1912 (no doc row) 🟢 ready agent · backbone lane ◐1912 PR #1913 merged (ca087751), unverified /build 1912 — unmet: the exposure read serves no owner_user_id, so the uploading analyst sees no Delete (reopened 19 Oct 05:15Z by the ui#824 rebuild) · residue of U35 · on the milestone, no doc row
U34 · W6 🟢 ready agent · UI lane ○ui832 ○ui836 ○ui838 ✓ui839 ○ui843 U33 merged, B22 ✓ cd ../climate-lama-ui && /phase-build 832 836 838 839 843 --no-pr-ci --max-runtime 130 130/— UI breaker 0.00, lane 0 of 2 · ui#832 and ui#843 waited in the 19 Oct session · AD-9, AD-10, PF-6, SH-14, SH-15, SH-26
U35 · W6 ⛔ blocked: U34 agent · UI lane ✓ui834 ○ui847 ✓ui860 ✓ui875 ○ui879 U34 lands and B21 lands cd ../climate-lama-ui && /phase-build 834 847 860 875 879 --no-pr-ci --max-runtime 180 180/— AD-8, AD-13, AD-17, EX-15, EX-16, EX-22, PF-7, SH-10, SH-16
U36 · W6 ⛔ blocked: U35 agent · UI lane ○ui861 ○ui869 ○ui877 ○ui880 ○ui888 U35 lands and B20b lands cd ../climate-lama-ui && /phase-build 861 869 877 880 888 --no-pr-ci --max-runtime 330 330/— DT-7, PF-12, RP-2, SH-24, SH-26
U37 · W6 ⛔ blocked: U36 agent · UI lane ✓ui1070 ✓ui1073 ○ui1071 ○ui1072 U36 lands and B23b lands cd ../climate-lama-ui && /phase-build 1070 1073 1071 1072 --no-pr-ci --max-runtime 140 140/— EV-20, EX-29, EX-30, EX-31
W7 · close ⛔ blocked: U33 to U37 open, U38b stack proof, 7 backbone stack boxes owner ⊘1655 B23c ✓ · U38b ◐ (doc); U33 to U37 open (gh) /release (R4) on both repos, then #1655 — exit 1 to 11, then docs/plan/mid-review-phase-17.md · 16 UI and 9 backbone milestone issues open (gh, #1655 included)
Ops 🟠 owner — #1501 open, PF-8 read to record owner ⊘1501 walk stop 2's release (R2) owner-run: read a USD portfolio's card EAD against GET /v1/results/{latest_result_id}, record it in #1501 — PF-8, PF-13

Next — P17b

# Wave Command Session After
1 U34 cd ../climate-lama-ui && /phase-build 832 836 838 839 843 --no-pr-ci --max-runtime 130 /rename lama-ui-phase-build-P17b-U34 — (UI breaker 0.00, lane 0 of 2)
2 U33 cd ../climate-lama-ui && /phase-build 824 825 842 846 854 --no-pr-ci --max-runtime 150 /rename lama-ui-phase-build-P17b-U33b — (PR ui#1232 open for #824; same lane as row 1)
3 Backbone #1912 /build 1912 /rename lama-build-P17b-1912 — (backbone lane 0 live; the analyst Delete waits on it)
4 Backbone trunk gate gh workflow run ci.yml -R CortoMaltese3/climate-lama --ref main any — (last green 17 Oct 10:49Z, past the 4 h debounce, 12 commits unverified)
5 UI trunk gate gh workflow run ci.yml -R CortoMaltese3/climate-lama-ui --ref main any — (newest run red, 32 commits past the last green)
6 UI smoke gh workflow run e2e-smoke.yml -R CortoMaltese3/climate-lama-ui --ref main any — (newest red 39029652207, last green 13 Oct)
7 Backbone stack boxes wsl -d Ubuntu -- bash scripts/smoke_walk_wsl.sh --keep-up /rename lama-phase-build-P17b-stack — (needs port 5433 stopped, no wave live)
8 Ops PF-8 record owner-run: record a USD portfolio's card EAD read in #1501 owner —
9 W7 /release (R4) on both repos, then #1655's Run owner U33 to U37, U38b and the stack boxes
  • drift — local HEAD 6e92270f is 3 commits behind origin/main 0aefffb4 (UI main checkout is 10 behind 7c0a8ef1); the docs PR branches from origin · the previous block (b737aefc) read U32 stale and U33 to U37 blocked: U32's five issues are all closed (PRs ui#1212 #1214 #1215 #1220 #1221), U33's issues are merged except the open boxes of ui#824 and ui#854, so U33 is stale and U34 is ready · the doc's rework-window records name sessions U35 and U37 for work on U33's issues (ui#854's stack comment calls its gate "U35-2", ui#824's comment says /phase-build U35), so the wave table's packing no longer matches the builders' · no builder transcript after 17 Oct is on this machine (the newest closeouts the scan returns are derived), so U30 to U33 and B24 to B27 rest on gh and the doc · the previous block's unassigned ui#1213, #1900 and #1905 are closed; #1912 is the one new milestone issue with no row
  • breaker — backbone 0.25 (1 of 4 builds: #1904 #1907 #1909 #1913; unattributed fix #1902, follow-ups #1905 and #1912) since 2026-10-17T10:48:59Z, at the 0.25 line and not over it · UI 0.00 (0 builds) in the window opened 2026-10-19T11:12:33Z by the repair ui#1234 via PR #1236 · the doc records four UI windows in 36 h (0.80, 0.50, 1.00, 0.50), each closed by a repair set (ui#1205 ui#1200, ui#1213, ui#1224, ui#1234), so no repair round is open
  • unassigned — on the milestone: #1912 (row B27 above, no doc row) · off-milestone found-in:phase-17b: #1793 (row B24), ui#1177, ui#1153, ui#914 (out on purpose since 2026-10-12) · superseded — none · any-time — none
  • gate — backbone: ci.yml 39026389550 green at 35cc21bf (17 Oct 10:49Z); origin is 12 commits past it and #1902 #1904 #1907 #1909 #1913 are code, covered by local wave gates only (owner rule: no CI per wave) · UI: newest ci.yml 39038608349 red at 7b2106d2 (17 Oct 13:56Z), last green 39031062765 at 1b4ef6b3, 32 commits behind origin, so every UI merge since is unverified by CI · e2e-smoke: newest 39029652207 red at 2bc9040c, last green 38393582151 at 44c30741
  • release — climate-lama v0.30.1 (8b833155, 63 behind, pyproject 0.30.1, Alembic 0113, deploy-prod 38528649564 ✓) · climate-lama-ui v0.20.0 (44c30741, 82 behind, package.json 0.20.0) · climate-lama-engine v0.6.0 (doc) · prod runs the 14 Oct releases, so R4 carries all of W6
  • holds — none: no release bump PR is open in either repo and no FREEZE or HOLD line in the P17b doc · lanes — backbone 0 live, UI 0 live builders (worktree list shows no fresh one; open PR ui#1232 is the only trace), 0 of 2 per lane
  • stale-worktree — climate-lama-ui/.claude/worktrees/phase-build-u25-u26-6da8a6 (last touched 13 Oct)