-- Oracle Canvas Schema — Section 16.4 of the Oracle Architecture Document v1.0 -- Run this against your PostgreSQL database to create the Oracle persistence layer. -- Requires: UUID extension, JSONB support (PostgreSQL 14+) -- ── Prerequisites ───────────────────────────────────────────────────────────── CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ── Core tables ─────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS oracle_canvas_pages ( page_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, owner_id TEXT NOT NULL, branch_id TEXT NOT NULL, branch_name TEXT NOT NULL DEFAULT 'main', page_type TEXT NOT NULL DEFAULT 'main' CHECK (page_type IN ('main', 'fork')), title TEXT NOT NULL DEFAULT 'Untitled Canvas', is_shared BOOLEAN NOT NULL DEFAULT FALSE, head_revision INTEGER NOT NULL DEFAULT 0, base_revision INTEGER NOT NULL DEFAULT 0, sharing_policy JSONB NOT NULL DEFAULT '{"shareMode":"direct_fork_only","allowReshare":false,"defaultForkVisibility":"private"}'::JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_canvas_page_revisions ( revision_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE, tenant_id TEXT NOT NULL, revision_number INTEGER NOT NULL, commit_kind TEXT NOT NULL CHECK (commit_kind IN ('prompt', 'merge', 'rollback', 'manual_edit')), commit_summary TEXT, actor_id TEXT NOT NULL, execution_id UUID, merge_request_id UUID, components_snapshot JSONB NOT NULL DEFAULT '[]'::JSONB, idempotency_key TEXT UNIQUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (page_id, revision_number) ); CREATE TABLE IF NOT EXISTS oracle_canvas_components ( component_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE, tenant_id TEXT NOT NULL, type TEXT NOT NULL, title TEXT NOT NULL, description TEXT, version INTEGER NOT NULL DEFAULT 1, lifecycle_state TEXT NOT NULL DEFAULT 'active' CHECK (lifecycle_state IN ('draft','active','superseded','archived','revoked')), data_source_descriptor JSONB NOT NULL, visualization_parameters JSONB NOT NULL DEFAULT '{}'::JSONB, data_bindings JSONB NOT NULL DEFAULT '{}'::JSONB, provenance JSONB NOT NULL, rendering_hints JSONB NOT NULL, layout JSONB NOT NULL, access_controls JSONB NOT NULL, style_signature JSONB NOT NULL DEFAULT '{}'::JSONB, validation_state JSONB NOT NULL DEFAULT '{}'::JSONB, audit_log TEXT[] NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_prompt_executions ( execution_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE, branch_id TEXT NOT NULL, actor_id TEXT NOT NULL, prompt TEXT NOT NULL, intent_class TEXT NOT NULL DEFAULT 'analytical', status TEXT NOT NULL DEFAULT 'received', model_runtime TEXT NOT NULL DEFAULT 'nemoclaw_hosted', semantic_model_version TEXT NOT NULL DEFAULT 'oracle_semantic_v1', retrieval_plan JSONB, visualization_plan JSONB, warnings TEXT[] NOT NULL DEFAULT '{}', summary TEXT, components_created TEXT[] NOT NULL DEFAULT '{}', client_request_id TEXT UNIQUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), completed_at TIMESTAMPTZ ); CREATE TABLE IF NOT EXISTS oracle_component_templates ( template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, name TEXT NOT NULL, category TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'catalog_active', origin TEXT NOT NULL DEFAULT 'premade', version TEXT NOT NULL DEFAULT '1.0.0', accepted_shapes TEXT[] NOT NULL DEFAULT '{}', style_signature JSONB DEFAULT NULL, validation_state JSONB DEFAULT NULL, provenance JSONB DEFAULT NULL, use_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_forks ( fork_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), source_page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id), source_branch_id TEXT NOT NULL, source_revision INTEGER NOT NULL, fork_page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id), fork_branch_id TEXT NOT NULL, recipient_user_id TEXT NOT NULL, created_by TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','merged','closed')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_merge_requests ( merge_request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, source_page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id), source_branch_id TEXT NOT NULL, source_head_revision INTEGER NOT NULL, target_page_id UUID NOT NULL REFERENCES oracle_canvas_pages(page_id), target_branch_id TEXT NOT NULL, target_base_revision INTEGER NOT NULL, title TEXT NOT NULL, description TEXT, status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','changes_requested','approved','merged','closed')), conflicts JSONB NOT NULL DEFAULT '[]'::JSONB, diff_summary JSONB DEFAULT NULL, resolutions JSONB DEFAULT NULL, created_by TEXT NOT NULL, reviewed_by TEXT, reviewer_comment TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_lineage_records ( lineage_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, source_kind TEXT NOT NULL, source_id TEXT NOT NULL, transformation_type TEXT NOT NULL, transformation_spec_hash TEXT, produced_kind TEXT NOT NULL, produced_id TEXT NOT NULL, policy_snapshot_id TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS oracle_audit_events ( audit_event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, action TEXT NOT NULL, actor_id TEXT NOT NULL, actor_type TEXT NOT NULL DEFAULT 'user', correlation_id TEXT NOT NULL, execution_id UUID, details JSONB NOT NULL DEFAULT '{}'::JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ── Indexes ─────────────────────────────────────────────────────────────────── -- Canvas pages: tenant lookup, branch lookup CREATE INDEX IF NOT EXISTS idx_oracle_pages_tenant ON oracle_canvas_pages(tenant_id); CREATE INDEX IF NOT EXISTS idx_oracle_pages_owner ON oracle_canvas_pages(owner_id); CREATE INDEX IF NOT EXISTS idx_oracle_pages_branch ON oracle_canvas_pages(branch_id); -- Revisions: page-scoped revision queries CREATE INDEX IF NOT EXISTS idx_oracle_revisions_page ON oracle_canvas_page_revisions(page_id, revision_number DESC); CREATE INDEX IF NOT EXISTS idx_oracle_revisions_tenant ON oracle_canvas_page_revisions(tenant_id); CREATE INDEX IF NOT EXISTS idx_oracle_revisions_execution ON oracle_canvas_page_revisions(execution_id); -- Components: page-scoped, lifecycle CREATE INDEX IF NOT EXISTS idx_oracle_components_page ON oracle_canvas_components(page_id, lifecycle_state); CREATE INDEX IF NOT EXISTS idx_oracle_components_tenant ON oracle_canvas_components(tenant_id); -- Prompt executions: page/actor lookup CREATE INDEX IF NOT EXISTS idx_oracle_executions_page ON oracle_prompt_executions(page_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_oracle_executions_actor ON oracle_prompt_executions(actor_id, created_at DESC); -- Templates: tenant + category + status CREATE INDEX IF NOT EXISTS idx_oracle_templates_tenant_cat ON oracle_component_templates(tenant_id, category, status); -- Forks: source and recipient lookup CREATE INDEX IF NOT EXISTS idx_oracle_forks_source ON oracle_forks(source_page_id); CREATE INDEX IF NOT EXISTS idx_oracle_forks_recipient ON oracle_forks(recipient_user_id); -- Merge requests: target/source page, status CREATE INDEX IF NOT EXISTS idx_oracle_mrs_target ON oracle_merge_requests(target_page_id, status); CREATE INDEX IF NOT EXISTS idx_oracle_mrs_source ON oracle_merge_requests(source_page_id, status); CREATE INDEX IF NOT EXISTS idx_oracle_mrs_tenant ON oracle_merge_requests(tenant_id, status); -- Lineage: source/produced lookups CREATE INDEX IF NOT EXISTS idx_oracle_lineage_source ON oracle_lineage_records(source_kind, source_id); CREATE INDEX IF NOT EXISTS idx_oracle_lineage_produced ON oracle_lineage_records(produced_kind, produced_id); CREATE INDEX IF NOT EXISTS idx_oracle_lineage_tenant ON oracle_lineage_records(tenant_id); -- Audit: entity lookup, correlation lookup CREATE INDEX IF NOT EXISTS idx_oracle_audit_entity ON oracle_audit_events(entity_type, entity_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_oracle_audit_correlation ON oracle_audit_events(correlation_id); CREATE INDEX IF NOT EXISTS idx_oracle_audit_tenant ON oracle_audit_events(tenant_id, created_at DESC);