fix: recheck policy decision expiry after audit custody
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
72f3513954
commit
f11b948e8c
7 changed files with 126 additions and 8 deletions
76
tests/test_decision_deadline.py
Normal file
76
tests/test_decision_deadline.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
"""A verified decision must remain usable through audit custody and dispatch."""
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from datetime import timedelta
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from hub_core.runtime.tables import runtime_messages, runtime_outcome_outbox
|
||||
from hub_core.security import boundary
|
||||
from hub_core.security.boundary import Decision
|
||||
from hub_core.security.policy import verify_decision
|
||||
from test_access_boundary import HEADERS
|
||||
from test_access_policy import case, sign
|
||||
from test_outcome_audit import target, rows, message
|
||||
|
||||
|
||||
@pytest.mark.parametrize('limit', ['decision', 'caller', 'freshness'])
|
||||
def test_signed_policy_preserves_earliest_verified_deadline(limit):
|
||||
request,envelope,now = case()
|
||||
expected = now + timedelta(seconds=30)
|
||||
if limit == 'decision':
|
||||
expected = now + timedelta(seconds=1)
|
||||
envelope['lifetime']['expires_at'] = expected.isoformat()
|
||||
elif limit == 'caller':
|
||||
expected = now + timedelta(seconds=2)
|
||||
envelope['provenance']['caller']['not_after'] = expected.isoformat()
|
||||
result = verify_decision(envelope,request=request,keys=sign(envelope),
|
||||
caller='workload:hub',now=now)
|
||||
assert result.valid_until == expected.timestamp()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('deadline', [None, True, '123', float('nan'), float('inf')])
|
||||
def test_decision_requires_finite_deadline(deadline):
|
||||
with pytest.raises(ValueError,match='deadline'):
|
||||
Decision(True,'decision','policy',deadline)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('elapsed,status', [(0,202), (1,503), (2,503)])
|
||||
def test_expiry_during_audit_prevents_business_commit(target,monkeypatch,elapsed,status):
|
||||
client,store,owners,_ = target
|
||||
now = time.time()
|
||||
clock = SimpleNamespace(time=lambda: now,monotonic=time.monotonic)
|
||||
monkeypatch.setattr(boundary,'time',clock)
|
||||
async def facts(actor,resource):
|
||||
return replace(owners.facts,checked_at=clock.time())
|
||||
owners.resolve = facts
|
||||
evaluate,append = owners.evaluate,owners.append
|
||||
async def policy(request):
|
||||
return replace(await evaluate(request),valid_until=now+1)
|
||||
async def audit(record):
|
||||
await append(record)
|
||||
clock.time = lambda: now+elapsed
|
||||
owners.evaluate,owners.append = policy,audit
|
||||
response = client.post('/ports/messaging/messages',headers=HEADERS,json=message())
|
||||
assert response.status_code == status
|
||||
if status == 503:
|
||||
assert response.json()['detail'] == 'policy_decision_expired'
|
||||
assert not rows(store,runtime_messages)
|
||||
assert not rows(store,runtime_outcome_outbox)
|
||||
assert [record['outcome'] for record in owners.records] == ['authorized','refused']
|
||||
assert owners.records[0]['correlation_id'] == owners.records[1]['correlation_id']
|
||||
else:
|
||||
assert len(rows(store,runtime_messages)) == len(rows(store,runtime_outcome_outbox)) == 1
|
||||
|
||||
|
||||
def test_already_expired_adapter_decision_is_not_authorized(target):
|
||||
client,store,owners,_ = target
|
||||
async def policy(request):
|
||||
return Decision(True,'expired','policy',1)
|
||||
owners.evaluate = policy
|
||||
response = client.post('/ports/messaging/messages',headers=HEADERS,json=message())
|
||||
assert response.status_code == 503
|
||||
assert [record['outcome'] for record in owners.records] == ['refused']
|
||||
assert not rows(store,runtime_messages)
|
||||
assert not rows(store,runtime_outcome_outbox)
|
||||
Loading…
Add table
Add a link
Reference in a new issue