test('automatic interaction capture requires observed completion and excludes path and sensitive data',()=>{constf=fixture();assert.equal(f.payload.pageUrl,'https://example.com/');assert.equal(validateInteractionEvidence(f.payload),true);for(constextraof[{content:'secret'},{contentSha256:'a'.repeat(64)},{target:{tag:'INPUT',label:'secret'}},{sensitiveField:true},{outcome:'authorized'},{captureSource:'page'},{bytes:16777217}])assert.equal(validateInteractionEvidence({...f.payload,...extra}),false);assert.equal(buildInteractionEvidence({action:'copy',sessionId,url:'https://example.com',sensitiveField:false,outcome:'authorized',captureSource:'guarded-clipboard'},f.context.policy),null);});
test('type binding rejects cross-type payloads without attempting evidence send',async()=>{constf=fixture();assert.throws(()=>validatedEvidence(f.payload,'download'),/Invalid/);f.record.protectedEvidence.type='download';constcalls=[];awaitassert.rejects(deliverAuditRecord({record:f.record,getContext:()=>f.context,send:asyncroute=>{calls.push(route);return{};}}),/Invalid/);assert.deepEqual(calls,['/api/device/events']);});
test('partial disable discards interaction tuples while preserving pending legacy downloads',()=>{constf=fixture();f.context.policy.evidence.captureInteractions=false;assert.equal(collectionAllowed({...f.context,type:'interaction'}),false);assert.equal(collectionAllowed({...f.context,type:'download'}),true);constdownload={eventId:'download',protectedEvidence:{deviceId:'device-a',payload:{}}};constresult=stripPendingEvidence([f.record,download],f.context);assert.equal('protectedEvidence'inresult[0],false);assert.equal(result[1],download);assert.ok(f.record.protectedEvidence);});
test('revocation during ACK never marks evidence delivered and replay preserves original tuple',async()=>{constf=fixture(),snapshot=structuredClone(f.record),bodies=[];constsend=async(route,body)=>{bodies.push(body);if(route.endsWith('/evidence')){f.context.policy.evidence.captureInteractions=false;return{id:sessionId,sourceEventId:1};}return{};};assert.deepEqual(awaitdeliverAuditRecord({record:f.record,getContext:()=>f.context,send}),{eventDelivered:true,evidenceDiscarded:true});assert.deepEqual(f.record,snapshot);assert.equal(bodies[1].type,'interaction');assert.equal('protectedEvidence'inbodies[0],false);});
test('native hook target tags are canonicalized without accepting labels or arbitrary DOM data',()=>{constf=fixture(),base={action:'click',sessionId,url:'https://example.com/page',sensitiveField:false,outcome:'completed',captureSource:'agent-adapter'};for(consttagof['a','button','input','textarea','select','div','span','p','body','unknown']){constvalue=buildInteractionEvidence({...base,target:{tag,role:'menuitem',x:12,y:34}},f.context.policy);assert.equal(value.target.tag,tag.toUpperCase());assert.equal(value.target.role,'menuitem');assert.deepEqual([value.target.x,value.target.y],[12,34]);}for(consttargetof[{tag:'button',label:'secret'},{tag:'a',selector:'#secret'},{tag:'body',text:'secret'},{tag:'script'},{tag:'button',x:Infinity},{tag:'button',y:-1}])assert.equal(buildInteractionEvidence({...base,target},f.context.policy),null);assert.equal(buildInteractionEvidence({...base,target:{tag:'select',role:'option'}},f.context.policy).target.role,'option');});