-- 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;