trading-system-docs/tolaria/c5-sync-service/approval_state.py
Red Queen 9c8d239ae5 AUTH.3D: Human DELETE Approval Authenticity (Ed25519, verify-only)
- approval_payload: kanonischer, deterministischer Approval-Payload
- approval_signature: Ed25519 sign/verify (Christian=Private Key, Executor=Public Key only)
- approval_verifier: verify-only, alle Bindings fail-closed
- approval_state: Lifecycle CREATED->CONSUMED, Single-Use, Reservation, OUTCOME_UNKNOWN
- test_approval_auth3d: T1-T30 + adversarial (50 Tests)
- test_approval_helpers: synthetische Test-Keypairs
- sensitivity_proof: Mutationen A-J machen Tests ROT

Nur synthetische Test-Keypairs. Kein produktives Deployment.
DELETE_OPERATION_ACTIVATION bleibt BLOCKED bis AUTH.3D geprueft.
2026-08-27 09:36:21 +00:00

165 lines
5.9 KiB
Python

"""
AUTH.3D — approval_state.py
Approval Lifecycle + Single-Use + Reservation (TOCTOU/Race Safety).
Lifecycle: CREATED → VERIFIED → RESERVED → EXECUTED → CONSUMED
- RESERVED: atomare Reservation vor dem Tolaria-DELETE (verhindert Race).
- CONSUMED: endgültig, niemals erneut ausführbar.
- OUTCOME_UNKNOWN: kein blinder Retry; manuelle Klärung.
NUR isolierter Code + Tests. KEIN produktives Deployment.
"""
from __future__ import annotations
import threading
import uuid
from typing import Any, Dict, Optional
# Approval States
ST_CREATED = "CREATED"
ST_VERIFIED = "VERIFIED"
ST_RESERVED = "RESERVED"
ST_EXECUTED = "EXECUTED"
ST_CONSUMED = "CONSUMED"
ST_INVALID = "INVALID"
ST_EXPIRED = "EXPIRED"
ST_REVOKED = "REVOKED"
ST_FAILED = "FAILED"
ST_OUTCOME_UNKNOWN = "OUTCOME_UNKNOWN"
# Terminal (nicht mehr ausführbar) States
_TERMINAL = {ST_CONSUMED, ST_INVALID, ST_EXPIRED, ST_REVOKED, ST_FAILED}
# Übergänge, die eine Ausführung erlauben
_EXECUTABLE = {ST_CREATED, ST_VERIFIED, ST_RESERVED}
class ApprovalStateError(Exception):
"""Ungültiger State-Übergang (FAIL CLOSED)."""
class ApprovalStateStore:
"""
In-Memory State Store für isolierte Tests.
In Produktion würde dies die C5-DB (delete_approvals) erweitern.
Hier: reine, thread-sichere In-Memory-Implementierung für Tests.
"""
def __init__(self):
self._lock = threading.RLock()
self._approvals: Dict[str, Dict[str, Any]] = {}
self._used_nonces: set = set()
def create(self, approval_id: str, nonce: str) -> Dict[str, Any]:
with self._lock:
if approval_id in self._approvals:
raise ApprovalStateError(f"Approval {approval_id} existiert bereits")
rec = {
"approval_id": approval_id,
"nonce": nonce,
"state": ST_CREATED,
"attempt_id": None,
"idempotency_key": None,
"consumed_at": None,
"result": None,
}
self._approvals[approval_id] = rec
return dict(rec)
def get(self, approval_id: str) -> Optional[Dict[str, Any]]:
with self._lock:
rec = self._approvals.get(approval_id)
return dict(rec) if rec else None
def transition(self, approval_id: str, new_state: str, **fields) -> Dict[str, Any]:
with self._lock:
rec = self._approvals.get(approval_id)
if rec is None:
raise ApprovalStateError(f"Approval {approval_id} existiert nicht")
if new_state == ST_CONSUMED:
if rec["state"] not in _EXECUTABLE:
raise ApprovalStateError(
f"Approval {approval_id} in State {rec['state']} kann nicht CONSUMED werden"
)
rec["state"] = new_state
rec.update(fields)
if new_state == ST_CONSUMED:
rec["consumed_at"] = fields.get("consumed_at")
return dict(rec)
def reserve(
self, approval_id: str, attempt_id: str, idempotency_key: str
) -> Dict[str, Any]:
"""
Atomare Reservation: APPROVED/RESERVED → RESERVED (nur wenn noch ausführbar).
Verhindert TOCTOU-Race: nur EINE Reservation pro Approval.
"""
with self._lock:
rec = self._approvals.get(approval_id)
if rec is None:
raise ApprovalStateError(f"Approval {approval_id} existiert nicht")
if rec["state"] not in _EXECUTABLE:
raise ApprovalStateError(
f"Approval {approval_id} in State {rec['state']} kann nicht reserviert werden"
)
# Idempotenz: gleicher Attempt wird nicht doppelt reserviert.
if rec.get("idempotency_key") == idempotency_key:
return dict(rec)
# Bereits reserviert (anderer Attempt) -> kein zweiter Reservation.
if rec["state"] == ST_RESERVED:
raise ApprovalStateError(
f"Approval {approval_id} ist bereits reserviert (Attempt {rec.get('attempt_id')})"
)
rec["state"] = ST_RESERVED
rec["attempt_id"] = attempt_id
rec["idempotency_key"] = idempotency_key
return dict(rec)
def mark_used_nonce(self, nonce: str) -> None:
with self._lock:
self._used_nonces.add(nonce)
def is_nonce_used(self, nonce: str) -> bool:
with self._lock:
return nonce in self._used_nonces
def consume(self, approval_id: str, result: str) -> Dict[str, Any]:
"""Markiert als CONSUMED (endgültig)."""
return self.transition(approval_id, ST_CONSUMED, result=result)
def mark_outcome_unknown(self, approval_id: str) -> Dict[str, Any]:
"""Markiert als OUTCOME_UNKNOWN (kein blinder Retry)."""
return self.transition(approval_id, ST_OUTCOME_UNKNOWN)
def recover_from_unknown(self, approval_id: str, target_absent: bool) -> Dict[str, Any]:
"""
Recovery aus OUTCOME_UNKNOWN:
- target_absent=True -> CONSUMED (Delete war erfolgreich)
- target_absent=False -> zurück zu RESERVED (Delete nicht ausgeführt)
"""
with self._lock:
rec = self._approvals.get(approval_id)
if rec is None:
raise ApprovalStateError(f"Approval {approval_id} existiert nicht")
if rec["state"] != ST_OUTCOME_UNKNOWN:
raise ApprovalStateError(
f"Approval {approval_id} ist nicht OUTCOME_UNKNOWN (State: {rec['state']})"
)
if target_absent:
rec["state"] = ST_CONSUMED
rec["result"] = "DELETE_OK"
else:
rec["state"] = ST_RESERVED
rec["result"] = None
return dict(rec)
def new_attempt_id() -> str:
return str(uuid.uuid4())
def new_idempotency_key() -> str:
return str(uuid.uuid4())