CREATE TABLE users ( id uuid PRIMARY KEY, external_subject text NOT NULL UNIQUE, display_name text NOT NULL, email text, status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), last_login_at timestamptz ); CREATE TABLE roles ( id uuid PRIMARY KEY, name text NOT NULL UNIQUE CHECK (name IN ('viewer', 'operator', 'editor', 'administrator')), created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE user_roles ( user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (user_id, role_id) ); CREATE TABLE data_sources ( id uuid PRIMARY KEY, type text NOT NULL, name text NOT NULL, enabled boolean NOT NULL DEFAULT true, configuration_ref text NOT NULL, capability_document jsonb NOT NULL DEFAULT '{}'::jsonb, health_state text NOT NULL DEFAULT 'unknown' CHECK (health_state IN ('healthy', 'degraded', 'unhealthy', 'unknown')), last_success_at timestamptz, last_error_code text, last_error_message text, freshness_policy jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE collectors ( id uuid PRIMARY KEY, datasource_id uuid NOT NULL REFERENCES data_sources(id) ON DELETE CASCADE, kind text NOT NULL, version text NOT NULL, heartbeat timestamptz, capabilities jsonb NOT NULL DEFAULT '[]'::jsonb, status text NOT NULL DEFAULT 'unknown', UNIQUE (datasource_id, kind) ); CREATE TABLE entities ( id uuid PRIMARY KEY, entity_type text NOT NULL, canonical_name text NOT NULL, display_name text NOT NULL, status text NOT NULL DEFAULT 'unknown', status_reasons jsonb NOT NULL DEFAULT '[]'::jsonb, first_seen_at timestamptz NOT NULL, last_seen_at timestamptz, tombstoned_at timestamptz, attributes jsonb NOT NULL DEFAULT '{}'::jsonb ); CREATE TABLE entity_aliases ( entity_id uuid NOT NULL REFERENCES entities(id) ON DELETE CASCADE, source_id uuid NOT NULL REFERENCES data_sources(id) ON DELETE CASCADE, external_type text NOT NULL, external_id text NOT NULL, PRIMARY KEY (source_id, external_type, external_id) ); CREATE TABLE dashboards ( id uuid PRIMARY KEY, slug text NOT NULL UNIQUE, name text NOT NULL, description text NOT NULL DEFAULT '', owner_user_id uuid REFERENCES users(id) ON DELETE SET NULL, scope text NOT NULL CHECK (scope IN ('personal', 'shared', 'system')), archived_at timestamptz, current_version_id uuid, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE dashboard_versions ( id uuid PRIMARY KEY, dashboard_id uuid NOT NULL REFERENCES dashboards(id) ON DELETE CASCADE, version_number integer NOT NULL CHECK (version_number > 0), schema_version integer NOT NULL CHECK (schema_version > 0), document jsonb NOT NULL, change_summary text NOT NULL DEFAULT '', created_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (dashboard_id, version_number) ); ALTER TABLE dashboards ADD CONSTRAINT dashboards_current_version_fk FOREIGN KEY (current_version_id) REFERENCES dashboard_versions(id) ON DELETE SET NULL; CREATE TABLE events ( id uuid PRIMARY KEY, event_type text NOT NULL, severity text NOT NULL, entity_id uuid REFERENCES entities(id) ON DELETE SET NULL, source_id uuid REFERENCES data_sources(id) ON DELETE SET NULL, occurred_at timestamptz NOT NULL, received_at timestamptz NOT NULL DEFAULT now(), dedup_key text NOT NULL, summary text NOT NULL, attributes jsonb NOT NULL DEFAULT '{}'::jsonb, correlation_id text, UNIQUE (source_id, dedup_key, occurred_at) ); CREATE TABLE audit_events ( id uuid PRIMARY KEY, actor text NOT NULL, action text NOT NULL, resource_type text NOT NULL, resource_id uuid, result text NOT NULL, occurred_at timestamptz NOT NULL DEFAULT now(), correlation_id text, before_diff jsonb, after_diff jsonb ); CREATE TABLE job_runs ( id uuid PRIMARY KEY, job_type text NOT NULL, job_key text NOT NULL, scheduled_at timestamptz NOT NULL, started_at timestamptz, completed_at timestamptz, status text NOT NULL, counts jsonb NOT NULL DEFAULT '{}'::jsonb, error_code text, correlation_id text, UNIQUE (job_type, job_key, scheduled_at) ); CREATE TABLE system_settings ( key text PRIMARY KEY, value jsonb NOT NULL, version bigint NOT NULL DEFAULT 1 CHECK (version > 0), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX entities_type_status_idx ON entities (entity_type, status); CREATE INDEX events_occurred_at_idx ON events (occurred_at DESC); CREATE INDEX audit_events_occurred_at_idx ON audit_events (occurred_at DESC); CREATE INDEX job_runs_status_idx ON job_runs (status, scheduled_at);