flex-auth/tools/exercise_t03_review_policy.py
tegwick ad7b7f536a
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m14s
Admit list for the informed-decision overview as compact-sitting v3 (FLEX-DEC-2026-017).
list is a separate rule with exact-record scope over the union of the eight
sitting records and the three T03 records, each pinned by approval id, digest and
its own memo version, with a 12-hour MFA window. read and the five acts keep the
v2 rule unchanged — same eight records, same 900-second window — so a list allow
satisfies nothing else.

The operator chose exact-record scope over the consumer's preferred type-wide
scope: the PDP checks no recipient, so type-wide scope with a relaxed window would
have left the consumer's structural match — which the consumer itself says is
not an entitlement — as the only scope. The 12-hour bound replaces the requested
"no bound" so the PDP still states one. The KeyCape stale-timestamp defect is not
worked around; read stays strict.

417 evaluator checks: 168 v2 unchanged, 231 list, 18 proving no act widens to the
T03 records. Also fixes tools/exercise_t03_review_policy.py, which had been
failing since f85479c moved the T03 records to memo version 2 and it still sent
version 1. Test-only.

Not deployed: the pin serving the live review surface changes only with operator
confirmation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 28468@bnt-lap001
Assistant-Session: c76569b2-6056-4dad-aea4-49cd7a018f5d
2026-09-21 23:06:40 +02:00

27 lines
2.9 KiB
Python

import json,time,copy,subprocess,tempfile
from pathlib import Path
import argparse
p=argparse.ArgumentParser();p.add_argument('--binary',required=True);p.add_argument('--receipt',type=Path,required=True);args=p.parse_args()
r=Path(__file__).resolve().parents[1]/'examples/informed-decision-t03'
records=json.loads((r/'records.json').read_text())
results=[]
with tempfile.TemporaryDirectory() as temp:
request_path=Path(temp)/'request.json'
def check(name,request,expected):
request_path.write_text(json.dumps(request))
result=subprocess.run([args.binary,'check','--registry',str(r/'registry.json'),'--policy',str(r/'policy.md'),'--request',str(request_path)],capture_output=True,text=True,check=True)
d=json.loads(result.stdout)
assert d['effect']==expected,(name,d)
results.append({'check':name,'effect':d['effect']})
return d
for memo,record in records.items():
request={'id':'local-regression','tenant':'tenant:platform','subject':{'id':'synthetic-reviewer','type':'human','tenant':'tenant:platform','attributes':{'groups':['net-kingdom-admins'],'roles':[],'tenant_source':'registration-supplied','principal_type_source':'authentication-derived','assurance':{'level':'aal2','mfa':True,'methods':['pwd','otp'],'source':'key-cape','at':int(time.time())}}},'resource':{'id':memo,'type':'decision-memo','system':'informed-decision','tenant':'tenant:platform'},'action':'accept','context':{'memo_version':record['memo_version'],'approval_id':record['approval_id'],'approval_binding_digest':record['binding_digest']},'policy_version':'v1'}
for action in ['read','acknowledge','accept','return','discuss','decline']:
d=check(record['action']+':'+action,request|{'action':action},'allow')
for name,path,value in [('wrong-group',['subject','attributes','groups'],['net-kingdom-users']),('no-group',['subject','attributes','groups'],[]),('service',['subject','type'],'service'),('stale-mfa',['subject','attributes','assurance','at'],int(time.time())-901),('future-mfa',['subject','attributes','assurance','at'],int(time.time())+300),('no-mfa',['subject','attributes','assurance','mfa'],False),('forged-human-route',['subject','attributes','principal_type_source'],'registration-supplied'),('wrong-tenant',['subject','tenant'],'tenant:other'),('other-memo',['resource','id'],'memo:other'),('changed-version',['context','memo_version'],record['memo_version']+1),('changed-approval',['context','approval_id'],'other'),('changed-digest',['context','approval_binding_digest'],'sha256:'+'0'*64),('consume',['action'],'consume')]:
candidate=copy.deepcopy(request); target=candidate
for key in path[:-1]:target=target[key]
target[path[-1]]=value
check(record['action']+':'+name,candidate,'deny')
args.receipt.write_text(json.dumps({'scope':'local actual evaluator with synthetic identity; no live human approvals','checks':results},indent=2)+'\n')
print(len(results),'policy checks passed')