import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { mkdir, readFile, writeFile, rm, mkdtemp } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createHash } from 'node:crypto'; import { inspection, receiptFor, sendInspection, DestinationError } from '../lib/inspection-demo.ts'; // Real loopback HTTP transport; a file-backed destination fixture. No vendor connection. const dir = await mkdtemp(join(tmpdir(), 'goodhandoff-inspection-test-')); const file = join(dir, 'destination.json'); let mode = 'none', writes = 0, wrongJob = false, requests = []; const json = async () => JSON.parse(await readFile(file, 'utf8')); const server = createServer(async (req, res) => { const route = new URL(req.url, 'http://fixture'); requests.push({ method: req.method, route: route.pathname, at: new Date().toISOString() }); const reply = (code, value) => { res.writeHead(code, {'Content-Type':'application/json'}); res.end(JSON.stringify(value)); }; try { if (req.method === 'GET' && route.pathname.startsWith('/jobs/')) return reply(200, { id: wrongJob ? 'DEMO-OTHER' : inspection.jobId }); if (req.method === 'GET' && route.pathname.startsWith('/receipts/')) { const records = await json(); return reply(200, records[decodeURIComponent(route.pathname.slice(10))] || null); } if (req.method === 'POST' && route.pathname === '/receipts') { writes++; let body = ''; for await (const chunk of req) body += chunk; const value = JSON.parse(body); if (mode === 'unavailable') return reply(503, {error:'Fixture write unavailable'}); const records = await json(), prior = records[value.key]; if (prior && JSON.stringify(prior) !== JSON.stringify(value)) return reply(409, {error:'Conflicting approved version'}); records[value.key] = value; await writeFile(file, JSON.stringify(records)); if (mode === 'response-lost') { mode = 'none'; res.destroy(); return; } return reply(201, {accepted:true}); } reply(404, {error:'Fixture route not found'}); } catch { reply(500, {error:'Fixture failure'}); } }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const base = `http://127.0.0.1:${server.address().port}`; const api = { getJob: async id => (await fetch(base+'/jobs/'+encodeURIComponent(id))).json(), find: async key => (await fetch(base+'/receipts/'+encodeURIComponent(key))).json(), write: async receipt => { let response; try { response = await fetch(base+'/receipts', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(receipt)}); } catch { throw new DestinationError(false); } if (!response.ok) throw new DestinationError(true); }, }; const results=[]; async function reset(failure='none') { mode=failure;writes=0;wrongJob=false;requests=[];await writeFile(file,'{}'); } async function check(name, scenario) { const evidence = await scenario(); results.push({name,result:'PASS',...evidence,requests:[...requests],destinationReadback:await json()}); process.stdout.write(`PASS: ${name}\n`); } try { await check('Unapproved inspection is held before any destination request', async()=>{ await reset(); const result=await sendInspection({...inspection,approved:false},api); assert.equal(result.status,'held');assert.equal(requests.length,0);assert.deepEqual(await json(),{}); return {outcomes:[result],writeRequests:writes,savedNotes:0,assignedTasks:0}; }); await check('Mismatched destination job is held without a write',async()=>{ await reset();wrongJob=true;const result=await sendInspection(inspection,api); assert.equal(result.status,'held');assert.equal(writes,0);assert.deepEqual(await json(),{}); return {outcomes:[result],writeRequests:writes,savedNotes:0,assignedTasks:0}; }); await check('Rejected write stays pending; recovery saves one note and one task',async()=>{ await reset('unavailable');const first=await sendInspection(inspection,api); assert.equal(first.status,'pending');assert.deepEqual(await json(),{}); mode='none';const second=await sendInspection(inspection,api);const records=await json(); assert.equal(second.status,'confirmed');assert.equal(Object.keys(records).length,1);assert.deepEqual(Object.values(records)[0],receiptFor(inspection));assert.equal(writes,2); return {outcomes:[first,second],afterFailure:{savedNotes:0,assignedTasks:0},writeRequests:writes,savedNotes:1,assignedTasks:1}; }); await check('Lost response is reconciled without a second write',async()=>{ await reset('response-lost');const first=await sendInspection(inspection,api);const before=await json(); assert.equal(first.status,'uncertain');assert.equal(Object.keys(before).length,1); const second=await sendInspection(inspection,api);assert.equal(second.status,'confirmed');assert.equal(writes,1);assert.deepEqual(await json(),before); return {outcomes:[first,second],afterFailure:{savedNotes:1,assignedTasks:1},writeRequests:writes,savedNotes:1,assignedTasks:1}; }); await check('Sending the same approved inspection again creates no duplicate',async()=>{ await reset();const first=await sendInspection(inspection,api);const before=await json();const second=await sendInspection(inspection,api); assert.equal(first.status,'confirmed');assert.equal(second.status,'confirmed');assert.equal(writes,1);assert.deepEqual(await json(),before); return {outcomes:[first,second],writeRequests:writes,savedNotes:1,assignedTasks:1}; }); await check('Conflicting content under the same approved version is held',async()=>{ await reset();await sendInspection(inspection,api);const before=await json();const result=await sendInspection({...inspection,note:'Changed without a new approval version'},api); assert.equal(result.status,'held');assert.equal(writes,1);assert.deepEqual(await json(),before); return {outcomes:[result],writeRequests:writes,savedNotes:1,assignedTasks:1}; }); const engine=await readFile(new URL('../lib/inspection-demo.ts',import.meta.url)); const harness=await readFile(new URL('./inspection-demo.test.mjs',import.meta.url)); const report={label:'Inspection handoff: executed synthetic HTTP tests',runAt:new Date().toISOString(),boundary:'Same handoff engine used by the browser demonstration, tested through real loopback HTTP requests against a local file-backed mock CRM. No vendor API, authentication system, real upload, customer message or production customer record was tested. The mock destination provides an atomic note/task write and receipt lookup; real CRM capabilities must be assessed separately.',engineSha256:createHash('sha256').update(engine).digest('hex'),harnessSha256:createHash('sha256').update(harness).digest('hex'),passed:results.length,total:results.length,checks:results}; await mkdir(new URL('../public/proof/inspection-demo/',import.meta.url),{recursive:true}); await writeFile(new URL('../public/proof/inspection-demo/results.json',import.meta.url),JSON.stringify(report,null,2)+'\n'); await writeFile(new URL('../public/proof/inspection-demo/engine.txt',import.meta.url),engine); await writeFile(new URL('../public/proof/inspection-demo/test-harness.txt',import.meta.url),harness); process.stdout.write(`Recorded ${results.length}/${results.length} passing checks and independent destination readbacks.\n`); } finally { server.closeAllConnections();await new Promise(resolve=>server.close(resolve));await rm(dir,{recursive:true,force:true}); }