-- Migration 032: Governance Engine -- Purpose: Implement Governance Layer for DAARION.city -- Based on: docs/foundation/Agent_Governance_Protocol_v1.md -- Date: 2025-11-29 -- Status: MVP Feature -- ============================================================================ -- 0. ENUM TYPES -- ============================================================================ -- Agent governance level (0-7 hierarchy) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'agent_gov_level') THEN CREATE TYPE agent_gov_level AS ENUM ( 'guest', -- Level 0 'personal', -- Level 1 'member', -- Level 2 'worker', -- Level 3 'core_team', -- Level 4 'orchestrator', -- Level 5 'district_lead', -- Level 6 'city_governance' -- Level 7 ); END IF; END $$; -- Agent status DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'agent_status') THEN CREATE TYPE agent_status AS ENUM ('active', 'suspended', 'revoked'); END IF; END $$; -- Revocation type DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'revocation_type') THEN CREATE TYPE revocation_type AS ENUM ('soft', 'hard', 'shadow'); END IF; END $$; -- Incident status DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'incident_status') THEN CREATE TYPE incident_status AS ENUM ('open', 'in_progress', 'resolved', 'closed'); END IF; END $$; -- Incident priority DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'incident_priority') THEN CREATE TYPE incident_priority AS ENUM ('low', 'medium', 'high', 'critical'); END IF; END $$; -- Incident escalation level DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'escalation_level') THEN CREATE TYPE escalation_level AS ENUM ('microdao', 'district', 'city'); END IF; END $$; -- Target scope type for incidents DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'target_scope_type') THEN CREATE TYPE target_scope_type AS ENUM ('city', 'district', 'microdao', 'room', 'node', 'agent'); END IF; END $$; -- Permission action DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'permission_action') THEN CREATE TYPE permission_action AS ENUM ('read', 'write', 'moderate', 'admin', 'superadmin'); END IF; END $$; -- ============================================================================ -- 1. AGENTS TABLE UPDATE - Governance Fields -- ============================================================================ -- Add governance fields to agents ALTER TABLE agents ADD COLUMN IF NOT EXISTS gov_level agent_gov_level DEFAULT 'personal'; ALTER TABLE agents ADD COLUMN IF NOT EXISTS status agent_status DEFAULT 'active'; ALTER TABLE agents ADD COLUMN IF NOT EXISTS revoked_at TIMESTAMPTZ; ALTER TABLE agents ADD COLUMN IF NOT EXISTS revoked_by TEXT; ALTER TABLE agents ADD COLUMN IF NOT EXISTS revocation_reason TEXT; ALTER TABLE agents ADD COLUMN IF NOT EXISTS revocation_type revocation_type; -- Create indexes CREATE INDEX IF NOT EXISTS idx_agents_gov_level ON agents(gov_level); CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status); CREATE INDEX IF NOT EXISTS idx_agents_revoked ON agents(revoked_at) WHERE revoked_at IS NOT NULL; -- Migrate existing agent_role to gov_level UPDATE agents SET gov_level = 'orchestrator'::agent_gov_level WHERE agent_role = 'orchestrator' AND gov_level = 'personal'; -- Set city governance agents UPDATE agents SET gov_level = 'city_governance'::agent_gov_level WHERE id IN ('daarwizz', 'dario', 'daria'); -- ============================================================================ -- 2. INCIDENTS TABLE -- ============================================================================ CREATE TABLE IF NOT EXISTS incidents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_by_dais_id TEXT NOT NULL, target_scope_type target_scope_type NOT NULL, target_scope_id TEXT NOT NULL, status incident_status NOT NULL DEFAULT 'open', priority incident_priority NOT NULL DEFAULT 'medium', assigned_to_dais_id TEXT, escalation_level escalation_level NOT NULL DEFAULT 'microdao', title TEXT NOT NULL, description TEXT, resolution TEXT, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), resolved_at TIMESTAMPTZ, closed_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_incidents_status ON incidents(status); CREATE INDEX IF NOT EXISTS idx_incidents_priority ON incidents(priority); CREATE INDEX IF NOT EXISTS idx_incidents_escalation ON incidents(escalation_level); CREATE INDEX IF NOT EXISTS idx_incidents_created_by ON incidents(created_by_dais_id); CREATE INDEX IF NOT EXISTS idx_incidents_assigned ON incidents(assigned_to_dais_id); CREATE INDEX IF NOT EXISTS idx_incidents_target ON incidents(target_scope_type, target_scope_id); CREATE INDEX IF NOT EXISTS idx_incidents_open ON incidents(status, priority) WHERE status IN ('open', 'in_progress'); -- ============================================================================ -- 3. INCIDENT HISTORY TABLE -- ============================================================================ CREATE TABLE IF NOT EXISTS incident_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), incident_id UUID NOT NULL REFERENCES incidents(id) ON DELETE CASCADE, action TEXT NOT NULL, -- created, assigned, escalated, resolved, closed, comment actor_dais_id TEXT NOT NULL, old_value JSONB, new_value JSONB, comment TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_incident_history_incident ON incident_history(incident_id); CREATE INDEX IF NOT EXISTS idx_incident_history_actor ON incident_history(actor_dais_id); -- ============================================================================ -- 4. PERMISSIONS TABLE -- ============================================================================ CREATE TABLE IF NOT EXISTS permissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), dais_id TEXT NOT NULL, target_type TEXT NOT NULL, -- room, microdao, node, district, city target_id TEXT NOT NULL, action permission_action NOT NULL, granted_by TEXT NOT NULL, expires_at TIMESTAMPTZ, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(dais_id, target_type, target_id, action) ); CREATE INDEX IF NOT EXISTS idx_permissions_dais ON permissions(dais_id); CREATE INDEX IF NOT EXISTS idx_permissions_target ON permissions(target_type, target_id); CREATE INDEX IF NOT EXISTS idx_permissions_valid ON permissions(dais_id) WHERE expires_at IS NULL OR expires_at > now(); -- ============================================================================ -- 5. REVOCATIONS TABLE (Audit trail for revocations) -- ============================================================================ CREATE TABLE IF NOT EXISTS agent_revocations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), agent_id TEXT NOT NULL REFERENCES agents(id), dais_id TEXT, revoked_by TEXT NOT NULL, revocation_type revocation_type NOT NULL, reason TEXT NOT NULL, scope TEXT NOT NULL, -- city, district:, microdao: keys_invalidated BOOLEAN NOT NULL DEFAULT true, wallet_disabled BOOLEAN NOT NULL DEFAULT true, room_access_revoked BOOLEAN NOT NULL DEFAULT true, node_privileges_removed BOOLEAN NOT NULL DEFAULT true, assignments_terminated BOOLEAN NOT NULL DEFAULT true, reversible BOOLEAN NOT NULL DEFAULT true, reversed_at TIMESTAMPTZ, reversed_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_revocations_agent ON agent_revocations(agent_id); CREATE INDEX IF NOT EXISTS idx_revocations_dais ON agent_revocations(dais_id); CREATE INDEX IF NOT EXISTS idx_revocations_type ON agent_revocations(revocation_type); -- ============================================================================ -- 6. UPDATE EVENT_OUTBOX WITH NEW GOVERNANCE EVENTS -- ============================================================================ -- Add actor_id column to event_outbox for audit ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS actor_id TEXT; ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS target_id TEXT; ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS scope TEXT; CREATE INDEX IF NOT EXISTS idx_outbox_actor ON event_outbox(actor_id); CREATE INDEX IF NOT EXISTS idx_outbox_target ON event_outbox(target_id); CREATE INDEX IF NOT EXISTS idx_outbox_scope ON event_outbox(scope); -- ============================================================================ -- 7. DAIS KEYS - Add revoked flag -- ============================================================================ ALTER TABLE dais_keys ADD COLUMN IF NOT EXISTS revoked BOOLEAN DEFAULT false; ALTER TABLE dais_keys ADD COLUMN IF NOT EXISTS revoked_reason TEXT; ALTER TABLE dais_keys ADD COLUMN IF NOT EXISTS revoked_by TEXT; -- Migrate existing revoked_at to revoked UPDATE dais_keys SET revoked = true WHERE revoked_at IS NOT NULL AND revoked = false; -- ============================================================================ -- 8. COMMENTS -- ============================================================================ COMMENT ON TABLE incidents IS 'Incident tracking for governance escalation'; COMMENT ON TABLE incident_history IS 'Audit trail for incident changes'; COMMENT ON TABLE permissions IS 'Explicit permissions for DAIS identities'; COMMENT ON TABLE agent_revocations IS 'Audit trail for agent revocations'; COMMENT ON COLUMN agents.gov_level IS 'Governance level: 0=guest to 7=city_governance'; COMMENT ON COLUMN agents.status IS 'Agent status: active, suspended, or revoked'; COMMENT ON COLUMN agents.revoked_at IS 'Timestamp when agent was revoked'; COMMENT ON COLUMN agents.revocation_type IS 'Type of revocation: soft, hard, or shadow'; COMMENT ON COLUMN incidents.escalation_level IS 'Current escalation: microdao → district → city'; COMMENT ON COLUMN incidents.target_scope_type IS 'What the incident is about: city, district, microdao, room, node, agent'; -- ============================================================================ -- 9. SEED DATA - City Governance Agents -- ============================================================================ -- Ensure city governance agents have correct level UPDATE agents SET gov_level = 'city_governance'::agent_gov_level, status = 'active'::agent_status WHERE id IN ('daarwizz', 'dario', 'daria'); -- ============================================================================ -- DONE -- ============================================================================ SELECT 'Migration 032 completed: Governance Engine' as result;