All checks were successful
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
447 lines
18 KiB
Python
447 lines
18 KiB
Python
"""Pose, replay, and conflict ledger with injected transport.
|
|
|
|
Not imported by the API runtime. The private dispatcher composes this ledger
|
|
under a process lock; dev tests retain the injectable adapter. It never reads a token
|
|
file, mints a credential, or dials a default host. Tests inject the transport
|
|
and, when a pose should carry a header, a token callable. The callable's
|
|
value is not retained.
|
|
|
|
Choosing the workload identity that projects an intake-audience token, and
|
|
reviewing that identity into the submitter registry, is an owner step. This
|
|
fixture does not name or admit an account.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sqlite3
|
|
import time
|
|
from contextlib import contextmanager
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Callable, Iterator
|
|
|
|
from api.services.informed_decision_contract import (
|
|
PROVENANCE_NOTE,
|
|
ContractError,
|
|
bounded_receipt,
|
|
correlate_resolution,
|
|
resolution_applies,
|
|
safe_error_code,
|
|
submission_digest,
|
|
validate_envelope,
|
|
)
|
|
|
|
Transport = Callable[[str, str, dict, dict], tuple[int, dict]]
|
|
TokenReader = Callable[[], str]
|
|
|
|
_RETRYABLE = frozenset({408, 429})
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ForwardResult:
|
|
status: str
|
|
idempotency_key: str
|
|
receipt: dict | None
|
|
drafted_by: str | None
|
|
drafted_by_provenance: str
|
|
provenance_note: str
|
|
detail: str = ""
|
|
|
|
|
|
class MemoForwarder:
|
|
def __init__(
|
|
self,
|
|
path: str | Path,
|
|
*,
|
|
transport: Transport,
|
|
intake_base_url: str,
|
|
hub_base_url: str,
|
|
token_reader: TokenReader | None = None,
|
|
clock: Callable[[], float] = time.time,
|
|
) -> None:
|
|
self.path = str(path)
|
|
self.transport = transport
|
|
self.intake_base_url = _base_url(intake_base_url)
|
|
self.hub_base_url = _base_url(hub_base_url)
|
|
self.token_reader = token_reader
|
|
self.clock = clock
|
|
Path(self.path).parent.mkdir(parents=True, exist_ok=True)
|
|
with self._connect() as db:
|
|
db.executescript(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS pose_ledger (
|
|
idempotency_key TEXT PRIMARY KEY,
|
|
submission_digest TEXT NOT NULL,
|
|
envelope_json TEXT NOT NULL,
|
|
drafted_by TEXT,
|
|
drafted_by_provenance TEXT NOT NULL
|
|
CHECK (drafted_by_provenance IN ('asserted', 'absent')),
|
|
provenance_note TEXT NOT NULL,
|
|
state TEXT NOT NULL
|
|
CHECK (state IN ('pending', 'delivered', 'blocked')),
|
|
receipt_json TEXT,
|
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
next_attempt REAL NOT NULL DEFAULT 0,
|
|
last_error TEXT,
|
|
blocked_reason TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS resolution_ledger (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
decision_id TEXT NOT NULL,
|
|
memo_id TEXT NOT NULL,
|
|
verb TEXT NOT NULL,
|
|
resolves TEXT,
|
|
status TEXT NOT NULL,
|
|
error_type TEXT,
|
|
payload_json TEXT,
|
|
next_attempt REAL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
"""
|
|
)
|
|
|
|
def submit(self, envelope: dict, *, dispatch: bool = True) -> ForwardResult:
|
|
checked = validate_envelope(envelope)
|
|
key = checked["idempotency_key"]
|
|
body_digest = submission_digest(checked)
|
|
drafted_by = checked.get("drafted_by")
|
|
provenance = "asserted" if drafted_by else "absent"
|
|
now = self.clock()
|
|
early: tuple[str, str] | None = None
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"SELECT * FROM pose_ledger WHERE idempotency_key=?", (key,)
|
|
).fetchone()
|
|
if row is None:
|
|
db.execute(
|
|
"""INSERT INTO pose_ledger (
|
|
idempotency_key, submission_digest, envelope_json, drafted_by,
|
|
drafted_by_provenance, provenance_note, state, attempts,
|
|
next_attempt, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, 0, ?, ?)""",
|
|
(
|
|
key, body_digest, _dump(checked), drafted_by, provenance,
|
|
PROVENANCE_NOTE, _iso(now), _iso(now),
|
|
),
|
|
)
|
|
elif row["submission_digest"] != body_digest:
|
|
db.execute(
|
|
"""UPDATE pose_ledger SET blocked_reason='idempotency_conflict',
|
|
updated_at=? WHERE idempotency_key=?""",
|
|
(_iso(now), key),
|
|
)
|
|
if row["state"] != "delivered":
|
|
db.execute(
|
|
"UPDATE pose_ledger SET state='blocked' WHERE idempotency_key=?",
|
|
(key,),
|
|
)
|
|
early = ("blocked", "idempotency_conflict")
|
|
elif row["state"] == "delivered":
|
|
early = ("replay", "replay")
|
|
elif row["state"] == "blocked":
|
|
early = ("blocked", row["blocked_reason"] or "blocked")
|
|
elif row["next_attempt"] > now:
|
|
early = ("pending", row["last_error"] or "waiting")
|
|
if early is not None:
|
|
return self._result(key, *early)
|
|
if not dispatch:
|
|
return self._result(key, "pending", "queued")
|
|
return self._attempt(key)
|
|
|
|
def run_due(self) -> ForwardResult | None:
|
|
now = self.clock()
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"""SELECT idempotency_key FROM pose_ledger
|
|
WHERE state='pending' AND next_attempt<=?
|
|
ORDER BY created_at LIMIT 1""",
|
|
(now,),
|
|
).fetchone()
|
|
key = None if row is None else row["idempotency_key"]
|
|
if key is None:
|
|
return None
|
|
return self._attempt(key)
|
|
|
|
def pose(self, key: str) -> dict | None:
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"SELECT * FROM pose_ledger WHERE idempotency_key=?", (key,)
|
|
).fetchone()
|
|
return dict(row) if row is not None else None
|
|
|
|
def observe_resolution(
|
|
self, *, decision_id: str, memo_id: str, verb: str, serves: dict | None, payload: dict | None
|
|
) -> str:
|
|
resolves = serves.get("resolves") if isinstance(serves, dict) else None
|
|
if not resolution_applies(verb, serves):
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status="not_applicable", error_type=None, payload=None, retry_at=None,
|
|
)
|
|
return "not_applicable"
|
|
prior = self._latest_resolution(decision_id, memo_id)
|
|
if prior and prior["status"] in {"resolved", "already_resolved", "conflict"}:
|
|
return prior["status"]
|
|
if prior and prior["status"] == "failed":
|
|
if prior["next_attempt"] is not None and prior["next_attempt"] > self.clock():
|
|
return "failed"
|
|
if payload is None and prior["payload_json"]:
|
|
payload = json.loads(prior["payload_json"])
|
|
if not isinstance(payload, dict):
|
|
raise ValueError("resolution payload is required")
|
|
encoded = _dump(payload)
|
|
if len(encoded.encode("utf-8")) > 8192:
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status="conflict", error_type="payload_unbounded", payload=None, retry_at=None,
|
|
)
|
|
return "conflict"
|
|
try:
|
|
status, _body = self.transport(
|
|
"POST",
|
|
f"{self.hub_base_url}/decisions/{decision_id}/resolve",
|
|
payload,
|
|
{},
|
|
)
|
|
status = int(status)
|
|
except OSError as exc:
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status="failed", error_type=type(exc).__name__, payload=payload,
|
|
retry_at=self.clock() + _backoff(1),
|
|
)
|
|
return "failed"
|
|
except (TypeError, ValueError):
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status="failed", error_type="transport_invalid", payload=payload,
|
|
retry_at=self.clock() + _backoff(1),
|
|
)
|
|
return "failed"
|
|
existing = None
|
|
if status == 409:
|
|
try:
|
|
read_status, existing = self.transport(
|
|
"GET", f"{self.hub_base_url}/decisions/{decision_id}", {}, {}
|
|
)
|
|
if int(read_status) != 200 or not isinstance(existing, dict):
|
|
existing = None
|
|
raise OSError("resolution_unreadable")
|
|
except OSError as exc:
|
|
error = exc.__class__.__name__ if str(exc) != "resolution_unreadable" else "resolution_unreadable"
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status="failed", error_type=error, payload=payload,
|
|
retry_at=self.clock() + _backoff(1),
|
|
)
|
|
return "failed"
|
|
outcome = correlate_resolution(memo_id, post_status=status, existing=existing)
|
|
self._append_resolution(
|
|
decision_id=decision_id, memo_id=memo_id, verb=verb, resolves=resolves,
|
|
status=outcome, error_type=None if outcome != "failed" else f"http_{status}",
|
|
payload=payload, retry_at=None if outcome != "failed" else self.clock() + _backoff(1),
|
|
)
|
|
return outcome
|
|
|
|
def run_due_resolution(self) -> str | None:
|
|
now = self.clock()
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"""SELECT * FROM resolution_ledger
|
|
WHERE status='failed' AND next_attempt IS NOT NULL AND next_attempt<=?
|
|
ORDER BY id DESC LIMIT 1""",
|
|
(now,),
|
|
).fetchone()
|
|
pending = dict(row) if row is not None else None
|
|
if pending is None:
|
|
return None
|
|
payload = json.loads(pending["payload_json"]) if pending["payload_json"] else None
|
|
serves = {"hub_decision_id": pending["decision_id"], "resolves": pending["resolves"]}
|
|
return self.observe_resolution(
|
|
decision_id=pending["decision_id"],
|
|
memo_id=pending["memo_id"],
|
|
verb=pending["verb"],
|
|
serves=serves,
|
|
payload=payload,
|
|
)
|
|
|
|
def resolutions(self) -> list[dict]:
|
|
with self._connect() as db:
|
|
rows = db.execute(
|
|
"SELECT * FROM resolution_ledger ORDER BY id"
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|
|
|
|
def _attempt(self, key: str) -> ForwardResult:
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"SELECT * FROM pose_ledger WHERE idempotency_key=?", (key,)
|
|
).fetchone()
|
|
current = dict(row) if row is not None else None
|
|
if current is None or current["state"] != "pending":
|
|
return self._result(key, current["state"] if current else "blocked", "not_pending")
|
|
envelope = json.loads(current["envelope_json"])
|
|
token, token_error = self._read_token()
|
|
if token_error:
|
|
self._mark_retry(key, token_error)
|
|
return self._result(key, "pending", token_error)
|
|
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
try:
|
|
status, body = self.transport(
|
|
"POST", f"{self.intake_base_url}/v1/memos", envelope, headers
|
|
)
|
|
status = int(status)
|
|
except OSError as exc:
|
|
self._mark_retry(key, type(exc).__name__)
|
|
return self._result(key, "pending", type(exc).__name__)
|
|
except (TypeError, ValueError):
|
|
self._mark_retry(key, "transport_invalid")
|
|
return self._result(key, "pending", "transport_invalid")
|
|
if status in (200, 201):
|
|
try:
|
|
receipt = bounded_receipt(body, envelope["memo"]["id"])
|
|
except ContractError as exc:
|
|
code = safe_error_code(exc.code, "receipt_mismatch")
|
|
self._mark_blocked(key, code)
|
|
return self._result(key, "blocked", code)
|
|
self._mark_delivered(key, receipt)
|
|
return self._result(key, "delivered", "delivered")
|
|
if status == 409 or (status not in _RETRYABLE and 400 <= status < 500):
|
|
code = safe_error_code(
|
|
body.get("error") if isinstance(body, dict) else None,
|
|
"conflict" if status == 409 else f"http_{status}",
|
|
)
|
|
self._mark_blocked(key, code)
|
|
return self._result(key, "blocked", code)
|
|
self._mark_retry(key, f"http_{status}")
|
|
return self._result(key, "pending", f"http_{status}")
|
|
|
|
def _read_token(self) -> tuple[str | None, str | None]:
|
|
if self.token_reader is None:
|
|
return None, "projected_token_absent"
|
|
try:
|
|
token = self.token_reader()
|
|
except Exception as exc:
|
|
return None, type(exc).__name__
|
|
if not isinstance(token, str) or not token.strip() or token != token.strip():
|
|
return None, "projected_token_absent"
|
|
if len(token) > 16384:
|
|
return None, "projected_token_absent"
|
|
return token, None
|
|
|
|
def _mark_retry(self, key: str, error: str) -> None:
|
|
now = self.clock()
|
|
with self._connect() as db:
|
|
attempts = db.execute(
|
|
"SELECT attempts FROM pose_ledger WHERE idempotency_key=?", (key,)
|
|
).fetchone()["attempts"] + 1
|
|
db.execute(
|
|
"""UPDATE pose_ledger SET state='pending', attempts=?, last_error=?,
|
|
next_attempt=?, updated_at=? WHERE idempotency_key=?""",
|
|
(attempts, error, now + _backoff(attempts), _iso(now), key),
|
|
)
|
|
|
|
def _mark_blocked(self, key: str, reason: str) -> None:
|
|
now = self.clock()
|
|
with self._connect() as db:
|
|
db.execute(
|
|
"""UPDATE pose_ledger SET state='blocked', blocked_reason=?, last_error=?,
|
|
updated_at=? WHERE idempotency_key=?""",
|
|
(reason, reason, _iso(now), key),
|
|
)
|
|
|
|
def _mark_delivered(self, key: str, receipt: dict) -> None:
|
|
now = self.clock()
|
|
with self._connect() as db:
|
|
db.execute(
|
|
"""UPDATE pose_ledger SET state='delivered', receipt_json=?, last_error=NULL,
|
|
blocked_reason=NULL, updated_at=? WHERE idempotency_key=?""",
|
|
(_dump(receipt), _iso(now), key),
|
|
)
|
|
|
|
def _result(self, key: str, status: str, detail: str) -> ForwardResult:
|
|
row = self.pose(key)
|
|
receipt = json.loads(row["receipt_json"]) if row and row["receipt_json"] else None
|
|
return ForwardResult(
|
|
status=status,
|
|
idempotency_key=key,
|
|
receipt=receipt,
|
|
drafted_by=row["drafted_by"] if row else None,
|
|
drafted_by_provenance=row["drafted_by_provenance"] if row else "absent",
|
|
provenance_note=row["provenance_note"] if row else PROVENANCE_NOTE,
|
|
detail=detail,
|
|
)
|
|
|
|
def _append_resolution(
|
|
self, *, decision_id, memo_id, verb, resolves, status, error_type, payload, retry_at
|
|
) -> None:
|
|
with self._connect() as db:
|
|
db.execute(
|
|
"""UPDATE resolution_ledger SET next_attempt=NULL
|
|
WHERE decision_id=? AND memo_id=? AND status='failed'""",
|
|
(decision_id, memo_id),
|
|
)
|
|
db.execute(
|
|
"""INSERT INTO resolution_ledger (
|
|
decision_id, memo_id, verb, resolves, status, error_type,
|
|
payload_json, next_attempt, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
(
|
|
decision_id, memo_id, verb, resolves, status, error_type,
|
|
_dump(payload) if payload is not None else None,
|
|
retry_at, _iso(self.clock()),
|
|
),
|
|
)
|
|
|
|
def _latest_resolution(self, decision_id: str, memo_id: str) -> dict | None:
|
|
with self._connect() as db:
|
|
row = db.execute(
|
|
"""SELECT * FROM resolution_ledger
|
|
WHERE decision_id=? AND memo_id=? ORDER BY id DESC LIMIT 1""",
|
|
(decision_id, memo_id),
|
|
).fetchone()
|
|
return dict(row) if row is not None else None
|
|
|
|
@contextmanager
|
|
def _connect(self) -> Iterator[sqlite3.Connection]:
|
|
db = sqlite3.connect(self.path)
|
|
db.row_factory = sqlite3.Row
|
|
try:
|
|
db.execute("PRAGMA journal_mode=WAL")
|
|
yield db
|
|
db.commit()
|
|
except Exception:
|
|
db.rollback()
|
|
raise
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def _base_url(value: str) -> str:
|
|
if (
|
|
not isinstance(value, str)
|
|
or not (value.startswith("http://") or value.startswith("https://"))
|
|
):
|
|
raise ValueError("base_url is required")
|
|
if "127.0.0.1" in value or "localhost" in value:
|
|
raise ValueError("base_url must be an injected test host")
|
|
return value.rstrip("/")
|
|
|
|
|
|
def _backoff(attempts: int) -> float:
|
|
return float(min(3600, 2 ** min(attempts, 11)))
|
|
|
|
|
|
def _dump(value: dict) -> str:
|
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
|
|
|
|
def _iso(timestamp: float) -> str:
|
|
return datetime.fromtimestamp(timestamp, timezone.utc).isoformat()
|
|
|
|
|
|
# Compatibility for the original injectable development fixture.
|
|
DevIntakeFixture = MemoForwarder
|