-- name: ListSignals :many SELECT sig.entity_id, se.slug, sig.kind, sig.severity, sig.state, te.slug AS target_slug, sig.check_id, sig.evidence, sig.likely_cause, sig.occurrence_count, sig.flap_count, sig.hold_down_until, sig.mute_until, sig.first_seen_at, sig.last_seen_at FROM signals sig JOIN entities se ON se.id = sig.entity_id LEFT JOIN entities te ON te.id = sig.target_entity_id WHERE (sqlc.narg('state')::text IS NULL OR sig.state = sqlc.narg('state')) AND (sqlc.narg('severity')::text IS NULL OR sig.severity = sqlc.narg('severity')) AND (sqlc.narg('target')::text IS NULL OR te.slug = sqlc.narg('target')) AND (sqlc.narg('kind')::text IS NULL OR sig.kind = sqlc.narg('kind')) AND (sqlc.narg('cursor')::text IS NULL OR se.slug > sqlc.narg('cursor')) ORDER BY se.slug LIMIT sqlc.arg('lim'); -- name: GetIdempotentResponse :one SELECT response_code, response_body, request_hash FROM idempotency_keys WHERE actor = $1 AND key = $2; -- name: PutIdempotentResponse :exec INSERT INTO idempotency_keys (actor, key, request_hash, response_code, response_body) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (actor, key) DO NOTHING; -- name: InsertAuditEntry :exec INSERT INTO audit_log (actor_type, actor_id, action, entity_id, method, path, status_code, detail, source_ip, correlation_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); -- name: InsertEvent :one INSERT INTO events (type, entity_id, severity, source, data, correlation_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, ts; -- name: ListEvents :many SELECT id, ts, type, entity_id, severity, source, data, correlation_id FROM events WHERE (sqlc.narg('type')::text IS NULL OR type = sqlc.narg('type')) AND (sqlc.narg('entity_id')::uuid IS NULL OR entity_id = sqlc.narg('entity_id')) AND (sqlc.narg('severity')::text IS NULL OR severity = sqlc.narg('severity')) AND (sqlc.narg('correlation_id')::text IS NULL OR correlation_id = sqlc.narg('correlation_id')) AND (sqlc.narg('from_ts')::timestamptz IS NULL OR ts >= sqlc.narg('from_ts')) AND (sqlc.narg('to_ts')::timestamptz IS NULL OR ts <= sqlc.narg('to_ts')) AND (sqlc.narg('before_id')::bigint IS NULL OR id < sqlc.narg('before_id')) ORDER BY id DESC LIMIT sqlc.arg('lim'); -- name: ListEventsAfter :many SELECT id, ts, type, entity_id, severity, source, data, correlation_id FROM events WHERE id > $1 ORDER BY id ASC LIMIT $2; -- ===================================================================== -- Phase 3 queries -- ===================================================================== -- name: ListEnabledCheckDefs :many -- Enabled AND due. interval_s used to be selected but never filtered on, so -- every check ran on every 30s pass and the declared intervals meant nothing. -- NULL last_run_at = never run = due now. SELECT cd.entity_id, cd.target_id, cd.target_type, cd.kind, cd.config, cd.interval_s, cd.timeout_s, cd.zone, cd.enabled, cd.updated_at, e.slug AS entity_slug FROM check_defs cd JOIN entities e ON e.id = cd.entity_id LEFT JOIN entities tgt ON tgt.id = cd.target_id WHERE cd.enabled = true AND (tgt.id IS NULL OR tgt.state NOT IN ('deprecated', 'destroyed')) AND (cd.last_run_at IS NULL OR cd.last_run_at <= now() - make_interval(secs => cd.interval_s)); -- name: MarkCheckRun :exec UPDATE check_defs SET last_run_at = now(), last_health = $2 WHERE entity_id = $1; -- name: WorstHealthForTarget :one -- An entity is as healthy as its unhealthiest check. Checks that have not run -- yet (last_health IS NULL) are ignored rather than counted as unknown, so a -- newly added check does not drag a known-good entity down before it has -- produced a verdict. SELECT COALESCE( (SELECT last_health FROM check_defs WHERE enabled AND target_id = $1 AND last_health IS NOT NULL ORDER BY CASE last_health WHEN 'down' THEN 0 WHEN 'degraded' THEN 1 WHEN 'stale' THEN 2 WHEN 'unknown' THEN 3 ELSE 4 END LIMIT 1), 'unknown')::text AS health; -- name: GetCheckDef :one SELECT * FROM check_defs WHERE entity_id = $1; -- name: InsertCheckDef :exec INSERT INTO check_defs (entity_id, target_id, target_type, kind, config, interval_s, timeout_s, zone, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); -- name: UpdateCheckDef :exec UPDATE check_defs SET kind = $2, config = $3, interval_s = $4, timeout_s = $5, target_id = $6, target_type = $7, zone = $8, enabled = $9, updated_at = now() WHERE entity_id = $1; -- name: UpsertSignal :one INSERT INTO signals (entity_id, kind, severity, target_entity_id, check_id, evidence, likely_cause, state) VALUES ($1, $2, $3, $4, $5, $6, $7, 'raised') ON CONFLICT (target_entity_id, kind) WHERE state NOT IN ('resolved','failed') DO UPDATE SET occurrence_count = signals.occurrence_count + 1, last_seen_at = now(), evidence = EXCLUDED.evidence, updated_at = now() RETURNING *; -- name: GetOpenSignalsForAutoAct :many -- Signals with auto-act classifications that haven't been executed yet SELECT s.*, c.entity_id AS classification_id, c.action, c.risk_class, c.route, c.blast_radius, c.correlation_id, c.reasoning FROM classifications c JOIN signals s ON s.entity_id = c.signal_entity_id LEFT JOIN executions e ON e.classification_id = c.entity_id WHERE c.route = 'auto-act' AND e.entity_id IS NULL AND (s.hold_down_until IS NULL OR s.hold_down_until < now()) AND (s.mute_until IS NULL OR s.mute_until < now()) ORDER BY s.last_seen_at ASC LIMIT $1; -- name: ListClassifications :many SELECT c.entity_id, c.signal_entity_id, c.target_entity_id, c.action, c.recommended_action, c.risk_class, c.route, c.blast_radius, c.pattern_confidence, c.skill_id, c.autonomy_check, c.reasoning, c.correlation_id, c.created_at, e.slug AS target_slug FROM classifications c JOIN entities e ON e.id = c.target_entity_id WHERE (sqlc.narg('route')::text IS NULL OR c.route = sqlc.narg('route')) AND (sqlc.narg('cursor')::text IS NULL OR e.slug > sqlc.narg('cursor')) ORDER BY e.slug LIMIT sqlc.arg('lim'); -- name: InsertExecution :exec INSERT INTO executions (entity_id, classification_id, signal_entity_id, target_entity_id, action, risk_class, approval_id, agent_id, skill_id, skill_version, status, correlation_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'proposed', $11); -- name: UpdateExecutionStatus :exec UPDATE executions SET status = $2, result = $3, duration_ms = $4, verified = $5, started_at = COALESCE(started_at, now()), completed_at = CASE WHEN $2 IN ('completed','failed','cancelled') THEN now() ELSE completed_at END WHERE entity_id = $1; -- name: GetExecution :one SELECT * FROM executions WHERE entity_id = $1; -- name: ListExecutions :many SELECT e.entity_id, e.classification_id, e.signal_entity_id, e.target_entity_id, e.action, e.risk_class, e.approval_id, e.agent_id, e.skill_id, e.skill_version, e.status, e.result, e.duration_ms, e.verified, e.correlation_id, e.started_at, e.completed_at, e.created_at, te.slug AS target_slug FROM executions e JOIN entities te ON te.id = e.target_entity_id WHERE (sqlc.narg('status')::text IS NULL OR e.status = sqlc.narg('status')) AND (sqlc.narg('cursor')::text IS NULL OR te.slug > sqlc.narg('cursor')) ORDER BY te.slug LIMIT sqlc.arg('lim'); -- name: GetFeedbackAfterWatermark :many SELECT f.entity_id, f.execution_id, f.outcome, f.observation, f.lesson, f.unexpected_side_effects, f.tags, f.created_at, e.action, e.risk_class, e.target_entity_id, et.name AS applies_type FROM feedback f JOIN executions e ON e.entity_id = f.execution_id JOIN entities ent ON ent.id = e.target_entity_id JOIN entity_types et ON et.name = ent.type WHERE f.created_at > $1 ORDER BY f.created_at ASC; -- name: UpsertPattern :exec INSERT INTO patterns (entity_id, applies_type, action, pattern, confidence, evidence_count, success_count, failure_count, status, version) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'hypothesized', 1) ON CONFLICT (applies_type, action) DO UPDATE SET evidence_count = patterns.evidence_count + EXCLUDED.evidence_count, success_count = patterns.success_count + EXCLUDED.success_count, failure_count = patterns.failure_count + EXCLUDED.failure_count, updated_at = now(); -- name: GetPattern :one SELECT * FROM patterns WHERE applies_type = $1 AND action = $2; -- name: ListPatterns :many SELECT p.* FROM patterns p WHERE (sqlc.narg('status')::text IS NULL OR p.status = sqlc.narg('status')) ORDER BY p.applies_type, p.action; -- name: UpdatePatternStatus :exec UPDATE patterns SET status = $2, version = version + 1, last_validated_at = CASE WHEN $2 = 'validated' THEN now() ELSE last_validated_at END WHERE entity_id = $1; -- name: UpdatePatternQuarantine :exec UPDATE patterns SET quarantined = $2 WHERE entity_id = $1; -- name: ListSkills :many SELECT * FROM skills WHERE (sqlc.narg('status')::text IS NULL OR status = sqlc.narg('status')) ORDER BY name, version DESC; -- name: UpdateSkillStatus :exec UPDATE skills SET status = $2, last_used_at = now() WHERE entity_id = $1 AND version = $2; -- name: InsertApproval :exec INSERT INTO approvals (entity_id, subject_entity_id, action, risk_class, kind, payload, status, token_hash, expires_at) VALUES ($1, $2, $3, $4, $5, $6, 'pending', $7, $8); -- name: ListApprovals :many SELECT a.*, e.slug AS subject_slug FROM approvals a JOIN entities e ON e.id = a.subject_entity_id WHERE (sqlc.narg('status')::text IS NULL OR a.status = sqlc.narg('status')) AND (sqlc.narg('cursor')::text IS NULL OR e.slug > sqlc.narg('cursor')) ORDER BY e.slug LIMIT sqlc.arg('lim'); -- name: GetApprovalByID :one SELECT * FROM approvals WHERE entity_id = $1; -- name: UpdateApprovalStatus :exec UPDATE approvals SET status = $2, decided_at = now(), decided_by = $3 WHERE entity_id = $1 AND status = 'pending'; -- name: GetAutonomySetting :one SELECT value FROM autonomy_settings WHERE key = $1; -- name: ListRiskClasses :many SELECT * FROM risk_classes ORDER BY name; -- name: ListApprovalRules :many SELECT * FROM approval_rules ORDER BY entity_type, action; -- name: InsertMetricSample :exec INSERT INTO metric_samples (entity_id, metric, value, tags, ts) VALUES ($1, $2, $3, $4, now()); -- name: QueryMetrics :many SELECT time_bucket(sqlc.arg('bucket_interval')::interval, ts) AS bucket, entity_id, metric, ROUND(avg(value)::numeric, 2) AS avg_val, ROUND(min(value)::numeric, 2) AS min_val, ROUND(max(value)::numeric, 2) AS max_val FROM metric_samples WHERE entity_id = $1 AND metric = $2 AND ts > $3 GROUP BY bucket, entity_id, metric ORDER BY bucket DESC; -- name: UpsertEntityStatus :exec INSERT INTO entity_status (entity_id, health, last_check_at, details) VALUES ($1, $2, $3, $4) ON CONFLICT (entity_id) DO UPDATE SET health = EXCLUDED.health, last_check_at = EXCLUDED.last_check_at, details = EXCLUDED.details, updated_at = now(); -- name: GetEntityStatus :one SELECT * FROM entity_status WHERE entity_id = $1;