201 lines
8.5 KiB
PL/PgSQL
201 lines
8.5 KiB
PL/PgSQL
-- Migration: Keys and Artifacts
|
|
-- Description: Creates key table for secrets management and artifact table for execution outputs
|
|
-- Version: 20250101000009
|
|
|
|
-- ============================================================================
|
|
-- KEY TABLE
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE key (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
ref TEXT NOT NULL UNIQUE,
|
|
owner_type owner_type_enum NOT NULL,
|
|
owner TEXT,
|
|
owner_identity BIGINT REFERENCES identity(id),
|
|
owner_pack BIGINT REFERENCES pack(id),
|
|
owner_pack_ref TEXT,
|
|
owner_action BIGINT, -- Forward reference to action table
|
|
owner_action_ref TEXT,
|
|
owner_sensor BIGINT, -- Forward reference to sensor table
|
|
owner_sensor_ref TEXT,
|
|
name TEXT NOT NULL,
|
|
encrypted BOOLEAN NOT NULL,
|
|
encryption_key_hash TEXT,
|
|
value TEXT NOT NULL,
|
|
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
CONSTRAINT key_ref_lowercase CHECK (ref = LOWER(ref)),
|
|
CONSTRAINT key_ref_format CHECK (ref ~ '^([^.]+\.)?[^.]+$')
|
|
);
|
|
|
|
-- Unique index on owner_type, owner, name
|
|
CREATE UNIQUE INDEX idx_key_unique ON key(owner_type, owner, name);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_key_ref ON key(ref);
|
|
CREATE INDEX idx_key_owner_type ON key(owner_type);
|
|
CREATE INDEX idx_key_owner_identity ON key(owner_identity);
|
|
CREATE INDEX idx_key_owner_pack ON key(owner_pack);
|
|
CREATE INDEX idx_key_owner_action ON key(owner_action);
|
|
CREATE INDEX idx_key_owner_sensor ON key(owner_sensor);
|
|
CREATE INDEX idx_key_created ON key(created DESC);
|
|
CREATE INDEX idx_key_owner_type_owner ON key(owner_type, owner);
|
|
CREATE INDEX idx_key_owner_identity_name ON key(owner_identity, name);
|
|
CREATE INDEX idx_key_owner_pack_name ON key(owner_pack, name);
|
|
|
|
-- Function to validate and set owner fields
|
|
CREATE OR REPLACE FUNCTION validate_key_owner()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
owner_count INTEGER := 0;
|
|
BEGIN
|
|
-- Count how many owner fields are set
|
|
IF NEW.owner_identity IS NOT NULL THEN owner_count := owner_count + 1; END IF;
|
|
IF NEW.owner_pack IS NOT NULL THEN owner_count := owner_count + 1; END IF;
|
|
IF NEW.owner_action IS NOT NULL THEN owner_count := owner_count + 1; END IF;
|
|
IF NEW.owner_sensor IS NOT NULL THEN owner_count := owner_count + 1; END IF;
|
|
|
|
-- System owner should have no owner fields set
|
|
IF NEW.owner_type = 'system' THEN
|
|
IF owner_count > 0 THEN
|
|
RAISE EXCEPTION 'System owner cannot have specific owner fields set';
|
|
END IF;
|
|
NEW.owner := 'system';
|
|
-- All other types must have exactly one owner field set
|
|
ELSIF owner_count != 1 THEN
|
|
RAISE EXCEPTION 'Exactly one owner field must be set for owner_type %', NEW.owner_type;
|
|
-- Validate owner_type matches the populated field and set owner
|
|
ELSIF NEW.owner_type = 'identity' THEN
|
|
IF NEW.owner_identity IS NULL THEN
|
|
RAISE EXCEPTION 'owner_identity must be set for owner_type identity';
|
|
END IF;
|
|
NEW.owner := NEW.owner_identity::TEXT;
|
|
ELSIF NEW.owner_type = 'pack' THEN
|
|
IF NEW.owner_pack IS NULL THEN
|
|
RAISE EXCEPTION 'owner_pack must be set for owner_type pack';
|
|
END IF;
|
|
NEW.owner := NEW.owner_pack::TEXT;
|
|
ELSIF NEW.owner_type = 'action' THEN
|
|
IF NEW.owner_action IS NULL THEN
|
|
RAISE EXCEPTION 'owner_action must be set for owner_type action';
|
|
END IF;
|
|
NEW.owner := NEW.owner_action::TEXT;
|
|
ELSIF NEW.owner_type = 'sensor' THEN
|
|
IF NEW.owner_sensor IS NULL THEN
|
|
RAISE EXCEPTION 'owner_sensor must be set for owner_type sensor';
|
|
END IF;
|
|
NEW.owner := NEW.owner_sensor::TEXT;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger to validate owner fields
|
|
CREATE TRIGGER validate_key_owner_trigger
|
|
BEFORE INSERT OR UPDATE ON key
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_key_owner();
|
|
|
|
-- Trigger for updated timestamp
|
|
CREATE TRIGGER update_key_updated
|
|
BEFORE UPDATE ON key
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_column();
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE key IS 'Keys store configuration values and secrets with ownership scoping';
|
|
COMMENT ON COLUMN key.ref IS 'Unique key reference (format: [owner.]name)';
|
|
COMMENT ON COLUMN key.owner_type IS 'Type of owner (system, identity, pack, action, sensor)';
|
|
COMMENT ON COLUMN key.owner IS 'Owner identifier (auto-populated by trigger)';
|
|
COMMENT ON COLUMN key.owner_identity IS 'Identity owner (if owner_type=identity)';
|
|
COMMENT ON COLUMN key.owner_pack IS 'Pack owner (if owner_type=pack)';
|
|
COMMENT ON COLUMN key.owner_pack_ref IS 'Pack reference for owner_pack';
|
|
COMMENT ON COLUMN key.owner_action IS 'Action owner (if owner_type=action)';
|
|
COMMENT ON COLUMN key.owner_sensor IS 'Sensor owner (if owner_type=sensor)';
|
|
COMMENT ON COLUMN key.name IS 'Key name within owner scope';
|
|
COMMENT ON COLUMN key.encrypted IS 'Whether the value is encrypted';
|
|
COMMENT ON COLUMN key.encryption_key_hash IS 'Hash of encryption key used';
|
|
COMMENT ON COLUMN key.value IS 'The actual value (encrypted if encrypted=true)';
|
|
|
|
|
|
-- Add foreign key constraints for action and sensor references
|
|
ALTER TABLE key
|
|
ADD CONSTRAINT key_owner_action_fkey
|
|
FOREIGN KEY (owner_action) REFERENCES action(id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE key
|
|
ADD CONSTRAINT key_owner_sensor_fkey
|
|
FOREIGN KEY (owner_sensor) REFERENCES sensor(id) ON DELETE CASCADE;
|
|
|
|
-- ============================================================================
|
|
-- ARTIFACT TABLE
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE artifact (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
ref TEXT NOT NULL,
|
|
scope owner_type_enum NOT NULL DEFAULT 'system',
|
|
owner TEXT NOT NULL DEFAULT '',
|
|
type artifact_type_enum NOT NULL,
|
|
retention_policy artifact_retention_enum NOT NULL DEFAULT 'versions',
|
|
retention_limit INTEGER NOT NULL DEFAULT 1,
|
|
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_artifact_ref ON artifact(ref);
|
|
CREATE INDEX idx_artifact_scope ON artifact(scope);
|
|
CREATE INDEX idx_artifact_owner ON artifact(owner);
|
|
CREATE INDEX idx_artifact_type ON artifact(type);
|
|
CREATE INDEX idx_artifact_created ON artifact(created DESC);
|
|
CREATE INDEX idx_artifact_scope_owner ON artifact(scope, owner);
|
|
CREATE INDEX idx_artifact_type_created ON artifact(type, created DESC);
|
|
|
|
-- Trigger
|
|
CREATE TRIGGER update_artifact_updated
|
|
BEFORE UPDATE ON artifact
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_column();
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE artifact IS 'Artifacts track files, logs, and outputs from executions';
|
|
COMMENT ON COLUMN artifact.ref IS 'Artifact reference/path';
|
|
COMMENT ON COLUMN artifact.scope IS 'Owner type (system, identity, pack, action, sensor)';
|
|
COMMENT ON COLUMN artifact.owner IS 'Owner identifier';
|
|
COMMENT ON COLUMN artifact.type IS 'Artifact type (file, url, progress, etc.)';
|
|
COMMENT ON COLUMN artifact.retention_policy IS 'How to retain artifacts (versions, days, hours, minutes)';
|
|
COMMENT ON COLUMN artifact.retention_limit IS 'Numeric limit for retention policy';
|
|
|
|
-- ============================================================================
|
|
-- QUEUE_STATS TABLE
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE queue_stats (
|
|
action_id BIGINT PRIMARY KEY REFERENCES action(id) ON DELETE CASCADE,
|
|
queue_length INTEGER NOT NULL DEFAULT 0,
|
|
active_count INTEGER NOT NULL DEFAULT 0,
|
|
max_concurrent INTEGER NOT NULL DEFAULT 1,
|
|
oldest_enqueued_at TIMESTAMPTZ,
|
|
total_enqueued BIGINT NOT NULL DEFAULT 0,
|
|
total_completed BIGINT NOT NULL DEFAULT 0,
|
|
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_queue_stats_last_updated ON queue_stats(last_updated);
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE queue_stats IS 'Real-time queue statistics for action execution ordering';
|
|
COMMENT ON COLUMN queue_stats.action_id IS 'Foreign key to action table';
|
|
COMMENT ON COLUMN queue_stats.queue_length IS 'Number of executions waiting in queue';
|
|
COMMENT ON COLUMN queue_stats.active_count IS 'Number of currently running executions';
|
|
COMMENT ON COLUMN queue_stats.max_concurrent IS 'Maximum concurrent executions allowed';
|
|
COMMENT ON COLUMN queue_stats.oldest_enqueued_at IS 'Timestamp of oldest queued execution (NULL if queue empty)';
|
|
COMMENT ON COLUMN queue_stats.total_enqueued IS 'Total executions enqueued since queue creation';
|
|
COMMENT ON COLUMN queue_stats.total_completed IS 'Total executions completed since queue creation';
|
|
COMMENT ON COLUMN queue_stats.last_updated IS 'Timestamp of last statistics update';
|