You are the build orchestrator for climate-lama. Deliver every agentically-buildable item below to landed-on-trunk, in worktree mode, spawning one agent per issue. Do not ask me for input — everything here is scoped to need none. Stop only on a genuine blocker.
Read docs/plan/triage-2026-08-01-orchestrator.md first. It is the plan; this prompt is the
execution order. Where they disagree, re-verify against live gh/git state and trust that.
Execution log — run started 2026-08-01 06:00 local¶
What the re-derivation below actually found when this prompt was first executed. Kept because it is the clearest evidence for why the re-derivation is mandatory: four of the prompt's own premises were wrong within ten minutes of being written.
| Premise as written | What was true at execution |
|---|---|
| #454 and #477 are buildable | Both were being built by other live sessions. Two locked worktrees held uncommitted work; file mtimes were 7 and 13 minutes old. Removed from scope. |
| S2 (#267 item 4) is a build item | Already fixed. 1a2b105 (2026-04-26) added DateTime(timezone=True); the writer was never naive. Verified and ticked on #267 — no issue created. |
Trunk 1e4d753 is ungated |
Correct. Dispatched ci.yml; came back green, so the gate was cleared before any build. |
Alembic head 0062, next 0063 |
Correct — no migrations in any open PR or live worktree. Slot free. |
Split issues created: S1 → #485, S3 → #486, S4 → #487. S2 was not created.
Net scope: 11 items, not 14 — #454, #477 and S2 all came out.
Outcome¶
All 11 dispatched. Ten merged; #481 correctly stopped rather than fabricating coefficients, and its documentation half landed as #509.
| Wave | Issues | Result |
|---|---|---|
| 1 | #103, #276, #483, #486 | all merged |
| 2 | #279, #487, #356 | all merged |
| 3 | #485, #482, #480, #481 | first three merged; #481 stopped by design |
Two red gates, both raw SQL in a migration — and this is the run's main lesson:
0063(#485): doubled percent signs in aRAISEformat string, which plpgsql reads as an escaped literal → zero specifiers against two arguments. Fixed in #502.0065(#480):-andjsonb_typeof()used against ajsoncolumn. Fixed in #507, and the underlying model-vs-schema drift filed as #508.
0064 (#482), which used typed op.add_column, passed first time. Nothing on this machine
runs PostgreSQL, so raw SQL in a migration is the only artifact that reaches trunk entirely
unverified — lint, type check and the memory gate all pass it. Gating after every
migration merge is what kept each break to a one-commit revert instead of a batch.
Follow-ups filed: #497 (prod-host deploy secrets — #356 is inert without them), #508 (json/jsonb drift), climate-lama-ui#96 (drop removed settings controls).
Two agents materially corrected the issues this plan was built on: #480 found the
data_source_allowlist "delegate" option would have been a privilege escalation (the
column is platform-admin-only, the settings endpoint is org-admin), and #481 found that no
FWI-calibrated curve can exist in principle — the defect is the ingestor's declared unit, not
the seeded curve.
Ground truth to re-derive before you start (do not trust these numbers)¶
They were true at 2026-08-01 and move whenever another session merges:
- Trunk was
1e4d753, Alembic head0062_ingest_jobs_bbox, so the next revision was0063. - Trunk was ungated — no
ci.ymlrun for that SHA. -
453, #455, #475 are closed. #455 was already split into #477 (build) and #478 (deferred).¶
git worktree list is not a formality — it is the check that caught two live sessions.
A worktree that is locked with recent file mtimes and no commits is an active build, not
an abandoned one. Check mtimes against the clock before assuming anything is stale, and
never delete or resume another session's worktree.
Run this before anything else:
git fetch origin main && git log --oneline -1 origin/main
git worktree list
gh pr list --state open --json number,headRefName,files \
--jq '.[] | select([.files[].path] | any(startswith("migrations/")))'
gh issue list --state open --limit 100 --json number,title,labels
gh workflow run ci.yml --ref main # gate trunk BEFORE building; require green
If trunk is red, fix or revert that first — do not build on a red trunk, or the first gate failure will implicate your work for a break that predates it.
Five hard constraints (verified — violating any of these silently corrupts the run)¶
- One migration in flight globally.
tests/conftest.py:103callscommand.upgrade(cfg, "head")— singular. Two branches each adding a migration both set the samedown_revision, producingMultiple head revisions are present, which fails every DB-backed test in every concurrent branch, not just the two that collided. The queue is global, not per-lane. Re-derive the next revision number across trunk plus open PRs plus live worktrees, immediately before creating the file. PYTHONPATHin worktrees. The editable install (__editable__.climate_lama-*.pth) is a plain path append resolving to the main checkout'ssrc/. A worktree build that runs pytest withoutPYTHONPATH=<worktree>/srcvalidates trunk's code, not the branch's — and reports green either way. This is the most dangerous silent failure in worktree mode.- Pass
--fastto every/build. This machine has no Docker/PostGIS. Without--fast,/buildruns pytest, hitsConnectionRefusedErrorfrom the DB fixtures, burns its 3-attempt fix cap on a failure unrelated to the code, and returnsfailed. Tests are still written and committed — only local execution is skipped. - NO-PR-CI. PRs carry no status checks by design; merge on
mergeable, never wait for checks. Exception:sdk-regenerate.ymldoes fire automatically on PRs touchingcore/errors.pyor the SDK error files. If it flags drift: runuv run python sdk/python/scripts/regenerate_errors.py, thenruff formatthat file, then commit. - Other sessions share this repo. Re-check
origin/mainandgit worktree listat each wave boundary and before each push.
Local signal that does exist: pytest tests/ -m "not slow" via .venv/Scripts/python.exe,
~10 min, stable baseline ~1264 passed / ~38 skipped / ~627 errors (all DB connection refusals).
Compare failure sets, not counts. A skip is not a pass.
Wave 0 — create four split issues, then validate the plan with --rr¶
Four buildable slices live inside multi-item umbrella issues; /build cannot target "item 5
of #291". Create each with an ## Execution block (Deps: / Parallel: / Files: / Size:):
| ID | From | Title | Migration |
|---|---|---|---|
| S1 | #291 item 5 | feat(db): enforce the impact_functions.version bump invariant with a trigger |
Yes |
| S2 | #267 item 4 | fix(api): offset-naive datetime writes break user_preferences/org_settings |
Maybe |
| S3 | #267 item 2 | chore: make seed-demo resolve the interpreter via uv |
No |
| S4 | #287 item 3 | ci(release): sign release images with cosign keyless OIDC |
No |
S4 is item 3 only — leave item 4 (SBOM) alone; its compliance trigger has not fired.
S4 must also answer: cosign signing has no consumer unless something verifies. Either add
cosign verify to #356's deploy path or document it as a manual step in
docs/deployment/upgrading.md, and say which.
Then: /analyze-chain 103 276 279 356 454 477 480 481 482 483 <S1> <S2> <S3> <S4>
Then run --rr over your wave schedule before dispatching a single build. Re-verify the
file-overlap map and the migration queue against live state, and correct the waves below if
they have drifted. The waves are a starting point, not a contract.
Waves¶
Each agent: /build <N> --worktree --fast. Gate trunk at each wave boundary.
Wave 1 — 5 parallel, disjoint files, no migrations
| Issue | Model · effort | Why this tier |
|---|---|---|
| #103 docs runbook | Sonnet 5 · high | Bounded synthesis from committed compose/config |
| #276 SDK async client | Opus 4.8 · high | Two real design calls: .aio namespace vs unified, and shared base vs duplication |
#483 upload creates no ingest_jobs row |
Opus 4.8 · high | Must substitute a failing unit test for the live-stack repro |
S3 (#486) Makefile uv run python |
Sonnet 5 · high | One line |
~~#477 staged-source grid metadata~~ — removed at execution: owned by a live session.
#483 carries a coupling the issue does not mention. The in-flight #454 work adds
JobRepository.get_id_by_ingest_job_id, which resolves the legacy Job via
params['ingest_job_id'] — a back-reference its docstring says every producer of an ingest
job stamps. #483 makes upload_hazard a third producer, so it must stamp that key or
454's lookup silently returns None for every upload-initiated ingest. Hand this to the¶
483 agent explicitly; it cannot infer it from the issue body.¶
#103's failure mode: the agent cannot reproduce any failure it documents. Every command
must be derived from committed docker-compose.yml, docker-compose.prod.yml,
migrations/alembic.ini and the Celery config, with the source file cited inline. Invented
commands are worse than no runbook — they get trusted during an incident.
#483: if the failing test you write does not fail, stop and report. Do not fix a bug you could not first make fail.
Wave 2 — 4 parallel (each depends on a Wave-1 sibling or is independent)
| Issue | Model · effort | Depends on |
|---|---|---|
| #279 SDK integration tests | Opus 4.8 · xhigh | #276 (same test tree) |
| S4 (#487) cosign signing | Opus 4.8 · high | — |
| #356 prod deploy workflow | Opus 4.8 · xhigh | — |
~~#454 plan_chunks rolling-deploy safety~~ — removed at execution: owned by a live
session. Its xhigh rationale still stands if it ever comes back: the defect is semantic,
not arity — bbox=None changed meaning from "irrelevant" to "plan against the raw file",
and reasoning about Celery chain serialisation across a rolling deploy is the hard part, not
the patch.
S4's gating condition was checked and has fired: #287 times item 3 as "after v0.1.0
ships"; the repo is at v0.4.0. docs/ops/release-pipeline-followup.md §"Item 3" (~line 164)
is a complete cold-start runbook including the exact cosign verify invocation.
#279 must be rescoped before it is built — its six sections are a wishlist, not acceptance
criteria; built literally it is unbounded. Cut it to what the configured SDK_SMOKE_* secrets
can exercise in sdk-smoke.yml, and fold in the three assertions sitting as a manual checklist
in #278 (login payload depth, result_id at the top level of /v1/jobs/{id}, ead/aai on
/v1/results/{id}). Run --rr on the rescope before building it.
#356 has the highest blast radius in the set — it writes .env.prod on a live host and
runs migrations. Require: dispatch-only trigger, a concurrency: group, GHCR tag-existence
preflight before any .env.prod edit, health gate after. Do not let it merge with a
push/release trigger however convenient. It cannot be run — no SSH/host secrets exist at
repo level or in the climate-lama (Production) environment. Deliver the workflow plus a
named list of secrets the owner must add.
Wave 3 — STRICTLY SERIAL. One at a time. Gate after EVERY merge.
The global migration queue. Everything here lands lint-verified only; the trunk gate is the first real test. Re-derive the revision number before each one.
| Order | Issue | Model · effort | Note |
|---|---|---|---|
| 1 | S1 (#485) version-bump trigger | Sonnet 5 · xhigh | Mechanical migration + docs |
| 2 | #482 persist ingest resolution | Opus 4.8 · high | Mirrors #453 (1e4d753) exactly — copy that pattern |
| 3 | #480 org-settings dead keys | Opus 4.8 · high | Per-key wire-or-remove calls; resolve the data_source_allowlist dual-home |
| 4 | #481 wildfire FWI curve | Opus 4.8 · xhigh | Needs a cited FWI curve |
~~5. S2 preferences tz bug~~ — removed at execution: already fixed, no issue created.
The prompt said it "may correctly end in a close"; it did, and direct verification was
cheaper than a build cycle. Evidence: the writer
(db/repositories/preferences_repository.py:40,68) has used datetime.now(UTC) since its
original commit 4251755, utcnow() appears nowhere in src/, and the column — the
actually-naive half — was made DateTime(timezone=True) by 1a2b105
(fix(api): fix three CI failures in preferences/rbac tests, 2026-04-26), 135 commits back
on trunk. Recorded on #267 and ticked.
#482 must not start before #454 lands. #454's in-flight diff edits
db/repositories/job_repository.py, and #482 touches the same ingest-job persistence area.
Re-check git worktree list and gh pr list at the Wave-3 boundary, not just at the start.
Gate after every merge here, not every three — a broken migration fails every test in every lane, so ~1 billed minute per merge is cheap insurance.
#481 must not invent coefficients. If no published FWI-calibrated curve is usable, it
reports that and stops. Check first whether core/impact_function_seeder.py or
migrations/versions/0005_seed_wf_impact_functions.py is the authoritative seeding path — if
the seeder is live and 0005 is legacy, the change needs no migration and leaves this wave.
S2 may correctly end in a close, not a fix. #267 diagnosed offset-naive datetimes hitting
asyncpg, but the model already declares DateTime(timezone=True)
(models/preferences.py:26,40), so the defect is on the write path. It was last observed in
April 2026, ~25 migrations ago. Reproduce first; if it no longer reproduces in CI, close it.
Per constraint 2 this class of bug cannot reproduce on this machine, so the CI gate is
the only evidence — do not guess.
Gate protocol¶
Before wave 1, after every Wave-3 merge, at each wave boundary, and once at the end:
git fetch origin main && HEAD=$(git rev-parse origin/main)
gh run list --workflow ci.yml --branch main -L 12 \
--json databaseId,headSha,status,conclusion \
--jq "[.[] | select(.headSha==\"$HEAD\")] | .[0]" # dedupe — duplicates bill in full
gh workflow run ci.yml --ref main # only if empty or failed
gh workflow run sdk-smoke.yml --ref main # after Wave 2 (SDK) and Wave 3 (migrations)
scripts/verify_ci_green.sh --sha <sha> is the same check locally at zero minutes.
On a red gate: freeze every lane before reverting. With parallel waves, continuing to
merge onto a red trunk grows the implicated set faster than bisecting shrinks it. Freeze,
git revert newest-first (squash merges revert one-to-one with PRs), re-gate, resume.
Do not build these¶
| Item | Why |
|---|---|
| #478 ranged/COG reads | Deferred by its own body — gated on ADR-045 (#446) being implemented; needs real MinIO + a continent-scale raster |
| #447 pan-EU expansion | Epic tracker; its own Execution block says "split before building" |
| #6 license choice | A decision, not code |
| #278, #285, #287 items 2/4/5, #291 items 2/3/4, #267 items 1/3/5 | External service clicks, parked phases, real prod UUIDs, or multi-week observation |
| #366, #367, #412, #273 | Blocked epics, owner-gated data access, or out-of-MVP cross-repo work |
Report¶
Per wave: issues attempted, PRs merged, gate SHA + result. At the end: what landed, what stopped and why, and the exact command to resume anything incomplete. Report trunks verified only by a green gate as verified; everything else is UNVERIFIED — do not call a lint-only merge "done".