Persist the agent-reported machine_uid and dedup connect_machines on it so a single physical machine can't register duplicate rows when its config-file agent_id regenerates (the ghost-session root cause). - migration 008: nullable connect_machines.machine_uid + partial unique index (WHERE machine_uid IS NOT NULL); idempotent, startup-applied. - upsert_machine: two-path dedup (ON CONFLICT machine_uid when present, else the legacy ON CONFLICT agent_id path, unchanged). - session reattach: a machine_uid index consulted before agent_id, with all removal paths purging it. - security: keyed (cak_) agents stay authoritative — their claimed machine_uid is dropped (effective_machine_uid=None); uid is dedup-only for un-keyed / support-code agents. Startup restore skips uid-indexing keyed machines and fails closed if the keyed-set query errors. 74 server tests pass; clippy clean. Implements specs/v2-stable-identity/plan.md Task 2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
2.6 KiB
SQL
42 lines
2.6 KiB
SQL
-- 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 — this
|
|
-- is what upsert_machine's `ON CONFLICT (machine_uid)` arbiter binds to.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_connect_machines_machine_uid
|
|
ON connect_machines (machine_uid)
|
|
WHERE machine_uid IS NOT NULL;
|