- 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.
186 lines
7.3 KiB
Python
186 lines
7.3 KiB
Python
"""
|
|
AUTH.3E — delete_executor_core.py
|
|
==================================
|
|
DELETE-Executor-Logik (DELETE-only, AUTH.3D-Validierung).
|
|
|
|
Eigenschaften:
|
|
* DELETE-only: claimt NUR C5_DELETE_OBJECT-Jobs
|
|
* Der DELETE-Job selbst ist KEINE Approval
|
|
* AUTH.3D-Composition: VALID_JOB AND VALID_STATE AND VALID_AUTH3D_SIGNATURE
|
|
AND VALID_NONCE AND VALID_EXPIRY AND NOT_CONSUMED AND VALID_OBJECT AND
|
|
VALID_PATH AND VALID_COMMIT AND VALID_PROVENANCE AND DELETE_CREDENTIAL
|
|
* Kein Faktor ersetzt einen anderen
|
|
* Fail-closed: kein HTTP ohne DELETE-Credential
|
|
* OUTCOME_UNKNOWN bei unklarem HTTP-Ergebnis (kein blinder Retry)
|
|
* Ein gestarteter DELETE wird bei Lease-Expiry NICHT blind wiederholt
|
|
|
|
Isoliert implementiert (KEIN produktiver Container). Nutzt job_store + job_schema.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Dict, Optional
|
|
|
|
from job_schema import JOB_TYPE_DELETE
|
|
from job_store import JobStore
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Result-Codes
|
|
# ---------------------------------------------------------------------------
|
|
RC_OK = "OK"
|
|
RC_CREDENTIAL_MISSING = "CREDENTIAL_MISSING"
|
|
RC_APPROVAL_MISSING = "APPROVAL_MISSING"
|
|
RC_APPROVAL_INVALID = "APPROVAL_INVALID"
|
|
RC_APPROVAL_EXPIRED = "APPROVAL_EXPIRED"
|
|
RC_APPROVAL_CONSUMED = "APPROVAL_CONSUMED"
|
|
RC_APPROVAL_MISMATCH = "APPROVAL_MISMATCH"
|
|
RC_OBJECT_MISMATCH = "OBJECT_MISMATCH"
|
|
RC_PATH_MISMATCH = "PATH_MISMATCH"
|
|
RC_COMMIT_MISMATCH = "COMMIT_MISMATCH"
|
|
RC_PROVENANCE_MISMATCH = "PROVENANCE_MISMATCH"
|
|
RC_MISSION_MISMATCH = "MISSION_MISMATCH"
|
|
RC_DELETE_DENIED = "DELETE_DENIED"
|
|
RC_OUTCOME_UNKNOWN = "OUTCOME_UNKNOWN"
|
|
RC_REJECTED = "REJECTED"
|
|
|
|
|
|
class DeleteExecutorError(Exception):
|
|
pass
|
|
|
|
|
|
class DeleteExecutorCore:
|
|
"""
|
|
DELETE-Executor. worker_scope="DELETE" (nur DELETE-Jobs claimbar).
|
|
|
|
approval_loader: Callable[[str], Optional[Dict[str, Any]]] — lädt die
|
|
AUTH.3D-Approval (approval_id) -> dict oder None.
|
|
approval_verify: Callable[[Dict[str, Any]], Dict[str, Any]] — verifiziert die
|
|
AUTH.3D-Signatur (Ed25519 PUBLIC KEY ONLY) -> {"valid": bool, "reason": str}.
|
|
tolaria_delete: Callable[[str], Dict[str, Any]] — führt den Tolaria-DELETE aus
|
|
(vault_path) -> {"status": "ok"|"error", "uncertain": bool}.
|
|
Muss fail-closed sein (kein HTTP ohne Credential).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
store: JobStore,
|
|
approval_loader: Callable[[str], Optional[Dict[str, Any]]],
|
|
approval_verify: Callable[[Dict[str, Any]], Dict[str, Any]],
|
|
tolaria_delete: Callable[[str], Dict[str, Any]],
|
|
):
|
|
if store.worker_scope != "DELETE":
|
|
raise DeleteExecutorError("DeleteExecutorCore requires worker_scope='DELETE'")
|
|
self.store = store
|
|
self.approval_loader = approval_loader
|
|
self.approval_verify = approval_verify
|
|
self.tolaria_delete = tolaria_delete
|
|
|
|
# -- Hauptverarbeitung --------------------------------------------------
|
|
|
|
def process_job(self, job_id: str, worker_id: str) -> Dict[str, Any]:
|
|
"""
|
|
Verarbeitet einen DELETE-Job durch die State Machine.
|
|
|
|
Ablauf:
|
|
1. Job laden (muss existieren)
|
|
2. Job-Type-Scope prüfen (nur DELETE)
|
|
3. READY -> CLAIMED (atomarer Claim)
|
|
4. CLAIMED -> EXECUTING
|
|
5. AUTH.3D-Composition validieren (Job + Approval exakt zusammenpassen)
|
|
6. Tolaria-DELETE ausführen
|
|
7. Ergebnis: SUCCEEDED / FAILED / OUTCOME_UNKNOWN / REJECTED
|
|
"""
|
|
job = self.store.get_job(job_id)
|
|
if job is None:
|
|
raise DeleteExecutorError(f"job not found: {job_id}")
|
|
|
|
# Scope: nur DELETE-Jobs
|
|
if job["job_type"] != JOB_TYPE_DELETE:
|
|
self.store.mark_rejected(job_id, RC_REJECTED)
|
|
return self.store.get_job(job_id)
|
|
|
|
# Atomarer Claim
|
|
try:
|
|
self.store.claim_job(job_id, worker_id)
|
|
except Exception:
|
|
# Nicht claimbar (bereits geclaimt) -> kein Doppel-Delete
|
|
return self.store.get_job(job_id)
|
|
|
|
self.store.begin_execution(job_id, worker_id)
|
|
|
|
# AUTH.3D-Composition (alle Faktoren, keiner ersetzt einen anderen).
|
|
# Job NACH dem Claim neu lesen, damit eine Payload-Manipulation nach
|
|
# dem Claim (TOCTOU) erkannt wird — Defense in depth.
|
|
job = self.store.get_job(job_id)
|
|
if job is None:
|
|
self.store.mark_failed(job_id, RC_DELETE_DENIED, worker_id)
|
|
return self.store.get_job(job_id)
|
|
gate = self._validate_auth3d_composition(job)
|
|
if gate["valid"] is not True:
|
|
self.store.mark_failed(job_id, gate["code"], worker_id)
|
|
return self.store.get_job(job_id)
|
|
|
|
# Tolaria-DELETE ausführen (fail-closed)
|
|
result = self.tolaria_delete(job["vault_path"])
|
|
status = result.get("status")
|
|
if status == "ok":
|
|
self.store.mark_succeeded(job_id, worker_id)
|
|
elif status == "error" and result.get("uncertain"):
|
|
self.store.mark_outcome_unknown(job_id, worker_id)
|
|
else:
|
|
self.store.mark_failed(job_id, result.get("code", RC_DELETE_DENIED), worker_id)
|
|
|
|
return self.store.get_job(job_id)
|
|
|
|
# -- AUTH.3D-Composition -------------------------------------------------
|
|
|
|
def _validate_auth3d_composition(self, job: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Validiert die AUTH.3D-Composition. Alle Faktoren müssen gelten:
|
|
VALID_JOB AND VALID_STATE AND VALID_AUTH3D_SIGNATURE AND VALID_NONCE
|
|
AND VALID_EXPIRY AND NOT_CONSUMED AND VALID_OBJECT AND VALID_PATH
|
|
AND VALID_COMMIT AND VALID_PROVENANCE AND DELETE_CREDENTIAL
|
|
|
|
Rückgabe: {"valid": True} oder {"valid": False, "code": RC_...}
|
|
"""
|
|
# VALID_JOB (Schema bereits beim Erzeugen validiert)
|
|
# VALID_STATE (EXECUTING — bereits durch begin_execution)
|
|
|
|
# Approval laden
|
|
approval = self.approval_loader(job["approval_id"])
|
|
if approval is None:
|
|
return {"valid": False, "code": RC_APPROVAL_MISSING}
|
|
|
|
# VALID_AUTH3D_SIGNATURE (Ed25519 PUBLIC KEY ONLY)
|
|
sig = self.approval_verify(approval)
|
|
if sig.get("valid") is not True:
|
|
return {"valid": False, "code": RC_APPROVAL_INVALID}
|
|
|
|
# VALID_NONCE / VALID_EXPIRY / NOT_CONSUMED
|
|
if approval.get("expired"):
|
|
return {"valid": False, "code": RC_APPROVAL_EXPIRED}
|
|
if approval.get("consumed"):
|
|
return {"valid": False, "code": RC_APPROVAL_CONSUMED}
|
|
|
|
# VALID_OBJECT
|
|
if approval.get("object_id") != job["object_id"]:
|
|
return {"valid": False, "code": RC_OBJECT_MISMATCH}
|
|
|
|
# VALID_PATH
|
|
if approval.get("vault_path") != job["vault_path"]:
|
|
return {"valid": False, "code": RC_PATH_MISMATCH}
|
|
|
|
# VALID_COMMIT
|
|
if approval.get("expected_commit") != job["expected_commit"]:
|
|
return {"valid": False, "code": RC_COMMIT_MISMATCH}
|
|
|
|
# VALID_PROVENANCE
|
|
if approval.get("expected_provenance_hash") != job["expected_provenance_hash"]:
|
|
return {"valid": False, "code": RC_PROVENANCE_MISMATCH}
|
|
|
|
# VALID_MISSION
|
|
if approval.get("mission_id") != job["mission_id"]:
|
|
return {"valid": False, "code": RC_MISSION_MISMATCH}
|
|
|
|
# DELETE_CREDENTIAL (fail-closed — tolaria_delete prüft selbst)
|
|
return {"valid": True}
|