-- 005_task_indexes.sql — index the lookups the control loops actually do.
--
-- Postgres does NOT auto-index the referencing side of a foreign key. `tasks.instance_id`
-- had no index, and three hot paths look rows up by it:
--
--   * ReconcileRepo._has_unfinished  — the idempotency guard on every enqueue, so once per
--     candidate per 60s tick.
--   * due_for_deprovision's `not exists` correlated subquery — once per instance per tick.
--   * the ON DELETE CASCADE itself, on every instance delete.
--
-- `tasks` is append-only in practice (done/failed rows are never pruned by the app; the
-- runbook prunes them by hand), so a sequential scan there gets slower every day the
-- system runs. This is the index that stops a 60-second control loop degrading into a
-- full table scan of all history.
--
-- Composite on (instance_id, kind, state) because _has_unfinished filters all three.
create index tasks_by_instance on tasks (instance_id, kind, state);

-- `instances.state` is filtered by ready_instances, instance_counts, due_for_deprovision
-- and list_upgradable — every reconciler check. Partial would not help: the sweeps look
-- for different states, so the whole column earns its index.
create index instances_by_state on instances (state);
