Add recipient-scoped review discovery and settle initial lifecycle contracts
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
dfcef879d0
commit
da2c5a720f
9 changed files with 224 additions and 10 deletions
|
|
@ -8,12 +8,14 @@ import pytest
|
|||
from informed_decision.disposition import Actor, ActorKind, Verb, DispositionRefused
|
||||
from informed_decision.lifecycle import Attribution, LifecycleRepository
|
||||
from informed_decision.lifecycle_service import Caller, LifecycleService, LifecycleError
|
||||
from informed_decision.policy import build_request
|
||||
from informed_decision.provenance import Claim, Route
|
||||
from informed_decision.records import memo_from
|
||||
from informed_decision.store import Conflict, Store
|
||||
from test_intake import envelope
|
||||
from test_intake_service import setup, request
|
||||
from test_policy_client import policy
|
||||
from test_browser_auth import signing_key
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -376,3 +378,79 @@ def test_retained_handoff_api_is_revision_scoped_and_reauthorizes(handoff):
|
|||
assert request(intake, receipt['envelope'])[0] == 409
|
||||
app.policy.transport.change = lambda data: data.update(effect='deny')
|
||||
assert call(api, path, query='version=1')[:2] == (403, {'error': 'policy_denied'})
|
||||
|
||||
|
||||
def test_overview_uses_retained_intake_context_and_fails_closed_per_row(handoff):
|
||||
from informed_decision.review import ReviewController
|
||||
from informed_decision.approval_client import ApprovalEngineError
|
||||
from test_policy_client import human
|
||||
app, actor, memo, options, intake, calls = handoff
|
||||
receipt = prepare(handoff)
|
||||
assert request(intake, receipt['envelope'])[0] == 201
|
||||
store = app.repository.store
|
||||
stored = store.memo(receipt['review_memo_id'])
|
||||
session = replace(human(), subject=stored.binding.principal.id)
|
||||
class Engine:
|
||||
failure = False
|
||||
def get_approval(self, approval_id):
|
||||
if self.failure:
|
||||
raise ApprovalEngineError(503, 'unavailable')
|
||||
return {'status': 'requested', 'binding': {'digest': stored.approval_binding_digest,
|
||||
'actor': 'informed-decision', 'human_control': True}}
|
||||
engine = Engine()
|
||||
controller = ReviewController(store, policy(), lambda session: engine, intake_attributes=True)
|
||||
[row] = controller.overview(session)
|
||||
assert row.question == memo.question
|
||||
sent = controller.policy.transport.calls[-1][3]
|
||||
assert sent['action'] == 'list'
|
||||
assert sent['context']['review_recipient'] == session.subject
|
||||
assert sent['context']['intake_submitter'] == 'state-hub'
|
||||
assert 'review_recipient' not in build_request(
|
||||
session, stored, 'read', 'v1', intake_context=store.intake_context(stored))['context']
|
||||
assert store.evidence() == []
|
||||
engine.failure = True
|
||||
[row] = controller.overview(session)
|
||||
assert row.group == 'unavailable' and row.question is None
|
||||
assert len(controller.policy.transport.calls) == 1
|
||||
|
||||
|
||||
def test_real_discovery_policy_lists_only_recipient_and_keeps_fresh_read(handoff, tmp_path, signing_key):
|
||||
import os
|
||||
from pathlib import Path
|
||||
from informed_decision.policy import build_request
|
||||
from informed_decision.review import ReviewController, ReviewError
|
||||
from review_components import flex_component
|
||||
from test_policy_client import human
|
||||
binary, source = os.environ.get('INFD_FLEX_AUTH_BINARY'), os.environ.get('INFD_FLEX_AUTH_SOURCE')
|
||||
if not binary or not source:
|
||||
pytest.skip('actual owner discovery policy required')
|
||||
fixture = Path(source) / 'examples/informed-decision-lifecycle-review'
|
||||
if json.loads((fixture / 'pin.json').read_text())['version'] != 'v13':
|
||||
pytest.skip('owner discovery v13 required')
|
||||
app, actor, memo, options, intake, calls = handoff
|
||||
options['memo_class'] = 'decision-record'
|
||||
receipt = prepare(handoff)
|
||||
# Admit this class in the synthetic intake registry used by the fixture.
|
||||
registry = json.loads(intake.submitters_path.read_text())
|
||||
for value in registry.values():
|
||||
value['memo_classes'].append('decision-record')
|
||||
intake.submitters_path.write_text(json.dumps(registry))
|
||||
assert request(intake, receipt['envelope'])[0] == 201
|
||||
store = app.repository.store
|
||||
stored = store.memo(receipt['review_memo_id'])
|
||||
session = replace(human(), subject=stored.binding.principal.id,
|
||||
assurance={**human().assurance, 'at': int(time.time()) - 3600})
|
||||
class Engine:
|
||||
def get_approval(self, approval_id):
|
||||
return {'status': 'requested', 'binding': {'digest': stored.approval_binding_digest,
|
||||
'actor': 'informed-decision', 'human_control': True}}
|
||||
with flex_component(binary, tmp_path, signing_key, fixture=fixture) as (pdp, calls):
|
||||
controller = ReviewController(store, pdp, lambda session: Engine(), intake_attributes=True)
|
||||
[row] = controller.overview(session)
|
||||
assert row.question == memo.question and row.group == 'open'
|
||||
with pytest.raises(ReviewError, match='policy_denied'):
|
||||
controller.open(session, stored.id)
|
||||
req = build_request(session, stored, 'list', pdp.version, intake_context=store.intake_context(stored))
|
||||
req['context']['review_recipient'] = 'another-human'
|
||||
assert pdp.check(req).outcome == 'policy_denied'
|
||||
assert store.evidence() == []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue