Connect policy-gated browser review and audit runtime

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-11 00:31:03 +02:00
parent 2cc32168ac
commit 83849b75d4
35 changed files with 2381 additions and 83 deletions

116
tests/browser_fixture.py Normal file
View file

@ -0,0 +1,116 @@
"""Local-only browser exercise over actual components and synthetic identity.
Run with all three INFD_* component source/binary variables, and pass an output
directory. The fixture controls below are NOT part of informed_decision.web.
"""
import json
import os
from pathlib import Path
import sys
import threading
import base64
import hashlib
import ssl
from datetime import datetime, timedelta, timezone
from urllib.parse import parse_qs, urlsplit
from wsgiref.simple_server import WSGIRequestHandler, make_server
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.x509.oid import NameOID
from informed_decision.oidc import KeyCapeLogin
from informed_decision.runtime import AuditPump
from informed_decision.web import App
from test_browser_auth import ISSUER, IssuerFixture, signing_key
from test_approval_component import component
from test_durable_component import receiver
from test_review_controller import review
from review_components import flex_component
def main():
directory=Path(sys.argv[1]);directory.mkdir(mode=0o700,parents=True,exist_ok=True)
key=signing_key.__wrapped__()
class Quiet(WSGIRequestHandler):
def log_message(self,*args):pass
tls_server=make_server('127.0.0.1',0,lambda e,s:[],handler_class=Quiet)
public_origin=f'https://decisions.coulomb.social:{tls_server.server_port}'
issuer_origin=f'https://keycape.test:{tls_server.server_port}'
# Test-only ports, real HTTPS and redirects. Exact production strings stay
# pinned by the separate registration/login contract tests.
import informed_decision.oidc as oidc_module
import informed_decision.web as web_module
import test_browser_auth as browser_auth
import test_approval_component as approval_component
oidc_module.ORIGIN=web_module.ORIGIN=public_origin
oidc_module.CALLBACK=browser_auth.CALLBACK=public_origin+'/auth/callback'
browser_auth.ISSUER=approval_component.ISSUER=issuer_origin
now=datetime.now(timezone.utc)
name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'Browser fixture')])
certificate=(x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(key.public_key())
.serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1))
.not_valid_after(now+timedelta(hours=1)).add_extension(x509.SubjectAlternativeName([
x509.DNSName('decisions.coulomb.social'),x509.DNSName('keycape.test')]),critical=False).sign(key,hashes.SHA256()))
cert_path=directory/'browser-cert.pem';cert_path.write_bytes(certificate.public_bytes(serialization.Encoding.PEM))
key_path=directory/'browser-key.pem';key_path.write_bytes(key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,serialization.NoEncryption()));key_path.chmod(0o600)
context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER);context.load_cert_chain(cert_path,key_path)
tls_server.socket=context.wrap_socket(tls_server.socket,server_side=True)
spki=base64.b64encode(hashlib.sha256(key.public_key().public_bytes(serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo)).digest()).decode()
engine_fixture=component.__wrapped__(key,directory);engine_parts=next(engine_fixture)
sink,receiver_transport,backend=receiver.__wrapped__(directory)
controller,_,_,memo,engine,transport=review.__wrapped__(engine_parts,directory)
issuer=IssuerFixture(key);login=KeyCapeLogin(issuer_origin,transport=issuer)
original_start=login.start
def start():
url,browser=original_start()
issuer.params={k:v[0] for k,v in parse_qs(urlsplit(url).query).items()}
return url,browser
login.start=start
with flex_component(os.environ['INFD_FLEX_AUTH_BINARY'],directory,key) as (policy,calls):
controller.policy=policy
pump=AuditPump(controller.store,sink);pump.tick()
assert pump.ready()
app=App(login,controller,readiness=pump.ready)
def fixture(environ,start_response):
path=environ.get('PATH_INFO')
if path=='/authorize':
params={k:v[0] for k,v in parse_qs(environ.get('QUERY_STRING','')).items()}
assert params['code_challenge_method']=='S256'
assert params['redirect_uri']==oidc_module.CALLBACK
start_response('303 See Other',[('Location',oidc_module.CALLBACK+'?state='+params['state']+'&code=synthetic-browser-code')])
return [b'']
if path=='/_fixture/status':
pump.tick()
intents=[controller.store.intent_for('fixture','human-fixture')]
payload={'entries':len(engine.get('fixture').entries),'intents':intents,
'outbox_states':[r['state'] for r in controller.store.outbox()],
'policy_outcomes':[r['outcome'] for r in controller.store.policy_observations()],
'decision_attributable':[r['decision_attributable'] for r in controller.store.policy_observations()],
'entry_posts':sum(m=='POST' for m,_ in transport.calls),
'consume_calls':sum(url.endswith('/consume') for _,url in transport.calls),
'tokenreviews':len(calls)}
raw=json.dumps(payload).encode();start_response('200 OK',[('Content-Type','application/json')]);return [raw]
if path=='/_fixture/refuse-caller':
policy.token_provider=lambda:'synthetic-wrong-caller'
start_response('200 OK',[('Content-Type','application/json')]);return [b'{}']
if path=='/_fixture/stop':
threading.Thread(target=server.shutdown,daemon=True).start()
start_response('200 OK',[('Content-Type','application/json')]);return [b'{}']
return app(environ,start_response)
tls_server.set_app(fixture)
tls_thread=threading.Thread(target=tls_server.serve_forever,daemon=True);tls_thread.start()
with make_server('127.0.0.1',0,fixture,handler_class=Quiet) as server:
(directory/'server.json').write_text(json.dumps({'origin':f'http://127.0.0.1:{server.server_port}',
'public_origin':public_origin,'issuer':issuer_origin,'certificate_spki':spki,'memo_id':memo.id,'fixture_only':True})+'\n')
print('Browser fixture ready',flush=True)
try:server.serve_forever()
except KeyboardInterrupt:pass
finally:
tls_server.shutdown();tls_server.server_close();tls_thread.join(timeout=5)
engine_fixture.close()
if __name__=='__main__':main()

117
tests/browser_review.mjs Normal file
View file

@ -0,0 +1,117 @@
// Automated browser contract exercise. All identities and grants are synthetic.
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import {createRequire} from 'node:module';
const require=createRequire(import.meta.url);
const {chromium}=require(process.env.INFD_PLAYWRIGHT_PACKAGE || 'playwright');
const directory=process.argv[2];
const fixture=JSON.parse(await fs.readFile(path.join(directory,'server.json'),'utf8'));
const checks=[];
const browser=await chromium.launch({headless:true,executablePath:process.env.INFD_CHROMIUM_EXECUTABLE,args:[
'--no-proxy-server',
'--host-resolver-rules=MAP decisions.coulomb.social 127.0.0.1, MAP keycape.test 127.0.0.1',
'--ignore-certificate-errors-spki-list='+fixture.certificate_spki
]});
const context=await browser.newContext({viewport:{width:1360,height:1000},acceptDownloads:true});
const errors=[];
const navigation=[];
const formOrigins=[];
try {
// Both test names resolve to the disposable HTTPS server. All redirects,
// browser cookies and form Origins travel through real HTTP/TLS. Only this
// fixture certificate's public key is trusted by the browser override.
const page=await context.newPage();page.setDefaultTimeout(10000);
page.on('pageerror',error=>errors.push(String(error)));
page.on('request',request=>{if(request.method()==='POST')formOrigins.push({
path:new URL(request.url()).pathname,origin:request.headers().origin || null});});
page.on('response',response=>navigation.push({path:new URL(response.url()).pathname,status:response.status()}));
page.on('dialog',async dialog=>{errors.push('Unexpected script dialog');await dialog.dismiss();});
await page.goto(fixture.public_origin+'/');
await page.getByRole('link',{name:'Sign in with KeyCape'}).click();
await page.getByRole('heading',{name:'Open a decision review'}).waitFor({timeout:10000});
const cookies=await context.cookies();
const session=cookies.find(c=>c.name==='__Host-infd-session');
assert(session?.secure && session?.httpOnly && session?.sameSite==='Lax');
checks.push('PKCE callback and secure browser session');
await page.getByLabel('Memo identifier').fill(fixture.memo_id);
await page.getByRole('button',{name:'Open review',exact:true}).click();
await page.getByRole('heading',{name:'The request',exact:true}).waitFor();
const accept=()=>page.getByRole('button',{name:'Accept the complete request',exact:true});
assert(await accept().isDisabled());
assert.equal(await page.locator('img,script').count(),0);
await page.screenshot({path:path.join(directory,'review-desktop.png'),fullPage:true});
checks.push('Entitled page escapes memo and packet markup');
await accept().evaluate(button=>button.disabled=false);
await accept().click();
await page.getByText('Record all required highlight acknowledgments before accepting or declining.').waitFor();
let state=await (await fetch(fixture.origin+'/_fixture/status')).json();
assert.equal(state.entries,0);assert.equal(state.entry_posts,0);
checks.push('Server refuses bypass of missing acknowledgments');
await page.goBack();
await page.getByLabel('I have reviewed this highlight').check();
await page.getByRole('button',{name:'Record acknowledgments',exact:true}).click();
assert(await accept().isEnabled());
checks.push('Explicit acknowledgment unlocks the form');
await page.locator('summary').filter({hasText:'Whole fixture document'}).click();
const [download]=await Promise.all([
page.waitForEvent('download'),page.getByRole('link',{name:'Download complete document',exact:true}).click()
]);
const bytes=await fs.readFile(await download.path());
assert(bytes.includes(Buffer.from('Full fixture terms.')));
checks.push('Complete attachment download stays behind entitlement');
await page.locator('summary').filter({hasText:'Return for improvement'}).click();
const returned=page.locator('form').filter({has:page.locator('select[name="reason"]')});
await returned.locator('select').selectOption('clarification_needed');
await returned.locator('textarea').fill('Synthetic browser return: clarify the fixture scope.');
await returned.getByRole('button',{name:'Return for improvement',exact:true}).click();
await page.getByText('Synthetic browser return: clarify the fixture scope.').waitFor();
state=await (await fetch(fixture.origin+'/_fixture/status')).json();assert.equal(state.entries,0);
checks.push('Return records a memo response without an engine entry');
await page.setViewportSize({width:390,height:844});
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1));
await page.screenshot({path:path.join(directory,'review-mobile.png'),fullPage:true});
checks.push('Mobile review fits the viewport');
await page.setViewportSize({width:1360,height:1000});
await accept().click();
await page.getByText('Approval entry recorded.',{exact:true}).waitFor();
assert.equal(await page.getByRole('button',{name:'Accept the complete request',exact:true}).count(),0);
await page.screenshot({path:path.join(directory,'recorded-desktop.png'),fullPage:true});
await page.reload();await page.getByText('Approval entry recorded.',{exact:true}).waitFor();
state=await (await fetch(fixture.origin+'/_fixture/status')).json();
assert.equal(state.entries,1);assert.equal(state.entry_posts,1);assert.equal(state.consume_calls,0);
assert(state.outbox_states.every(s=>s==='delivered'));
assert(state.decision_attributable.every(value=>value===0));
checks.push('One actual approval entry, stable on browser reload');
checks.push('Audit receiver holds all commitments; unsigned decision gap retained');
await fetch(fixture.origin+'/_fixture/refuse-caller');
await page.reload();await page.getByRole('heading',{name:'Review unavailable'}).waitFor();
assert(!(await page.content()).includes('private-brief-sentinel'));
state=await (await fetch(fixture.origin+'/_fixture/status')).json();
assert.equal(state.policy_outcomes.at(-1),'caller_refused');assert.equal(state.entries,1);
checks.push('Real workload caller refusal hides review content');
await page.goto(fixture.public_origin+'/');
await page.getByRole('button',{name:'Sign out',exact:true}).click();
await page.getByRole('link',{name:'Sign in with KeyCape'}).waitFor();
assert(!(await context.cookies()).some(c=>c.name==='__Host-infd-session'));
assert(formOrigins.every(form=>form.origin===fixture.public_origin));
checks.push('Sign-out clears the session; all forms carry the exact origin');
assert.deepEqual(errors,[]);
checks.push('No page errors or injected script execution');
const receipt={fixture_only:true,browser:'Chromium',checks_passed:checks.length,checks,
fixture_transport:'real HTTPS on ephemeral ports; synthetic issuer and TokenReview',
native_human_login_proven:false,native_policy_admitted:false,factory_attempts:0,paid_model_calls:0,
final_component_state:state};
await fs.writeFile(path.join(directory,'browser-result.json'),JSON.stringify(receipt,null,2)+'\n');
process.stdout.write(JSON.stringify({checks_passed:checks.length,entries:state.entries,entry_posts:state.entry_posts,fixture_only:true})+'\n');
} finally {
if(checks.length<12 && context.pages()[0]) {
const page=context.pages()[0], url=new URL(page.url());
await fs.writeFile(path.join(directory,'browser-debug.json'),JSON.stringify({
path:url.pathname,body:await page.locator('body').innerText(),navigation,formOrigins,
cookie_names:(await context.cookies()).map(c=>c.name),errors},null,2));
await page.screenshot({path:path.join(directory,'browser-debug.png'),fullPage:true});
}
await browser.close();
await fetch(fixture.origin+'/_fixture/stop').catch(()=>{});
}

View file

@ -0,0 +1,58 @@
[
{
"id": "synthetic-allow",
"request": {
"id": "fixture-pin",
"tenant": "tenant:platform",
"subject": {
"id": "human-fixture",
"type": "human",
"tenant": "tenant:platform"
},
"action": "read",
"resource": {
"id": "memo:memo-1",
"type": "decision-memo",
"system": "informed-decision",
"tenant": "tenant:platform"
},
"context": {
"memo_version": 1,
"approval_id": "fixture",
"approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"policy_version": "v1"
},
"expect": {
"effect": "allow"
}
},
{
"id": "synthetic-refusal",
"request": {
"id": "fixture-pin",
"tenant": "tenant:platform",
"subject": {
"id": "human-fixture",
"type": "human",
"tenant": "tenant:platform"
},
"action": "consume",
"resource": {
"id": "memo:memo-1",
"type": "decision-memo",
"system": "informed-decision",
"tenant": "tenant:platform"
},
"context": {
"memo_version": 1,
"approval_id": "fixture",
"approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"policy_version": "v1"
},
"expect": {
"effect": "deny"
}
}
]

6
tests/fixtures/review-policy/pin.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"package": "informed-decision.fixture",
"version": "v1",
"package_digest": "sha256:bf39cd7fb33c3db6ba5adb3c036911216e35ef03c1168034e38fe741df67653a",
"fixture_only": true
}

50
tests/fixtures/review-policy/policy.md vendored Normal file
View file

@ -0,0 +1,50 @@
---
id: informed-decision.fixture
name: Synthetic review API contract fixture
namespace: informed-decision:decision-memo
version: v1
status: ready
package: flexauth.informed_decision.fixture
allow_ttl: 60s
actions: [read, acknowledge, accept, return, discuss, decline]
owner: fixture-only
fixtures: [fixtures.json]
caring:
profile: caring-0.4.0-rc2
enforce: false
activation:
mode: local
---
# Synthetic component fixture, never a production policy
Only the locally signed human-fixture and memo:memo-1 can exercise this test.
No production approver, group, mandate or permission is established here.
No presentation claim, view hash, acknowledgment or local outcome is input.
```rego
import rego.v1
decision := {"effect": "allow", "reason": "synthetic_review_fixture"} if {
input.tenant == "tenant:platform"
input.subject.id == "human-fixture"
input.subject.type == "human"
input.resource.system == "informed-decision"
input.resource.type == "decision-memo"
input.resource.id == "memo:memo-1"
input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"}
input.context.approval_id == "fixture"
} else := {"effect": "deny", "reason": "fixture_scope_refused"} if {
true
}
```
```rego test
package flexauth.informed_decision.fixture_test
import rego.v1
import data.flexauth.informed_decision.fixture
test_unknown_request_denied if {
fixture.decision.effect == "deny" with input as {}
}
```

View file

@ -0,0 +1,18 @@
{
"subjects": [
{
"id": "human-fixture",
"type": "Human",
"display_name": "Synthetic browser reviewer",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [],
"tenant": "tenant:platform",
"metadata": {
"source": "synthetic-contract-fixture"
}
}
]
}

View file

@ -0,0 +1,79 @@
"""Disposable actual Flex Auth HTTP process, with synthetic Kubernetes custody.
The evaluator and inbound caller authenticator are real. TokenReview and its
identities are fixtures; this does not admit any production caller or policy.
"""
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import os
from pathlib import Path
import socket
import subprocess
import threading
import time
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.x509.oid import NameOID
from informed_decision.http_transport import JSONTransport, TransportError
from informed_decision.policy import PolicyClient
FIXTURE = Path(__file__).parent / 'fixtures/review-policy'
@contextmanager
def flex_component(binary, directory, signing_key):
tokens={'synthetic-workload-token':'system:serviceaccount:informed-decision:review',
'synthetic-wrong-caller':'system:serviceaccount:other:other'}
calls=[]
class Reviewer(BaseHTTPRequestHandler):
def log_message(self,*args):pass
def do_POST(self):
body=json.loads(self.rfile.read(int(self.headers['Content-Length'])))
token=body['spec']['token'];calls.append({'known':token in tokens,'audiences':body['spec']['audiences']})
valid=(self.path=='/apis/authentication.k8s.io/v1/tokenreviews'
and self.headers.get('Authorization')=='Bearer synthetic-reviewer'
and body['spec']['audiences']==['flex-auth'] and token in tokens)
raw=json.dumps({'status':{'authenticated':valid,'audiences':['flex-auth'] if valid else [],
'user':{'username':tokens.get(token,'')}}}).encode()
self.send_response(201);self.send_header('Content-Type','application/json');self.end_headers();self.wfile.write(raw)
server=ThreadingHTTPServer(('127.0.0.1',0),Reviewer)
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
now=datetime.now(timezone.utc)
name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'synthetic-tokenreview-fixture')])
cert=(x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(signing_key.public_key())
.serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1))
.not_valid_after(now+timedelta(hours=1)).add_extension(x509.BasicConstraints(ca=True,path_length=None),critical=True)
.sign(signing_key,hashes.SHA256()))
ca=directory/'fixture-ca.pem';ca.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
reviewer=directory/'reviewer-token';reviewer.write_text('synthetic-reviewer');reviewer.chmod(0o600)
with socket.socket() as listener:
listener.bind(('127.0.0.1',0));port=listener.getsockname()[1]
origin=f'http://127.0.0.1:{port}'
process=subprocess.Popen([str(binary),'serve','--addr',f'127.0.0.1:{port}',
'--registry',str(FIXTURE/'registry.json'),'--policy',str(FIXTURE/'policy.md'),
'--caller-auth-mode','enforce','--caller-binding','informed-decision=system:serviceaccount:informed-decision:review',
'--caller-kubernetes-url',f'http://127.0.0.1:{server.server_port}',
'--caller-reviewer-token-file',str(reviewer),'--caller-ca-file',str(ca)],stdout=subprocess.DEVNULL,stderr=subprocess.PIPE)
try:
transport=JSONTransport(allow_internal_http=True)
for _ in range(100):
if process.poll() is not None:
raise RuntimeError('fixture evaluator exited: '+process.stderr.read().decode()[:1000])
try:
if transport.request('GET',origin+'/healthz')[0]==200:break
except TransportError:pass
time.sleep(.05)
else:raise RuntimeError('fixture evaluator did not start')
pin=json.loads((FIXTURE/'pin.json').read_text());pin.pop('fixture_only')
client=PolicyClient(origin,lambda:'synthetic-workload-token',allow_internal_http=True,**pin)
yield client,calls
finally:
process.terminate()
try:process.wait(timeout=5)
except subprocess.TimeoutExpired:process.kill();process.wait(timeout=5)
process.stderr.close();server.shutdown();server.server_close();thread.join(timeout=5)

View file

@ -274,7 +274,7 @@ def test_issuer_error_and_duplicate_params_do_not_leak_or_exchange(login):
@pytest.mark.parametrize("origin,csrf,expected", [(ORIGIN, "correct", 303), (ORIGIN, "wrong", 403),
(None, "correct", 403), ("https://attacker.test", "correct", 403)])
(None, "correct", 403), ("null", "correct", 403), ("https://attacker.test", "correct", 403)])
def test_logout_requires_origin_and_session_csrf(login, origin, csrf, expected):
sid = finish(login)
token = login[0].session(sid).csrf if csrf == "correct" else "wrong"
@ -284,6 +284,12 @@ def test_logout_requires_origin_and_session_csrf(login, origin, csrf, expected):
assert bool(login[0].session(sid)) == (expected != 303)
@pytest.mark.parametrize("path,policy", [("/", "same-origin"), ("/review", "same-origin"),
("/auth/callback", "no-referrer"), ("/presentations/pres-fixture/packet/0", "no-referrer")])
def test_browser_referrer_policy_preserves_form_origin_and_protects_auth_urls(login, path, policy):
assert dict(call(App(login[0]), path)["headers"])["Referrer-Policy"] == policy
@pytest.mark.parametrize("path,method,expected", [("/healthz", "GET", 200), ("/readyz", "GET", 503),
("/approvals/approval-fixture", "GET", 404), ("/approvals/approval-fixture/accept", "POST", 404),
("/v1/approvals/approval-fixture/entries", "POST", 404), ("/auth/logout", "GET", 404)])

View file

@ -22,6 +22,8 @@ def upstream():
"/ok": (200, b'{"ok":true}'), "/refuse": (403, b'{"error":"forbidden"}'),
"/broken": (200, b"broken"), "/array": (200, b"[]"),
"/large": (200, b" " * 262145),
"/plain-refuse": (403, b"forbidden secret-sentinel"),
"/large-refuse": (401, b"x" * 262145),
}.get(self.path, (500, b"{}"))
self.send_response(status)
self.end_headers()
@ -60,3 +62,9 @@ def test_json_success_and_typed_refusal_preserved():
client = JSONTransport(allow_internal_http=True)
assert client.request("GET", origin + "/ok") == (200, {"ok": True})
assert client.request("GET", origin + "/refuse") == (403, {"error": "forbidden"})
@pytest.mark.parametrize("path,status", [("/plain-refuse",403),("/large-refuse",401)])
def test_http_caller_refusal_is_not_relabelled_as_transport_outage(path,status):
with upstream() as (origin,calls):
assert JSONTransport(allow_internal_http=True).request("GET",origin+path)==(status,{})

114
tests/test_policy_client.py Normal file
View file

@ -0,0 +1,114 @@
import copy
from dataclasses import replace
from datetime import datetime, timezone
import json
import time
import pytest
from informed_decision.http_transport import TransportError
from informed_decision.oidc import HumanSession
from informed_decision.policy import CONTRACT, PolicyClient, PolicyError, build_request, submitted_digest
from informed_decision.provenance import Claim, Route
from test_skeleton import make_memo
PIN = "sha256:" + "b" * 64
STAMP = lambda t: datetime.fromtimestamp(t, timezone.utc).isoformat()
def human():
return HumanSession("human-fixture", Claim("tenant:platform", Route.REGISTRATION),
Claim("human", Route.AUTHENTICATION), {"at": int(time.time()) - 20, "level": "aal2", "mfa": True,
"methods": ["pwd", "otp"], "source": "key-cape"}, time.time()+600, "synthetic-browser-token", roles=("Reviewer",))
def envelope(request):
now = time.time()
return {"id": "decision-fixture", "contract_version": CONTRACT, "request_id": request["id"],
"effect": "allow", "matched_policy_version": "v1", "subject": copy.deepcopy(request["subject"]),
"resource": copy.deepcopy(request["resource"]),
"binding": {**copy.deepcopy({k:request[k] for k in ("tenant","subject","action","resource","context")}),
"request_digest": "sha256:" + "e" * 64, "submitted_request_digest": submitted_digest(request)},
"lifetime": {"kind":"ttl", "not_before":STAMP(now-1), "expires_at":STAMP(now+60)},
"provenance": {"evaluator":"flex-auth/local", "mode":"standalone", "policy_package":"informed-decision.fixture",
"policy_version":"v1", "policy_package_digest":PIN, "registry_snapshot_digest":"sha256:"+"c"*64,
"decision_time":STAMP(now)}}
class PolicyTransport:
def __init__(self): self.calls=[]; self.change=lambda d: None; self.status=200
def request(self, method, url, *, headers=None, body=None):
self.calls.append((method,url,headers,json.loads(body)))
data=envelope(json.loads(body)); self.change(data)
return self.status, data
def policy(transport=None):
return PolicyClient("https://policy.test", lambda:"synthetic-workload-token", package="informed-decision.fixture",
version="v1", package_digest=PIN, transport=transport or PolicyTransport())
def request():
return build_request(human(), make_memo(approval_binding_digest="sha256:"+"a"*64), "read", "v1")
def test_request_preserves_identity_routes_and_excludes_presentation_claims():
req=request(); attributes=req["subject"]["attributes"]
assert attributes["tenant_source"]=="registration-supplied" and attributes["principal_type_source"]=="authentication-derived"
assert attributes["roles"]==["Reviewer"]
assert set(req["context"])=={"memo_version","approval_id","approval_binding_digest"}
assert "synthetic-browser-token" not in json.dumps(req)
assert not any(k in json.dumps(req) for k in ["view_hash","presentation_id","acked_highlight_ids"])
def test_fresh_exact_enriched_decision_and_separate_workload_identity():
client=policy();req=request();observed=client.check(req)
observed.require_current(time.time())
data=json.loads(observed.decision_json)
assert data["binding"]["request_digest"]!=data["binding"]["submitted_request_digest"]
assert client.transport.calls[0][2]["Authorization"]=="Bearer synthetic-workload-token"
assert observed.usable_until<=time.time()+30
with pytest.raises(PolicyError):observed.require_current(time.time()+31)
@pytest.mark.parametrize("mutate", [
lambda d:d.update(contract_version="unknown"), lambda d:d.update(request_id="other"),
lambda d:d["binding"].pop("submitted_request_digest"),
lambda d:d["binding"].update(submitted_request_digest="sha256:"+"0"*64),
lambda d:d["binding"].update(action="accept"), lambda d:d["binding"]["subject"].update(id="other"),
lambda d:d["binding"]["resource"].update(system="other"),
lambda d:d["binding"]["context"].update(memo_version=99),
lambda d:d["binding"].update(tenant="tenant:other"), lambda d:d["resource"].update(id="memo:other"),
lambda d:d["provenance"].update(policy_package="other"), lambda d:d["provenance"].update(policy_version="other"),
lambda d:d["provenance"].update(policy_package_digest="sha256:"+"0"*64),
lambda d:d.update(matched_policy_version="other"), lambda d:d["provenance"].pop("registry_snapshot_digest"),
lambda d:d["provenance"].update(decision_time=STAMP(time.time()-300)),
lambda d:d["lifetime"].update(expires_at=STAMP(time.time()-1)),
lambda d:d["lifetime"].update(not_before=STAMP(time.time()+300)),
lambda d:d["lifetime"].update(kind="forever"), lambda d:d.update(lifetime={}),
lambda d:d.update(obligations=[{"type":"unimplemented"}]), lambda d:d.update(obligations={}),
lambda d:d.update(obligations=None), lambda d:d.update(effect="redact"),
lambda d:d.update(effect="audit_only"), lambda d:d.update(effect="not_applicable"),
lambda d:d.update(effect="deny"), lambda d:d.update(diagnostics={"bad":float("nan")}),
])
def test_unusable_decision_never_allows(mutate):
transport=PolicyTransport();transport.change=mutate
observed=policy(transport).check(request())
with pytest.raises(PolicyError):observed.require_current(time.time())
@pytest.mark.parametrize("status", [400,401,403,404,409,500,503])
def test_upstream_errors_never_persist_response_body(status):
transport=PolicyTransport();transport.status=status
transport.change=lambda d:d.update(reason="secret-sentinel",diagnostics={"token":"secret-sentinel"})
observed=policy(transport).check(request())
assert observed.decision_json is None and "secret-sentinel" not in repr(observed)
with pytest.raises(PolicyError):observed.require_current(time.time())
def test_transport_and_missing_caller_have_typed_observations():
class Broken:
def request(self,*a,**kw):raise TransportError("secret-sentinel")
client=policy(Broken());assert client.check(request()).outcome=="policy_unavailable"
client.token_provider=lambda:None
assert client.check(request()).outcome=="caller_unavailable"

View file

@ -0,0 +1,76 @@
from dataclasses import replace
import json
import os
from pathlib import Path
import time
import pytest
from informed_decision.disposition import Verb
from informed_decision.policy import build_request
from informed_decision.runtime import AuditPump
from informed_decision.web import SESSION_COOKIE
from test_browser_auth import call, signing_key
from test_approval_component import component
from test_review_controller import review, opened
from test_durable_component import receiver
from review_components import flex_component
@pytest.fixture
def pdp(tmp_path,signing_key):
binary=os.environ.get('INFD_FLEX_AUTH_BINARY')
if not binary:pytest.skip('set INFD_FLEX_AUTH_BINARY to test the actual evaluator and caller boundary')
assert Path(binary).is_file()
with flex_component(binary,tmp_path,signing_key) as value:yield value
def test_actual_pdp_enrichment_and_workload_caller_boundary(pdp,review):
client,calls=pdp;controller,session,_,memo,*_=review
request=build_request(session,memo,'read',client.version)
observation=client.check(request);assert observation.outcome=='allow',observation
decision=json.loads(observation.decision_json)
assert decision['binding']['request_digest']!=decision['binding']['submitted_request_digest']
assert calls[-1]=={'known':True,'audiences':['flex-auth']}
client.token_provider=lambda:'synthetic-wrong-caller'
assert client.check(request).outcome=='caller_refused'
client.token_provider=lambda:session.access_token
assert client.check(request).outcome=='caller_refused' # Browser JWT is never a workload credential.
def test_actual_request_digest_handles_go_string_escaping(pdp,review):
client,_=pdp;_,session,_,memo,*_=review
session=replace(session,roles=('Reviewer <x>& ü \u2028 \u2029',))
observation=client.check(build_request(session,memo,'read',client.version))
assert observation.outcome=='allow',observation
def test_actual_policy_engine_and_independent_audit_receive_browser_act(pdp,review,receiver):
controller,session,app,memo,engine,transport=review;controller.policy=pdp[0]
page=opened(review);p=page.presentation
controller.acknowledge(session,p.id,['h-1'])
controller.act(session,p.id,Verb.ACCEPT,operation_id='real-component-click')
assert len(engine.get('fixture').entries)==1
sink,receiver_transport,_=receiver
pump=AuditPump(controller.store,sink);pump.tick();assert pump.ready()
assert all(row['state']=='delivered' for row in controller.store.outbox())
observations=controller.store.policy_observations()
assert len(observations)==3 and all(row['outcome']=='allow' and row['decision_attributable']==0 for row in observations)
response=call(app,'/presentations/'+p.id,cookie=SESSION_COOKIE+'=fixture-session')
assert 'Approval entry recorded.' in response['body']
event=controller.store.evidence()[0]
status,body=receiver_transport.request('GET','https://audit.test/v1/events/'+event['id'],headers={'Authorization':'Bearer synthetic-auditor-token'})
assert status==200 and body['details']['data']['view_hash']==p.view_hash
assert 'private-brief-sentinel' not in json.dumps(body)
report=json.loads((controller.store.path.parent/'audit-reconciliation.json').read_text())
assert report['source_time_basis']=='occurred_at' and report['receiver_time_basis']=='accepted_at'
assert not report['completeness_proven'] and not report['automatic_loss_finding']
def test_actual_policy_refusal_prevents_content_and_engine_read(pdp,review):
controller,session,app,memo,engine,transport=review;controller.policy=pdp[0]
controller.store.save_memo(replace(memo,id='not-assigned-in-fixture'))
before=len(transport.calls)
response=call(app,'/review',query='memo_id=not-assigned-in-fixture',cookie=SESSION_COOKIE+'=fixture-session')
assert response['status']==403 and 'private-brief-sentinel' not in response['body']
assert len(transport.calls)==before and controller.store.evidence()==[]

View file

@ -0,0 +1,254 @@
from concurrent.futures import ThreadPoolExecutor
from dataclasses import replace
import json
import sqlite3
import time
from urllib.parse import urlencode
import uuid
import pytest
from informed_decision.approval_http import ApprovalHTTPClient
from informed_decision.disposition import DispositionRefused, Verb
from informed_decision.http_transport import TransportError
from informed_decision.memo import BindingLevel, Highlight, PacketItem, Principal, Scope, StepKind
from informed_decision.policy import PolicyError
from informed_decision.review import ReviewController, ReviewError
from informed_decision.store import Conflict, Store
from informed_decision.web import App, SESSION_COOKIE
from informed_decision.oidc import ORIGIN
from test_approval_component import component, signing_key
from test_browser_auth import call
from test_policy_client import policy
from test_skeleton import make_memo
@pytest.fixture
def review(component, tmp_path):
client,engine,transport,session,_=component
private=tmp_path/'private';private.mkdir(mode=0o700)
store=Store(private/'review.sqlite')
packet=store.put_document(b'<script>private-packet-sentinel</script>\nFull fixture terms.')
memo=make_memo(approval_id='fixture',approval_binding_digest=client.get_approval('fixture')['binding']['digest'],
question='Approve the synthetic factory delivery?', requested_act='deliver',
highlights=(Highlight('h-1','doc-1','Disposable test target; no production action',required_ack=True),),
packet=(PacketItem('doc-1','Whole fixture document',packet),),
brief='Disposable browser/component exercise. private-brief-sentinel <img src=x onerror=alert(1)>')
memo=replace(memo,binding=replace(memo.binding,principal=Principal(session.subject,'person','Fixture reviewer'),
target=Scope('resource','fixture','Synthetic factory target','test')))
store.save_memo(memo)
controller=ReviewController(store,policy(),lambda s:ApprovalHTTPClient('https://approval.test',s,transport=transport))
class Login:
def session(self,sid):return session if sid=='fixture-session' else None
app=App(Login(),controller,readiness=lambda:True)
return controller,session,app,memo,engine,transport
def opened(review):
controller,session,*_=review
return controller.open(session,'memo-1')
def post(review,p,route,**params):
_,session,app,*_=review
return call(app,'/presentations/'+p.id+'/'+route,'POST',cookie=SESSION_COOKIE+'=fixture-session',
origin=ORIGIN,body=urlencode({'csrf':session.csrf,**params}).encode())
def test_read_denial_precedes_disclosure_engine_read_and_presentation(review):
controller,session,app,memo,engine,transport=review
controller.policy.transport.change=lambda d:d.update(effect='deny')
before=len(transport.calls)
r=call(app,'/review',query='memo_id=memo-1',cookie=SESSION_COOKIE+'=fixture-session')
assert r['status']==403 and 'private-brief-sentinel' not in r['body']
assert len(transport.calls)==before and controller.store.evidence()==[]
assert controller.store.policy_observations()[0]['outcome']=='policy_denied'
def test_no_session_or_wrong_named_recipient_cannot_check_or_render(review):
controller,session,app,*_=review
assert call(app,'/review',query='memo_id=memo-1')['status']==401
with pytest.raises(ReviewError):controller.open(replace(session,subject='other'),'memo-1')
assert controller.policy.transport.calls==[] and controller.store.policy_observations()==[]
@pytest.mark.parametrize('change', [{'locale':'de'}, {'binding_level':BindingLevel.AES},
{'binding_level':BindingLevel.QES}, {'step_kind':StepKind.REVIEW}])
def test_unsupported_profile_cannot_misstate_locale_or_binding_level(review,change):
controller,session,_,memo,*_=review
controller.store.save_memo(memo.next_version(**change))
with pytest.raises(ReviewError,match='unsupported_review_profile'):
controller.open(session,memo.id)
assert controller.policy.transport.calls==[] and controller.store.evidence()==[]
def test_open_persists_exact_policy_and_binding_but_not_browser_credential(review):
controller,session,app,*_=review
page=opened(review);content=controller.store.presentation_content(page.presentation.id)
observed=controller.store.policy_observations()
assert observed[0]['decision_attributable']==0
assert content['policy_observation_id']==observed[0]['id']
assert content['approval_binding']['digest']==page.memo.approval_binding_digest
assert session.access_token not in json.dumps(observed)
assert all('policy_observation_id' not in r['envelope'] for r in controller.store.evidence())
response=call(app,'/presentations/'+page.presentation.id,cookie=SESSION_COOKIE+'=fixture-session')
assert response['status']==200 and '&lt;img' in response['body'] and '<img src=x' not in response['body']
assert '&lt;script&gt;private-packet-sentinel' in response['body']
@pytest.mark.parametrize('fault',['origin','csrf','duplicate','content-type','field'])
def test_post_form_refusals_precede_policy_and_side_effects(review,fault):
controller,session,app,*_=review;p=opened(review).presentation
body=urlencode({'csrf':session.csrf,'h0':'h-1'}).encode();origin=ORIGIN;ctype='application/x-www-form-urlencoded'
if fault=='origin':origin='https://other.test'
if fault=='csrf':body=b'csrf=wrong&h0=h-1'
if fault=='duplicate':body+=b'&h0=other'
if fault=='content-type':ctype='application/json'
if fault=='field':body+=b'&subject=other'
before=len(controller.policy.transport.calls)
r=call(app,'/presentations/'+p.id+'/ack','POST',cookie=SESSION_COOKIE+'=fixture-session',body=body,origin=origin,content_type=ctype)
assert r['status'] in (400,403) and len(controller.policy.transport.calls)==before
assert not controller.store.presentation(p.id).acked_highlight_ids
def test_highlights_gate_accept_then_one_real_entry_and_original_view(review):
controller,session,app,memo,engine,transport=review;p=opened(review).presentation
op=str(uuid.uuid4())
assert post(review,p,'act',verb='accept',operation_id=op)['status']==409
assert not engine.get('fixture').entries
assert post(review,p,'ack',h0='h-1')['status']==303
assert post(review,p,'act',verb='accept',operation_id=op)['status']==303
assert len(engine.get('fixture').entries)==1
posts=sum(method=='POST' for method,_ in transport.calls)
assert post(review,p,'act',verb='accept',operation_id=op)['status']==303
assert sum(method=='POST' for method,_ in transport.calls)==posts==1
page=controller.open(session,memo.id)
assert page.presentation.id==p.id and page.intent['state']=='confirmed'
response=call(app,'/presentations/'+p.id,cookie=SESSION_COOKIE+'=fixture-session')
assert 'Approval entry recorded.' in response['body']
assert 'Accept the complete request</button>' not in response['body']
assert not any(url.endswith('/consume') for _,url in transport.calls)
@pytest.mark.parametrize('verb',[Verb.RETURN,Verb.DISCUSS,Verb.DECLINE])
def test_nonaccept_actions_preserve_vocabulary_without_engine_mutation(review,verb):
controller,session,app,memo,engine,transport=review;p=opened(review).presentation
controller.acknowledge(session,p.id,['h-1'])
result=post(review,p,'act',verb=verb.value,operation_id=str(uuid.uuid4()),reason='clarification_needed' if verb is Verb.RETURN else '',note='Private response')
assert result['status']==303 and not engine.get('fixture').entries
assert controller.store.dispositions_for(p.id)[0].verb is verb
response=call(app,'/presentations/'+p.id,cookie=SESSION_COOKIE+'=fixture-session')
assert 'Private response' in response['body']
assert all('Private response' not in r['envelope'] for r in controller.store.evidence())
def test_fresh_act_refusal_after_allowed_render_cannot_submit(review):
controller,session,app,memo,engine,transport=review;p=opened(review).presentation
controller.acknowledge(session,p.id,['h-1'])
controller.policy.transport.change=lambda d:d.update(effect='deny')
assert post(review,p,'act',verb='accept',operation_id=str(uuid.uuid4()))['status']==403
assert not engine.get('fixture').entries and controller.store.dispositions_for(p.id)==[]
def test_policy_storage_failure_prevents_presentation(review):
controller,session,*_=review
with sqlite3.connect(controller.store.path) as db:
db.execute("CREATE TRIGGER fail_observation BEFORE INSERT ON policy_observations BEGIN SELECT RAISE(ABORT,'disk failure'); END")
with pytest.raises(sqlite3.IntegrityError):opened(review)
assert controller.store.evidence()==[]
def test_revision_between_policy_and_present_cannot_render_unchecked_version(review):
controller,session,_,memo,*_=review
controller.policy.transport.change=lambda d:controller.store.save_memo(memo.next_version(brief='changed'))
with pytest.raises(Conflict):opened(review)
assert controller.store.evidence()==[]
def test_stale_presentation_and_wrong_actor_cannot_ack_or_act(review):
controller,session,_,memo,engine,_=review;p=opened(review).presentation
with pytest.raises(ReviewError):controller.acknowledge(replace(session,subject='other'),p.id,['h-1'])
controller.store.save_memo(memo.next_version(brief='new version'))
with pytest.raises(ReviewError):controller.acknowledge(session,p.id,['h-1'])
with pytest.raises(ReviewError):controller.act(session,p.id,Verb.ACCEPT,operation_id='stale')
assert not engine.get('fixture').entries
def test_expiry_after_last_engine_read_prevents_reservation_and_post(review):
controller,session,_,memo,engine,transport=review;p=opened(review).presentation
controller.acknowledge(session,p.id,['h-1'])
original=transport.request;reads=0
def request(method,url,**kwargs):
nonlocal reads
result=original(method,url,**kwargs)
if method=='GET':
reads+=1
if reads==2:controller.clock=lambda:time.time()+40
return result
transport.request=request
with pytest.raises(PolicyError):controller.act(session,p.id,Verb.ACCEPT,operation_id='late')
assert not engine.get('fixture').entries
assert controller.store.intent_for('fixture',session.subject)['state']=='prepared'
def test_parallel_clicks_submit_once(review):
controller,session,_,memo,engine,transport=review;p=opened(review).presentation
controller.acknowledge(session,p.id,['h-1'])
def click(_):return controller.act(session,p.id,Verb.ACCEPT,operation_id='one-click')
with ThreadPoolExecutor(max_workers=2) as pool:assert list(pool.map(click,range(2)))==[p.id,p.id]
assert len(engine.get('fixture').entries)==1
assert sum(method=='POST' for method,_ in transport.calls)==1
def test_lost_entry_reply_is_visible_and_never_retried(review):
controller,session,app,memo,engine,transport=review;p=opened(review).presentation
controller.acknowledge(session,p.id,['h-1'])
original=transport.request
def lose_reply(method,url,**kwargs):
result=original(method,url,**kwargs)
if method=='POST':raise TransportError('lost response')
return result
transport.request=lose_reply
controller.act(session,p.id,Verb.ACCEPT,operation_id='lost')
assert controller.store.intent_for('fixture',session.subject)['state']=='unresolved'
response=call(app,'/presentations/'+p.id,cookie=SESSION_COOKIE+'=fixture-session')
assert 'Submission outcome is not confirmed.' in response['body']
assert 'Accept the complete request</button>' not in response['body']
controller.act(session,p.id,Verb.ACCEPT,operation_id='new-click')
assert len(engine.get('fixture').entries)==1 and sum(m=='POST' for m,_ in transport.calls)==1
def test_unknown_existing_entry_never_acquires_a_new_presentation_link(review):
controller,session,_,memo,engine,transport=review;p=opened(review).presentation
ApprovalHTTPClient('https://approval.test',session,transport=transport).add_entry('fixture')
with pytest.raises(ReviewError,match='existing_entry_unlinked'):
controller.act(session,p.id,Verb.ACCEPT,operation_id='unlinked')
assert controller.store.intent_for('fixture',session.subject) is None
assert sum(m=='POST' for m,_ in transport.calls)==1
def test_packet_download_rechecks_entitlement_and_forces_attachment(review):
controller,session,app,*_=review;p=opened(review).presentation
path='/presentations/'+p.id+'/packet/0'
response=call(app,path,cookie=SESSION_COOKIE+'=fixture-session')
assert response['status']==200
assert dict(response['headers'])['Content-Type']=='application/octet-stream'
assert dict(response['headers'])['Content-Disposition'].startswith('attachment;')
controller.policy.transport.change=lambda d:d.update(effect='deny')
denied=call(app,path,cookie=SESSION_COOKIE+'=fixture-session')
assert denied['status']==403 and 'private-packet-sentinel' not in denied['body']
def test_audit_not_ready_blocks_accept_before_policy_or_post(review):
controller,session,app,memo,engine,_=review;p=opened(review).presentation
app.readiness=lambda:False
assert post(review,p,'act',verb='accept',operation_id=str(uuid.uuid4()))['status']==503
assert not engine.get('fixture').entries
assert call(app,'/readyz')['status']==503
def test_renderer_release_cannot_silently_change_under_a_memo(review):
controller,session,_,memo,*_=review
controller.store.save_memo(memo.next_version(ui_release='informed-decision@0.1.0'))
with pytest.raises(ReviewError,match='renderer_changed'):opened(review)
assert controller.store.evidence()==[]

View file

@ -0,0 +1,82 @@
from dataclasses import replace
import json
import sqlite3
import time
import pytest
from informed_decision.runtime import Runtime, AuditPump, token_file
from informed_decision.store import Store
from test_policy_client import policy,request
from test_durable_store import storage,present
from test_durable_component import receiver
def test_schema_one_upgrade_preserves_existing_evidence(storage):
store,memo=storage;p=present(store,memo)
before=store.evidence()
with sqlite3.connect(store.path) as db:
db.execute('DROP TABLE policy_observations');db.execute('PRAGMA user_version=1')
reopened=Store(store.path)
assert reopened.retrieve_presentation(p.id)[0]==memo and reopened.evidence()==before
assert reopened.policy_observations()==[]
observation=policy().check(request());reopened.observe_policy(observation)
assert Store(store.path).policy_observations()[0]['decision_attributable']==0
with sqlite3.connect(store.path) as db:
for statement in ['DELETE FROM policy_observations',"UPDATE policy_observations SET outcome='allow'"]:
with pytest.raises(sqlite3.IntegrityError):db.execute(statement)
def test_caller_token_is_reread_on_rotation_and_bounded(tmp_path):
path=tmp_path/'token';path.write_text('synthetic-first\n');read=token_file(path)
assert read()=='synthetic-first'
path.write_text('synthetic-rotated');assert read()=='synthetic-rotated'
path.write_bytes(b'x'*32769)
with pytest.raises(ValueError):read()
with pytest.raises(ValueError):token_file('relative')
def config(tmp_path):
private=tmp_path/'private';private.mkdir(mode=0o700)
path=private/'runtime.json'
data={'schema':'informed-decision.review-runtime.v1','evidence_db':str(private/'review.sqlite'),
'approval_origin':'https://approval.test','policy':{'origin':'https://policy.test','package':'fixture',
'version':'v1','package_digest':'sha256:'+'a'*64,'caller_token_file':str(private/'unprovisioned-caller')},
'audit':{'origin':'https://audit.test','sender_token_file':str(private/'unprovisioned-sender')}}
path.write_text(json.dumps(data));path.chmod(0o600)
return path,data
def test_config_load_does_not_provision_or_read_credentials(tmp_path):
path,data=config(tmp_path);runtime=Runtime.from_file(path)
assert not runtime.pump.ready()
assert not (path.parent/'unprovisioned-caller').exists()
runtime.pump.tick();assert not runtime.pump.ready()
assert all(r['state']=='blocked' for r in runtime.controller.store.outbox())
@pytest.mark.parametrize('fault',['permissions','symlink','unknown-key','relative-store','missing-pin','unsafe-origin'])
def test_unsafe_or_incomplete_config_refused(tmp_path,fault):
path,data=config(tmp_path)
if fault=='permissions':path.chmod(0o644)
elif fault=='symlink':
linked=path.parent/'link.json';linked.symlink_to(path);path=linked
elif fault=='unknown-key':data['allow_everyone']=True
elif fault=='relative-store':data['evidence_db']='relative.db'
elif fault=='missing-pin':data['policy']['package_digest']=''
elif fault=='unsafe-origin':data['approval_origin']='http://public.example.com'
if fault not in ('permissions','symlink'):path.write_text(json.dumps(data))
with pytest.raises(ValueError):Runtime.from_file(path)
def test_pump_retains_blocked_delivery_and_requires_repair(storage,receiver):
store,memo=storage;present(store,memo)
sink,_,_=receiver;original=sink.token_provider;sink.token_provider=lambda:'synthetic-wrong-token'
pump=AuditPump(store,sink);pump.tick();assert not pump.ready()
ids=[r['id'] for r in store.outbox() if r['state']=='blocked'];assert ids
sink.token_provider=original
pump.tick();assert not pump.ready() # No implicit credential repair/requeue.
for ident in ids:store.requeue_blocked(ident)
pump.tick();assert pump.ready()
assert (store.path.parent/'audit-reconciliation.json').stat().st_mode & 0o777 == 0o600
pump.clock=lambda:time.time()+91;assert not pump.ready()