2026-10-13 16:58:15 +02:00
'use strict' ;
2026-10-13 18:04:02 +02:00
const { validateDownloadEvidence , validateInteractionEvidence } = require ( './evidence-capture.cjs' );
function evidenceType ( record ){ return record ? . protectedEvidence ? . type ?? 'download' ;}
function policyAllows ( context , type = 'download' ){ return [ 'download' , 'interaction' ]. includes ( type ) && context ? . policy ? . evidence ? . enabled === true && context . policy . evidence [ type === 'download' ? 'captureDownloads' : 'captureInteractions' ] === true && Date . parse ( context . policy . expiresAt ) > Date . now ();}
function collectionAllowed ( context ){ return !! context ? . ready && policyAllows ( context , context ? . type ?? 'download' );}
function validatedEvidence ( payload , type = 'download' ){ if ( ! ( type === 'download' ? validateDownloadEvidence ( payload ) : type === 'interaction' && validateInteractionEvidence ( payload ))) throw Error ( 'Invalid protected evidence' ); return structuredClone ( payload );}
2026-10-13 23:19:22 +02:00
// eslint-disable-next-line no-unused-vars -- Destructure only to strip protected evidence before releasing ordinary event metadata.
2026-10-13 18:04:02 +02:00
function stripPendingEvidence ( queue , context ){ return queue . map ( record =>{ if ( ! record . protectedEvidence ) return record ; if ( context && policyAllows ( context , evidenceType ( record ))) return record ; const { protectedEvidence ,... event } = record ; return event ;});}
2026-10-13 16:58:15 +02:00
async function deliverAuditRecord ({ record , send , getContext }){
const { protectedEvidence ,... event } = record ; await send ( '/api/device/events' , event );
if ( ! protectedEvidence ) return { eventDelivered : true , evidenceDelivered : false };
2026-10-13 18:04:02 +02:00
const type = evidenceType ( record );
function state (){ const context = getContext (); if ( protectedEvidence . deviceId !== context ? . deviceId || context ? . policy &&! policyAllows ( context , type )) return 'discard' ; if ( ! context ? . ready ||! context . policy ) return 'pause' ; return 'allow' ;}
const current = state (); if ( current === 'discard' ) return { eventDelivered : true , evidenceDiscarded : true }; if ( current === 'pause' ) throw Error ( 'Evidence delivery paused until managed context is ready' );
const payload = validatedEvidence ( protectedEvidence . payload , type );
if ( type === 'interaction' ){
2026-10-13 18:24:44 +02:00
const expectedKind = payload . captureSource === 'guarded-clipboard' ? `clipboard. ${ payload . action } ` : payload . captureSource === 'human-observer' ? 'browser.interaction' : 'agent.action' ;
2026-10-13 18:04:02 +02:00
if ( event . kind !== expectedKind || payload . captureSource === 'guarded-clipboard' &&! [ 'copy' , 'cut' , 'paste' ]. includes ( payload . action ) || payload . captureSource === 'agent-adapter' && payload . action !== 'click' ) throw Error ( 'Invalid protected evidence source' );
}
try { const result = await send ( '/api/device/evidence' ,{ eventId : event . eventId , type , payload });
// A successful network response must not resurrect collection after a
// concurrent policy revocation or a change of enrolled device.
const after = state (); if ( after === 'discard' ) return { eventDelivered : true , evidenceDiscarded : true }; if ( after === 'pause' ) throw Error ( 'Evidence delivery paused until managed context is ready' );
if ( ! /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i . test ( result ? . id || '' ) ||! Number . isSafeInteger ( result . sourceEventId ) || result . sourceEventId < 1 ) throw Error ( 'Evidence acknowledgement invalid' ); return { eventDelivered : true , evidenceDelivered : true };}
2026-10-13 16:58:15 +02:00
catch ( error ){ if ( error . status === 403 ) return { eventDelivered : true , evidenceDiscarded : true }; throw error ;}
}
2026-10-13 18:04:02 +02:00
module . exports = { validatedEvidence , collectionAllowed , stripPendingEvidence , deliverAuditRecord , evidenceType , policyAllows };