railiance-platform/scripts/task_execution_cleanup.py
codex 59d1441cf8 fix(execution): retain scoped cleanup after uncertain outcomes
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 01:51:53 +02:00

72 lines
3.5 KiB
Python

"""Fixed cleanup driver for a trusted owner mediator; no native transport exists here.
Call from the mediator's finally block while its original Journal.locked context
is still live. The owner adapter retains credential/handle custody and reconstructs
and compares the native call with the expected digest before effect. Its three
methods take no caller-selected path, body, shell command or token. This source
component neither validates consume nor creates a native execution entitlement.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import re
from typing import Protocol
from task_execution_journal import Binding, Cleanup, Journal, Refused
@dataclass(frozen=True)
class CleanupResult:
confirmed: bool
evidence_digest: str
def validate(self):
if type(self.confirmed) is not bool or type(self.evidence_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", self.evidence_digest):
raise Refused("invalid owner cleanup evidence")
class OwnerCleanup(Protocol):
# Confirm requires actual native revoke/absence evidence; a timeout is never
# proof of absence. Implementations must use bounded calls and discard raw
# backend failure output. Protected handles never leave the owner adapter.
def revoke_task_token(self, expected_call_digest: str) -> CleanupResult: ...
def revoke_identity_token(self, expected_call_digest: str) -> CleanupResult: ...
def delete_grant_policy(self, expected_call_digest: str) -> CleanupResult: ...
def run_cleanup(journal: Journal, binding: Binding, owner: OwnerCleanup):
"""Attempt each fixed obligation once, preserving business/unknown outcomes.
Ordinary adapter failures record unconfirmed evidence and do not suppress the
remaining independent cleanup. BaseException/process death leaves durable
pending intent; neither this driver nor a reopened journal retries it.
Journal failures propagate and prohibit dispatch without durable intent.
"""
obligations = journal.cleanup_status(binding)['obligations']
if not obligations:
raise Refused("explicit owner cleanup obligations required")
if any(item['outcome'] != 'owed' for item in obligations):
raise Refused("cleanup already attempted; owner reconciliation required")
for item in obligations:
obligation = Cleanup(item['kind'], item['call_digest'])
journal.before_cleanup(binding, item['ordinal'], obligation)
try:
if obligation.kind == 'task-token-revoke':
result = owner.revoke_task_token(obligation.call_digest)
elif obligation.kind == 'identity-token-revoke':
result = owner.revoke_identity_token(obligation.call_digest)
elif obligation.kind == 'grant-policy-delete':
result = owner.delete_grant_policy(obligation.call_digest)
else:
raise Refused("unsupported cleanup obligation")
if type(result) is not CleanupResult:
raise Refused("typed owner cleanup evidence required")
result.validate()
except Exception:
# Closed metadata only. Never hash/log a backend exception, response,
# token, accessor or secret-bearing stdout/stderr.
result = CleanupResult(False, hashlib.sha256(b'owner-cleanup-unconfirmed/v1').hexdigest())
journal.after_cleanup(binding, item['ordinal'], obligation,
confirmed=result.confirmed, evidence_digest=result.evidence_digest)
return journal.cleanup_status(binding)