orchestrata_ultimate.refactor.codemod.observability.graph_writer
Reference › orchestrata_ultimate › refactor › codemod › observability › graph_writer
GraphWriterObserver records codemod runs in the graph database without slowing the operation. It buffers up to buffer_size records (100 by default) of two kinds: a CodemodRun built from each COMMIT_COMPLETED event and a CodemodPass built from each PASS_COMPLETED event. When a buffer is full the new record is dropped, a warning is logged and a counter increases (dropped_count), so the codemod itself is never blocked. flush writes the buffered records through a BoltSyncCodemodRepository from repository; pending_runs and pending_passes report what is waiting. The record types are in _types. The workspace builds it as one of its default observers.
At a glance
| Source | src/orchestrata_ultimate/refactor/codemod/observability/graph_writer.py |
| Lines of code | 148 |
| Classes | 1 |
| Functions | 0 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.refactor.codemod._types, orchestrata_ultimate.refactor.codemod.observability.events, orchestrata_ultimate.refactor.codemod.persistence.repository |
| Imported by | orchestrata_ultimate.refactor.codemod.observability, orchestrata_ultimate.refactor.codemod.workspace |
| Tests that import it | tests/refactor/codemod/test_observability.py |
Class diagram
Dependency diagram
API
Classes
GraphWriterObserver
Decorators: traces(spec='110', story='US-110-005', reqs=['REQ-110-026', 'REQ-110-028']) · Line 30
Batched, non-blocking graph database writer.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(repository: BoltSyncCodemodRepository | None = None, buffer_size: int = _DEFAULT_BUFFER_SIZE) -> None |
|
on_event |
on_event(event: CodemodEvent) -> None |
Buffer event for graph persistence; drop on overflow. |
flush |
flush() -> None |
Write all buffered records to the graph database. |
dropped_count property |
dropped_count() -> int |
Number of events dropped due to buffer overflow. |
pending_runs property |
pending_runs() -> int |
Number of CodemodRun records awaiting flush. |
pending_passes property |
pending_passes() -> int |
Number of CodemodPass records awaiting flush. |
Source
"""GraphWriterObserver — async writes to graph DB, batched, non-blocking.
SPEC-110 §15.2: Buffers events up to ``buffer_size`` and flushes them to
the graph database via ``BoltSyncCodemodRepository``. On buffer overflow,
events are dropped (non-blocking observer) — the codemod operation must
never stall waiting for a graph write.
"""
from __future__ import annotations
import logging
from collections import deque
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.refactor.codemod._types import CodemodPass, CodemodRun
from orchestrata_ultimate.refactor.codemod.observability.events import (
CodemodEvent,
CodemodEventType,
)
from orchestrata_ultimate.refactor.codemod.persistence.repository import (
BoltSyncCodemodRepository,
)
logger = logging.getLogger(__name__)
_DEFAULT_BUFFER_SIZE = 100
@traces(spec="110", story="US-110-005", reqs=["REQ-110-026", "REQ-110-028"])
class GraphWriterObserver:
"""Batched, non-blocking graph database writer.
Events are buffered up to ``buffer_size`` (default 100). When the buffer
is full, new events are **dropped** (not blocking the codemod operation).
A flush writes accumulated ``CodemodRun`` and ``CodemodPass`` records to
the graph database via ``BoltSyncCodemodRepository``.
The observer extracts persistence-relevant data from events:
- ``COMMIT_COMPLETED`` → ``CodemodRun`` record
- ``PASS_COMPLETED`` → ``CodemodPass`` record
"""
def __init__(
self,
repository: BoltSyncCodemodRepository | None = None,
buffer_size: int = _DEFAULT_BUFFER_SIZE,
) -> None:
self._repository = repository
self._buffer_size = buffer_size
self._runs: deque[CodemodRun] = deque(maxlen=buffer_size)
self._passes: deque[tuple[str, CodemodPass]] = deque(maxlen=buffer_size)
self._dropped: int = 0
def on_event(self, event: CodemodEvent) -> None:
"""Buffer *event* for graph persistence; drop on overflow."""
et = event.type
data = event.data
corr = event.correlation_id
if et == CodemodEventType.COMMIT_COMPLETED:
run = CodemodRun(
run_id=corr,
project=data.get("project", "unknown"),
started_at=data.get("started_at", ""),
completed_at=data.get("completed_at"),
status=data.get("status", "success"),
dry_run=data.get("dry_run", False),
pass_count=data.get("pass_count", 0),
finding_count=data.get("finding_count", 0),
file_count=data.get("file_count", 0),
duration_ms=int(data.get("duration_seconds", 0) * 1000),
)
if len(self._runs) >= self._buffer_size:
self._dropped += 1
logger.warning(
"GraphWriterObserver: dropped CodemodRun %s (buffer overflow, %d dropped)",
corr,
self._dropped,
)
return
self._runs.append(run)
elif et == CodemodEventType.PASS_COMPLETED:
pass_record = CodemodPass(
pass_id=f"{corr}:{data.get('pass_name', 'unknown')}",
run_id=corr,
pass_name=data.get("pass_name", "unknown"),
pass_type=data.get("pass_type", "transform"),
file_count=data.get("file_count", 0),
duration_ms=int(data.get("duration_seconds", 0) * 1000),
)
if len(self._passes) >= self._buffer_size:
self._dropped += 1
logger.warning(
"GraphWriterObserver: dropped CodemodPass %s (buffer overflow, %d dropped)",
pass_record.pass_id,
self._dropped,
)
return
self._passes.append((corr, pass_record))
def flush(self) -> None:
"""Write all buffered records to the graph database.
Errors are logged and swallowed — graph writes must never block
or fail the codemod operation (ERR-110-012).
"""
if self._repository is None:
logger.warning(
"GraphWriterObserver: no repository configured, discarding buffer"
)
self._runs.clear()
self._passes.clear()
return
while self._runs:
run = self._runs.popleft()
try:
self._repository.save_run(run)
except Exception:
logger.exception(
"GraphWriterObserver: failed to save CodemodRun %s", run.run_id
)
while self._passes:
run_id, pass_record = self._passes.popleft()
try:
self._repository.save_pass(run_id, pass_record)
except Exception:
logger.exception(
"GraphWriterObserver: failed to save CodemodPass %s",
pass_record.pass_id,
)
@property
def dropped_count(self) -> int:
"""Number of events dropped due to buffer overflow."""
return self._dropped
@property
def pending_runs(self) -> int:
"""Number of CodemodRun records awaiting flush."""
return len(self._runs)
@property
def pending_passes(self) -> int:
"""Number of CodemodPass records awaiting flush."""
return len(self._passes)