import assert from 'node:assert/strict' import {X509Certificate} from 'node:crypto' import {checkServerIdentity} from 'node:tls' import {sha} from './operator-files.mjs' export const roleKeys=Object.freeze(['runtime','operator','capture','publisher','control']) export const STARTUP_OPTIONS='-c statement_timeout=10000 -c lock_timeout=5000 -c client_encoding=UTF8' export function assertJsDriverEnvironment(environment){assert(!Object.hasOwn(environment,'NODE_PG_FORCE_NATIVE'),'Native pg driver selector refused')} const hex=v=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v) const exact=(v,keys)=>v&&typeof v==='object'&&!Array.isArray(v)&&Object.keys(v).length===keys.length&&keys.every(k=>Object.hasOwn(v,k)) function parsedApproval(value){ assert(exact(value,['format','manifestSha256','definitionSha256','catalogSha256','expectedHeadSha256','validityMs','expiresAt'])) assert(value.format==='application-writer-publication-approval-v1'&&['manifestSha256','definitionSha256','catalogSha256'].every(k=>hex(value[k]))&&(value.expectedHeadSha256===null||hex(value.expectedHeadSha256))) assert(Number.isSafeInteger(value.validityMs)&&value.validityMs>0&&value.validityMs<=86400000&&Number.isSafeInteger(value.expiresAt)&&value.expiresAt>0) return structuredClone(value) } export function approval(value){ const pinned=parsedApproval(value);assert(pinned.expiresAt>Date.now());return pinned } // Pinned historical inspection data only; never publication authorization. export function reconciliationApproval(value){return parsedApproval(value)} export function poolOptions(transport,request,ca){ assert(exact(transport,['host','port','user','password','database'])) assert(typeof transport.host==='string'&&transport.host===request.tls.serverName&&Number.isSafeInteger(transport.port)&&transport.port>0&&transport.port<=65535&&transport.user===request.roles.publisher.name&&typeof transport.password==='string'&&transport.password.length>0&&transport.password.length<=4096&&transport.database===request.source.databaseName) assert(sha(ca)===request.tls.caSha256);new X509Certificate(ca) return {host:transport.host,port:transport.port,user:transport.user,password:transport.password,database:transport.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-writer-publisher',client_encoding:'UTF8', ssl:{ca,servername:request.tls.serverName,rejectUnauthorized:true,checkServerIdentity(host,cert){ const failed=checkServerIdentity(request.tls.serverName,cert);if(failed)return failed try{if(host!==request.tls.serverName||sha(new X509Certificate(cert.raw).publicKey.export({format:'der',type:'spki'}))!==request.tls.spkiSha256)return Error('Publisher TLS SPKI refused')}catch{return Error('Publisher TLS certificate refused')} }}} } export function assertPublicationApproval(current,catalogue,structure,pinned,manifestSha256){ assert(pinned.expiresAt>Date.now()&&pinned.manifestSha256===manifestSha256&&pinned.definitionSha256===catalogue.definitionSha256) assert(sha(JSON.stringify({base:catalogue.catalogSha256,security:structure.sha256}))===pinned.catalogSha256) assert((current?.headSha256??null)===pinned.expectedHeadSha256) }