wip(k1): checkpoint connection recovery rewrite
Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
This commit is contained in:
@@ -82,6 +82,8 @@ class OperationRecord:
|
||||
result: dict[str, Any] | None = None
|
||||
error: dict[str, Any] | None = None
|
||||
evidence_refs: tuple[str, ...] = ()
|
||||
context: dict[str, Any] = field(default_factory=dict)
|
||||
events: list[dict[str, Any]] = field(default_factory=list)
|
||||
# A keyed, non-reversible digest supplied by the service. It is deliberately
|
||||
# excluded from API snapshots: callers only need mismatch detection, while
|
||||
# the journal must never retain action inputs or secret material.
|
||||
@@ -108,6 +110,8 @@ class OperationRecord:
|
||||
"result": dict(self.result) if self.result is not None else None,
|
||||
"error": dict(self.error) if self.error is not None else None,
|
||||
"evidence_refs": list(self.evidence_refs),
|
||||
"context": dict(self.context),
|
||||
"events": [dict(event) for event in self.events],
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +148,7 @@ class OperationJournal:
|
||||
deadline_seconds: float | None = None,
|
||||
cancellable: bool = False,
|
||||
request_fingerprint: str | None = None,
|
||||
context: Mapping[str, Any] | None = None,
|
||||
) -> tuple[OperationRecord, bool]:
|
||||
action = action.strip()
|
||||
if not action:
|
||||
@@ -189,7 +194,9 @@ class OperationJournal:
|
||||
),
|
||||
cancellable=cancellable,
|
||||
request_fingerprint=request_fingerprint,
|
||||
context=dict(context or {}),
|
||||
)
|
||||
self._append_event_locked(record)
|
||||
self._records[resolved_id] = record
|
||||
self._order.append(resolved_id)
|
||||
if idempotency_key is not None:
|
||||
@@ -225,6 +232,7 @@ class OperationJournal:
|
||||
if status in TERMINAL_OPERATION_STATUSES:
|
||||
record.completed_at = self._clock()
|
||||
self._trim_locked()
|
||||
self._append_event_locked(record)
|
||||
return record
|
||||
|
||||
def request_cancel(self, operation_id: str) -> OperationRecord:
|
||||
@@ -239,6 +247,7 @@ class OperationJournal:
|
||||
record.state_revision += 1
|
||||
record.stage_code = "cancellation-requested"
|
||||
record.message_code = "operation.cancellation_requested"
|
||||
self._append_event_locked(record)
|
||||
return record
|
||||
|
||||
def transition_if_pending(
|
||||
@@ -276,12 +285,32 @@ class OperationJournal:
|
||||
if status in TERMINAL_OPERATION_STATUSES:
|
||||
record.completed_at = self._clock()
|
||||
self._trim_locked()
|
||||
self._append_event_locked(record)
|
||||
return record
|
||||
|
||||
def get(self, operation_id: str) -> OperationRecord:
|
||||
with self._lock:
|
||||
return self._require_locked(operation_id)
|
||||
|
||||
def deadline_reached(self, operation_id: str | None) -> bool:
|
||||
"""Evaluate one operation deadline on the journal-owned server clock.
|
||||
|
||||
Device lifecycle reducers use this instead of browser timers or a
|
||||
second wall-clock source. Terminal records deliberately retain the
|
||||
same answer so an idempotent local-cleanup retry can continue after the
|
||||
operation outcome itself has already been sealed.
|
||||
"""
|
||||
|
||||
if operation_id is None:
|
||||
return False
|
||||
with self._lock:
|
||||
record = self._records.get(operation_id)
|
||||
return bool(
|
||||
record is not None
|
||||
and record.deadline_at is not None
|
||||
and self._clock() >= record.deadline_at
|
||||
)
|
||||
|
||||
def latest(self) -> OperationRecord | None:
|
||||
with self._lock:
|
||||
return self._records[self._order[-1]] if self._order else None
|
||||
@@ -298,6 +327,32 @@ class OperationJournal:
|
||||
except KeyError as exc:
|
||||
raise KeyError(f"unknown operation: {operation_id}") from exc
|
||||
|
||||
def _append_event_locked(self, record: OperationRecord) -> None:
|
||||
"""Append one bounded, secret-free stage fact for operator diagnosis."""
|
||||
|
||||
error = record.error or {}
|
||||
result = record.result or {}
|
||||
side_effect_status = error.get("side_effect_status", result.get("side_effect_status"))
|
||||
event: dict[str, Any] = {
|
||||
"schema_version": "missioncore.operation-event/v1",
|
||||
"sequence": record.sequence,
|
||||
"status": record.status,
|
||||
"stage_code": record.stage_code,
|
||||
"message_code": record.message_code,
|
||||
"observed_at": _iso(self._clock()),
|
||||
"side_effect_status": (
|
||||
side_effect_status if isinstance(side_effect_status, str) else None
|
||||
),
|
||||
"error_code": error.get("code") if isinstance(error.get("code"), str) else None,
|
||||
"safe_to_retry": (
|
||||
error.get("safe_to_retry")
|
||||
if isinstance(error.get("safe_to_retry"), bool)
|
||||
else None
|
||||
),
|
||||
"automatic_retry": False,
|
||||
}
|
||||
record.events.append(event)
|
||||
|
||||
def _trim_locked(self) -> None:
|
||||
while len(self._order) > self._max_records:
|
||||
oldest_id = next(
|
||||
|
||||
Reference in New Issue
Block a user