All checks were successful
Admin-gated soft-delete + purge so operators can clear ghost machines/sessions (the ~15-rows-for-one-host accumulation) from the console. - migration 009: deleted_at on connect_sessions + connect_machines, with partial indexes WHERE deleted_at IS NULL. - DELETE /api/machines/:agent_id?purge=true and DELETE /api/sessions/:id?purge=true soft-delete the row and purge the in-memory session (remove_session); the non-purge path keeps the legacy hard-delete / live-only disconnect. POST /api/machines/bulk-remove handles multi-select (batch cap 500). All admin-gated (AdminUser -> 403; tightens the prior any-user delete) and audited to connect_session_events (actor + target + trusted client IP). - list/get queries filter deleted_at IS NULL so removed units leave the console; upsert revives (deleted_at = NULL) a genuinely-reconnecting machine. The keyed-reattach identity resolver (get_machine_by_id) is intentionally unfiltered. Dashboard removal UI is the A3b follow-up. 86 server tests pass; fmt/clippy/test clean. Implements specs/v2-stable-identity/plan.md Task 5 (server portion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
2.5 KiB
SQL
44 lines
2.5 KiB
SQL
-- Migration: 009_session_machine_soft_delete.sql
|
|
-- Purpose: Give connect_machines and connect_sessions a soft-delete marker
|
|
-- (deleted_at) so an operator can PURGE a stale machine/session —
|
|
-- removing it from the live console — without destroying its audit
|
|
-- history (SPEC-004 / v2-stable-identity Task 5).
|
|
--
|
|
-- Task 5 is the operator-removal mechanism that finally purges the ~14 live
|
|
-- ghost connect_machines rows left by the duplicate-registration bug. The
|
|
-- live-only admin "disconnect" (DELETE /api/sessions/:id) and the legacy hard
|
|
-- DELETE /api/machines/:agent_id stay as they were; the NEW purge path
|
|
-- (`?purge=true`) sets deleted_at, drops the in-memory session via
|
|
-- SessionManager::remove_session, and writes an audit row. A soft delete keeps
|
|
-- the row (and its connect_session_events history) for the audit trail per the
|
|
-- project convention "prefer deleted_at over hard deletes" (CLAUDE.md), while
|
|
-- every list/get query filters `deleted_at IS NULL` so the purged unit
|
|
-- disappears from the dashboard and the startup reconcile never restores it.
|
|
--
|
|
-- Idempotent: ADD COLUMN IF NOT EXISTS. The columns are NULLABLE with no default,
|
|
-- so adding them is a metadata-only change on Postgres (no table rewrite, no row
|
|
-- locks held for a scan) — safe to apply online. A NULL deleted_at means "live";
|
|
-- a non-null timestamp means "removed at that instant". Applied on server startup
|
|
-- by sqlx::migrate!(); never pre-applied via psql. Ordered after 008.
|
|
-- See .claude/standards/gururmm/sqlx-migrations.md.
|
|
|
|
-- 1. connect_sessions.deleted_at: when set, the session was operator-purged and is
|
|
-- excluded from every list/get query. NULL = live.
|
|
ALTER TABLE connect_sessions ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
|
|
|
-- 2. connect_machines.deleted_at: when set, the machine was operator-purged. This
|
|
-- is the marker that hides the ghost duplicate rows from /api/machines and the
|
|
-- startup reconcile. NULL = live.
|
|
ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
|
|
|
-- 3. Partial indexes so the hot "live rows only" filter (deleted_at IS NULL) used
|
|
-- by every list query stays an index scan as the soft-deleted set grows. The
|
|
-- predicate matches the WHERE clause the queries use.
|
|
CREATE INDEX IF NOT EXISTS idx_connect_machines_live
|
|
ON connect_machines (hostname)
|
|
WHERE deleted_at IS NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_connect_sessions_live
|
|
ON connect_sessions (started_at DESC)
|
|
WHERE deleted_at IS NULL;
|