-- M9 P3: agent node registry and heartbeat metadata. -- Node tokens are stored as SHA-256 digests and are only returned at create / -- rotation time. Heartbeat payloads are bounded metadata, not task content. CREATE TABLE IF NOT EXISTS gateway.agent_nodes ( id uuid PRIMARY KEY, code text NOT NULL UNIQUE CHECK (length(code) BETWEEN 1 AND 128), name text NOT NULL CHECK (length(name) BETWEEN 1 AND 128), description text NOT NULL DEFAULT '' CHECK (length(description) <= 4000), endpoint text NOT NULL DEFAULT '' CHECK (length(endpoint) <= 512), node_type text NOT NULL DEFAULT 'worker' CHECK (node_type IN ('worker', 'gateway', 'executor')), pool_type text NOT NULL DEFAULT 'private' CHECK (pool_type IN ('public', 'private')), pool_code text NOT NULL DEFAULT 'default' CHECK (length(pool_code) BETWEEN 1 AND 64), enabled boolean NOT NULL DEFAULT true, token_prefix text NOT NULL CHECK (length(token_prefix) BETWEEN 4 AND 32), token_hash bytea NOT NULL CHECK (octet_length(token_hash) = 32), version text NOT NULL DEFAULT '' CHECK (length(version) <= 128), capabilities jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(capabilities) = 'object'), metadata jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(metadata) = 'object'), last_heartbeat_at timestamptz, last_heartbeat_ip inet, last_error text NOT NULL DEFAULT '' CHECK (length(last_error) <= 4000), created_by uuid REFERENCES gateway.admin_accounts(id), created_at timestamptz NOT NULL DEFAULT clock_timestamp(), updated_at timestamptz NOT NULL DEFAULT clock_timestamp() ); CREATE INDEX IF NOT EXISTS agent_nodes_pool_idx ON gateway.agent_nodes (pool_type, pool_code, enabled, updated_at DESC); CREATE INDEX IF NOT EXISTS agent_nodes_heartbeat_idx ON gateway.agent_nodes (last_heartbeat_at DESC NULLS LAST, enabled);