fix: recheck policy decision expiry after audit custody
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 3s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 15:56:52 +02:00
parent 72f3513954
commit f11b948e8c
7 changed files with 126 additions and 8 deletions

View file

@ -73,12 +73,15 @@ class Decision:
allowed: bool
decision_id: str
policy_version: str
valid_until: float
caller: str = ""
signed_envelope: str | None = None
def __post_init__(self):
if type(self.allowed) is not bool or not self.decision_id or not self.policy_version:
raise ValueError("explicit boolean decision and provenance required")
if type(self.valid_until) not in {int, float} or not math.isfinite(self.valid_until):
raise ValueError("finite decision deadline required")
class Identity(Protocol):
@ -182,6 +185,7 @@ class AccessController:
raise AccessFailure(403, "root_entitlement_required")
context = Authorization(actor, action, resource, facts, correlation_id, request_digest)
decision = await self._dependency("policy", lambda: self.policy.evaluate(context))
self._check_decision_deadline(decision)
await self.append_audit({
"profile": PROFILE, "correlation_id": correlation_id,
"issuer": actor.issuer, "subject": actor.subject,
@ -196,6 +200,7 @@ class AccessController:
})
if not decision.allowed:
raise AccessFailure(403, "policy_denied")
self._check_decision_deadline(decision)
if actor.expires_at <= time.time():
raise AccessFailure(401, "expired_access_token")
if time.time() - facts.checked_at > 5:
@ -204,6 +209,11 @@ class AccessController:
return replace(context, decision_id=decision.decision_id,
policy_version=decision.policy_version, policy_caller=decision.caller)
def _check_decision_deadline(self, decision: Decision) -> None:
if time.time() >= decision.valid_until:
self._observations["policy"] = ("unavailable", time.monotonic())
raise AccessFailure(503, "policy_decision_expired")
async def _resolve_facts(self, actor, resource):
facts = await self.facts.resolve(actor, resource)
if (facts.issuer, facts.subject, facts.actor_tenant) != (