informed-decision/tests/test_lifecycle_flow.py
tegwick 5fec7de4cb Implement lifecycle workspace publication and audit delivery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
2026-10-04 20:17:24 +02:00

74 lines
4.2 KiB
Python

import json
from urllib.parse import urlencode
import uuid
from informed_decision.audit import OutboxWorker
from informed_decision.lifecycle_http import LifecycleAPI
from informed_decision.lifecycle_service import Caller
from informed_decision.runtime import Runtime
from informed_decision.web import App, SESSION_COOKIE
from informed_decision.oidc import ORIGIN
from informed_decision.policy import PolicyClient
from informed_decision.records import dumps
from test_browser_auth import login, signing_key, finish, call
from test_deployment_candidate import inputs, module
from test_durable_component import receiver
from test_lifecycle_http import call as api_call
from test_policy_client import PolicyTransport
from test_review_runtime import config
from test_skeleton import make_memo
def test_agent_to_human_collaboration_and_audit_without_configuration_changes(tmp_path, login, receiver):
path, configuration = config(tmp_path)
configuration['lifecycle_policy'] = {**configuration['policy'], 'package': 'informed-decision.lifecycle'}
configuration['lifecycle_delivery'] = True
path.write_text(json.dumps(configuration))
original_configuration = path.read_bytes()
runtime = Runtime.from_file(path)
transport = PolicyTransport()
transport.change = lambda data: data['provenance'].update(policy_package='informed-decision.lifecycle')
lifecycle = runtime.lifecycle
lifecycle.policy = PolicyClient('https://policy.test', lambda: 'synthetic-caller',
package='informed-decision.lifecycle', version='v1', package_digest=configuration['policy']['package_digest'],
transport=transport)
# Match the immutable configured digest in the simulated PDP return.
transport.change = lambda data: data['provenance'].update(
policy_package='informed-decision.lifecycle', policy_package_digest=configuration['policy']['package_digest'])
sid = finish(login)
session = login[0].session(sid)
from informed_decision.lifecycle import Attribution
from informed_decision.disposition import Actor, ActorKind
agent = Caller(Attribution(session.tenant.value, Actor('synthetic-agent', ActorKind.AGENT)),
session.expires_at, '{"principal_type_source":"authentication-derived"}')
def authenticate(token):
if token != 'fixture':
raise ValueError()
return agent
api = LifecycleAPI(lifecycle, authenticate)
app = App(login[0], runtime.controller, readiness=runtime.pump.ready, lifecycle=lifecycle)
cookie = f'{SESSION_COOKIE}={sid}'
memo = make_memo(id='agent-posed', approval_id=None, packet=(), highlights=())
status, receipt, _ = api_call(api, '/v1/decisions', method='POST', body={'memo': json.loads(dumps(memo))})
assert status == 200 and receipt['workspace_url'] == ORIGIN + '/decisions/agent-posed?version=1'
assert call(app, '/decisions/agent-posed', cookie=cookie)['status'] == 200
def post(action, **values):
values.update(csrf=session.csrf, key=str(uuid.uuid4()))
return call(app, '/decisions/agent-posed/' + action, 'POST', cookie=cookie, origin=ORIGIN,
body=urlencode(values).encode())
assert post('publish', version='1', subjects=session.subject, groups='')['status'] == 303
assert post('comment', version='1', text='Please add the alternative.')['status'] == 303
assert post('revise', version='1', question=memo.question, brief='Alternative added.', summary='Responded to feedback')['status'] == 303
assert api_call(api, '/v1/decisions/agent-posed')[1]['version'] == 2
from dataclasses import replace
second = replace(memo, id='second-agent-decision')
assert api_call(api, '/v1/decisions', method='POST', body={'memo': json.loads(dumps(second))},
HTTP_IDEMPOTENCY_KEY='second')[0] == 200
assert not runtime.pump.ready()
runtime.pump.worker = OutboxWorker(runtime.controller.store, receiver[0])
runtime.pump.tick()
assert runtime.pump.ready()
assert runtime.controller.store.lifecycle_counts() == {'records': 2, 'events': 5, 'pending': 0}
assert path.read_bytes() == original_configuration
assert runtime.controller.store.memos_for(memo.binding.principal.id) == []
assert api_call(api, '/v1/decisions/agent-posed/accept', method='POST', body={})[0] == 404