Files
safetynet/apps/browser/broker-audit-persistence.cjs
T

41 lines
2.3 KiB
JavaScript

'use strict';
const {BrokerAuditEventSchema}=require('../../packages/core/broker-audit.cjs');
// Parent-only adapter. persistQueue must synchronously encrypt, write, fsync and
// commit the complete queue, return undefined on success and throw on failure.
// An OTLP send, boolean result or asynchronous enqueue is insufficient.
// Bind current() to the authenticated child and the accepted policy generation.
function createBrokerAuditPersistence({current,policyVersion,getQueue,persistQueue,onFailure}={}){
if(![current,getQueue,persistQueue,onFailure].every(fn=>typeof fn==='function')||!Number.isSafeInteger(policyVersion)||policyVersion<1)throw Error('broker-audit-parent-invalid');
let failed=false;
const fail=()=>{const first=!failed;failed=true;if(first)try{onFailure();}catch{}throw Error('broker-audit-parent-unavailable');};
const active=()=>{try{return !failed&&current()===true;}catch{return false;}};
return Object.freeze({
persist(input){
if(!active())return fail();
let event,queue;
try{
let nodes=0;
const plain=(value,depth=0)=>{
if(++nodes>128||depth>6)return false;
if(!value||typeof value!=='object')return true;
if(![Object.prototype,null].includes(Object.getPrototypeOf(value)))return false;
return Object.values(Object.getOwnPropertyDescriptors(value)).every(item=>'value'in item&&plain(item.value,depth+1));
};
if(!input||typeof input!=='object'||!plain(input))return fail();
event=BrokerAuditEventSchema.parse(input);queue=getQueue();
if(event.policyVersion!==policyVersion||!Array.isArray(queue)||queue.length>=10000||queue.some(item=>item.eventId===event.eventId))return fail();
Object.freeze(event.request);if(event.file)Object.freeze(event.file);if(event.broker)Object.freeze(event.broker);Object.freeze(event);
queue.push(event);
const result=persistQueue();
// Fail closed for an accidentally asynchronous writer; suppress its rejected
// promise without issuing a receipt or retrying uncertain persistence.
if(result&&typeof result.then==='function'){Promise.resolve(result).catch(()=>{});return fail();}
if(result!==undefined)return fail();
if(!active())return fail();
return Object.freeze({persisted:true,eventId:event.eventId});
}catch{return fail();}
}
});
}
module.exports={createBrokerAuditPersistence};