informed-decision/tests/test_intake_delivery_runtime.py
tegwick b4f637326b Record that hub forwarding preserves governed booleans.
state-hub d61ba32 accepts requires_new_bind, required_ack, and sealed.
The p01 cross-repo check now expects a delivered pose that keeps those
bools. T08 stays waiting on the service account, grant, and T05/T06.

Assistant: grok
Assistant-Session: 01a10286-9185-7bb2-b411-89cf6ab4c0c6
2026-10-03 22:27:26 +02:00

201 lines
9.4 KiB
Python

"""Delivery contracts, recovery and opt-in runtime composition."""
import json
import os
from pathlib import Path
import sqlite3
import sys
import uuid
import pytest
from informed_decision.audit import AuditDeliveryError, OutboxWorker
from informed_decision.evidence import EventClass
from informed_decision.intake_delivery import IntakeDeliveryWorker
from informed_decision.intake_hub import HubDelivery
from informed_decision.runtime import AuditPump, Runtime
from informed_decision.store import Store
from test_durable_component import receiver
from test_intake import envelope
from test_intake_service import setup, request
from test_review_runtime import config
class HubTransport:
def __init__(self, status=201):
self.status, self.calls = status, []
def request(self, method, url, *, headers, body):
payload = json.loads(body) if body else None
self.calls.append((method, url, headers, body))
if url.endswith('/notifications'):
receipt = {'message_id': str(uuid.uuid4()), 'delivery_id': headers['Idempotency-Key'],
'status': 'accepted', 'authoritative_review': 'signed-in-overview', **payload}
else:
receipt = {'id': url.split('/decisions/')[1].split('/')[0],
'status': 'resolved', **(payload or {})}
return self.status, receipt
def test_notification_replay_uses_identical_request_and_checks_receipt():
transport = HubTransport()
hub = HubDelivery('https://hub.test', transport=transport)
ident = str(uuid.uuid4())
metadata = {'memo_id': 'review-1', 'question': 'Proceed?', 'memo_class': 'credential-lane', 'submitter': 'state-hub'}
assert hub.notify(ident, metadata)
assert hub.notify(ident, metadata)
assert transport.calls[0] == transport.calls[1]
assert json.loads(transport.calls[0][3]) == metadata
assert transport.calls[0][1].endswith('/informed-decision/notifications')
assert transport.calls[0][2]['Idempotency-Key'] == ident
transport.status = 202
assert not hub.notify(ident, metadata) # Relay queue acknowledgement is not a receipt.
with pytest.raises(ValueError):
hub.notify(ident, {**metadata, 'packet': 'private'})
transport.request = lambda *args, **kwargs: (201, {'id': str(uuid.uuid4())})
assert not hub.notify(ident, metadata)
def test_resolution_is_fixed_origin_and_idempotent():
transport = HubTransport(status=200)
hub = HubDelivery('https://hub.test', transport=transport)
url = 'https://hub.test/decisions/' + str(uuid.uuid4())
for _ in range(2):
hub.resolution('POST', url + '/resolve', {'rationale': 'memo review-1', 'decided_by': 'human'})
assert transport.calls[0] == transport.calls[1]
hub.resolution('GET', url, {})
transport.status = 202
assert hub.resolution('POST', url + '/resolve', {'rationale': 'memo review-1', 'decided_by': 'human'}) == (503, {})
for method, target in [('GET', url + '/resolve'), ('POST', url),
('POST', url.replace('hub.test', 'other.test') + '/resolve'),
('POST', 'https://hub.test/decisions/' + '-' * 36 + '/resolve')]:
with pytest.raises(ValueError):
hub.resolution(method, target, {})
def test_runtime_delivery_is_opt_in_and_writeback_remains_off(tmp_path, monkeypatch):
monkeypatch.delenv('INFD_HUB_WRITEBACK', raising=False)
path, data = config(tmp_path)
assert Runtime.from_file(path).pump.intake_worker is None
data['intake_delivery'] = {'hub_origin': 'https://hub.test'}
path.write_text(json.dumps(data))
worker = Runtime.from_file(path).pump.intake_worker
assert worker is not None and worker.audit and worker.notify and worker.hub_transport
from informed_decision.hub_writeback import enabled
assert not enabled(worker.env)
for invalid in [None, {}, {**data['intake_delivery'], 'allow_all': True},
{'hub_origin': 'http://public.example.com'}]:
data['intake_delivery'] = invalid
path.write_text(json.dumps(data))
with pytest.raises(ValueError):
Runtime.from_file(path)
def test_posed_evidence_survives_restart_and_is_immutable(setup):
service, store, _, _ = setup
assert request(service, envelope())[0] == 201
store.stage_intake_audit()
before = store.evidence()
assert len(before) == 1 and before[0]['class'] == EventClass.MEMO_POSED.value
Store(store.path).stage_intake_audit()
assert store.evidence() == before and len(store.outbox()) == 1
with store._connection() as db:
with pytest.raises(sqlite3.IntegrityError):
db.execute('DELETE FROM evidence')
assert store.intake_audit_pending()
def test_actual_posed_receiver_lost_reply_deduplicates_and_reconciles(setup, receiver):
service, store, _, _ = setup
assert request(service, envelope())[0] == 201
sink, transport, _ = receiver
store.stage_intake_audit()
first = store.claim_delivery(now=10)
transport.lose_reply = True
with pytest.raises(AuditDeliveryError):
sink.deliver(first[0], first[2])
replay = Store(store.path).claim_delivery(now=41)
assert replay[0] == first[0] and replay[2] == first[2]
reference = sink.deliver(replay[0], replay[2])
store.finish_delivery(replay[0], replay[1], reference=reference)
assert store.settle_intake_audit() == 1 and not store.intake_audit_pending()
report = OutboxWorker(store, sink).reconcile('2020-01-01T00:00:00Z', '2100-01-01T00:00:00Z')
assert report['count_values_match']
assert report['counts'][EventClass.MEMO_POSED.value] == {'source': 1, 'receiver': 1}
assert store.outbox()[0]['receiver_reference'] == 'audit:' + first[0]
def test_pump_includes_posed_evidence_and_retains_failed_notifications(setup, receiver):
service, store, _, _ = setup
assert request(service, envelope())[0] == 201
sink, _, _ = receiver
worker = IntakeDeliveryWorker(store, audit=sink, notify=lambda *args: False)
pump = AuditPump(store, sink, intake_worker=worker)
pump.tick()
assert pump.ready() and not store.intake_audit_pending()
with store._connection() as db:
assert db.execute("SELECT state FROM intake_deliveries WHERE kind='notification'").fetchone()[0] == 'pending'
snapshot = json.loads((store.path.parent / 'audit-reconciliation.json').read_text())
assert snapshot['count_values_match'] and EventClass.MEMO_POSED.value in snapshot['counts']
def test_failed_posed_delivery_blocks_readiness_and_no_empty_heartbeat(setup, receiver):
service, store, _, _ = setup
assert request(service, envelope())[0] == 201
sink, _, _ = receiver
sink.token_provider = lambda: 'invalid-synthetic-token'
pump = AuditPump(store, sink, intake_worker=IntakeDeliveryWorker(store, audit=sink))
pump.tick()
assert not pump.ready() and store.intake_audit_pending()
heartbeats = [json.loads(row['envelope']) for row in store.evidence()
if row['class'] == EventClass.HEARTBEAT.value]
assert all(row['data']['class'] != EventClass.MEMO_POSED.value for row in heartbeats)
@pytest.fixture
def hub_contract():
source = os.environ.get('INFD_STATE_HUB_SOURCE')
if not source:
pytest.skip('set INFD_STATE_HUB_SOURCE for actual hub contract checks')
assert (Path(source) / 'api/services/memo_forwarding.py').is_file()
sys.path.insert(0, source)
from api.services import informed_decision_contract, memo_forwarding
return informed_decision_contract, memo_forwarding
def test_actual_hub_forwarder_preserves_governed_boolean_fields(setup, hub_contract, tmp_path):
service, store, creates, _ = setup
contract, forwarding = hub_contract
body = envelope()
body['idempotency_key'] = contract.stable_idempotency_key(body['serves']['hub_decision_id'], body['memo']['id'])
calls = []
def transport(method, url, payload, headers):
assert method == 'POST' and url == 'https://intake.test/v1/memos'
assert headers['Authorization'] == 'Bearer projected-token'
calls.append(payload)
return request(service, payload)
fixture = forwarding.DevIntakeFixture(tmp_path / 'hub.sqlite', transport=transport,
intake_base_url='https://intake.test', hub_base_url='https://hub.test',
token_reader=lambda: 'projected-token')
# Owner commit d61ba32 accepts the shared schema's bool leaves. Do not strip
# or coerce requires_new_bind, required_ack, or sealed.
posed = fixture.submit(body)
assert posed.status == 'delivered' and len(calls) == 1 and len(creates) == 1
memo = calls[0]['memo']
assert memo['binding']['target']['requires_new_bind'] is False
assert memo['highlights'][0]['required_ack'] is True
assert memo['sealed'] is False
assert store.memos_for('keycape-human')
assert b'projected-token' not in Path(fixture.path).read_bytes()
def test_notification_body_matches_actual_hub_parser(hub_contract):
contract, _ = hub_contract
class Transport(HubTransport):
def request(self, method, url, *, headers, body):
parsed = contract.parse_notification(json.loads(body))
assert parsed.as_body() == json.loads(body)
assert contract.parse_delivery_id(headers['Idempotency-Key'])
return super().request(method, url, headers=headers, body=body)
hub = HubDelivery('https://hub.test', transport=Transport())
assert hub.notify(str(uuid.uuid4()), {'memo_id': 'review-1', 'question': 'Proceed?',
'memo_class': 'credential-lane', 'submitter': 'state-hub'})