ListEnabledCheckDefs now LEFT JOINs the target entity and excludes rows whose target is deprecated or destroyed, so retired things (secrets-issuance, homelab-mcp, the dead secrets ingress route) stop generating permanent false alarms instead of waiting for an operator to disable the check_def by hand. coverageSweep's None() branch previously did nothing, so a type changed from declared monitoring to `monitoring: none` (dns-zone) left its open `unmonitored` signals lingering forever — a None() entity never gains a check, so the hasCheck resolution path never fired. It now resolves those signals.
269 lines
11 KiB
SQL
269 lines
11 KiB
SQL
-- 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; |