-- A model's proposal for how a mission's team should be shaped, and whether a -- human accepted it. -- -- `routes/planner.rs` has had Opus proposing 2-6 members with a model each since -- the Master Planner shipped, and none of it has ever reached a mission: the -- proposal lived in React state and died with the tab. Missions instead got a -- team template picked in the wizard, whose roles are fixed and whose every claw -- is minted `claude-sonnet-5` — a hardcode in `mint_team_from_template`, and the -- reason no mission has ever run heterogeneous providers. -- -- Persisting the proposal is what makes it reviewable and what makes an approval -- an event rather than a click. A proposal is NEVER applied on arrival: a model -- sizing a team is a suggestion about how to spend money on VMs, and the row -- records who accepted it and when. -- -- `status`: -- proposed — the model's answer, applied to nothing. -- approved — written onto the mission (`config.roster`); terminal. -- rejected — declined; kept, because the ones a human turned down are the -- only record of what the planner gets wrong. CREATE TABLE IF NOT EXISTS mission_team_proposals ( id UUID PRIMARY KEY, mission_id UUID NOT NULL REFERENCES missions (id) ON DELETE CASCADE, workspace_id UUID NOT NULL, -- The roster itself: {"topology_kind": "...", "members": [{role, backend, -- model, rationale}, ...]}. Stored as the model produced it (after -- validation) rather than normalised into columns — the shape is the -- planner's contract, and splitting it here would mean migrating this table -- every time the planner learns a field. roster JSONB NOT NULL, -- Which model authored it, so a bad roster can be traced to a model rather -- than to "the planner". author_model TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'proposed' CHECK (status IN ('proposed', 'approved', 'rejected')), -- Why the proposal was refused, or why an approval could not be applied. note TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), decided_at TIMESTAMPTZ, decided_by UUID ); CREATE INDEX IF NOT EXISTS mission_team_proposals_mission_idx ON mission_team_proposals (mission_id, created_at DESC); -- At most one approved roster per mission. Two approved proposals would be two -- answers to "what shape is this mission", and the executor reads one field — -- so the second would silently win by being written last. CREATE UNIQUE INDEX IF NOT EXISTS mission_team_proposals_one_approved ON mission_team_proposals (mission_id) WHERE status = 'approved';