@obsunified/collector 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-XKQTVIRE.js → chunk-ZSF5XP3B.js} +8 -2
- package/dist/chunk-ZSF5XP3B.js.map +1 -0
- package/dist/index.d.ts +359 -2
- package/dist/index.js +5000 -334
- package/dist/index.js.map +1 -1
- package/dist/{openai-FJYOQ7WO.js → openai-GSJZGFGL.js} +2 -2
- package/package.json +2 -2
- package/src/__tests__/autosre-closed-loop.test.ts +2222 -0
- package/src/index.ts +43 -0
- package/src/lib/analyses-runner.ts +46 -0
- package/src/lib/autosre-change-correlation.ts +807 -0
- package/src/lib/autosre-findings.ts +763 -0
- package/src/lib/autosre-ontology-cache.test.ts +78 -0
- package/src/lib/autosre-ontology.ts +938 -0
- package/src/lib/autosre-remediation-gate.ts +2127 -0
- package/src/lib/eval-cases-store.ts +63 -1
- package/src/lib/evidence-references.ts +6 -0
- package/src/lib/housekeeping.test.ts +150 -0
- package/src/lib/housekeeping.ts +109 -1
- package/src/lib/store/ingest.ts +46 -6
- package/src/migrations/050_autosre_causal_ontology.sql +216 -0
- package/src/migrations/051_incident_active_dedup_unique.sql +31 -0
- package/src/migrations/alert-state-notified.test.ts +3 -3
- package/src/migrations/parity.test.ts +4 -0
- package/src/migrations/project-scoped-keys.test.ts +1 -1
- package/src/migrations-postgres/050_autosre_causal_ontology.sql +216 -0
- package/src/migrations-postgres/051_incident_active_dedup_unique.sql +31 -0
- package/src/plugins/alerts-evaluator.ts +29 -1
- package/src/plugins/autosre-routes.ts +1134 -0
- package/src/plugins/connected-routes/manifest.ts +10 -1
- package/src/plugins/connected-routes.ts +241 -0
- package/src/plugins/default-span-enrichment.ts +13 -1
- package/src/plugins/eval-cases-routes.ts +57 -2
- package/src/plugins/evidence-retrieval-routes.test.ts +32 -0
- package/src/plugins/evidence-retrieval-routes.ts +643 -5
- package/dist/chunk-XKQTVIRE.js.map +0 -1
- /package/dist/{openai-FJYOQ7WO.js.map → openai-GSJZGFGL.js.map} +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Migration 050: AutoSRE Causal Ontology, Change Correlation & Incident Memory
|
|
3
|
+
-- (RFC 0013)
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- 0. Denormalize environment, version, and target_node_id onto existing tables
|
|
7
|
+
-- for O(log N) cohort rate lift and cross-trace contention queries.
|
|
8
|
+
ALTER TABLE telemetry_spans ADD COLUMN environment TEXT NOT NULL DEFAULT 'default';
|
|
9
|
+
ALTER TABLE telemetry_spans ADD COLUMN service_version TEXT;
|
|
10
|
+
ALTER TABLE telemetry_spans ADD COLUMN commit_sha TEXT;
|
|
11
|
+
ALTER TABLE telemetry_spans ADD COLUMN target_node_id TEXT;
|
|
12
|
+
ALTER TABLE telemetry_spans ADD COLUMN error_signature TEXT;
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_spans_cohort_attribution
|
|
15
|
+
ON telemetry_spans (project_id, environment, service_name, service_version, received_at DESC);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_spans_contention_lookup
|
|
18
|
+
ON telemetry_spans (project_id, environment, target_node_id, start_time, end_time)
|
|
19
|
+
WHERE target_node_id IS NOT NULL;
|
|
20
|
+
|
|
21
|
+
ALTER TABLE actions ADD COLUMN environment TEXT NOT NULL DEFAULT 'default';
|
|
22
|
+
ALTER TABLE actions ADD COLUMN service_version TEXT;
|
|
23
|
+
ALTER TABLE actions ADD COLUMN target_node_id TEXT;
|
|
24
|
+
|
|
25
|
+
-- Extend eval_runs and add eval_case_results for verifiable PairedCounterfactualProof
|
|
26
|
+
ALTER TABLE eval_runs ADD COLUMN environment TEXT NOT NULL DEFAULT 'default';
|
|
27
|
+
ALTER TABLE eval_runs ADD COLUMN service_version TEXT;
|
|
28
|
+
ALTER TABLE eval_runs ADD COLUMN commit_sha TEXT;
|
|
29
|
+
ALTER TABLE eval_runs ADD COLUMN config_digest TEXT;
|
|
30
|
+
ALTER TABLE eval_runs ADD COLUMN input_payload_hash TEXT;
|
|
31
|
+
ALTER TABLE eval_runs ADD COLUMN failure_fingerprint TEXT;
|
|
32
|
+
|
|
33
|
+
ALTER TABLE eval_case_results ADD COLUMN input_payload_hash TEXT;
|
|
34
|
+
ALTER TABLE eval_case_results ADD COLUMN failure_fingerprint TEXT;
|
|
35
|
+
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_eval_case_results_counterfactual
|
|
37
|
+
ON eval_case_results (project_id, eval_case_id, input_payload_hash, passed);
|
|
38
|
+
|
|
39
|
+
-- 1. Environment-Scoped Persistent Operational Ontology Nodes
|
|
40
|
+
CREATE TABLE IF NOT EXISTS ontology_nodes (
|
|
41
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
42
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
43
|
+
id TEXT NOT NULL,
|
|
44
|
+
kind TEXT NOT NULL,
|
|
45
|
+
name TEXT NOT NULL,
|
|
46
|
+
namespace_or_endpoint TEXT NOT NULL DEFAULT 'default',
|
|
47
|
+
is_scoped_identity INTEGER NOT NULL DEFAULT 1,
|
|
48
|
+
current_version TEXT,
|
|
49
|
+
current_commit_sha TEXT,
|
|
50
|
+
current_config_digest TEXT,
|
|
51
|
+
attributes_json TEXT,
|
|
52
|
+
first_seen_at TEXT NOT NULL,
|
|
53
|
+
last_seen_at TEXT NOT NULL,
|
|
54
|
+
PRIMARY KEY (project_id, environment, id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- 2. Directed Operational Ontology Edges (Cycles Permitted; Handled via Tarjan SCC)
|
|
58
|
+
CREATE TABLE IF NOT EXISTS ontology_edges (
|
|
59
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
60
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
61
|
+
id TEXT NOT NULL,
|
|
62
|
+
source_node_id TEXT NOT NULL,
|
|
63
|
+
predicate TEXT NOT NULL,
|
|
64
|
+
target_node_id TEXT NOT NULL,
|
|
65
|
+
relationship_certainty TEXT NOT NULL DEFAULT 'exact',
|
|
66
|
+
call_count INTEGER NOT NULL DEFAULT 0,
|
|
67
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
last_duration_ms REAL,
|
|
69
|
+
first_seen_at TEXT NOT NULL,
|
|
70
|
+
last_seen_at TEXT NOT NULL,
|
|
71
|
+
PRIMARY KEY (project_id, environment, id)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_ontology_edges_source
|
|
75
|
+
ON ontology_edges (project_id, environment, source_node_id);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_ontology_edges_target
|
|
78
|
+
ON ontology_edges (project_id, environment, target_node_id);
|
|
79
|
+
|
|
80
|
+
-- 3. Explicit & Inferred Change Events
|
|
81
|
+
CREATE TABLE IF NOT EXISTS change_events (
|
|
82
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
83
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
84
|
+
id TEXT NOT NULL,
|
|
85
|
+
target_node_id TEXT NOT NULL,
|
|
86
|
+
kind TEXT NOT NULL,
|
|
87
|
+
source TEXT NOT NULL,
|
|
88
|
+
relationship_certainty TEXT NOT NULL DEFAULT 'exact',
|
|
89
|
+
summary TEXT NOT NULL,
|
|
90
|
+
service_name TEXT,
|
|
91
|
+
version TEXT,
|
|
92
|
+
previous_version TEXT,
|
|
93
|
+
commit_sha TEXT,
|
|
94
|
+
previous_commit_sha TEXT,
|
|
95
|
+
config_digest TEXT,
|
|
96
|
+
repository_url TEXT,
|
|
97
|
+
pull_request_url TEXT,
|
|
98
|
+
actor_id TEXT,
|
|
99
|
+
caused_by_action_id TEXT,
|
|
100
|
+
agent_run_id TEXT,
|
|
101
|
+
touched_paths_json TEXT,
|
|
102
|
+
attributes_json TEXT,
|
|
103
|
+
occurred_at TEXT NOT NULL,
|
|
104
|
+
received_at TEXT NOT NULL,
|
|
105
|
+
expires_at TEXT,
|
|
106
|
+
PRIMARY KEY (project_id, environment, id)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_change_events_node_time
|
|
110
|
+
ON change_events (project_id, environment, target_node_id, occurred_at DESC);
|
|
111
|
+
|
|
112
|
+
-- 4. Durable Incident Investigations & Runner Lease Coordination
|
|
113
|
+
CREATE TABLE IF NOT EXISTS incident_investigations (
|
|
114
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
115
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
116
|
+
id TEXT NOT NULL,
|
|
117
|
+
dedup_key TEXT NOT NULL,
|
|
118
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
119
|
+
target_node_id TEXT NOT NULL,
|
|
120
|
+
symptom_anchor_kind TEXT NOT NULL,
|
|
121
|
+
symptom_anchor_id TEXT NOT NULL,
|
|
122
|
+
symptom_fingerprint TEXT,
|
|
123
|
+
alert_count INTEGER NOT NULL DEFAULT 1,
|
|
124
|
+
latest_bundle_json TEXT,
|
|
125
|
+
lease_owner_id TEXT,
|
|
126
|
+
lease_expires_at TEXT,
|
|
127
|
+
dispatch_attempts INTEGER NOT NULL DEFAULT 0,
|
|
128
|
+
last_dispatched_at TEXT,
|
|
129
|
+
opened_at TEXT NOT NULL,
|
|
130
|
+
updated_at TEXT NOT NULL,
|
|
131
|
+
resolved_at TEXT,
|
|
132
|
+
expires_at TEXT,
|
|
133
|
+
PRIMARY KEY (project_id, environment, id)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_active_dedup
|
|
137
|
+
ON incident_investigations (project_id, environment, dedup_key, status);
|
|
138
|
+
|
|
139
|
+
-- 5. Closed-Loop Investigation Findings (Separating Recovery vs Causal Proof)
|
|
140
|
+
CREATE TABLE IF NOT EXISTS investigation_findings (
|
|
141
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
142
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
143
|
+
id TEXT NOT NULL,
|
|
144
|
+
incident_id TEXT,
|
|
145
|
+
fingerprint TEXT NOT NULL,
|
|
146
|
+
fingerprint_input_json TEXT NOT NULL,
|
|
147
|
+
title TEXT NOT NULL,
|
|
148
|
+
summary TEXT NOT NULL,
|
|
149
|
+
causal_proof_tier TEXT NOT NULL DEFAULT 'unverified_locus',
|
|
150
|
+
confidence REAL NOT NULL,
|
|
151
|
+
confidence_breakdown_json TEXT NOT NULL,
|
|
152
|
+
symptom_entity_kind TEXT NOT NULL,
|
|
153
|
+
symptom_entity_id TEXT NOT NULL,
|
|
154
|
+
fault_locus_entity_kind TEXT NOT NULL,
|
|
155
|
+
fault_locus_entity_id TEXT NOT NULL,
|
|
156
|
+
root_cause_entity_kind TEXT,
|
|
157
|
+
root_cause_entity_id TEXT,
|
|
158
|
+
contributing_change_id TEXT,
|
|
159
|
+
cohort_verdict TEXT,
|
|
160
|
+
contending_trace_id TEXT,
|
|
161
|
+
causal_chain_json TEXT NOT NULL,
|
|
162
|
+
cited_evidence_ids_json TEXT NOT NULL,
|
|
163
|
+
code_references_json TEXT,
|
|
164
|
+
counterfactual_proof_json TEXT,
|
|
165
|
+
recovery_status TEXT NOT NULL DEFAULT 'not_applicable',
|
|
166
|
+
causal_verification_status TEXT NOT NULL DEFAULT 'unverified',
|
|
167
|
+
watchdog_step INTEGER NOT NULL DEFAULT 0,
|
|
168
|
+
baseline_request_rate_per_min REAL,
|
|
169
|
+
next_watchdog_check_at TEXT,
|
|
170
|
+
actor_type TEXT NOT NULL,
|
|
171
|
+
actor_id TEXT NOT NULL,
|
|
172
|
+
agent_run_id TEXT,
|
|
173
|
+
created_at TEXT NOT NULL,
|
|
174
|
+
updated_at TEXT NOT NULL,
|
|
175
|
+
expires_at TEXT,
|
|
176
|
+
PRIMARY KEY (project_id, environment, id)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_findings_fingerprint
|
|
180
|
+
ON investigation_findings (project_id, environment, fingerprint, created_at DESC);
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_findings_watchdog
|
|
183
|
+
ON investigation_findings (project_id, recovery_status, next_watchdog_check_at);
|
|
184
|
+
|
|
185
|
+
-- 6. Pre-Execution Remediation Proposals & Single-Use Grants
|
|
186
|
+
CREATE TABLE IF NOT EXISTS remediation_proposals (
|
|
187
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
188
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
189
|
+
id TEXT NOT NULL,
|
|
190
|
+
incident_id TEXT NOT NULL,
|
|
191
|
+
finding_id TEXT NOT NULL,
|
|
192
|
+
idempotency_key TEXT NOT NULL,
|
|
193
|
+
action_kind TEXT NOT NULL,
|
|
194
|
+
target_node_id TEXT NOT NULL,
|
|
195
|
+
expected_current_state_json TEXT NOT NULL,
|
|
196
|
+
desired_target_state_json TEXT NOT NULL,
|
|
197
|
+
requested_autonomy TEXT NOT NULL,
|
|
198
|
+
effective_autonomy TEXT NOT NULL,
|
|
199
|
+
policy_decision_reason TEXT NOT NULL,
|
|
200
|
+
status TEXT NOT NULL DEFAULT 'proposed',
|
|
201
|
+
grant_token_hash TEXT,
|
|
202
|
+
grant_expires_at TEXT,
|
|
203
|
+
approved_by_user_id TEXT,
|
|
204
|
+
approved_at TEXT,
|
|
205
|
+
claimed_by_runner_id TEXT,
|
|
206
|
+
claimed_at TEXT,
|
|
207
|
+
claim_expires_at TEXT,
|
|
208
|
+
remediation_agent_run_id TEXT,
|
|
209
|
+
resulting_change_id TEXT,
|
|
210
|
+
failure_reason TEXT,
|
|
211
|
+
created_at TEXT NOT NULL,
|
|
212
|
+
updated_at TEXT NOT NULL,
|
|
213
|
+
expires_at TEXT,
|
|
214
|
+
PRIMARY KEY (project_id, environment, id),
|
|
215
|
+
UNIQUE (project_id, environment, idempotency_key)
|
|
216
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- Make active-incident deduplication race-free (RFC 0013 § 6.A, governance review B4).
|
|
2
|
+
--
|
|
3
|
+
-- `openOrDeduplicateIncident` used to SELECT-then-INSERT, so two concurrent
|
|
4
|
+
-- alerts for the same symptom could each open an incident. A partial UNIQUE
|
|
5
|
+
-- index lets the database arbitrate; the application inserts with
|
|
6
|
+
-- ON CONFLICT DO NOTHING and re-reads the winner.
|
|
7
|
+
|
|
8
|
+
-- Collapse any pre-existing duplicate active rows (keep the newest per key) so
|
|
9
|
+
-- the index can be built. Older duplicates are closed as escalated, not deleted.
|
|
10
|
+
UPDATE incident_investigations
|
|
11
|
+
SET status = 'escalated'
|
|
12
|
+
WHERE status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying')
|
|
13
|
+
AND EXISTS (
|
|
14
|
+
SELECT 1 FROM incident_investigations o
|
|
15
|
+
WHERE o.project_id = incident_investigations.project_id
|
|
16
|
+
AND o.environment = incident_investigations.environment
|
|
17
|
+
AND o.dedup_key = incident_investigations.dedup_key
|
|
18
|
+
AND o.status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying')
|
|
19
|
+
AND (
|
|
20
|
+
o.opened_at > incident_investigations.opened_at
|
|
21
|
+
OR (o.opened_at = incident_investigations.opened_at AND o.id > incident_investigations.id)
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_incidents_active_dedup
|
|
26
|
+
ON incident_investigations (project_id, environment, dedup_key)
|
|
27
|
+
WHERE status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying');
|
|
28
|
+
|
|
29
|
+
-- Watchdog (governance review B11): a finding is only `recovered` after two
|
|
30
|
+
-- consecutive checkpoints with sufficient traffic and a healthy error rate.
|
|
31
|
+
ALTER TABLE investigation_findings ADD COLUMN consecutive_ok_checks INTEGER NOT NULL DEFAULT 0;
|
|
@@ -44,9 +44,9 @@ describe("migration 049 (alert_state.notified)", () => {
|
|
|
44
44
|
.map((c) => (c as { name: string }).name);
|
|
45
45
|
expect(columnsBefore).not.toContain("notified");
|
|
46
46
|
|
|
47
|
-
expect(applySqliteMigrations(db.raw, { from: "049" })).toEqual(
|
|
48
|
-
"049_alert_state_notified.sql",
|
|
49
|
-
|
|
47
|
+
expect(applySqliteMigrations(db.raw, { from: "049", upTo: "049" })).toEqual(
|
|
48
|
+
["049_alert_state_notified.sql"],
|
|
49
|
+
);
|
|
50
50
|
|
|
51
51
|
const notified = db.raw
|
|
52
52
|
.prepare("PRAGMA table_info(alert_state)")
|
|
@@ -259,7 +259,7 @@ describe("migrations 045–048 (project-scoped composite keys)", () => {
|
|
|
259
259
|
).toEqual({ input_hash: "hash", action_id: "01ACTION" });
|
|
260
260
|
|
|
261
261
|
// Re-applying the rebuild files is a no-op in effect.
|
|
262
|
-
applySqliteMigrations(raw, { from: "045" });
|
|
262
|
+
applySqliteMigrations(raw, { from: "045", upTo: "048" });
|
|
263
263
|
for (const [table, key] of Object.entries(EXPECTED_KEYS)) {
|
|
264
264
|
expect(primaryKeyColumns(raw, table), table).toEqual(key);
|
|
265
265
|
expect(
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Migration 050: AutoSRE Causal Ontology, Change Correlation & Incident Memory
|
|
3
|
+
-- (RFC 0013 — PostgreSQL mirror)
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- 0. Denormalize environment, version, and target_node_id onto existing tables
|
|
7
|
+
-- for O(log N) cohort rate lift and cross-trace contention queries.
|
|
8
|
+
ALTER TABLE telemetry_spans ADD COLUMN IF NOT EXISTS environment TEXT NOT NULL DEFAULT 'default';
|
|
9
|
+
ALTER TABLE telemetry_spans ADD COLUMN IF NOT EXISTS service_version TEXT;
|
|
10
|
+
ALTER TABLE telemetry_spans ADD COLUMN IF NOT EXISTS commit_sha TEXT;
|
|
11
|
+
ALTER TABLE telemetry_spans ADD COLUMN IF NOT EXISTS target_node_id TEXT;
|
|
12
|
+
ALTER TABLE telemetry_spans ADD COLUMN IF NOT EXISTS error_signature TEXT;
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_spans_cohort_attribution
|
|
15
|
+
ON telemetry_spans (project_id, environment, service_name, service_version, received_at DESC);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_spans_contention_lookup
|
|
18
|
+
ON telemetry_spans (project_id, environment, target_node_id, start_time, end_time)
|
|
19
|
+
WHERE target_node_id IS NOT NULL;
|
|
20
|
+
|
|
21
|
+
ALTER TABLE actions ADD COLUMN IF NOT EXISTS environment TEXT NOT NULL DEFAULT 'default';
|
|
22
|
+
ALTER TABLE actions ADD COLUMN IF NOT EXISTS service_version TEXT;
|
|
23
|
+
ALTER TABLE actions ADD COLUMN IF NOT EXISTS target_node_id TEXT;
|
|
24
|
+
|
|
25
|
+
-- Extend eval_runs and add eval_case_results for verifiable PairedCounterfactualProof
|
|
26
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS environment TEXT NOT NULL DEFAULT 'default';
|
|
27
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS service_version TEXT;
|
|
28
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS commit_sha TEXT;
|
|
29
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS config_digest TEXT;
|
|
30
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS input_payload_hash TEXT;
|
|
31
|
+
ALTER TABLE eval_runs ADD COLUMN IF NOT EXISTS failure_fingerprint TEXT;
|
|
32
|
+
|
|
33
|
+
ALTER TABLE eval_case_results ADD COLUMN IF NOT EXISTS input_payload_hash TEXT;
|
|
34
|
+
ALTER TABLE eval_case_results ADD COLUMN IF NOT EXISTS failure_fingerprint TEXT;
|
|
35
|
+
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_eval_case_results_counterfactual
|
|
37
|
+
ON eval_case_results (project_id, eval_case_id, input_payload_hash, passed);
|
|
38
|
+
|
|
39
|
+
-- 1. Environment-Scoped Persistent Operational Ontology Nodes
|
|
40
|
+
CREATE TABLE IF NOT EXISTS ontology_nodes (
|
|
41
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
42
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
43
|
+
id TEXT NOT NULL,
|
|
44
|
+
kind TEXT NOT NULL,
|
|
45
|
+
name TEXT NOT NULL,
|
|
46
|
+
namespace_or_endpoint TEXT NOT NULL DEFAULT 'default',
|
|
47
|
+
is_scoped_identity INTEGER NOT NULL DEFAULT 1,
|
|
48
|
+
current_version TEXT,
|
|
49
|
+
current_commit_sha TEXT,
|
|
50
|
+
current_config_digest TEXT,
|
|
51
|
+
attributes_json TEXT,
|
|
52
|
+
first_seen_at TEXT NOT NULL,
|
|
53
|
+
last_seen_at TEXT NOT NULL,
|
|
54
|
+
PRIMARY KEY (project_id, environment, id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- 2. Directed Operational Ontology Edges (Cycles Permitted; Handled via Tarjan SCC)
|
|
58
|
+
CREATE TABLE IF NOT EXISTS ontology_edges (
|
|
59
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
60
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
61
|
+
id TEXT NOT NULL,
|
|
62
|
+
source_node_id TEXT NOT NULL,
|
|
63
|
+
predicate TEXT NOT NULL,
|
|
64
|
+
target_node_id TEXT NOT NULL,
|
|
65
|
+
relationship_certainty TEXT NOT NULL DEFAULT 'exact',
|
|
66
|
+
call_count INTEGER NOT NULL DEFAULT 0,
|
|
67
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
last_duration_ms DOUBLE PRECISION,
|
|
69
|
+
first_seen_at TEXT NOT NULL,
|
|
70
|
+
last_seen_at TEXT NOT NULL,
|
|
71
|
+
PRIMARY KEY (project_id, environment, id)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_ontology_edges_source
|
|
75
|
+
ON ontology_edges (project_id, environment, source_node_id);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_ontology_edges_target
|
|
78
|
+
ON ontology_edges (project_id, environment, target_node_id);
|
|
79
|
+
|
|
80
|
+
-- 3. Explicit & Inferred Change Events
|
|
81
|
+
CREATE TABLE IF NOT EXISTS change_events (
|
|
82
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
83
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
84
|
+
id TEXT NOT NULL,
|
|
85
|
+
target_node_id TEXT NOT NULL,
|
|
86
|
+
kind TEXT NOT NULL,
|
|
87
|
+
source TEXT NOT NULL,
|
|
88
|
+
relationship_certainty TEXT NOT NULL DEFAULT 'exact',
|
|
89
|
+
summary TEXT NOT NULL,
|
|
90
|
+
service_name TEXT,
|
|
91
|
+
version TEXT,
|
|
92
|
+
previous_version TEXT,
|
|
93
|
+
commit_sha TEXT,
|
|
94
|
+
previous_commit_sha TEXT,
|
|
95
|
+
config_digest TEXT,
|
|
96
|
+
repository_url TEXT,
|
|
97
|
+
pull_request_url TEXT,
|
|
98
|
+
actor_id TEXT,
|
|
99
|
+
caused_by_action_id TEXT,
|
|
100
|
+
agent_run_id TEXT,
|
|
101
|
+
touched_paths_json TEXT,
|
|
102
|
+
attributes_json TEXT,
|
|
103
|
+
occurred_at TEXT NOT NULL,
|
|
104
|
+
received_at TEXT NOT NULL,
|
|
105
|
+
expires_at TEXT,
|
|
106
|
+
PRIMARY KEY (project_id, environment, id)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_change_events_node_time
|
|
110
|
+
ON change_events (project_id, environment, target_node_id, occurred_at DESC);
|
|
111
|
+
|
|
112
|
+
-- 4. Durable Incident Investigations & Runner Lease Coordination
|
|
113
|
+
CREATE TABLE IF NOT EXISTS incident_investigations (
|
|
114
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
115
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
116
|
+
id TEXT NOT NULL,
|
|
117
|
+
dedup_key TEXT NOT NULL,
|
|
118
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
119
|
+
target_node_id TEXT NOT NULL,
|
|
120
|
+
symptom_anchor_kind TEXT NOT NULL,
|
|
121
|
+
symptom_anchor_id TEXT NOT NULL,
|
|
122
|
+
symptom_fingerprint TEXT,
|
|
123
|
+
alert_count INTEGER NOT NULL DEFAULT 1,
|
|
124
|
+
latest_bundle_json TEXT,
|
|
125
|
+
lease_owner_id TEXT,
|
|
126
|
+
lease_expires_at TEXT,
|
|
127
|
+
dispatch_attempts INTEGER NOT NULL DEFAULT 0,
|
|
128
|
+
last_dispatched_at TEXT,
|
|
129
|
+
opened_at TEXT NOT NULL,
|
|
130
|
+
updated_at TEXT NOT NULL,
|
|
131
|
+
resolved_at TEXT,
|
|
132
|
+
expires_at TEXT,
|
|
133
|
+
PRIMARY KEY (project_id, environment, id)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_active_dedup
|
|
137
|
+
ON incident_investigations (project_id, environment, dedup_key, status);
|
|
138
|
+
|
|
139
|
+
-- 5. Closed-Loop Investigation Findings (Separating Recovery vs Causal Proof)
|
|
140
|
+
CREATE TABLE IF NOT EXISTS investigation_findings (
|
|
141
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
142
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
143
|
+
id TEXT NOT NULL,
|
|
144
|
+
incident_id TEXT,
|
|
145
|
+
fingerprint TEXT NOT NULL,
|
|
146
|
+
fingerprint_input_json TEXT NOT NULL,
|
|
147
|
+
title TEXT NOT NULL,
|
|
148
|
+
summary TEXT NOT NULL,
|
|
149
|
+
causal_proof_tier TEXT NOT NULL DEFAULT 'unverified_locus',
|
|
150
|
+
confidence DOUBLE PRECISION NOT NULL,
|
|
151
|
+
confidence_breakdown_json TEXT NOT NULL,
|
|
152
|
+
symptom_entity_kind TEXT NOT NULL,
|
|
153
|
+
symptom_entity_id TEXT NOT NULL,
|
|
154
|
+
fault_locus_entity_kind TEXT NOT NULL,
|
|
155
|
+
fault_locus_entity_id TEXT NOT NULL,
|
|
156
|
+
root_cause_entity_kind TEXT,
|
|
157
|
+
root_cause_entity_id TEXT,
|
|
158
|
+
contributing_change_id TEXT,
|
|
159
|
+
cohort_verdict TEXT,
|
|
160
|
+
contending_trace_id TEXT,
|
|
161
|
+
causal_chain_json TEXT NOT NULL,
|
|
162
|
+
cited_evidence_ids_json TEXT NOT NULL,
|
|
163
|
+
code_references_json TEXT,
|
|
164
|
+
counterfactual_proof_json TEXT,
|
|
165
|
+
recovery_status TEXT NOT NULL DEFAULT 'not_applicable',
|
|
166
|
+
causal_verification_status TEXT NOT NULL DEFAULT 'unverified',
|
|
167
|
+
watchdog_step INTEGER NOT NULL DEFAULT 0,
|
|
168
|
+
baseline_request_rate_per_min DOUBLE PRECISION,
|
|
169
|
+
next_watchdog_check_at TEXT,
|
|
170
|
+
actor_type TEXT NOT NULL,
|
|
171
|
+
actor_id TEXT NOT NULL,
|
|
172
|
+
agent_run_id TEXT,
|
|
173
|
+
created_at TEXT NOT NULL,
|
|
174
|
+
updated_at TEXT NOT NULL,
|
|
175
|
+
expires_at TEXT,
|
|
176
|
+
PRIMARY KEY (project_id, environment, id)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_findings_fingerprint
|
|
180
|
+
ON investigation_findings (project_id, environment, fingerprint, created_at DESC);
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_findings_watchdog
|
|
183
|
+
ON investigation_findings (project_id, recovery_status, next_watchdog_check_at);
|
|
184
|
+
|
|
185
|
+
-- 6. Pre-Execution Remediation Proposals & Single-Use Grants
|
|
186
|
+
CREATE TABLE IF NOT EXISTS remediation_proposals (
|
|
187
|
+
project_id TEXT NOT NULL DEFAULT 'default',
|
|
188
|
+
environment TEXT NOT NULL DEFAULT 'default',
|
|
189
|
+
id TEXT NOT NULL,
|
|
190
|
+
incident_id TEXT NOT NULL,
|
|
191
|
+
finding_id TEXT NOT NULL,
|
|
192
|
+
idempotency_key TEXT NOT NULL,
|
|
193
|
+
action_kind TEXT NOT NULL,
|
|
194
|
+
target_node_id TEXT NOT NULL,
|
|
195
|
+
expected_current_state_json TEXT NOT NULL,
|
|
196
|
+
desired_target_state_json TEXT NOT NULL,
|
|
197
|
+
requested_autonomy TEXT NOT NULL,
|
|
198
|
+
effective_autonomy TEXT NOT NULL,
|
|
199
|
+
policy_decision_reason TEXT NOT NULL,
|
|
200
|
+
status TEXT NOT NULL DEFAULT 'proposed',
|
|
201
|
+
grant_token_hash TEXT,
|
|
202
|
+
grant_expires_at TEXT,
|
|
203
|
+
approved_by_user_id TEXT,
|
|
204
|
+
approved_at TEXT,
|
|
205
|
+
claimed_by_runner_id TEXT,
|
|
206
|
+
claimed_at TEXT,
|
|
207
|
+
claim_expires_at TEXT,
|
|
208
|
+
remediation_agent_run_id TEXT,
|
|
209
|
+
resulting_change_id TEXT,
|
|
210
|
+
failure_reason TEXT,
|
|
211
|
+
created_at TEXT NOT NULL,
|
|
212
|
+
updated_at TEXT NOT NULL,
|
|
213
|
+
expires_at TEXT,
|
|
214
|
+
PRIMARY KEY (project_id, environment, id),
|
|
215
|
+
UNIQUE (project_id, environment, idempotency_key)
|
|
216
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- (PostgreSQL mirror) Make active-incident deduplication race-free (RFC 0013 § 6.A, governance review B4).
|
|
2
|
+
--
|
|
3
|
+
-- `openOrDeduplicateIncident` used to SELECT-then-INSERT, so two concurrent
|
|
4
|
+
-- alerts for the same symptom could each open an incident. A partial UNIQUE
|
|
5
|
+
-- index lets the database arbitrate; the application inserts with
|
|
6
|
+
-- ON CONFLICT DO NOTHING and re-reads the winner.
|
|
7
|
+
|
|
8
|
+
-- Collapse any pre-existing duplicate active rows (keep the newest per key) so
|
|
9
|
+
-- the index can be built. Older duplicates are closed as escalated, not deleted.
|
|
10
|
+
UPDATE incident_investigations
|
|
11
|
+
SET status = 'escalated'
|
|
12
|
+
WHERE status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying')
|
|
13
|
+
AND EXISTS (
|
|
14
|
+
SELECT 1 FROM incident_investigations o
|
|
15
|
+
WHERE o.project_id = incident_investigations.project_id
|
|
16
|
+
AND o.environment = incident_investigations.environment
|
|
17
|
+
AND o.dedup_key = incident_investigations.dedup_key
|
|
18
|
+
AND o.status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying')
|
|
19
|
+
AND (
|
|
20
|
+
o.opened_at > incident_investigations.opened_at
|
|
21
|
+
OR (o.opened_at = incident_investigations.opened_at AND o.id > incident_investigations.id)
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_incidents_active_dedup
|
|
26
|
+
ON incident_investigations (project_id, environment, dedup_key)
|
|
27
|
+
WHERE status IN ('open', 'dispatched', 'leased', 'remediating', 'verifying');
|
|
28
|
+
|
|
29
|
+
-- Watchdog (governance review B11): a finding is only `recovered` after two
|
|
30
|
+
-- consecutive checkpoints with sufficient traffic and a healthy error rate.
|
|
31
|
+
ALTER TABLE investigation_findings ADD COLUMN IF NOT EXISTS consecutive_ok_checks BIGINT NOT NULL DEFAULT 0;
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
type Logger,
|
|
12
12
|
} from "../framework/logger";
|
|
13
13
|
import { AlertsStore, compareValue } from "../lib/alerts-store";
|
|
14
|
+
import {
|
|
15
|
+
openOrDeduplicateIncident,
|
|
16
|
+
resolveServiceIncidentTarget,
|
|
17
|
+
} from "../lib/autosre-remediation-gate";
|
|
14
18
|
import { sqlDbFor } from "../lib/sql-db";
|
|
15
19
|
import {
|
|
16
20
|
assertSafeWebhookUrl,
|
|
@@ -238,6 +242,7 @@ async function evaluateOneRule(
|
|
|
238
242
|
tracer: ChildSpanRunner | undefined,
|
|
239
243
|
ruleTimeoutMs: number,
|
|
240
244
|
delivery: WebhookDeliveryOptions,
|
|
245
|
+
onAlertFiring?: (rule: AlertRule) => Promise<void>,
|
|
241
246
|
): Promise<{ fired: number; resolved: number; redelivered: number }> {
|
|
242
247
|
const none = { fired: 0, resolved: 0, redelivered: 0 };
|
|
243
248
|
try {
|
|
@@ -296,6 +301,9 @@ async function evaluateOneRule(
|
|
|
296
301
|
// transition; otherwise the same-state branch below retries on the
|
|
297
302
|
// next tick without a second transition.
|
|
298
303
|
await store.transitionState(rule.id, rule.projectId, next, now);
|
|
304
|
+
if (next === "firing" && onAlertFiring) {
|
|
305
|
+
await onAlertFiring(rule).catch(() => undefined);
|
|
306
|
+
}
|
|
299
307
|
const ok = await deliver(next);
|
|
300
308
|
// Acknowledge before writing history: if the evaluation insert
|
|
301
309
|
// fails, the delivered flag is already durable and the next tick
|
|
@@ -364,6 +372,7 @@ export async function evaluateRuleBatch(
|
|
|
364
372
|
ruleTimeoutMs?: number;
|
|
365
373
|
allowPrivateWebhookTargets?: boolean;
|
|
366
374
|
hostLookup?: HostLookup;
|
|
375
|
+
onAlertFiring?: (rule: AlertRule) => Promise<void>;
|
|
367
376
|
},
|
|
368
377
|
): Promise<{
|
|
369
378
|
evaluated: number;
|
|
@@ -393,6 +402,7 @@ export async function evaluateRuleBatch(
|
|
|
393
402
|
options?.tracer,
|
|
394
403
|
ruleTimeoutMs,
|
|
395
404
|
delivery,
|
|
405
|
+
options?.onAlertFiring,
|
|
396
406
|
),
|
|
397
407
|
);
|
|
398
408
|
return {
|
|
@@ -421,7 +431,8 @@ export async function evaluateAllRules(
|
|
|
421
431
|
const logger = options?.logger ?? consoleLogger;
|
|
422
432
|
const tracer = options?.tracer;
|
|
423
433
|
const allowPrivateWebhookTargets = env.ALERT_WEBHOOK_ALLOW_PRIVATE === "true";
|
|
424
|
-
const
|
|
434
|
+
const db = sqlDbFor(env);
|
|
435
|
+
const store = new AlertsStore(db, { allowPrivateWebhookTargets });
|
|
425
436
|
const rules = await store.listEnabledRules();
|
|
426
437
|
return evaluateRuleBatch(store, rules, {
|
|
427
438
|
logger,
|
|
@@ -430,6 +441,23 @@ export async function evaluateAllRules(
|
|
|
430
441
|
ruleTimeoutMs: options?.ruleTimeoutMs,
|
|
431
442
|
allowPrivateWebhookTargets,
|
|
432
443
|
hostLookup: options?.hostLookup,
|
|
444
|
+
onAlertFiring: async (rule) => {
|
|
445
|
+
// Target the rule's service when it names one (environment taken from
|
|
446
|
+
// that service's latest spans); otherwise fall back to the rule itself.
|
|
447
|
+
const target = await resolveServiceIncidentTarget(
|
|
448
|
+
db,
|
|
449
|
+
rule.projectId,
|
|
450
|
+
(rule.query as { serviceName?: string }).serviceName,
|
|
451
|
+
`alert:${rule.id}`,
|
|
452
|
+
logger,
|
|
453
|
+
);
|
|
454
|
+
await openOrDeduplicateIncident(db, rule.projectId, {
|
|
455
|
+
environment: target.environment,
|
|
456
|
+
targetNodeId: target.targetNodeId,
|
|
457
|
+
symptomAnchorKind: "alert",
|
|
458
|
+
symptomAnchorId: rule.id,
|
|
459
|
+
});
|
|
460
|
+
},
|
|
433
461
|
});
|
|
434
462
|
}
|
|
435
463
|
|