From 53825190ca8ee2ed965e3521fc6dc001ec9f7848 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 27 Sep 2026 15:33:09 +0200 Subject: [PATCH] Point the OpenRouter cycle at memo version 3. Fresh receipts use the new approval IDs. A matching live policy and AppRole skips apply without claiming that approval. A difference fails closed. Assistant: grok Assistant-Session: 01a0e2a1-8058-7553-9999-b7d106c17047 --- .../t03-renewed-execution.md | 13 +++++--- scripts/create-t03-approval-requests.py | 2 +- scripts/t03-attended-delivery.py | 4 +-- scripts/t03-native-execution.py | 32 ++++++++++++++----- tests/test_t03_revocation_probe.py | 13 +++++++- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/docs/credential-lane-designs/t03-renewed-execution.md b/docs/credential-lane-designs/t03-renewed-execution.md index fd5526a..5cb8657 100644 --- a/docs/credential-lane-designs/t03-renewed-execution.md +++ b/docs/credential-lane-designs/t03-renewed-execution.md @@ -1,8 +1,11 @@ # T03 continuation after Railiance Clock deployment -The original requests expired unconsumed. Replacement request IDs and immutable -memo version 2 are recorded in the creation receipt and Secrets Engine workplan. -The exact apply/verify/exec action requests and checker pins are unchanged. +The 2026-09-16 requests were consumed. The next cycle uses three new approval +IDs and immutable memo version 3. The creation receipt is +`docs/evidence/2026-09-27-t03-approval-requests.json`; execution receipts are +`secrets-engine/docs/evidence/2026-09-27-t03-native-execution.json` and +`docs/evidence/2026-09-27-t03-attended-delivery.json`. The exact +apply/verify/exec action requests and checker pins are unchanged. Run the requester and execution worker with `/home/worsch/secrets-engine/.venv/bin/python`; this environment includes Clock, @@ -20,6 +23,6 @@ The outer lane remains secrets-engine-approval-client-login; its reviewed Both retain their own self-revocation and private runtime cleanup. Human review: https://decisions.coulomb.social/review?memo_id=SECRETS-WP-0010-T03-apply -(and identifiers ending -verify and -exec). Version 2 is required. The requester -has no approval or consume scope; no human entries are copied from version 1. +(and identifiers ending -verify and -exec). Version 3 is required. The requester +has no approval or consume scope; no human entries are copied from version 2. No execution may begin before the new approvals pass native claim/PDP checks. diff --git a/scripts/create-t03-approval-requests.py b/scripts/create-t03-approval-requests.py index ec3d867..f8edaaa 100644 --- a/scripts/create-t03-approval-requests.py +++ b/scripts/create-t03-approval-requests.py @@ -7,7 +7,7 @@ from urllib.parse import urlencode from urllib.error import HTTPError import jwt ROOT=Path('/home/worsch/railiance-platform') -RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-renewed-approval-requests.json' +RECEIPT=ROOT/'docs/evidence/2026-09-27-t03-approval-requests.json' POLICY='workload-kv-read-secrets-engine-requester-client' KV='platform/data/workloads/secrets-engine/approval-requester' ISSUER='https://kc.coulomb.social' diff --git a/scripts/t03-attended-delivery.py b/scripts/t03-attended-delivery.py index f67fa32..ca49a05 100644 --- a/scripts/t03-attended-delivery.py +++ b/scripts/t03-attended-delivery.py @@ -5,7 +5,7 @@ from datetime import datetime,timezone from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler ROOT=Path('/home/worsch/railiance-platform') spec=importlib.util.spec_from_file_location('preflight',ROOT/'scripts/approval-client-reader-preflight.py');pre=importlib.util.module_from_spec(spec);spec.loader.exec_module(pre) -RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-attended-delivery.json' +RECEIPT=ROOT/'docs/evidence/2026-09-27-t03-attended-delivery.json' class NoRedirect(HTTPRedirectHandler): def redirect_request(self,*args,**kwargs):return None @@ -19,7 +19,7 @@ if RESUME:RECEIPT=ROOT/'docs/evidence/2026-09-16-t03-resume-attended-delivery.js def main(receipt): require(Path.home().parent.name=='.warden-attended-login' and not os.getenv('BAO_TOKEN') and not os.getenv('VAULT_TOKEN'),'attended_reader_required') - require(not Path('/home/worsch/secrets-engine/docs/evidence/'+('2026-09-16-t03-resume-exec.json' if RESUME else '2026-09-15-t03-native-execution.json')).exists(),'execution_receipt_requires_reconciliation') + require(not Path('/home/worsch/secrets-engine/docs/evidence/'+('2026-09-16-t03-resume-exec.json' if RESUME else '2026-09-27-t03-native-execution.json')).exists(),'execution_receipt_requires_reconciliation') identity=pre.bao('token','lookup','-format=json')['data'];pre.validate_identity(identity) for path,expected in pre.EXPECTED.items():require(sorted(pre.bao('token','capabilities','-format=json',path))==expected,'reader_scope_failed') runtime=Path('/run/user')/str(os.getuid());private(runtime,True) diff --git a/scripts/t03-native-execution.py b/scripts/t03-native-execution.py index 6e21695..93b8da2 100644 --- a/scripts/t03-native-execution.py +++ b/scripts/t03-native-execution.py @@ -16,10 +16,12 @@ from secrets_engine.decisions import resolve_decision,require_approved from secrets_engine.openbao import OpenBaoClient from secrets_engine.exec_owner import validate_delivery_target from secrets_engine.plan import build_plan -RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-native-execution.json' +RECEIPT=ROOT/'docs/evidence/2026-09-27-t03-native-execution.json' KUBE=['kubectl','--kubeconfig','/home/worsch/.kube/config-railiance01'] ROLE='se-prod-openrouter-llm-connect'; LANE='openrouter-llm-connect' -IDS={'apply':'9935335c-8e9a-566e-a48e-6a5b5f4882eb','verify':'273d6882-6253-5dc9-ac54-544f92ef5e56','exec':'7ba0c13b-68cd-5b3e-9481-42ba9e385e68'} +PRIOR_IDS={'apply':'9935335c-8e9a-566e-a48e-6a5b5f4882eb','verify':'273d6882-6253-5dc9-ac54-544f92ef5e56','exec':'7ba0c13b-68cd-5b3e-9481-42ba9e385e68'} +IDS={'apply':'b5fcbfcc-ad56-456f-bc36-c823052e9917','verify':'4e9ff881-d59f-4143-9155-929afbe8dd43','exec':'a24e0898-b3c5-4e50-85a8-18a2c2aa229e'} +LIMITS={'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8} def require(value,code): if not value:raise ValueError(code) @@ -86,10 +88,18 @@ def verify_session_cleanup(client): del probe_token return {'unrelated_path_denied':True,'session_revocation_verified':True,'revoked_token_lookup_status':403} +def native_apply_disposition(policy_hcl,existing,role): + if existing is None and role is None:return 'absent' + require(isinstance(existing,str) and isinstance(role,dict) and existing.strip()==policy_hcl.strip() and all(role.get(k)==v for k,v in LIMITS.items()) and role.get('token_policies')==[ROLE],'existing_native_objects_require_reconciliation') + return 'satisfied' +def fresh_apply_plan(policy_hcl,existing,role): + if native_apply_disposition(policy_hcl,existing,role)=='absent':return tuple(IDS),None + return ('verify','exec'),{'action':'apply','already_satisfied':True,'limits':dict(LIMITS)} + def resume_receipt(): prior=json.loads((ROOT/'docs/evidence/2026-09-15-t03-native-execution.json').read_text()) require(prior.get('phase')=='verify_attempt_started' and prior.get('failure_code')=='revoked_token_still_usable','unexpected_prior_failure') - require(prior.get('actions')==[{'action':'apply','approval_id':IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}],'prior_apply_not_verified') + require(prior.get('actions')==[{'action':'apply','approval_id':PRIOR_IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}],'prior_apply_not_verified') return prior def admin(directory,negative,resume=False): @@ -122,7 +132,14 @@ def admin(directory,negative,resume=False): fd=os.open(directory/'pdp-caller',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) with os.fdopen(fd,'w') as out:out.write(run(*KUBE,'-n','secrets-engine','create','token','secrets-engine','--audience=flex-auth','--duration=10m').strip()) # Validate real claims and exact current decisions before any consumption. + # Fresh apply is classified first. A match is left out of this loop so the new apply approval is not claimed or consumed. actions=('exec',) if resume else tuple(IDS) + if not resume: + client=OpenBaoClient.resolve('http://127.0.0.1:18200') + existing=client.read_policy(ROLE) + role=bao('read','-format=json','auth/approle/role/'+ROLE)['data'] if client.approle_exists(ROLE) else None + actions,satisfied=fresh_apply_plan(build_plan(entries['apply'],'prod').policy_hcl,existing,role) + if satisfied:receipt['actions'].append(satisfied) for action in actions: auth=authorize_action(configs[action],entries[action],action,fields=() if action=='apply' else tuple(entries[action].fields),policy_targets=(ROLE,),auth_targets=(ROLE,)) require(auth is not None,'native_authorization_missing') @@ -135,13 +152,13 @@ def admin(directory,negative,resume=False): require(existing is not None and existing.strip()==plan.policy_hcl.strip(),'applied_policy_drift') role=bao('read','-format=json','auth/approle/role/'+ROLE)['data'] require(all(role.get(k)==v for k,v in prior['actions'][0]['limits'].items()) and role.get('token_policies')==[ROLE],'applied_role_drift') - code="import json;from approval_engine.store import Engine;e=Engine('/data/approvals.sqlite');print(json.dumps({k:e.claim(v) for k,v in "+repr(IDS)+".items()}))" + code="import json;from approval_engine.store import Engine;e=Engine('/data/approvals.sqlite');print(json.dumps({k:e.claim(v) for k,v in "+repr(PRIOR_IDS)+".items()}))" claims=json.loads(run(*KUBE,'-n','approval-engine','exec','statefulset/approval-engine','--','python','-c',code)) - require(all(claims[a]['consumed'] and claims[a]['approval_id']==IDS[a] for a in ('apply','verify')) and claims['exec']['valid_now'] and not claims['exec']['consumed'],'resume_approval_state_mismatch') + require(all(claims[a]['consumed'] and claims[a]['approval_id']==PRIOR_IDS[a] for a in ('apply','verify')) and claims['exec']['valid_now'] and not claims['exec']['consumed'],'resume_approval_state_mismatch') receipt['prior_receipt']='2026-09-15-t03-native-execution.json' receipt['completed_actions_not_replayed']=['apply','verify'] receipt['supplemental_cleanup']=verify_session_cleanup(client);save(receipt) - else: + elif 'apply' in actions: require(existing is None and not client.approle_exists(ROLE),'existing_native_objects_require_reconciliation') for action in actions: receipt['phase']=action+'_attempt_started';save(receipt) @@ -156,8 +173,7 @@ def admin(directory,negative,resume=False): row={'action':action,'approval_id':IDS[action],'exit_code':rc} if action=='apply': role=bao('read','-format=json','auth/approle/role/'+ROLE)['data'] - limits={'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8} - require(all(role.get(k)==v for k,v in limits.items()) and role.get('token_policies')==[ROLE],'role_limits_mismatch');row['limits']=limits + require(all(role.get(k)==v for k,v in LIMITS.items()) and role.get('token_policies')==[ROLE],'role_limits_mismatch');row['limits']=dict(LIMITS) elif action=='verify': row.update(verify_session_cleanup(client)) else: diff --git a/tests/test_t03_revocation_probe.py b/tests/test_t03_revocation_probe.py index 589fdb1..044918e 100644 --- a/tests/test_t03_revocation_probe.py +++ b/tests/test_t03_revocation_probe.py @@ -28,11 +28,22 @@ class TestProbe(unittest.TestCase): def test_server_failure_is_not_revocation_proof(self): with self.assertRaisesRegex(ValueError,'revocation_probe_not_definitive'):self.check(500) def test_reconciled_prior_phase(self): - prior={'phase':'verify_attempt_started','failure_code':'revoked_token_still_usable','actions':[{'action':'apply','approval_id':m.IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}]} + prior={'phase':'verify_attempt_started','failure_code':'revoked_token_still_usable','actions':[{'action':'apply','approval_id':m.PRIOR_IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}]} with patch.object(Path,'read_text',return_value=json.dumps(prior)): self.assertEqual(m.resume_receipt(),prior) prior['actions'][0]['approval_id']='other' with patch.object(Path,'read_text',return_value=json.dumps(prior)): with self.assertRaisesRegex(ValueError,'prior_apply_not_verified'):m.resume_receipt() + def test_fresh_apply_branch(self): + hcl='path "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n' + role=dict(m.LIMITS,token_policies=[m.ROLE]) + self.assertEqual(m.fresh_apply_plan(hcl,None,None),(tuple(m.IDS),None)) + actions,row=m.fresh_apply_plan(hcl,hcl+'\n',role) + self.assertEqual(actions,('verify','exec')) + self.assertNotIn('apply',actions) + self.assertEqual(row,{'action':'apply','already_satisfied':True,'limits':dict(m.LIMITS)}) + self.assertNotIn('approval_id',row) + for existing,live in ((hcl,None),(None,role),('other\n',role),(hcl,dict(role,token_ttl=1)),(hcl,dict(role,token_policies=[m.ROLE,'default'])),(hcl,dict(role,token_policies=m.ROLE))): + with self.assertRaisesRegex(ValueError,'existing_native_objects_require_reconciliation'):m.fresh_apply_plan(hcl,existing,live) if __name__=='__main__':unittest.main()