trading-system-docs/tolaria/c5-sync-service/job_state_machine.py
Red Queen 10761f52b2 AUTH.3E: Executor Command Channel + Runtime Boundary Contract
- job_schema: geschlossene Job-Type-Allowlist (C5_SAVE_OBJECT/C5_DELETE_OBJECT), Pfad-/Längen-Validierung
- job_state_machine: deterministische States (CREATED/READY/CLAIMED/EXECUTING/SUCCEEDED/FAILED)
- job_claim: atomare Claim-/Lease-/Recovery-Logik (kein TOCTOU)
- job_store: getrennte SQLite-Inbox-DBs (c5a_save.db/c5a_delete.db), delegiert an job_claim
- save_executor_core: SAVE-only, Content-Rekonstruktion, Provenance-Validierung
- delete_executor_core: DELETE-only, AUTH.3D-Composition, TOCTOU-Defense (Re-Read nach Claim)
- test_job_channel: T1-T40 + adversarial (72 Tests)
- test_job_channel_adversarial: adversarial + Substitution + DB-Manipulation
- sensitivity_proof_auth3e: Mutationen A-L (12/12 Invarianten PRESENT)
- AUTH3B/3C/3D/3E_DESIGN: autoritative Security-Dokumentation (e25 Reconciliation)

COMMAND != AUTHORIZATION. Kein generischer Dispatcher. RQ credential-free.
Keine produktive Mutation. Keine echten Credentials.
2026-08-27 10:03:32 +00:00

103 lines
3.7 KiB
Python

"""
AUTH.3E — job_state_machine.py
===============================
Deterministische Job-State-Machine für den Executor Command Channel.
States:
CREATED, READY, CLAIMED, EXECUTING, SUCCEEDED, FAILED, REJECTED, OUTCOME_UNKNOWN
Eigenschaften:
* Geschlossene Transition-Allowlist
* Ungültige Transition -> FAIL CLOSED (InvalidTransitionError)
* DELETE: KEIN RETRYABLE — ein gestarteter DELETE wird bei Lease-Expiry in
OUTCOME_UNKNOWN überführt, NICHT zurück zu READY (kein blinder Retry)
* SAVE: RETRYABLE nur bei transienten Fehlern (TolariaUnavailable)
Isoliert implementiert (KEIN produktiver Container).
"""
from __future__ import annotations
from typing import Dict, FrozenSet, Optional, Tuple
# ---------------------------------------------------------------------------
# States
# ---------------------------------------------------------------------------
ST_CREATED = "CREATED"
ST_READY = "READY"
ST_CLAIMED = "CLAIMED"
ST_EXECUTING = "EXECUTING"
ST_SUCCEEDED = "SUCCEEDED"
ST_FAILED = "FAILED"
ST_REJECTED = "REJECTED"
ST_OUTCOME_UNKNOWN = "OUTCOME_UNKNOWN"
ALL_STATES = frozenset({
ST_CREATED, ST_READY, ST_CLAIMED, ST_EXECUTING,
ST_SUCCEEDED, ST_FAILED, ST_REJECTED, ST_OUTCOME_UNKNOWN,
})
# ---------------------------------------------------------------------------
# Erlaubte Transitionen (geschlossene Allowlist)
# ---------------------------------------------------------------------------
# (from_state, to_state)
ALLOWED_TRANSITIONS: FrozenSet[Tuple[str, str]] = frozenset({
# CREATED
(ST_CREATED, ST_READY), # Schema validiert, Job akzeptiert
(ST_CREATED, ST_REJECTED), # Schema/Allowlist/Injection-Fehler
# READY
(ST_READY, ST_CLAIMED), # atomarer Claim
(ST_READY, ST_REJECTED), # Gate-Fehler vor Claim
# CLAIMED
(ST_CLAIMED, ST_EXECUTING), # Claim bestätigt
(ST_CLAIMED, ST_REJECTED), # Gate-Fehler nach Claim
# EXECUTING
(ST_EXECUTING, ST_SUCCEEDED), # bestätigter Erfolg
(ST_EXECUTING, ST_FAILED), # bestätigter Fehler, kein Mutationseffekt
(ST_EXECUTING, ST_OUTCOME_UNKNOWN), # unklares HTTP-Ergebnis
(ST_EXECUTING, ST_REJECTED), # Gate-Fehler während Execution
# OUTCOME_UNKNOWN (terminal für DELETE; SAVE kann via Human/Admin reaktiviert werden)
(ST_OUTCOME_UNKNOWN, ST_REJECTED), # expliziter Human-/Admin-Eingriff
})
# Terminal-States (keine weiteren Transitionen ohne expliziten Eingriff)
TERMINAL_STATES = frozenset({ST_SUCCEEDED, ST_FAILED, ST_REJECTED})
class InvalidTransitionError(ValueError):
"""Ungültige Transition -> FAIL CLOSED."""
def is_valid_transition(from_state: str, to_state: str) -> bool:
return (from_state, to_state) in ALLOWED_TRANSITIONS
def transition(from_state: str, to_state: str) -> str:
"""
Führt eine State-Transition aus. Wirft InvalidTransitionError bei ungültiger
Transition (FAIL CLOSED).
"""
if from_state not in ALL_STATES:
raise InvalidTransitionError(f"unknown from_state: {from_state!r}")
if to_state not in ALL_STATES:
raise InvalidTransitionError(f"unknown to_state: {to_state!r}")
if not is_valid_transition(from_state, to_state):
raise InvalidTransitionError(
f"invalid transition: {from_state} -> {to_state} (FAIL CLOSED)"
)
return to_state
def is_terminal(state: str) -> bool:
return state in TERMINAL_STATES
def is_retryable(state: str, job_type: str) -> bool:
"""
SAVE: RETRYABLE nur aus OUTCOME_UNKNOWN (transient) — via Human/Admin.
DELETE: NIE retryable — ein gestarteter DELETE wird nicht blind wiederholt.
"""
if job_type == "C5_DELETE_OBJECT":
return False
# SAVE
return state == ST_OUTCOME_UNKNOWN