Retain durable reservations until provider termination or explicit recovery, protect unresolved pools and credentials, and add scoped reconciliation. Consolidate lifecycle and secret policies and split prompt/media stages with regression coverage.
58 lines
2.4 KiB
Python
58 lines
2.4 KiB
Python
"""Private, atomic job-ID journal. Contains identifiers/status only, never payloads."""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
import threading
|
|
|
|
from .serverless_state import TERMINAL
|
|
FIELDS = {"record_id", "execution_id", "worker_id", "participant_id", "endpoint_id", "job_id", "status", "created_at", "updated_at", "error", "prompt_id"}
|
|
|
|
|
|
class JobLedger:
|
|
def __init__(self, path):
|
|
self.path = Path(path)
|
|
self._lock = threading.RLock()
|
|
|
|
def _read(self):
|
|
if self.path.is_symlink():
|
|
raise RuntimeError("Unsafe serverless job journal path")
|
|
if not self.path.exists():
|
|
return {}
|
|
try:
|
|
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
if not isinstance(data, dict) or any(not isinstance(v, dict) or set(v) - FIELDS for v in data.values()):
|
|
raise ValueError()
|
|
return data
|
|
except (ValueError, OSError):
|
|
raise RuntimeError("Serverless job journal is unreadable; preserve it for recovery before submitting more work") from None
|
|
|
|
def all(self):
|
|
with self._lock:
|
|
return list(self._read().values())
|
|
|
|
def update(self, record_id, **fields):
|
|
if set(fields) - FIELDS:
|
|
raise ValueError("Unexpected serverless journal field")
|
|
with self._lock:
|
|
data = self._read()
|
|
data.setdefault(record_id, {"record_id": record_id}).update(fields)
|
|
# Keep every unresolved job, plus a bounded terminal history.
|
|
completed = sorted((k for k, v in data.items() if v.get("status") in TERMINAL), key=lambda k: data[k].get("updated_at", 0))
|
|
for key in completed[:-200]:
|
|
data.pop(key)
|
|
if self.path.parent.is_symlink():
|
|
raise RuntimeError("Unsafe serverless journal directory")
|
|
self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
fd, temporary = tempfile.mkstemp(prefix=".jobs-", dir=self.path.parent)
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8") as stream:
|
|
json.dump(data, stream, allow_nan=False)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
os.replace(temporary, self.path)
|
|
finally:
|
|
if os.path.exists(temporary):
|
|
os.unlink(temporary)
|
|
return dict(data[record_id])
|