@simbimbo/brainstem 0.0.3 → 0.0.4
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/CHANGELOG.md +13 -0
- package/README.md +25 -0
- package/brainstem/__init__.py +1 -1
- package/brainstem/adapters.py +120 -0
- package/brainstem/api.py +391 -57
- package/brainstem/config.py +70 -0
- package/brainstem/ingest.py +411 -33
- package/brainstem/interesting.py +56 -1
- package/brainstem/listener.py +175 -0
- package/brainstem/models.py +1 -0
- package/brainstem/recurrence.py +38 -1
- package/brainstem/source_drivers.py +150 -0
- package/brainstem/storage.py +305 -12
- package/docs/README.md +94 -0
- package/docs/adapters.md +97 -401
- package/docs/api.md +223 -278
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/tests/test_adapters.py +94 -0
- package/tests/test_api.py +726 -0
- package/tests/test_canonicalization.py +8 -0
- package/tests/test_config.py +24 -0
- package/tests/test_file_ingest.py +77 -0
- package/tests/test_interesting.py +10 -0
- package/tests/test_listener.py +253 -0
- package/tests/test_recurrence.py +2 -0
- package/tests/test_source_drivers.py +95 -0
- package/tests/test_storage.py +101 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.4 — 2026-03-22
|
|
4
|
+
|
|
5
|
+
Release-prep alignment for the current intake foundation state.
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
- aligns packaging/runtime version metadata for the next release cut (`0.0.4` in `package.json`, `pyproject.toml`, and `brainstem.__version__`)
|
|
9
|
+
- updates README/API-facing documentation to call out only implemented runtime and listener surfaces
|
|
10
|
+
- keeps claims bounded to implemented behavior; no new feature claims
|
|
11
|
+
|
|
12
|
+
### Notes
|
|
13
|
+
- no runtime behavior or interface changes in this release-prep pass
|
|
14
|
+
- no publish action performed
|
|
15
|
+
|
|
3
16
|
## 0.0.3 — 2026-03-22
|
|
4
17
|
|
|
5
18
|
Intake Foundation follow-up release for **brAInstem**.
|
package/README.md
CHANGED
|
@@ -48,6 +48,31 @@ For a given tenant or environment, brAInstem should answer:
|
|
|
48
48
|
- Have we seen this before?
|
|
49
49
|
- What happened right before the last similar incident?
|
|
50
50
|
|
|
51
|
+
## Current implemented runtime surface
|
|
52
|
+
|
|
53
|
+
Current implementation is intentionally bounded:
|
|
54
|
+
- FastAPI runtime at `brainstem.api:app`
|
|
55
|
+
- Implemented API endpoints include:
|
|
56
|
+
- `POST /ingest/event`, `POST /ingest/batch`, `POST /replay/raw`
|
|
57
|
+
- `GET /interesting`, `GET /candidates`, `GET /signatures`, `GET /canonical_events`
|
|
58
|
+
- `GET /stats`, `GET /failures`, `GET /failures/{id}`
|
|
59
|
+
- `GET /raw_envelopes`, `GET /ingest/recent`, `GET /sources`, `GET /sources/status`
|
|
60
|
+
- `GET /runtime`, `GET /status`, `GET /healthz`
|
|
61
|
+
- UDP syslog listener at `brainstem.listener`
|
|
62
|
+
- source-driver intake for `syslog` and `file`
|
|
63
|
+
- SQLite-backed persistence with default DB at `.brainstem-state/brainstem.sqlite3`
|
|
64
|
+
- optional API token in `BRAINSTEM_API_TOKEN`
|
|
65
|
+
|
|
66
|
+
Runtime config can also be inspected at `/runtime` and includes listener defaults (`syslog_host`, `syslog_port`, thresholds, limits).
|
|
67
|
+
|
|
68
|
+
## Quick run path (API + listener + file/syslog intake)
|
|
69
|
+
|
|
70
|
+
See the compact run surface in:
|
|
71
|
+
|
|
72
|
+
- [`docs/README.md`](docs/README.md)
|
|
73
|
+
|
|
74
|
+
It covers API startup, listener ingest, and file ingest end-to-end with only implemented entry points and current payload shapes.
|
|
75
|
+
|
|
51
76
|
## Relationship to ocmemog
|
|
52
77
|
|
|
53
78
|
Shared DNA:
|
package/brainstem/__init__.py
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from .models import RawInputEnvelope
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class RawInputAdapter(Protocol):
|
|
12
|
+
"""Contract for source-specific envelope producers."""
|
|
13
|
+
|
|
14
|
+
source_type: str
|
|
15
|
+
|
|
16
|
+
def parse_raw_input(
|
|
17
|
+
self,
|
|
18
|
+
payload: Any,
|
|
19
|
+
*,
|
|
20
|
+
tenant_id: str,
|
|
21
|
+
source_path: str = "",
|
|
22
|
+
) -> RawInputEnvelope:
|
|
23
|
+
"""Build a RawInputEnvelope from source payload."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_ADAPTER_REGISTRY: Dict[str, RawInputAdapter] = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_raw_input_adapter(adapter: RawInputAdapter) -> None:
|
|
30
|
+
"""Register/replace an adapter for a source type."""
|
|
31
|
+
|
|
32
|
+
_ADAPTER_REGISTRY[adapter.source_type] = adapter
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_raw_input_adapter(source_type: str) -> RawInputAdapter:
|
|
36
|
+
"""Return the adapter for `source_type`."""
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
return _ADAPTER_REGISTRY[source_type]
|
|
40
|
+
except KeyError as exc:
|
|
41
|
+
raise ValueError(f"unsupported source_type: {source_type}") from exc
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def list_raw_input_source_types() -> list[str]:
|
|
45
|
+
"""Return currently registered source types (inspectable list)."""
|
|
46
|
+
|
|
47
|
+
return sorted(_ADAPTER_REGISTRY.keys())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_raw_input_envelope(
|
|
51
|
+
source_type: str,
|
|
52
|
+
payload: Any,
|
|
53
|
+
*,
|
|
54
|
+
tenant_id: str,
|
|
55
|
+
source_path: str = "",
|
|
56
|
+
) -> RawInputEnvelope:
|
|
57
|
+
"""Build a RawInputEnvelope using the source-type registry."""
|
|
58
|
+
|
|
59
|
+
adapter = get_raw_input_adapter(source_type)
|
|
60
|
+
return adapter.parse_raw_input(payload, tenant_id=tenant_id, source_path=source_path)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class SyslogRawInputAdapter:
|
|
65
|
+
"""Simple adapter for syslog-like line-oriented payloads."""
|
|
66
|
+
|
|
67
|
+
source_type: str = "syslog"
|
|
68
|
+
|
|
69
|
+
def parse_raw_input(self, payload: Any, *, tenant_id: str, source_path: str = "") -> RawInputEnvelope:
|
|
70
|
+
text = ("" if payload is None else str(payload)).rstrip("\n")
|
|
71
|
+
timestamp = datetime.utcnow().isoformat() + "Z"
|
|
72
|
+
host = ""
|
|
73
|
+
service = ""
|
|
74
|
+
message = text
|
|
75
|
+
|
|
76
|
+
parts = text.split()
|
|
77
|
+
if len(parts) >= 5:
|
|
78
|
+
host = parts[3]
|
|
79
|
+
rest = " ".join(parts[4:])
|
|
80
|
+
if ":" in rest:
|
|
81
|
+
svc, _, msg = rest.partition(":")
|
|
82
|
+
service = svc.strip()
|
|
83
|
+
message = msg.strip() or rest.strip()
|
|
84
|
+
else:
|
|
85
|
+
message = rest.strip()
|
|
86
|
+
|
|
87
|
+
return RawInputEnvelope(
|
|
88
|
+
tenant_id=tenant_id,
|
|
89
|
+
source_type=self.source_type,
|
|
90
|
+
timestamp=timestamp,
|
|
91
|
+
message_raw=message,
|
|
92
|
+
host=host,
|
|
93
|
+
service=service,
|
|
94
|
+
source_path=source_path,
|
|
95
|
+
metadata={"raw_line": text},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class FileRawInputAdapter:
|
|
101
|
+
"""Simple adapter for generic line-oriented log files."""
|
|
102
|
+
|
|
103
|
+
source_type: str = "file"
|
|
104
|
+
|
|
105
|
+
def parse_raw_input(self, payload: Any, *, tenant_id: str, source_path: str = "") -> RawInputEnvelope:
|
|
106
|
+
text = "" if payload is None else str(payload)
|
|
107
|
+
timestamp = datetime.utcnow().isoformat() + "Z"
|
|
108
|
+
|
|
109
|
+
return RawInputEnvelope(
|
|
110
|
+
tenant_id=tenant_id,
|
|
111
|
+
source_type=self.source_type,
|
|
112
|
+
timestamp=timestamp,
|
|
113
|
+
message_raw=text.rstrip("\n"),
|
|
114
|
+
source_path=source_path,
|
|
115
|
+
metadata={"raw_line": text},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
register_raw_input_adapter(SyslogRawInputAdapter())
|
|
120
|
+
register_raw_input_adapter(FileRawInputAdapter())
|