73 lines
3.5 KiB
Python
73 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)
|