Files
nisse/nisse-dev/src/nisse_dev/oplog.py
T
dongarandClaude Fable 5 18d4fcd701
CI / checks (push) Successful in 9s
CI / checks (pull_request) Successful in 8s
Implement A9 state-change logging in the state store
Retrofit for the conventions merged in #4 (issue #3):

- OpLog: JSON-lines emitter with the fixed envelope (ts, agent,
  event, task_id, cause); task_id set by the orchestrator at
  unit-of-work start
- StateStore owns emission: transition, intent begin/commit/abort,
  and schema migrations each emit their event right after the
  durable write commits; callers pass the cause
- reconcile_on_boot emits boot.reconcile with action and verdicts,
  and resolves intents with reconciliation-verdict causes
- Failed resolutions emit nothing (a line exists iff the write
  committed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 21:13:36 +00:00

34 lines
1.1 KiB
Python

"""Structured operational logging (spec A9).
One JSON object per line to stderr (journald captures it when running as a
service). Every event carries the fixed envelope: ts, agent, event, task_id,
cause. Logs are diagnostic, never load-bearing — nothing may read them back
to derive state.
"""
import json
import sys
from datetime import UTC, datetime
from typing import Any, TextIO
class OpLog:
def __init__(self, agent: str, stream: TextIO | None = None) -> None:
self.agent = agent
# Correlation ID for the enclosing unit of work; the orchestrator sets
# it when a unit starts and clears it when the unit ends (A9).
self.task_id: str | None = None
self._stream = stream if stream is not None else sys.stderr
def emit(self, event: str, *, cause: str, **fields: Any) -> None:
record: dict[str, Any] = {
"ts": datetime.now(UTC).isoformat(),
"agent": self.agent,
"event": event,
"task_id": self.task_id,
"cause": cause,
**fields,
}
self._stream.write(json.dumps(record) + "\n")
self._stream.flush()