-- Migration: 008_machine_uid.sql -- Purpose: Give connect_machines a deterministic, recomputable hardware identity -- (machine_uid) and dedup registrations on it (SPEC-004 / v2-stable-identity -- Task 2). -- -- Today connect_machines is keyed only on `agent_id` — a random UUID the agent -- persists in its config (generate_agent_id()). A lost/missing config produces a -- fresh UUID, and because upsert_machine dedups `ON CONFLICT (agent_id)`, that -- fresh id inserts a NEW row: the duplicate-registration bug (15 rows for 5 real -- hosts in production). The agent (Task 1) now derives a stable `machine_uid` from -- durable hardware/OS identifiers (Windows MachineGuid, hashed; recomputable) and -- reports it on the connect handshake and on AgentStatus. This migration persists -- it and adds the uniqueness needed to dedup the un-keyed / shared-key / config-loss -- fleet on that stable identity. -- -- SECURITY (SPEC-004): a client-asserted machine_uid is spoofable and is therefore -- NOT a trust boundary. For per-agent (`cak_`) keyed agents the key's machine -- binding stays authoritative (the server dedups those on their authenticated -- agent_id, never on a claimed uid). machine_uid is a *correctness* aid for the -- un-keyed path only. The UNIQUE index below enforces "one row per machine_uid" -- at the schema level so a concurrent/racing un-keyed insert cannot create a -- duplicate behind the application-level ON CONFLICT. -- -- Idempotent: ADD COLUMN IF NOT EXISTS + CREATE UNIQUE INDEX IF NOT EXISTS. The -- column is NULLABLE: legacy rows and agents that do not report a uid (older agents, -- some support-code clients) carry NULL, and the partial index excludes NULLs so any -- number of un-keyed rows may coexist without a uid. Applied on server startup by -- sqlx::migrate!(); never pre-applied via psql. Ordered after 007. -- See .claude/standards/gururmm/sqlx-migrations.md. -- 1. machine_uid: deterministic hardware identity reported by the agent. NULLABLE -- so legacy rows and non-reporting agents are unaffected. ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS machine_uid TEXT; -- 2. Enforce one row per machine_uid, but ONLY for rows that actually have one. -- A partial UNIQUE index (WHERE machine_uid IS NOT NULL) lets unlimited legacy -- NULL rows coexist while making a non-null machine_uid a true dedup key. Because -- this index is PARTIAL, Postgres only binds an ON CONFLICT inference clause to it -- when the clause REPEATS the same predicate: upsert_machine's arbiter must be -- `ON CONFLICT (machine_uid) WHERE machine_uid IS NOT NULL` (a bare -- `ON CONFLICT (machine_uid)` raises "no unique or exclusion constraint matching -- the ON CONFLICT specification" at runtime and persists no row). CREATE UNIQUE INDEX IF NOT EXISTS idx_connect_machines_machine_uid ON connect_machines (machine_uid) WHERE machine_uid IS NOT NULL;