Implement lifecycle workspace publication and audit delivery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
ee472acc87
commit
5fec7de4cb
21 changed files with 1022 additions and 78 deletions
74
tests/test_lifecycle_flow.py
Normal file
74
tests/test_lifecycle_flow.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue