- Backend: - Migration 032: agent_gov_level, status, incidents, permissions tables - Domain types for governance layer - Permission Engine with all governance checks - Governance Service (promote/demote/roles) - Revocation Service (revoke/suspend/reinstate) - Audit Service (events filtering and stats) - Incidents Service (create/assign/escalate/resolve) - REST API routes for governance, audit, incidents - Frontend: - TypeScript types for governance - API clients for governance, audit, incidents - GovernanceLevelBadge component - CityGovernancePanel component - AuditDashboard component - IncidentsList component with detail modal Based on: Agent_Governance_Protocol_v1.md
258 lines
11 KiB
SQL
258 lines
11 KiB
SQL
-- 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:<id>, microdao:<id>
|
|
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;
|
|
|