weed-sim / key_os /observer.py
tostido's picture
WEED-SIM: evolutionary genetics sandbox with embedded Observer Bus
ae853c1
Raw
History Blame Contribute Delete
5.83 kB
from __future__ import annotations
from dataclasses import asdict, dataclass, field, replace
from typing import Any, Literal, Protocol
from .models import utc_now
from .security import redact, stable_hash
SIGNAL_SCHEMA = "observer_bus.signal/v1"
INBOX_SCHEMA = "observer_bus.signal_inbox/v1"
STATE_SCHEMA = "observer_bus.signal_state/v1"
SignalLane = Literal[
"continuity",
"repo",
"tests",
"mcp",
"filesystem",
"runtime",
"space",
"security",
"workflow",
"custom",
]
ACK_STATES = {"new", "seen", "deferred", "handled", "blocked", "superseded"}
@dataclass(frozen=True)
class SignalRoute:
command: str = ""
path: str = ""
method: str = "GET"
target: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class SignalEvidence:
kind: str = "metadata"
items: list[dict[str, Any]] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return redact(asdict(self))
@dataclass(frozen=True)
class SignalSource:
adapter: str
fingerprint: str
host: str = "local"
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class SignalAck:
state: str = "new"
updated_at: str | None = None
note: str | None = None
def to_dict(self) -> dict[str, Any]:
state = self.state if self.state in ACK_STATES else "new"
return {"state": state, "updated_at": self.updated_at, "note": self.note}
@dataclass(frozen=True)
class SignalExpiry:
expires_at: str | None = None
stale_after_seconds: int | None = 86400
state: str = "fresh"
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class Signal:
id: str
lane: str
severity: str
priority: int
title: str
message: str
route: SignalRoute = field(default_factory=SignalRoute)
evidence: SignalEvidence = field(default_factory=SignalEvidence)
source: SignalSource = field(default_factory=lambda: SignalSource(adapter="unknown", fingerprint=""))
timestamp: str = field(default_factory=utc_now)
ack: SignalAck = field(default_factory=SignalAck)
expiry: SignalExpiry = field(default_factory=SignalExpiry)
tags: tuple[str, ...] = ()
@classmethod
def create(
cls,
lane: str,
severity: str,
title: str,
message: str,
*,
route: SignalRoute | None = None,
priority: int | None = None,
evidence: SignalEvidence | None = None,
source_adapter: str = "key_os",
source_fingerprint: str | None = None,
timestamp: str | None = None,
tags: tuple[str, ...] = (),
) -> "Signal":
if priority is None:
priority = {"error": 100, "warn": 80, "info": 50, "ok": 20}.get(severity, 50)
route = route or SignalRoute()
evidence = evidence or SignalEvidence()
identity = {
"lane": lane,
"severity": severity,
"title": title,
"message": message,
"route": route.to_dict(),
"evidence": evidence.to_dict(),
"source_adapter": source_adapter,
}
signal_id = "sig_" + stable_hash(identity)[:20]
fingerprint = source_fingerprint or stable_hash(identity)
kwargs = {}
if timestamp:
kwargs["timestamp"] = timestamp
return cls(
id=signal_id,
lane=lane,
severity=severity,
priority=max(0, min(100, int(priority))),
title=title,
message=message,
route=route,
evidence=evidence,
source=SignalSource(adapter=source_adapter, fingerprint=fingerprint),
tags=tags,
**kwargs
)
def with_ack(self, state: dict[str, Any] | None) -> "Signal":
if not state:
return self
ack_state = str(state.get("state") or "new")
if ack_state not in ACK_STATES:
ack_state = "new"
return replace(
self,
ack=SignalAck(
state=ack_state,
updated_at=state.get("updated_at"),
note=state.get("note"),
),
)
def to_dict(self) -> dict[str, Any]:
route = self.route.to_dict()
source = self.source.to_dict()
data = {
"schema": SIGNAL_SCHEMA,
"id": self.id,
"signal_id": self.id,
"lane": self.lane,
"severity": self.severity,
"priority": self.priority,
"title": self.title,
"message": self.message,
"evidence": self.evidence.to_dict(),
"source": source,
"source_fingerprint": source["fingerprint"],
"timestamp": self.timestamp,
"route": route,
"route_command": route["command"],
"route_path": route["path"],
"command": route["command"],
"path": route["path"],
"ack": self.ack.to_dict(),
"expiry": self.expiry.to_dict(),
"tags": list(self.tags),
"detail": self.evidence.metadata,
}
return redact(data)
@dataclass(frozen=True)
class AdapterStatus:
name: str
lane: str
ok: bool
message: str = ""
detail: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return redact(asdict(self))
class ObserverAdapter(Protocol):
name: str
lane: str
capabilities: tuple[str, ...]
def probe(self) -> AdapterStatus:
...
def collect(self) -> list[Signal]:
...
def fingerprint(self) -> str:
...