Files
microdao-daarion/microdao_schema.sql

248 lines
11 KiB
PL/PgSQL

-- MicroDAO core schema (DDL)
-- Requires: PostgreSQL 14+, extensions pgcrypto and pgvector
-- Safe to run multiple times (uses IF NOT EXISTS where possible)
-- 0) Extensions
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS vector;
-- 1) Enum types
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'org_type') THEN
CREATE TYPE org_type AS ENUM ('solo', 'dao');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'visibility') THEN
CREATE TYPE visibility AS ENUM ('private', 'dao', 'public');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'notification_status') THEN
CREATE TYPE notification_status AS ENUM ('queued', 'sent', 'failed', 'read');
END IF;
END $$;
-- 2) Helpers
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at := NOW();
RETURN NEW;
END $$;
-- 3) Users and Orgs
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT,
plan TEXT DEFAULT 'free',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TRIGGER trg_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TABLE IF NOT EXISTS orgs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
type org_type NOT NULL DEFAULT 'solo',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TRIGGER trg_orgs_updated_at
BEFORE UPDATE ON orgs
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
-- (optional) membership table for future use
CREATE TABLE IF NOT EXISTS org_members (
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (org_id, user_id)
);
-- 4) Channels and Messages
CREATE TABLE IF NOT EXISTS channels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
name TEXT NOT NULL,
visibility visibility NOT NULL DEFAULT 'dao',
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_channels_org ON channels(org_id);
CREATE INDEX IF NOT EXISTS idx_channels_meta_gin ON channels USING GIN (meta);
CREATE TRIGGER trg_channels_updated_at
BEFORE UPDATE ON channels
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TABLE IF NOT EXISTS messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
author_id UUID NOT NULL REFERENCES users(id) ON DELETE SET NULL,
content TEXT NOT NULL,
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_messages_channel_created ON messages(channel_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_messages_meta_gin ON messages USING GIN (meta);
-- 5) Documents and USDO entities
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL, -- e.g., 'upload', 'ipfs://cid', 'arxiv:XXXX'
owner_type TEXT NOT NULL CHECK (owner_type IN ('user','org')),
owner_id UUID NOT NULL,
format TEXT NOT NULL, -- 'pdf','html','image','md'
meta JSONB NOT NULL DEFAULT '{}', -- e.g., filename, hashes, policyId
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_documents_owner_user
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
-- If owner_type='org', create a partial FK via trigger or accept soft constraint
);
-- Optional soft constraint: ensure owner exists in either table (implemented via trigger in app-layer).
CREATE INDEX IF NOT EXISTS idx_documents_owner ON documents(owner_id);
CREATE INDEX IF NOT EXISTS idx_documents_meta_gin ON documents USING GIN (meta);
CREATE TRIGGER trg_documents_updated_at
BEFORE UPDATE ON documents
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TABLE IF NOT EXISTS usdo_entities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
kind TEXT NOT NULL, -- 'section','equation','table','figure','code','reference', etc.
payload JSONB NOT NULL, -- normalized USDO fragment
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_usdo_doc_kind ON usdo_entities(doc_id, kind);
CREATE INDEX IF NOT EXISTS idx_usdo_payload_gin ON usdo_entities USING GIN (payload);
CREATE INDEX IF NOT EXISTS idx_usdo_meta_gin ON usdo_entities USING GIN (meta);
-- 6) Chunks and RAG index
-- pgvector storage for text/image embeddings; adjust dimensions to your model
CREATE TABLE IF NOT EXISTS chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
text TEXT NOT NULL,
meta JSONB NOT NULL DEFAULT '{}', -- offsets, section ref, citations, policy, etc.
acl visibility NOT NULL DEFAULT 'dao',
vec VECTOR(1536), -- set to your embedding dimension
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_chunks_doc ON chunks(doc_id);
CREATE INDEX IF NOT EXISTS idx_chunks_acl ON chunks(acl);
CREATE INDEX IF NOT EXISTS idx_chunks_meta_gin ON chunks USING GIN (meta);
-- IVF index for ANN search; tune lists per dataset size
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_indexes WHERE schemaname = 'public' AND indexname = 'idx_chunks_vec_ivfflat'
) THEN
CREATE INDEX idx_chunks_vec_ivfflat ON chunks USING ivfflat (vec vector_l2_ops) WITH (lists = 100);
END IF;
END $$;
CREATE TABLE IF NOT EXISTS rag_index (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
chunk_id UUID NOT NULL UNIQUE REFERENCES chunks(id) ON DELETE CASCADE,
space visibility NOT NULL, -- private/dao/public space
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_rag_space ON rag_index(space);
CREATE INDEX IF NOT EXISTS idx_rag_meta_gin ON rag_index USING GIN (meta);
-- 7) Agent events, Policies, Rewards, Notifications, Audit
CREATE TABLE IF NOT EXISTS agent_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
run_id UUID NOT NULL,
type TEXT NOT NULL, -- e.g., 'run.started','run.completed','run.failed','policy.blocked'
payload JSONB NOT NULL DEFAULT '{}',
ts TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_agent_events_run ON agent_events(run_id, ts DESC);
CREATE INDEX IF NOT EXISTS idx_agent_events_type ON agent_events(type);
CREATE INDEX IF NOT EXISTS idx_agent_events_payload_gin ON agent_events USING GIN (payload);
CREATE TABLE IF NOT EXISTS policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scope TEXT NOT NULL, -- e.g., 'org:{id}', 'channel:{id}', 'global'
rules JSONB NOT NULL, -- structured policy rules
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_policies_scope ON policies(scope);
CREATE INDEX IF NOT EXISTS idx_policies_rules_gin ON policies USING GIN (rules);
CREATE TRIGGER trg_policies_updated_at
BEFORE UPDATE ON policies
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TABLE IF NOT EXISTS rewards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id UUID NOT NULL REFERENCES users(id) ON DELETE SET NULL,
kind TEXT NOT NULL, -- e.g., 'ingest','compute','contribution'
amount NUMERIC(18,6) NOT NULL CHECK (amount >= 0),
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_rewards_actor ON rewards(actor_id);
CREATE INDEX IF NOT EXISTS idx_rewards_kind ON rewards(kind);
CREATE INDEX IF NOT EXISTS idx_rewards_meta_gin ON rewards USING GIN (meta);
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
kind TEXT NOT NULL, -- e.g., 'ingest.completed','quota.exceeded'
payload JSONB NOT NULL DEFAULT '{}',
status notification_status NOT NULL DEFAULT 'queued',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_status ON notifications(user_id, status);
CREATE INDEX IF NOT EXISTS idx_notifications_kind ON notifications(kind);
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor UUID, -- user id or service principal
action TEXT NOT NULL, -- e.g., 'policy.update','channel.visibility.changed'
target JSONB NOT NULL DEFAULT '{}', -- embeds ids, kinds
ts TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip INET
);
CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit_logs(ts DESC);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_target_gin ON audit_logs USING GIN (target);
-- 8) Basic RLS scaffolding (disabled by default)
-- Enable and tailor at the application stage. Left commented intentionally.
-- ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE orgs ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE channels ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE chunks ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE rag_index ENABLE ROW LEVEL SECURITY;
-- Example policy patterns (to customize):
-- CREATE POLICY org_member_can_read_channels ON channels
-- FOR SELECT USING (EXISTS (SELECT 1 FROM org_members m WHERE m.org_id = channels.org_id AND m.user_id = auth.uid()));
-- CREATE POLICY author_can_edit_message ON messages
-- FOR UPDATE USING (author_id = auth.uid());
-- 9) Comments for maintainability
COMMENT ON TABLE users IS 'End users of MicroDAO. Plan used for quotas.';
COMMENT ON TABLE orgs IS 'Organizations/DAOs.';
COMMENT ON TABLE channels IS 'Chat channels per org with visibility.';
COMMENT ON TABLE messages IS 'Messages in channels with JSONB meta for mentions, citations.';
COMMENT ON TABLE documents IS 'Ingested sources. USDO is derived from here.';
COMMENT ON TABLE usdo_entities IS 'USDO fragments: sections, equations, tables, figures, code, references.';
COMMENT ON TABLE chunks IS 'RAG chunks with embeddings and ACL.';
COMMENT ON COLUMN chunks.vec IS 'Embedding vector; dimension must match your model.';
COMMENT ON TABLE rag_index IS 'Space mapping for chunks (private/dao/public).';
COMMENT ON TABLE agent_events IS 'Operational events for agent runs.';
COMMENT ON TABLE policies IS 'Policy documents and metadata.';
COMMENT ON TABLE rewards IS 'Rewards issued to actors for contributions/compute.';
COMMENT ON TABLE notifications IS 'User notifications across channels.';
COMMENT ON TABLE audit_logs IS 'Immutable audit trail for compliance.';