"""Pure proposed grant projection: source provenance, never approval/native proof.""" import copy from pathlib import Path import sys import pytest sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) import task_execution_grant as grant @pytest.fixture def record(): return {'id':'CCR-FIXTURE-GRANT','kind':'credential-change-request','schema_version':1, 'request_type':'task-execution-grant','title':'Proposed source fixture','status':'proposed', 'created':'2026-10-16','updated':'2026-10-16','requester':{'agent':'fixture','reason':'Local proof'}, 'review':{'required':True,'required_approvers':['railiance-platform-owner','secrets-engine-owner','access-engine-owner','approval-engine-owner'],'comments':[]}, 'target':{'domain':'infotech','tenant':'platform','workload':'secrets-engine','environment':'production','purpose':'fixture'}, 'execution':{'action':'execute-ccr','descriptor_binding':{'operation_deadline':'2026-10-16T12:00:00Z','ccr_immutable_sha256':'sha256:'+'a'*64}, 'source_pins':{'/fixture/source':'sha256:'+'b'*64},'cleanup':[{'kind':'task-token-revoke','call_digest':'c'*64}]}, 'openbao':{'policy':{'name':'task-exec-fixture','hcl':'exact owner generated HCL'}, 'token_create_body':{'ttl':'120s','num_uses':12,'renewable':False}},'evidence':{'events':[]}} def comment():return {'at':'2026-10-16T12:00:00Z','reviewer':'railiance-platform-owner','decision':'approved','comment':'Source only; no execution authority'} def event():return {'at':'2026-10-16T12:00:00Z','kind':'test','result':'unknown','reference_sha256':'sha256:'+'e'*64} def test_only_typed_review_comments_and_evidence_events_are_excluded(record): changed=copy.deepcopy(record);changed['review']['comments'].append(comment());changed['evidence']['events'].append(event()) assert grant.immutable_digest(changed)==grant.immutable_digest(record) grant.require_append_only(record,changed) assert record['review']['comments']==[] # detached immutable projection @pytest.mark.parametrize('field',['status','updated','title','requester','target','execution','openbao','review']) def test_every_other_root_or_effectful_field_changes_digest(record,field): changed=copy.deepcopy(record) if field=='status':changed[field]='approved' elif field=='updated':changed[field]='2026-10-17' elif field=='title':changed[field]='different title' elif field=='requester':changed[field]['reason']='different requester reason' elif field=='target':changed[field]['purpose']='different purpose' elif field=='execution':changed[field]['source_pins']['/fixture/source']='sha256:'+'f'*64 elif field=='openbao':changed[field]['policy']['hcl']='different HCL' elif field=='review':changed[field]['required_approvers'].reverse() if field=='review': with pytest.raises(grant.Refused):grant.immutable_digest(changed) else: assert grant.immutable_digest(changed)!=grant.immutable_digest(record) with pytest.raises(grant.Refused):grant.require_append_only(record,changed) @pytest.mark.parametrize('location,key',[('root','audit'),('root','receipts'),('root','evidence_exclusions'),('evidence','files'),('evidence','policy'),('review','approval_ref'),('review','exclusions')]) def test_guessed_evidence_exclusions_or_unknown_fields_refuse(record,location,key): changed=copy.deepcopy(record) (changed if location=='root' else changed[location])[key]=[] with pytest.raises(grant.Refused):grant.immutable_digest(changed) def test_append_only_rejects_removal_rewrite_and_reordering(record): previous=copy.deepcopy(record);previous['review']['comments']=[comment(),dict(comment(),comment='Second')] previous['evidence']['events']=[event()] for field,value in [('comments',[]),('comments',[dict(comment(),decision='declined')]),('comments',list(reversed(previous['review']['comments']))),('events',[])]: changed=copy.deepcopy(previous);changed['review' if field=='comments' else 'evidence'][field]=value assert grant.immutable_digest(changed)==grant.immutable_digest(previous) with pytest.raises(grant.Refused):grant.require_append_only(previous,changed) @pytest.mark.parametrize('change',['policy','token_budget','cleanup','source','deadline','target_ccr','wrong_hash','grant_id']) def test_bind_reconstructs_effects_and_exact_existing_descriptor_join(record,change): expected=copy.deepcopy(record);changed=copy.deepcopy(record);digest=grant.immutable_digest(record);grant_id=record['id'] if change=='policy':changed['openbao']['policy']['hcl']='path "*" {}' elif change=='token_budget':changed['openbao']['token_create_body']['num_uses']=13 elif change=='cleanup':changed['execution']['cleanup'][0]['kind']='kv-delete' elif change=='source':changed['execution']['source_pins']['/fixture/source']='sha256:'+'f'*64 elif change=='deadline':changed['execution']['descriptor_binding']['operation_deadline']='2027-01-01T00:00:00Z' elif change=='target_ccr':changed['execution']['descriptor_binding']['ccr_immutable_sha256']='sha256:'+'f'*64 elif change=='wrong_hash':digest='sha256:'+'f'*64 elif change=='grant_id':grant_id='CCR-OTHER' with pytest.raises(grant.Refused):grant.bind(changed,grant_id=grant_id,expected_execution=expected['execution'],expected_openbao=expected['openbao'],expected_digest=digest) def test_proposed_source_record_can_bind_without_becoming_approved(record): projection=grant.bind(record,grant_id=record['id'],expected_execution=record['execution'],expected_openbao=record['openbao'],expected_digest=grant.immutable_digest(record)) assert projection['status']=='proposed' @pytest.mark.parametrize('change',['schema_bool','comment_extra','event_extra','unaware_comment','event_missing','nan','tuple','integer_key','status_list']) def test_closed_types_refuse_instead_of_silent_coercion(record,change): if change=='schema_bool':record['schema_version']=True elif change=='comment_extra':record['review']['comments']=[dict(comment(),policy='unreviewed')] elif change=='event_extra':record['evidence']['events']=[dict(event(),token='never accepted')] elif change=='unaware_comment':record['review']['comments']=[dict(comment(),at='2026-10-16T12:00:00')] elif change=='event_missing':record['evidence']['events']=[{'kind':'test'}] elif change=='nan':record['execution']['value']=float('nan') elif change=='tuple':record['execution']['value']=(1,2) elif change=='integer_key':record['execution'][1]='invalid' elif change=='status_list':record['status']=[] with pytest.raises(grant.Refused):grant.immutable_digest(record)