diff --git a/migrations/108_entity_notion_mappings.sql b/migrations/108_entity_notion_mappings.sql new file mode 100644 index 00000000..818a41d8 --- /dev/null +++ b/migrations/108_entity_notion_mappings.sql @@ -0,0 +1,43 @@ +-- Migration 106: entity_notion_mappings + projection_checkpoints (KG → Notion projection, Phase 0) +-- +-- The Notion analogue of entity_rid_mappings (the vault projection table). Projects a +-- canonical personal-KOI entity onto a Notion page with independent sync state. Keyed on +-- (canonical_uri, notion_database_id) because a single entity may legitimately have one +-- page per Notion DB (Team Members DB vs the unified Entities DB). One LIVE page per +-- (canonical_uri, db) is enforced by a partial unique index; merged losers are archived, +-- not deleted, so history survives and the invariant holds. +-- +-- Spec: ~/.claude/plans/kg-notion-entity-projection-spec.md §7.1. Additive; touches no +-- existing table. Safe to re-run (IF NOT EXISTS). + +CREATE TABLE IF NOT EXISTS entity_notion_mappings ( + notion_page_id TEXT UNIQUE NOT NULL, + canonical_uri TEXT NOT NULL, -- → entity_registry.fuseki_uri + notion_database_id TEXT NOT NULL, + entity_type TEXT, + name TEXT, + content_hash TEXT, + sync_status TEXT DEFAULT 'linked' + CHECK (sync_status IN ('linked','local_only','pending_sync','conflict','archived')), + relation_truncated BOOLEAN DEFAULT FALSE, -- degree > 100 (spec §5.5) + last_synced TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + visibility_scope TEXT DEFAULT 'public' +); + +-- One LIVE page per (canonical_uri, db). Archived rows excluded so a merged loser can +-- coexist historically without colliding with the survivor. +CREATE UNIQUE INDEX IF NOT EXISTS uq_notion_live_uri_db + ON entity_notion_mappings (canonical_uri, notion_database_id) + WHERE sync_status <> 'archived'; + +CREATE INDEX IF NOT EXISTS idx_notion_mappings_canonical + ON entity_notion_mappings (canonical_uri); + +-- Merge-poller checkpoint (Phase 3): monotonic high-water mark on entity_merge_log.id, +-- committed in the SAME transaction as the loser-row retire. +CREATE TABLE IF NOT EXISTS projection_checkpoints ( + name TEXT PRIMARY KEY, -- e.g. 'notion_entity_merge_poller' + last_processed_id BIGINT NOT NULL DEFAULT 0, + updated_at TIMESTAMPTZ DEFAULT NOW() +); diff --git a/migrations/109_entity_visibility_scope.sql b/migrations/109_entity_visibility_scope.sql new file mode 100644 index 00000000..d6099630 --- /dev/null +++ b/migrations/109_entity_visibility_scope.sql @@ -0,0 +1,37 @@ +-- 107_entity_visibility_scope.sql +-- Phase 1, KG two-viewport plan (~/.claude/plans/design-the-indigenomics-twinkly-dijkstra.md): +-- add the 4-value AUDIENCE visibility axis to entity_registry. +-- +-- CORRECTED per read-only recon (B1): entity_registry ONLY. Do NOT add a CHECK to +-- entity_rid_mappings.visibility_scope — that is a SEPARATE, pre-existing 2-value axis +-- (public / node_private = Notion-projection privacy). A 4-value CHECK there would +-- CheckViolation-kill vault->registry sync on the first node_private write. +-- +-- Additive + metadata-only (constant DEFAULT, no row rewrite on ~21k rows). No code reads +-- entity_registry.visibility_scope yet (recon: 0 readers), so the 'unclassified' default +-- cannot blank any read path. Reversible via 107_entity_visibility_scope_down.sql. +-- Apply directly: psql personal_koi -v ON_ERROR_STOP=1 -f 107_entity_visibility_scope.sql +BEGIN; + +ALTER TABLE entity_registry + ADD COLUMN IF NOT EXISTS visibility_scope TEXT NOT NULL DEFAULT 'unclassified', + ADD COLUMN IF NOT EXISTS source_context TEXT, + ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS revoked_at TIMESTAMPTZ; + +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'entity_registry_visibility_scope_check' + AND conrelid = 'public.entity_registry'::regclass + ) THEN + ALTER TABLE entity_registry + ADD CONSTRAINT entity_registry_visibility_scope_check + CHECK (visibility_scope IN ('public', 'team', 'confidential', 'unclassified')); + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_entity_registry_visibility_scope + ON entity_registry (visibility_scope); + +COMMIT; diff --git a/migrations/109_entity_visibility_scope_down.sql b/migrations/109_entity_visibility_scope_down.sql new file mode 100644 index 00000000..abe598f9 --- /dev/null +++ b/migrations/109_entity_visibility_scope_down.sql @@ -0,0 +1,17 @@ +-- 107_entity_visibility_scope_down.sql — reverses 107 (entity_registry only). +-- Does NOT touch entity_rid_mappings.visibility_scope (a separate pre-existing column). +-- Apply: psql personal_koi -v ON_ERROR_STOP=1 -f 107_entity_visibility_scope_down.sql +BEGIN; + +DROP INDEX IF EXISTS idx_entity_registry_visibility_scope; + +ALTER TABLE entity_registry + DROP CONSTRAINT IF EXISTS entity_registry_visibility_scope_check; + +ALTER TABLE entity_registry + DROP COLUMN IF EXISTS visibility_scope, + DROP COLUMN IF EXISTS source_context, + DROP COLUMN IF EXISTS expires_at, + DROP COLUMN IF EXISTS revoked_at; + +COMMIT;