Once the orchestrator started seeing fleet + SWARM shard sources via
list_running_deckies (a844148), every event row landing on a fleet decky
broke the FK to topology_deckies — the column now carries opaque ids
("local:omega-decky" for fleet, "host_uuid:decky_name" for shards) that
will never match topology_deckies.uuid.
Symptom on the operator's mothership:
IntegrityError 1452 — orchestrator_events_ibfk_2 FK violated on every
tick once the reconciler populated fleet_deckies.
Index on dst_decky_uuid is preserved (the dashboard reads
"events for this decky" frequently); only the FK is removed. Keeps
data integrity loose by design — events are append-only history that
should outlive the deckies they reference.
Existing MySQL deployments need the FK dropped manually:
ALTER TABLE orchestrator_events
DROP FOREIGN KEY orchestrator_events_ibfk_2,
DROP FOREIGN KEY orchestrator_events_ibfk_1;
SQLite users are unaffected — SQLite doesn't enforce FKs by default.
63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
"""Orchestrator-emitted activity events.
|
|
|
|
Purpose-built sibling to ``logs.Log`` so attacker-originated events stay
|
|
cleanly separable from synthetic life-injection events at query time.
|
|
The orchestrator worker is the sole writer.
|
|
"""
|
|
from datetime import datetime, timezone
|
|
from typing import Any, List, Optional
|
|
from uuid import uuid4
|
|
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import Column, Index, Text
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
|
|
class OrchestratorEvent(SQLModel, table=True):
|
|
"""One orchestrator-driven action against a decky.
|
|
|
|
``kind`` discriminates the two MVP flavours:
|
|
|
|
* ``"traffic"`` — a protocol-driven interaction (SSH command exec for
|
|
MVP). ``src_decky_uuid`` is the *logical* originator and may differ
|
|
from the actual TCP source for the duration of the MVP, where the
|
|
orchestrator process drives the connection from the host. ``v1``
|
|
will execute the connection from inside the source container.
|
|
* ``"file"`` — a filesystem touch via ``docker exec`` against the
|
|
destination decky. ``src_decky_uuid`` is null.
|
|
|
|
``payload`` is the per-action JSON envelope: command run, exit code,
|
|
stdout/stderr digest, file path, byte counts, etc. Schema is
|
|
deliberately loose — the worker can extend it without a migration.
|
|
"""
|
|
__tablename__ = "orchestrator_events"
|
|
__table_args__ = (
|
|
Index("ix_orchestrator_events_dst_ts", "dst_decky_uuid", "ts"),
|
|
)
|
|
uuid: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
|
|
ts: datetime = Field(
|
|
default_factory=lambda: datetime.now(timezone.utc), index=True
|
|
)
|
|
kind: str = Field(index=True, max_length=16) # traffic|file
|
|
protocol: str = Field(index=True, max_length=16) # ssh for MVP
|
|
action: str = Field(max_length=64) # exec:uptime|file:create|...
|
|
# No FK to topology_deckies: dst/src may be a TopologyDecky.uuid
|
|
# (MazeNET source), a "host_uuid:name" composite (fleet / SWARM shard
|
|
# sources), or — for retired deckies — a row that's already gone. The
|
|
# column is an opaque identifier matching whatever
|
|
# ``BaseRepository.list_running_deckies`` emits in its ``uuid`` field.
|
|
# Index is kept; the FK was misleading and broke fleet-source events.
|
|
src_decky_uuid: Optional[str] = Field(default=None, index=True)
|
|
dst_decky_uuid: str = Field(index=True)
|
|
success: bool = Field(default=False, index=True)
|
|
payload: str = Field(
|
|
sa_column=Column("payload", Text, nullable=False, default="{}")
|
|
)
|
|
|
|
|
|
class OrchestratorEventsResponse(BaseModel):
|
|
total: int
|
|
limit: int
|
|
offset: int
|
|
data: List[dict[str, Any]]
|