cocottetech/@platform/infrastructure/sql/migrations/0011_ingest_state.sql
2026-06-08 01:02:47 -07:00

72 lines
3.1 KiB
PL/PgSQL

-- 0011_ingest_state.sql
--
-- Makes photo ingestion a managed, Cockpit-governed service instead of a one-off
-- batch. One singleton row per Person holds both the CONTROL plane (the Cockpit
-- toggles enabled / requests a run / pauses) and the STATUS plane (the
-- content-ingestor worker reports progress here after each batch). The worker
-- talks only to platform.api (single API plane) — it never reaches iOS directly;
-- iOS reads/writes this state through platform.api's /ingestion endpoints.
--
-- Layers on users/orgs (0001). Reuses 0001 helpers: current_user_uuid() (RLS) +
-- touch_updated_at(). platform.db is Postgres ≥ 14 (INFRA.md §1).
BEGIN;
CREATE TYPE ingest_run_state AS ENUM ('idle', 'running', 'paused');
CREATE TABLE ingest_state (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
org_id UUID NULL REFERENCES orgs(id) ON DELETE CASCADE,
-- Control plane (set by the Cockpit via POST /ingestion/control)
enabled BOOLEAN NOT NULL DEFAULT FALSE, -- continuous polling on/off
run_requested BOOLEAN NOT NULL DEFAULT FALSE, -- one-shot "run now" trigger; worker clears it
-- Status plane (reported by the worker via PUT /ingestion/progress)
state ingest_run_state NOT NULL DEFAULT 'idle',
cursor TIMESTAMPTZ NULL, -- newest-first paging cursor (photos.taken_at)
total_photos INTEGER NOT NULL DEFAULT 0, -- library size with uploaded bytes
processed INTEGER NOT NULL DEFAULT 0, -- assets classified+landed so far
hot_count INTEGER NOT NULL DEFAULT 0,
stocked_count INTEGER NOT NULL DEFAULT 0,
explicit_count INTEGER NOT NULL DEFAULT 0,
failed_count INTEGER NOT NULL DEFAULT 0,
last_run_at TIMESTAMPTZ NULL,
last_error TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (user_id),
CONSTRAINT ingest_state_counts_chk CHECK (
total_photos >= 0 AND processed >= 0 AND hot_count >= 0
AND stocked_count >= 0 AND explicit_count >= 0 AND failed_count >= 0
)
);
CREATE INDEX idx_ingest_state_org ON ingest_state(org_id) WHERE org_id IS NOT NULL;
CREATE TRIGGER trg_touch_ingest_state BEFORE UPDATE ON ingest_state
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();
-- Row-level security (per 0001 convention). current_user_uuid() reads the
-- app.current_user_id GUC set by platform.api per request.
ALTER TABLE ingest_state ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON ingest_state
USING (
user_id = current_user_uuid()
OR (org_id IS NOT NULL
AND org_id IN (SELECT org_id FROM org_members
WHERE user_id = current_user_uuid()))
)
WITH CHECK (
user_id = current_user_uuid()
OR (org_id IS NOT NULL
AND org_id IN (SELECT org_id FROM org_members
WHERE user_id = current_user_uuid()))
);
COMMENT ON TABLE ingest_state IS
'Singleton-per-Person control+status for photo ingestion. Cockpit sets enabled/run_requested; the content-ingestor worker reports progress. Makes ingestion a managed service surfaced in the iOS Cockpit.';
COMMIT;