Files
clawmates/migrations/0073_phase_capacity_wait.sql
T
Omar Sobh d84d17207f feat(placement): place per phase, and let a full fleet queue
Phase 1b: wires the capacity model from 3a2d76a into the launch path, and turns
the existing pending-phase loop into the queue.

Placement moves from mission launch to PHASE launch. A node chosen at launch is
chosen once, minutes before the first VM boots and hours before the last — and
re-placing between phases is free, because mission state lives on the gateway
checkout and every VM is inject -> run -> collect -> destroy. Pinning early
bought nothing and cost the ability to react to a node filling or draining
mid-mission. One call site serves both the solo and composed paths so they cannot
disagree; the composed worker reads `missions.target_node_id`, which placement
writes before dispatch.

QUEUEING, with no new machinery: a phase with no admissible node keeps its
`pending` status and creates no `topology_runs` row. `start_pending_phases`
retries every 10s — that loop already was a queue; nothing downstream ever sees a
run that did not happen.

The risk that creates is the one this codebase keeps paying for: a phase waiting
for capacity looks exactly like a phase nothing is working on. So the wait is
RECORDED, not merely logged — migration 0073 adds `capacity_blocked_since` and
`capacity_note`, stamped once and preserved across retries so the wait is
measured from the first refusal. It is bounded at two full turns: a fleet that
frees will free within one, and a phase that waited two hours must say so rather
than sit pending forever looking like a bug.

Mission launch still fails when NO node could ever run the backend — that is not
transient, waiting will not fix it, and `microvm-negctl` asserts such a mission
stays `draft`. Capacity refusals are transient and queue; capability refusals are
not and fail. The two are separate variants precisely so they cannot be confused.

257 lib tests, 20 binaries.
2026-08-08 08:45:42 -07:00

16 lines
886 B
SQL

-- Why a phase is waiting, so a queue never looks like a stall.
--
-- Placement moves from mission launch to PHASE launch, which makes the existing
-- `start_pending_phases` loop the queue: a phase with no admissible node simply
-- stays `pending` and is retried every 10s. That is the whole queue mechanism —
-- no new status, no sweeper, no migration for a `queued` state.
--
-- The risk it creates is the one this codebase keeps paying for: a phase waiting
-- for capacity is indistinguishable from a phase nothing is working on. These two
-- columns are what make the difference visible. `capacity_blocked_since` also
-- bounds the wait, so a fleet that never frees fails the phase with a reason
-- instead of holding it forever.
ALTER TABLE mission_phases
ADD COLUMN IF NOT EXISTS capacity_blocked_since timestamptz,
ADD COLUMN IF NOT EXISTS capacity_note text;