@simbimbo/brainstem 0.0.3 → 0.0.5

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/tests/test_api.py CHANGED
@@ -1,11 +1,15 @@
1
1
  from pathlib import Path
2
2
 
3
+ import pytest
3
4
  from fastapi.testclient import TestClient
4
5
 
6
+ from brainstem import __version__
5
7
  from brainstem.api import app
8
+ from brainstem.fingerprint import normalize_message
6
9
  from brainstem.models import RawInputEnvelope
7
10
  from brainstem.storage import (
8
11
  init_db,
12
+ get_raw_envelope_by_id,
9
13
  set_raw_envelope_status,
10
14
  store_raw_envelopes,
11
15
  )
@@ -71,6 +75,376 @@ def test_ingest_batch_and_interesting(tmp_path: Path) -> None:
71
75
  assert interesting_payload["items"]
72
76
 
73
77
 
78
+ def test_ingest_batch_mixed_success_and_failure_returns_per_item_accounting(tmp_path: Path) -> None:
79
+ client = TestClient(app)
80
+ db_path = tmp_path / "brainstem_batch_accounting.sqlite3"
81
+ payload = {
82
+ "threshold": 2,
83
+ "db_path": str(db_path),
84
+ "events": [
85
+ {
86
+ "tenant_id": "client-a",
87
+ "source_type": "syslog",
88
+ "message_raw": "Failed password for admin from 10.1.2.3",
89
+ "host": "fw-01",
90
+ "service": "sshd",
91
+ },
92
+ {
93
+ "tenant_id": "client-b",
94
+ "source_type": "syslog",
95
+ "message_raw": "VPN tunnel dropped and recovered",
96
+ "host": "fw-02",
97
+ "service": "charon",
98
+ },
99
+ {
100
+ "tenant_id": "client-a",
101
+ "source_type": "syslog",
102
+ "message_raw": "",
103
+ "host": "fw-01",
104
+ "service": "sshd",
105
+ },
106
+ ],
107
+ }
108
+ response = client.post("/ingest/batch", json=payload)
109
+ assert response.status_code == 200
110
+ batch_payload = response.json()
111
+
112
+ assert batch_payload["ok"] is True
113
+ assert batch_payload["item_count"] == 3
114
+ assert batch_payload["event_count"] == 2
115
+ assert batch_payload["parse_failed"] == 1
116
+ assert "item_results" in batch_payload
117
+ assert len(batch_payload["item_results"]) == 3
118
+
119
+ item_by_index = {item["index"]: item for item in batch_payload["item_results"]}
120
+ assert set(item_by_index.keys()) == {0, 1, 2}
121
+ assert item_by_index[0]["status"] == "canonicalized"
122
+ assert item_by_index[1]["status"] == "canonicalized"
123
+ assert item_by_index[2]["status"] == "parse_failed"
124
+ assert batch_payload["item_results"][2]["raw_envelope_id"] is not None
125
+ assert item_by_index[2]["failure_reason"] == "message_raw is empty and cannot be canonicalized"
126
+
127
+ for index, item in item_by_index.items():
128
+ assert item["tenant_id"] in {"client-a", "client-b"}
129
+ assert item["source_type"] == "syslog"
130
+ assert isinstance(item["index"], int)
131
+ assert "raw_envelope_id" in item
132
+ assert "failure_reason" in item
133
+
134
+
135
+ def test_ingest_logicmonitor_endpoint_persists_and_surfaces_via_runtime_inspection(tmp_path: Path) -> None:
136
+ client = TestClient(app)
137
+ db_path = tmp_path / "logicmonitor.sqlite3"
138
+
139
+ response = client.post(
140
+ "/ingest/logicmonitor",
141
+ json={
142
+ "tenant_id": "client-a",
143
+ "source_path": "/logicmonitor/webhook",
144
+ "threshold": 2,
145
+ "db_path": str(db_path),
146
+ "events": [
147
+ {
148
+ "resource_id": 123,
149
+ "resource_name": "edge-fw-01",
150
+ "severity": "warning",
151
+ "timestamp": "2026-03-22T00:00:00Z",
152
+ "message_raw": "VPN tunnel dropped and recovered",
153
+ "metadata": {
154
+ "datasource": "IPSec Tunnel",
155
+ "instance_name": "site-b",
156
+ },
157
+ },
158
+ {
159
+ "resource_id": 124,
160
+ "resource_name": "edge-fw-02",
161
+ "severity": "warning",
162
+ "timestamp": "2026-03-22T00:00:01Z",
163
+ "message_raw": "VPN tunnel dropped and recovered",
164
+ "metadata": {
165
+ "datasource": "IPSec Tunnel",
166
+ "instance_name": "site-c",
167
+ },
168
+ },
169
+ ],
170
+ },
171
+ )
172
+ assert response.status_code == 200
173
+ payload = response.json()
174
+ assert payload["ok"] is True
175
+ assert payload["event_count"] == 2
176
+ assert payload["signature_count"] >= 1
177
+
178
+ raw_response = client.get(f"/raw_envelopes?db_path={db_path}&source_type=logicmonitor&limit=10")
179
+ assert raw_response.status_code == 200
180
+ raw_payload = raw_response.json()
181
+ assert raw_payload["count"] == 2
182
+ assert all(item["source_type"] == "logicmonitor" for item in raw_payload["items"])
183
+ assert all(item["canonicalization_status"] == "canonicalized" for item in raw_payload["items"])
184
+
185
+ canonical_response = client.get(f"/canonical_events?db_path={db_path}&source=logicmonitor&limit=10")
186
+ assert canonical_response.status_code == 200
187
+ canonical_payload = canonical_response.json()
188
+ assert canonical_payload["count"] == 2
189
+ assert all(item["source"] == "logicmonitor" for item in canonical_payload["items"])
190
+
191
+ sources_response = client.get(f"/sources?db_path={db_path}&limit=10")
192
+ assert sources_response.status_code == 200
193
+ sources_payload = sources_response.json()
194
+ source_types = [entry["value"] for entry in sources_payload["items"]["source_type"]]
195
+ assert "logicmonitor" in source_types
196
+
197
+ runtime_response = client.get("/runtime")
198
+ assert runtime_response.status_code == 200
199
+ runtime_payload = runtime_response.json()
200
+ assert runtime_payload["runtime"]["capability_flags"]["ingest_endpoints"]["logicmonitor_events"] is True
201
+
202
+
203
+ def test_candidates_endpoint_returns_candidate_inspection_payload_and_supports_filtering(tmp_path: Path) -> None:
204
+ client = TestClient(app)
205
+ db_path = tmp_path / "brainstem_candidates.sqlite3"
206
+ ingest_response = client.post(
207
+ "/ingest/batch",
208
+ json={
209
+ "threshold": 2,
210
+ "db_path": str(db_path),
211
+ "events": [
212
+ {
213
+ "tenant_id": "client-a",
214
+ "source_type": "syslog",
215
+ "message_raw": "Failed password for admin from 10.1.2.3",
216
+ "host": "fw-01",
217
+ "service": "sshd",
218
+ },
219
+ {
220
+ "tenant_id": "client-a",
221
+ "source_type": "syslog",
222
+ "message_raw": "Failed password for admin from 10.1.2.3",
223
+ "host": "fw-01",
224
+ "service": "sshd",
225
+ },
226
+ ],
227
+ },
228
+ )
229
+ assert ingest_response.status_code == 200
230
+ candidates = client.get(f"/candidates?db_path={db_path}&limit=10")
231
+ assert candidates.status_code == 200
232
+ candidates_payload = candidates.json()
233
+ assert candidates_payload["ok"] is True
234
+ assert candidates_payload["count"] >= 1
235
+ assert len(candidates_payload["items"]) >= 1
236
+
237
+ item = candidates_payload["items"][0]
238
+ assert item["title"]
239
+ assert item["summary"]
240
+ assert item["decision_band"] in {"watch", "review", "urgent_human_review", "promote_to_incident_memory", "ignore"}
241
+ assert item["attention_band"] in {"ignore_fast", "background", "watch", "investigate", "promote"}
242
+ assert item["attention_score"] >= 0
243
+ assert item["score_total"] == item["attention_score"]
244
+ assert isinstance(item["score_breakdown"], dict)
245
+ assert item["raw_envelope_ids"]
246
+ assert isinstance(item["raw_envelopes"], list)
247
+ assert [envelope["id"] for envelope in item["raw_envelopes"]] == item["raw_envelope_ids"]
248
+
249
+ filtered_by_decision = client.get(
250
+ f"/candidates?db_path={db_path}&decision_band={item['decision_band']}&limit=10"
251
+ )
252
+ assert filtered_by_decision.status_code == 200
253
+ filtered_payload = filtered_by_decision.json()
254
+ assert filtered_payload["count"] >= 1
255
+ assert all(i["decision_band"] == item["decision_band"] for i in filtered_payload["items"])
256
+
257
+ filtered_by_type = client.get(f"/candidates?db_path={db_path}&candidate_type={item['candidate_type']}&limit=1")
258
+ assert filtered_by_type.status_code == 200
259
+ limited_payload = filtered_by_type.json()
260
+ assert limited_payload["count"] == 1
261
+ assert len(limited_payload["items"]) <= 1
262
+
263
+
264
+ def test_signatures_endpoint_returns_signature_payload_and_supports_filtering(tmp_path: Path) -> None:
265
+ client = TestClient(app)
266
+ db_path = tmp_path / "brainstem_signatures.sqlite3"
267
+ ingest_response = client.post(
268
+ "/ingest/batch",
269
+ json={
270
+ "threshold": 2,
271
+ "db_path": str(db_path),
272
+ "events": [
273
+ {
274
+ "tenant_id": "client-a",
275
+ "source_type": "syslog",
276
+ "message_raw": "Failed password for admin from 10.1.2.3",
277
+ "host": "fw-01",
278
+ "service": "sshd",
279
+ },
280
+ {
281
+ "tenant_id": "client-a",
282
+ "source_type": "syslog",
283
+ "message_raw": "Failed password for admin from 10.1.2.3",
284
+ "host": "fw-01",
285
+ "service": "sshd",
286
+ },
287
+ {
288
+ "tenant_id": "client-a",
289
+ "source_type": "syslog",
290
+ "message_raw": "Different event in another family",
291
+ "host": "fw-01",
292
+ "service": "systemd",
293
+ },
294
+ ],
295
+ },
296
+ )
297
+ assert ingest_response.status_code == 200
298
+
299
+ signatures = client.get(f"/signatures?db_path={db_path}&limit=10")
300
+ assert signatures.status_code == 200
301
+ signatures_payload = signatures.json()
302
+ assert signatures_payload["ok"] is True
303
+ assert signatures_payload["count"] >= 2
304
+ assert len(signatures_payload["items"]) >= 2
305
+
306
+ first_signature = signatures_payload["items"][0]
307
+ assert first_signature["signature_key"]
308
+ assert first_signature["event_family"]
309
+ assert first_signature["normalized_pattern"]
310
+ assert isinstance(first_signature["occurrence_count"], int)
311
+ assert first_signature["occurrence_count"] >= 2
312
+ assert isinstance(first_signature["raw_envelope_ids"], list)
313
+ assert first_signature["raw_envelope_count"] == len(first_signature["raw_envelope_ids"])
314
+ assert isinstance(first_signature["recurrence"], dict)
315
+ assert first_signature["recurrence"]["signature_id"] > 0
316
+ assert first_signature["raw_envelope_count"] >= 1
317
+
318
+ family_filtered = client.get(
319
+ f"/signatures?db_path={db_path}&event_family={first_signature['event_family']}&limit=10"
320
+ )
321
+ assert family_filtered.status_code == 200
322
+ family_filtered_payload = family_filtered.json()
323
+ assert family_filtered_payload["count"] >= 1
324
+ assert all(item["event_family"] == first_signature["event_family"] for item in family_filtered_payload["items"])
325
+
326
+ service_filtered = client.get(
327
+ f"/signatures?db_path={db_path}&service=sshd&limit=10"
328
+ )
329
+ assert service_filtered.status_code == 200
330
+ service_filtered_payload = service_filtered.json()
331
+ assert service_filtered_payload["count"] >= 1
332
+ assert all(item["service"] == "sshd" for item in service_filtered_payload["items"])
333
+
334
+ min_occurrence_filtered = client.get(
335
+ f"/signatures?db_path={db_path}&min_occurrence_count=2&limit=10"
336
+ )
337
+ assert min_occurrence_filtered.status_code == 200
338
+ min_occurrence_payload = min_occurrence_filtered.json()
339
+ assert min_occurrence_payload["count"] >= 1
340
+ assert all(item["occurrence_count"] >= 2 for item in min_occurrence_payload["items"])
341
+
342
+ limited = client.get(f"/signatures?db_path={db_path}&limit=1")
343
+ assert limited.status_code == 200
344
+ limited_payload = limited.json()
345
+ assert limited_payload["count"] == 1
346
+ assert len(limited_payload["items"]) <= 1
347
+
348
+
349
+ def test_canonical_events_endpoint_returns_normalized_fields_and_supports_filters(tmp_path: Path) -> None:
350
+ client = TestClient(app)
351
+ db_path = tmp_path / "brainstem_canonical_events.sqlite3"
352
+ ingest_response = client.post(
353
+ "/ingest/batch",
354
+ json={
355
+ "threshold": 1,
356
+ "db_path": str(db_path),
357
+ "events": [
358
+ {
359
+ "tenant_id": "client-a",
360
+ "source_type": "syslog",
361
+ "message_raw": "IPsec SA rekey succeeded on host 10.1.2.3",
362
+ "host": "fw-01",
363
+ "service": "charon",
364
+ "severity": "info",
365
+ },
366
+ {
367
+ "tenant_id": "client-a",
368
+ "source_type": "syslog",
369
+ "message_raw": "Service restart detected on node 2",
370
+ "host": "fw-01",
371
+ "service": "systemd",
372
+ "severity": "warning",
373
+ },
374
+ {
375
+ "tenant_id": "client-a",
376
+ "source_type": "file",
377
+ "message_raw": "Configuration drift detected for node 3",
378
+ "host": "fw-02",
379
+ "service": "charon",
380
+ "severity": "critical",
381
+ },
382
+ {
383
+ "tenant_id": "client-b",
384
+ "source_type": "file",
385
+ "message_raw": "",
386
+ "host": "fw-02",
387
+ "service": "sshd",
388
+ "severity": "info",
389
+ },
390
+ ],
391
+ },
392
+ )
393
+ assert ingest_response.status_code == 200
394
+
395
+ tenant_events = client.get(f"/canonical_events?db_path={db_path}&tenant_id=client-a&limit=10")
396
+ assert tenant_events.status_code == 200
397
+ tenant_payload = tenant_events.json()
398
+ assert tenant_payload["ok"] is True
399
+ assert tenant_payload["count"] == 3
400
+ assert tenant_payload["items"][0]["tenant_id"] == "client-a"
401
+ expected_normalized = {
402
+ normalize_message("IPsec SA rekey succeeded on host 10.1.2.3"),
403
+ normalize_message("Service restart detected on node 2"),
404
+ normalize_message("Configuration drift detected for node 3"),
405
+ }
406
+ first = tenant_payload["items"][0]
407
+ assert first["raw_envelope_id"] > 0
408
+ assert first["tenant_id"]
409
+ assert first["source"] in {"syslog", "file"}
410
+ assert first["host"]
411
+ assert first["service"]
412
+ assert first["severity"] in {"info", "warning", "critical"}
413
+ assert first["message_raw"]
414
+ assert first["message_normalized"] == normalize_message(first["message_raw"])
415
+ assert set(item["message_normalized"] for item in tenant_payload["items"]) == expected_normalized
416
+
417
+ limited = client.get(f"/canonical_events?db_path={db_path}&tenant_id=client-a&limit=1")
418
+ assert limited.status_code == 200
419
+ limited_payload = limited.json()
420
+ assert limited_payload["count"] == 1
421
+ assert len(limited_payload["items"]) <= 1
422
+
423
+ host_filtered = client.get(f"/canonical_events?db_path={db_path}&tenant_id=client-a&host=fw-01")
424
+ assert host_filtered.status_code == 200
425
+ host_payload = host_filtered.json()
426
+ assert host_payload["count"] == 2
427
+ assert all(item["host"] == "fw-01" for item in host_payload["items"])
428
+
429
+ source_filtered = client.get(f"/canonical_events?db_path={db_path}&tenant_id=client-a&source=file")
430
+ assert source_filtered.status_code == 200
431
+ source_payload = source_filtered.json()
432
+ assert source_payload["count"] == 1
433
+ assert source_payload["items"][0]["source"] == "file"
434
+
435
+ service_filtered = client.get(f"/canonical_events?db_path={db_path}&tenant_id=client-a&service=charon")
436
+ assert service_filtered.status_code == 200
437
+ service_payload = service_filtered.json()
438
+ assert service_payload["count"] == 2
439
+ assert all(item["service"] == "charon" for item in service_payload["items"])
440
+
441
+ severity_filtered = client.get(f"/canonical_events?db_path={db_path}&severity=warning&tenant_id=client-a")
442
+ assert severity_filtered.status_code == 200
443
+ severity_payload = severity_filtered.json()
444
+ assert severity_payload["count"] == 1
445
+ assert severity_payload["items"][0]["severity"] == "warning"
446
+
447
+
74
448
  def test_stats_after_successful_and_failed_ingest(tmp_path: Path) -> None:
75
449
  client = TestClient(app)
76
450
  db_path = tmp_path / "brainstem.sqlite3"
@@ -127,6 +501,175 @@ def test_healthz_is_ready() -> None:
127
501
  assert response.json()["ok"] is True
128
502
 
129
503
 
504
+ def test_healthz_reports_api_token_status(monkeypatch: pytest.MonkeyPatch) -> None:
505
+ client = TestClient(app)
506
+ monkeypatch.delenv("BRAINSTEM_API_TOKEN", raising=False)
507
+ response = client.get("/healthz")
508
+ assert response.status_code == 200
509
+ payload = response.json()
510
+ assert payload["api_token_enabled"] is False
511
+ assert payload["runtime"]["auth_state"]["api_token_configured"] is False
512
+
513
+ monkeypatch.setenv("BRAINSTEM_API_TOKEN", "local-token")
514
+ response = client.get("/healthz")
515
+ assert response.status_code == 200
516
+ payload = response.json()
517
+ assert payload["api_token_enabled"] is True
518
+ assert payload["runtime"]["auth_state"]["api_token_configured"] is True
519
+
520
+
521
+ def test_healthz_reports_runtime_summary() -> None:
522
+ client = TestClient(app)
523
+ response = client.get("/healthz")
524
+ assert response.status_code == 200
525
+ payload = response.json()
526
+ runtime = payload["runtime"]
527
+ assert runtime["version"] == __version__
528
+ assert runtime["capability_flags"]["ingest_endpoints"]["single_event"] is True
529
+ assert runtime["defaults"]["interesting_limit"] == 5
530
+ assert runtime["limits"]["replay_raw_max_ids"] == 32
531
+
532
+
533
+ def test_status_endpoint_reports_operator_summary() -> None:
534
+ client = TestClient(app)
535
+ response = client.get("/status")
536
+ assert response.status_code == 200
537
+ payload = response.json()
538
+ assert payload["ok"] is True
539
+ assert payload["status"] == "ok"
540
+ assert payload["api_token_enabled"] == payload["runtime"]["auth_state"]["api_token_configured"]
541
+ assert payload["runtime"]["capability_flags"]["inspection_endpoints"]["raw_envelopes"] is True
542
+ assert payload["runtime"]["runtime"]["api_token_env"] == "BRAINSTEM_API_TOKEN"
543
+
544
+
545
+ def test_status_and_healthz_are_coherent() -> None:
546
+ client = TestClient(app)
547
+ status_response = client.get("/status")
548
+ healthz_response = client.get("/healthz")
549
+ assert status_response.status_code == 200
550
+ assert healthz_response.status_code == 200
551
+ assert status_response.json() == healthz_response.json()
552
+
553
+
554
+ def test_runtime_endpoint_includes_source_capability_matrix() -> None:
555
+ client = TestClient(app)
556
+ response = client.get("/runtime")
557
+ assert response.status_code == 200
558
+ source_capabilities = response.json()["runtime"]["capability_flags"]["source_capabilities"]
559
+ source_types = source_capabilities["source_types"]
560
+
561
+ assert "syslog" in source_types
562
+ assert "file" in source_types
563
+ assert "logicmonitor" in source_types
564
+
565
+ per_source = {item["source_type"]: item["modes"] for item in source_capabilities["ingest_modes_by_source_type"]}
566
+ assert {"mode": "udp_listener", "endpoint": "brainstem.listener"} in per_source["syslog"]
567
+ assert {"mode": "single_event_api", "endpoint": "/ingest/event"} in per_source["file"]
568
+ assert {"mode": "batch_api", "endpoint": "/ingest/batch"} in per_source["file"]
569
+ assert {"mode": "logicmonitor_webhook", "endpoint": "/ingest/logicmonitor"} in per_source["logicmonitor"]
570
+
571
+
572
+ def test_runtime_endpoint_reports_config_object(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
573
+ custom_db = tmp_path / "runtime.sqlite3"
574
+ monkeypatch.setenv("BRAINSTEM_DB_PATH", str(custom_db))
575
+ client = TestClient(app)
576
+
577
+ response = client.get("/runtime")
578
+ assert response.status_code == 200
579
+ runtime = response.json()["runtime"]
580
+
581
+ config = runtime["runtime"]["config"]
582
+ assert config["api_token_env_var"] == "BRAINSTEM_API_TOKEN"
583
+ assert config["listener"]["syslog_host"] == "127.0.0.1"
584
+ assert config["listener"]["syslog_port"] == 5514
585
+ assert config["listener"]["syslog_source_path"] == "/dev/udp"
586
+ assert config["defaults"]["ingest_threshold"] == 2
587
+ assert config["db"]["default_path"] == str(custom_db)
588
+ assert runtime["defaults"] == config["defaults"]
589
+
590
+
591
+ def test_runtime_endpoint_provides_same_summary(monkeypatch: pytest.MonkeyPatch) -> None:
592
+ client = TestClient(app)
593
+ monkeypatch.setenv("BRAINSTEM_API_TOKEN", "runtime-token")
594
+ response = client.get("/runtime")
595
+ assert response.status_code == 200
596
+ payload = response.json()
597
+ assert payload["ok"] is True
598
+ runtime = payload["runtime"]
599
+ assert runtime["auth_state"]["api_token_configured"] is True
600
+ assert runtime["runtime"]["api_token_env"] == "BRAINSTEM_API_TOKEN"
601
+ assert runtime["limits"]["replay_raw_max_ids"] == 32
602
+
603
+
604
+ def test_unprotected_routes_remain_open_when_api_token_not_configured(
605
+ monkeypatch: pytest.MonkeyPatch,
606
+ tmp_path: Path,
607
+ ) -> None:
608
+ monkeypatch.delenv("BRAINSTEM_API_TOKEN", raising=False)
609
+ client = TestClient(app)
610
+ db_path = tmp_path / "open.sqlite3"
611
+
612
+ ingest_response = client.post(
613
+ f"/ingest/event?threshold=1&db_path={db_path}",
614
+ json={
615
+ "tenant_id": "client-a",
616
+ "source_type": "syslog",
617
+ "message_raw": "Service restarted",
618
+ "host": "fw-01",
619
+ "service": "systemd",
620
+ },
621
+ )
622
+ assert ingest_response.status_code == 200
623
+
624
+ healthz_response = client.get(f"/interesting?db_path={db_path}&limit=10")
625
+ assert healthz_response.status_code == 200
626
+ assert healthz_response.json()["ok"] is True
627
+
628
+
629
+ def test_api_token_is_required_for_write_and_inspection_routes_when_enabled(
630
+ monkeypatch: pytest.MonkeyPatch,
631
+ tmp_path: Path,
632
+ ) -> None:
633
+ monkeypatch.setenv("BRAINSTEM_API_TOKEN", "valid-token")
634
+ client = TestClient(app)
635
+ db_path = tmp_path / "auth.sqlite3"
636
+
637
+ unauthenticated = client.post(
638
+ f"/ingest/event?threshold=1&db_path={db_path}",
639
+ json={
640
+ "tenant_id": "client-a",
641
+ "source_type": "syslog",
642
+ "message_raw": "Service restarted",
643
+ "host": "fw-01",
644
+ "service": "systemd",
645
+ },
646
+ )
647
+ assert unauthenticated.status_code == 401
648
+
649
+ wrong_token = client.get(f"/interesting?db_path={db_path}&limit=10", headers={"X-API-Token": "wrong"})
650
+ assert wrong_token.status_code == 401
651
+
652
+ authed = client.post(
653
+ f"/ingest/event?threshold=1&db_path={db_path}",
654
+ headers={"Authorization": "Bearer valid-token"},
655
+ json={
656
+ "tenant_id": "client-a",
657
+ "source_type": "syslog",
658
+ "message_raw": "Service restarted",
659
+ "host": "fw-01",
660
+ "service": "systemd",
661
+ },
662
+ )
663
+ assert authed.status_code == 200
664
+
665
+ read_authed = client.get(
666
+ f"/interesting?db_path={db_path}&limit=10",
667
+ headers={"X-API-Token": "valid-token"},
668
+ )
669
+ assert read_authed.status_code == 200
670
+ assert read_authed.json()["ok"] is True
671
+
672
+
130
673
  def test_failures_endpoint_lists_recent_parse_failures(tmp_path: Path) -> None:
131
674
  client = TestClient(app)
132
675
  db_path = tmp_path / "brainstem.sqlite3"
@@ -213,6 +756,94 @@ def test_failures_endpoint_filters_by_status_and_fetches_single_record(tmp_path:
213
756
  assert invalid.status_code == 422
214
757
 
215
758
 
759
+ def test_raw_envelopes_endpoint_supports_status_and_source_filters(tmp_path: Path) -> None:
760
+ client = TestClient(app)
761
+ db_path = tmp_path / "brainstem.sqlite3"
762
+ init_db(str(db_path))
763
+ raw_ids = store_raw_envelopes(
764
+ [
765
+ RawInputEnvelope(
766
+ tenant_id="tenant-a",
767
+ source_type="syslog",
768
+ source_id="fw-01",
769
+ source_path="/var/log/syslog",
770
+ timestamp="2026-03-22T00:00:01Z",
771
+ message_raw="VPN tunnel recovered",
772
+ ),
773
+ RawInputEnvelope(
774
+ tenant_id="tenant-a",
775
+ source_type="syslog",
776
+ source_id="fw-01",
777
+ source_path="/var/log/auth.log",
778
+ timestamp="2026-03-22T00:00:02Z",
779
+ message_raw="",
780
+ ),
781
+ RawInputEnvelope(
782
+ tenant_id="tenant-b",
783
+ source_type="file",
784
+ source_id="agent-01",
785
+ source_path="/tmp/agent.log",
786
+ timestamp="2026-03-22T00:00:03Z",
787
+ message_raw="backup finished",
788
+ ),
789
+ RawInputEnvelope(
790
+ tenant_id="tenant-a",
791
+ source_type="file",
792
+ source_id="fw-01",
793
+ source_path="/var/log/syslog",
794
+ timestamp="2026-03-22T00:00:04Z",
795
+ message_raw="disk pressure warning",
796
+ ),
797
+ ],
798
+ db_path=str(db_path),
799
+ )
800
+ set_raw_envelope_status(raw_ids[1], "parse_failed", db_path=str(db_path), failure_reason="seeded parse failure")
801
+ set_raw_envelope_status(raw_ids[2], "unsupported", db_path=str(db_path), failure_reason="seeded unsupported")
802
+
803
+ response = client.get(f"/raw_envelopes?db_path={db_path}&limit=10")
804
+ assert response.status_code == 200
805
+ payload = response.json()
806
+ assert payload["ok"] is True
807
+ assert payload["count"] == 4
808
+ assert [item["id"] for item in payload["items"]] == [raw_ids[3], raw_ids[2], raw_ids[1], raw_ids[0]]
809
+
810
+ parse_failed = client.get(f"/raw_envelopes?db_path={db_path}&status=parse_failed&limit=10")
811
+ assert parse_failed.status_code == 200
812
+ parse_payload = parse_failed.json()
813
+ assert parse_payload["count"] == 1
814
+ assert parse_payload["items"][0]["id"] == raw_ids[1]
815
+ assert parse_payload["items"][0]["canonicalization_status"] == "parse_failed"
816
+
817
+ syslog_only = client.get(f"/raw_envelopes?db_path={db_path}&source_type=syslog&limit=10")
818
+ assert syslog_only.status_code == 200
819
+ syslog_payload = syslog_only.json()
820
+ assert [item["id"] for item in syslog_payload["items"]] == [raw_ids[1], raw_ids[0]]
821
+
822
+ fw_source = client.get(f"/raw_envelopes?db_path={db_path}&source_id=fw-01&limit=10")
823
+ assert fw_source.status_code == 200
824
+ fw_payload = fw_source.json()
825
+ assert [item["id"] for item in fw_payload["items"]] == [raw_ids[3], raw_ids[1], raw_ids[0]]
826
+
827
+ source_path = client.get(f"/raw_envelopes?db_path={db_path}&source_path=/var/log/syslog&limit=10")
828
+ assert source_path.status_code == 200
829
+ path_payload = source_path.json()
830
+ assert [item["id"] for item in path_payload["items"]] == [raw_ids[3], raw_ids[0]]
831
+
832
+ tenant_and_source = client.get(
833
+ f"/raw_envelopes?db_path={db_path}&tenant_id=tenant-a&source_type=file&source_path=/var/log/syslog&limit=10"
834
+ )
835
+ assert tenant_and_source.status_code == 200
836
+ tenant_source_payload = tenant_and_source.json()
837
+ assert [item["id"] for item in tenant_source_payload["items"]] == [raw_ids[3]]
838
+
839
+
840
+ def test_raw_envelopes_endpoint_rejects_invalid_status_filter(tmp_path: Path) -> None:
841
+ client = TestClient(app)
842
+ db_path = tmp_path / "brainstem.sqlite3"
843
+ response = client.get(f"/raw_envelopes?db_path={db_path}&status=bogus")
844
+ assert response.status_code == 422
845
+
846
+
216
847
  def test_sources_endpoint_summarizes_ingest_dimensions(tmp_path: Path) -> None:
217
848
  client = TestClient(app)
218
849
  db_path = tmp_path / "brainstem.sqlite3"
@@ -274,6 +905,73 @@ def test_sources_endpoint_summarizes_ingest_dimensions(tmp_path: Path) -> None:
274
905
  }
275
906
 
276
907
 
908
+ def test_sources_status_endpoint_returns_source_health_like_summary(tmp_path: Path) -> None:
909
+ client = TestClient(app)
910
+ db_path = tmp_path / "brainstem.sqlite3"
911
+ ingest_response = client.post(
912
+ "/ingest/batch",
913
+ json={
914
+ "threshold": 1,
915
+ "db_path": str(db_path),
916
+ "events": [
917
+ {
918
+ "tenant_id": "client-a",
919
+ "source_type": "syslog",
920
+ "source_id": "fw-01",
921
+ "source_name": "edge-fw-01",
922
+ "source_path": "/var/log/syslog",
923
+ "message_raw": "Service restarted",
924
+ "host": "fw-01",
925
+ "service": "systemd",
926
+ },
927
+ {
928
+ "tenant_id": "client-a",
929
+ "source_type": "syslog",
930
+ "source_id": "fw-01",
931
+ "source_name": "edge-fw-01",
932
+ "source_path": "/var/log/syslog",
933
+ "message_raw": "",
934
+ "host": "fw-01",
935
+ "service": "systemd",
936
+ },
937
+ {
938
+ "tenant_id": "client-a",
939
+ "source_type": "logicmonitor",
940
+ "source_id": "lm-01",
941
+ "source_name": "edge-lm-01",
942
+ "source_path": "/alerts",
943
+ "message_raw": "Disk space low",
944
+ "host": "lm-01",
945
+ "service": "logicmonitor",
946
+ },
947
+ ],
948
+ },
949
+ )
950
+ assert ingest_response.status_code == 200
951
+
952
+ response = client.get(f"/sources/status?db_path={db_path}&limit=10")
953
+ assert response.status_code == 200
954
+ payload = response.json()
955
+ assert payload["ok"] is True
956
+ assert payload["count"] == 2
957
+ fw01 = next(item for item in payload["items"] if item["source_type"] == "syslog" and item["source_id"] == "fw-01")
958
+ assert fw01["raw_count"] == 2
959
+ assert fw01["canonicalized_count"] == 1
960
+ assert fw01["parse_failed_count"] == 1
961
+ assert fw01["unsupported_count"] == 0
962
+ assert fw01["source_path"] == "/var/log/syslog"
963
+ assert fw01["first_seen_at"] <= fw01["last_seen_at"]
964
+
965
+ filtered = client.get(
966
+ f"/sources/status?db_path={db_path}&source_type=syslog&source_id=fw-01&source_path=/var/log/syslog&limit=10"
967
+ )
968
+ assert filtered.status_code == 200
969
+ filtered_payload = filtered.json()
970
+ assert filtered_payload["count"] == 1
971
+ assert filtered_payload["items"][0]["source_id"] == "fw-01"
972
+ assert filtered_payload["items"][0]["source_path"] == "/var/log/syslog"
973
+
974
+
277
975
  def test_ingest_recent_endpoint_returns_recent_intake_and_allows_status_filter(tmp_path: Path) -> None:
278
976
  client = TestClient(app)
279
977
  db_path = tmp_path / "brainstem.sqlite3"
@@ -317,3 +1015,117 @@ def test_ingest_recent_endpoint_returns_recent_intake_and_allows_status_filter(t
317
1015
  failed_payload = failed.json()
318
1016
  assert failed_payload["count"] == 1
319
1017
  assert failed_payload["items"][0]["canonicalization_status"] == "parse_failed"
1018
+
1019
+
1020
+ def test_replay_raw_endpoint_replays_parse_failed_and_received_records(tmp_path: Path) -> None:
1021
+ client = TestClient(app)
1022
+ db_path = tmp_path / "brainstem.sqlite3"
1023
+ init_db(str(db_path))
1024
+ raw_envelope_ids = store_raw_envelopes(
1025
+ [
1026
+ RawInputEnvelope(
1027
+ tenant_id="client-a",
1028
+ source_type="syslog",
1029
+ timestamp="2026-03-22T00:00:01Z",
1030
+ message_raw="can canonicalize first",
1031
+ host="fw-01",
1032
+ service="sshd",
1033
+ ),
1034
+ RawInputEnvelope(
1035
+ tenant_id="client-a",
1036
+ source_type="syslog",
1037
+ timestamp="2026-03-22T00:00:02Z",
1038
+ message_raw="can canonicalize second",
1039
+ host="fw-01",
1040
+ service="sshd",
1041
+ ),
1042
+ RawInputEnvelope(
1043
+ tenant_id="client-a",
1044
+ source_type="syslog",
1045
+ timestamp="2026-03-22T00:00:03Z",
1046
+ message_raw="",
1047
+ host="fw-01",
1048
+ service="sshd",
1049
+ ),
1050
+ ],
1051
+ db_path=str(db_path),
1052
+ )
1053
+ set_raw_envelope_status(raw_envelope_ids[0], "parse_failed", db_path=str(db_path), failure_reason="seeded parse failure")
1054
+ set_raw_envelope_status(raw_envelope_ids[2], "parse_failed", db_path=str(db_path), failure_reason="seeded parse failure")
1055
+
1056
+ response = client.post(
1057
+ "/replay/raw",
1058
+ json={
1059
+ "db_path": str(db_path),
1060
+ "raw_envelope_ids": raw_envelope_ids,
1061
+ "threshold": 1,
1062
+ },
1063
+ )
1064
+ assert response.status_code == 200
1065
+ payload = response.json()
1066
+ assert payload["ok"] is True
1067
+ assert payload["attempted_raw_envelope_ids"] == raw_envelope_ids
1068
+ assert payload["event_count"] == 2
1069
+ assert payload["parse_failed"] == 1
1070
+
1071
+ parse_failed_row = get_raw_envelope_by_id(raw_envelope_ids[0], db_path=str(db_path))
1072
+ assert parse_failed_row is not None
1073
+ assert parse_failed_row["canonicalization_status"] == "canonicalized"
1074
+
1075
+ received_row = get_raw_envelope_by_id(raw_envelope_ids[1], db_path=str(db_path))
1076
+ assert received_row is not None
1077
+ assert received_row["canonicalization_status"] == "canonicalized"
1078
+
1079
+ still_failed_row = get_raw_envelope_by_id(raw_envelope_ids[2], db_path=str(db_path))
1080
+ assert still_failed_row is not None
1081
+ assert still_failed_row["canonicalization_status"] == "parse_failed"
1082
+ assert still_failed_row["failure_reason"] == "message_raw is empty and cannot be canonicalized"
1083
+
1084
+
1085
+ def test_replay_raw_endpoint_skips_non_replayable_statuses_without_force(tmp_path: Path) -> None:
1086
+ client = TestClient(app)
1087
+ db_path = tmp_path / "brainstem.sqlite3"
1088
+ init_db(str(db_path))
1089
+ (canonicalized_id,) = store_raw_envelopes(
1090
+ [
1091
+ RawInputEnvelope(
1092
+ tenant_id="client-a",
1093
+ source_type="syslog",
1094
+ timestamp="2026-03-22T00:00:01Z",
1095
+ message_raw="already canonicalized",
1096
+ host="fw-01",
1097
+ service="sshd",
1098
+ )
1099
+ ],
1100
+ db_path=str(db_path),
1101
+ )
1102
+ set_raw_envelope_status(canonicalized_id, "canonicalized", db_path=str(db_path))
1103
+
1104
+ skip = client.post(
1105
+ "/replay/raw",
1106
+ json={
1107
+ "db_path": str(db_path),
1108
+ "raw_envelope_ids": [canonicalized_id],
1109
+ "threshold": 1,
1110
+ },
1111
+ )
1112
+ assert skip.status_code == 200
1113
+ skipped_payload = skip.json()
1114
+ assert skipped_payload["attempted_raw_envelope_ids"] == []
1115
+ assert skipped_payload["event_count"] == 0
1116
+ assert skipped_payload["skipped"][0]["reason"] == "not_replayable"
1117
+
1118
+ force = client.post(
1119
+ "/replay/raw",
1120
+ json={
1121
+ "db_path": str(db_path),
1122
+ "raw_envelope_ids": [canonicalized_id],
1123
+ "threshold": 1,
1124
+ "force": True,
1125
+ "allowed_statuses": ["canonicalized"],
1126
+ },
1127
+ )
1128
+ assert force.status_code == 200
1129
+ force_payload = force.json()
1130
+ assert force_payload["attempted_raw_envelope_ids"] == [canonicalized_id]
1131
+ assert force_payload["event_count"] == 1