// @requirements REQ-EU-ORDER-CONFIRMATION REQ-PRODUCT-ORDER-CONFIRMATION REQ-PRODUCT-EVIDENCE-LIFECYCLE REQ-PRODUCT-CF-ADMIN import {test} from 'node:test'; import assert from 'node:assert/strict'; import {createSyntheticStorage} from '../examples/synthetic.mjs'; import {createPaidOrderDefinition,createPaidOrderReview} from 'notabenda-authoring/orders'; import {archivePaidOrderReview,activatePaidOrderReview,admitPaidOrder,getPaidOrderAttempt,paidOrderCheckoutRef,createPaidOrderStore} from 'notabenda-cf/orders'; import {preparePaidOrderConfirmation,readPaidOrderConfirmation,readPaidOrderConfirmationFile,claimPaidOrderConfirmation,settlePaidOrderConfirmation,cancelPaidOrderConfirmation,dispatchPaidOrderConfirmation} from 'notabenda-cf/order-confirmations'; import {createEvidenceLifecycle,createAdminWorker,createEvidenceExportSegment} from 'notabenda-cf'; const at='2026-10-19T12:00:00.000Z',encode=s=>new TextEncoder().encode(s),reject=(p,code)=>assert.rejects(p,e=>e.code===code),query={query:'',kind:'',from:'',to:'',keyPrefix:'',limit:50}; async function fixture({checkoutId='intent-one',storage=createSyntheticStorage([])}={}){ const clock={value:at},context={siteId:'confirmation-shop',scopeId:'checkout',checkoutId,now:()=>new Date(clock.value)},ref=await paidOrderCheckoutRef(context),definition=createPaidOrderDefinition({profile:'eu-paid-distance-order-v1',siteId:context.siteId,locale:'en',direction:'ltr',reviewStatus:'reviewed',documents:[],items:[{id:'service',productId:'consultation',name:'Planning appointment',kind:'service',characteristics:['One remote appointment'],commitment:{duration:'One appointment',termination:'No renewal',minimum:'One appointment'},guarantee:null}]}),fixed=amountMinor=>({kind:'fixed',amountMinor}),quote={quoteId:'quote-one',revision:'one',contextRef:ref,definitionId:definition.definitionId,issuedAt:at,expiresAt:'2026-10-19T12:15:00.000Z',lines:[{itemId:'service',quantity:1}],pricing:{currency:'EUR',fractionDigits:2,payments:[{id:'appointment',lineIds:['service'],label:'Appointment',amount:fixed('12000'),due:{kind:'now'},recurrence:{kind:'once'}}],totalPrice:fixed('12000'),dueNow:fixed('12000'),taxAndChargesNotice:'VAT included'}},review=createPaidOrderReview(definition,quote),request={schemaVersion:1,requestId:crypto.randomUUID(),reviewId:review.reviewId,definitionId:definition.definitionId,quoteId:quote.quoteId,quoteRevision:quote.revision,contextRef:ref}; await archivePaidOrderReview(storage,review,context);await activatePaidOrderReview(storage,{reviewId:review.reviewId,expectedRevision:0},context);const receipt=await admitPaidOrder(storage,request,context,{hostPrefix:'shop/stock/',async decide(){return {status:'admit',writes:[]};}}),base=`notabenda/orders/${context.siteId}/intents/${ref}/`,body='Contract C-1042\nPrepared for Alex Morgan\nOne planning appointment. Total EUR 120 including VAT.\nFull reviewed contract, trader, delivery and withdrawal information supplied by the host fixture.\n'; const bundle={request,contract:{reference:'C-1042',concludedAt:at},addressee:{name:'Alex Morgan',recipientRef:'customer-42',destinationRef:'contact-42',destinationVersion:'version-3'},issuedAt:at,locale:'en',reviewStatus:'reviewed',coverage:{selfContained:true,preContractInformation:'included-in-files',digitalContent:{status:'not-applicable',reason:'Service appointment; no digital-content supply.'}},files:[{id:'customer-copy',filename:'confirmation.txt',mediaType:'text/plain',role:'confirmation',bytes:encode(body)},{id:'terms',filename:'terms.html',mediaType:'text/html',role:'attachment',bytes:encode('

Retained terms

')} ]}; return {storage,context,clock,ref,request,receipt,review,base,bundle,body,store:()=>createPaidOrderStore(storage,{siteId:context.siteId,scopeId:context.scopeId})}; } test('confirmation seals personally addressed complete host files and pending job atomically, separately from the original admission',async()=>{ const f=await fixture(),before=await f.storage.get(`notabenda/orders/${f.context.siteId}/events/checkout/${f.receipt.id}`),a=await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);assert.equal(a.delivery.status,'pending');assert.equal(a.manifest.files.length,2);assert.deepEqual(await readPaidOrderConfirmation(f.storage,f.context),a);assert.deepEqual(await readPaidOrderConfirmationFile(f.storage,'customer-copy',f.context),encode(f.body)); assert.deepEqual(await getPaidOrderAttempt(f.storage,f.request,f.context),f.receipt);assert.deepEqual(await f.storage.get(`notabenda/orders/${f.context.siteId}/events/checkout/${f.receipt.id}`),before);assert.equal((await f.store().get(f.receipt.id)).data.confirmationDelivery.status,'pending'); f.clock.value='2020-01-01T00:00:00.000Z';assert.deepEqual(await preparePaidOrderConfirmation(f.storage,f.bundle,f.context),a); for(const mutate of [b=>b.addressee.destinationVersion='version-4',b=>b.files[0].bytes=encode(f.body+'Changed'),b=>b.contract.reference='C-1042-correction']){const b=structuredClone(f.bundle);mutate(b);if(b.contract.reference!=='C-1042')b.files[0].bytes=encode(f.body.replace('C-1042',b.contract.reference));await reject(preparePaidOrderConfirmation(f.storage,b,f.context),'idempotency-conflict');} }); test('receipt-only, unreviewed, incomplete binding, unsupported files and reference-only digital acknowledgments cannot prepare',async()=>{ const f=await fixture();for(const change of [b=>b.files=[],b=>b.reviewStatus='draft',b=>b.coverage.selfContained=false,b=>b.coverage.preContractInformation='linked',b=>b.addressee.destinationVersion='',b=>b.files[0].bytes=encode('Request received.'),b=>b.files[0].mediaType='text/html',b=>b.files[1].filename='../terms.html',b=>b.files[1].filename='CONFIRMATION.TXT',b=>b.files[0].bytes=new Uint8Array([255]),b=>b.coverage.digitalContent={status:'included',evidenceRef:'consent-42'},b=>b.coverage.digitalContent={status:'included',evidenceRef:'consent-42',confirmationText:'Your prior express consent and acknowledgment.'}]){const b=structuredClone(f.bundle);change(b);await reject(preparePaidOrderConfirmation(f.storage,b,f.context),'invalid-confirmation');} const b=structuredClone(f.bundle);b.coverage.digitalContent={status:'included',evidenceRef:'consent-42',confirmationText:'Your prior express consent and acknowledgment.'};b.files[0].bytes=encode(f.body+b.coverage.digitalContent.confirmationText);assert.equal((await preparePaidOrderConfirmation(f.storage,b,f.context)).manifest.coverage.digitalContent.status,'included'); }); test('caller bytes are detached; failed chunk sealing leaves neither files nor outbox',async()=>{ const f=await fixture(),bytes=f.bundle.files[0].bytes,p=preparePaidOrderConfirmation(f.storage,f.bundle,f.context);bytes.fill(0);await p;assert.deepEqual(await readPaidOrderConfirmationFile(f.storage,'customer-copy',f.context),encode(f.body)); const g=await fixture(),transaction=g.storage.transaction;g.storage.transaction=callback=>transaction.call(g.storage,tx=>callback({...tx,async put(k,v){if(k.endsWith('confirmation/delivery'))throw Error('Interrupted queue');return tx.put(k,v);}}));await assert.rejects(preparePaidOrderConfirmation(g.storage,g.bundle,g.context),/Interrupted queue/);assert.equal((await g.storage.list({prefix:g.base+'confirmation/',limit:100})).size,0); }); test('large original files use verified chunks and all payload bounds apply before publication',async()=>{ const f=await fixture();f.bundle.files[1].bytes=encode('x'.repeat(300000));const a=await preparePaidOrderConfirmation(f.storage,f.bundle,f.context),sha=a.manifest.files[1].sha256;assert.equal((await f.storage.list({prefix:f.base+'confirmation/files/'+sha+'/chunks/',limit:100})).size,5);const key=f.base+'confirmation/files/'+sha+'/chunks/0001',bytes=await f.storage.get(key);bytes[0]^=1;await f.storage.put(key,bytes);await reject(claimPaidOrderConfirmation(f.storage,f.context),'invalid-record');assert.equal((await readPaidOrderConfirmation(f.storage,f.context)).delivery.status,'pending'); const g=await fixture();g.bundle.files[1].bytes=new Uint8Array(2097153);await reject(preparePaidOrderConfirmation(g.storage,g.bundle,g.context),'invalid-asset'); }); test('lost provider responses and replacement leases retain exact files, frozen destination and stable downstream ID',async()=>{ const f=await fixture();await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);const received=new Map();let first;const deliver=async({lease})=>{const payload={manifest:lease.archive.manifest,files:lease.files.map(f=>({id:f.id,bytes:[...f.bytes]}))};if(received.has(lease.deliveryId))assert.deepEqual(payload,received.get(lease.deliveryId));else{received.set(lease.deliveryId,payload);first=lease;throw Error('Lost response after provider acceptance');}return {providerRef:'mail-42'};}; assert.deepEqual(await dispatchPaidOrderConfirmation(f.storage,f.context,{deliver}),{provider:'uncertain',settlement:'settled',deliveryId:first.deliveryId}); // A new host wrapper models a later process against the same persisted state. const recovered={get:k=>f.storage.get(k),list:o=>f.storage.list(o),transaction:fn=>f.storage.transaction(fn)};assert.deepEqual(await dispatchPaidOrderConfirmation(recovered,f.context,{deliver}),{provider:'accepted',settlement:'settled',deliveryId:first.deliveryId});assert.equal(received.size,1);assert.deepEqual(await getPaidOrderAttempt(f.storage,f.request,f.context),f.receipt);assert.equal(await claimPaidOrderConfirmation(f.storage,f.context),undefined); }); test('expired/stale leases cannot settle replacements; provider copies cannot change private lease identity',async()=>{ const f=await fixture();await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);const one=await claimPaidOrderConfirmation(f.storage,f.context,{leaseMs:1000});assert.equal(await claimPaidOrderConfirmation(f.storage,f.context),undefined);f.clock.value='2026-10-19T12:00:02.000Z';const two=await claimPaidOrderConfirmation(f.storage,f.context);assert.equal(two.deliveryId,one.deliveryId);assert.notEqual(two.token,one.token);assert.deepEqual(await settlePaidOrderConfirmation(f.storage,one,{status:'accepted',providerRef:'old-provider'},f.context),{settled:false});await settlePaidOrderConfirmation(f.storage,two,{status:'uncertain'},f.context); const result=await dispatchPaidOrderConfirmation(f.storage,f.context,{async deliver({lease}){lease.token=crypto.randomUUID();lease.archive.manifest.addressee.destinationVersion='changed';lease.files[0].bytes.fill(0);return {providerRef:'new-provider'};}});assert.equal(result.settlement,'settled');const stored=await readPaidOrderConfirmation(f.storage,f.context);assert.equal(stored.manifest.addressee.destinationVersion,'version-3');assert.deepEqual(await readPaidOrderConfirmationFile(f.storage,'customer-copy',f.context),encode(f.body)); }); test('dispatch timeout, abort, slow committed claim and lost settlement preserve provider uncertainty',async()=>{ const f=await fixture();await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);const controller=new AbortController();controller.abort();let calls=0;assert.deepEqual(await dispatchPaidOrderConfirmation(f.storage,f.context,{signal:controller.signal,async deliver(){calls++;return {providerRef:'x'};}}),{provider:'not-invoked',settlement:'not-claimed'});assert.equal(calls,0); let finish;const result=await dispatchPaidOrderConfirmation(f.storage,f.context,{leaseMs:1000,timeoutMs:100,deliver:()=>new Promise(resolve=>{finish=resolve;})});assert.equal(result.provider,'uncertain');finish({providerRef:'late'});assert.equal((await readPaidOrderConfirmation(f.storage,f.context)).delivery.status,'uncertain'); const transaction=f.storage.transaction;let delayed=true;f.storage.transaction=async fn=>{const value=await transaction.call(f.storage,fn);if(delayed){delayed=false;f.clock.value='2026-10-19T12:02:00.000Z';}return value;};const late=await dispatchPaidOrderConfirmation(f.storage,f.context,{leaseMs:1000,async deliver(){calls++;return {providerRef:'not-called'};}});assert.equal(late.provider,'not-invoked');assert.equal(calls,0);f.storage.transaction=transaction; const g=await fixture();await preparePaidOrderConfirmation(g.storage,g.bundle,g.context);const tx=g.storage.transaction;let n=0;g.storage.transaction=async fn=>{const value=await tx.call(g.storage,fn);if(++n===2)throw Error('Lost settlement response');return value;};const unknown=await dispatchPaidOrderConfirmation(g.storage,g.context,{async deliver(){return {providerRef:'mail-accepted'};}});assert.equal(unknown.provider,'accepted');assert.equal(unknown.settlement,'unknown');g.storage.transaction=tx;assert.equal((await readPaidOrderConfirmation(g.storage,g.context)).delivery.status,'accepted'); }); test('retirement blocks pending, leased and uncertain confirmation work; cancellation, hold and bounded purge remain scoped',async()=>{ const f=await fixture(),other=await fixture({checkoutId:'other',storage:f.storage});await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);const lifecycle=await createEvidenceLifecycle(f.storage,{family:'orders',siteId:f.context.siteId,scopeId:f.context.scopeId,streamId:f.context.checkoutId},{now:f.context.now}),decision={requestId:crypto.randomUUID(),reason:'operator-erasure',eligibleAt:at,legalHold:false,externalWorkStopped:true};await reject(lifecycle.retire(decision),'pending-delivery');const lease=await claimPaidOrderConfirmation(f.storage,f.context);await reject(lifecycle.retire(decision),'pending-delivery');await settlePaidOrderConfirmation(f.storage,lease,{status:'uncertain'},f.context);await reject(lifecycle.retire(decision),'pending-delivery');await reject(cancelPaidOrderConfirmation(f.storage,{expectedVersion:1,externalWorkStopped:false},f.context),'invalid-delivery');await cancelPaidOrderConfirmation(f.storage,{expectedVersion:1,externalWorkStopped:true},f.context);await lifecycle.retire(decision); for(const operation of [()=>readPaidOrderConfirmation(f.storage,f.context),()=>readPaidOrderConfirmationFile(f.storage,'terms',f.context),()=>claimPaidOrderConfirmation(f.storage,f.context),()=>preparePaidOrderConfirmation(f.storage,f.bundle,f.context)])await reject(operation(),'context-retired');let state;do{state=await lifecycle.purge({requestId:decision.requestId,legalHold:false,limit:2});}while(state.phase!=='complete');assert.equal((await f.storage.list({prefix:f.base,limit:100})).size,0);assert.deepEqual(await getPaidOrderAttempt(other.storage,other.request,other.context),other.receipt); }); test('malformed or mismatched confirmation records fail closed for reads, dispatch and retirement',async()=>{ const f=await fixture();await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);const key=f.base+'confirmation/delivery',original=await f.storage.get(key);await f.storage.put(key,{...original,status:'accepted',providerRef:'fabricated'});await reject(readPaidOrderConfirmation(f.storage,f.context),'invalid-record');await reject(claimPaidOrderConfirmation(f.storage,f.context),'invalid-record');const lifecycle=await createEvidenceLifecycle(f.storage,{family:'orders',siteId:f.context.siteId,scopeId:f.context.scopeId,streamId:f.context.checkoutId},{now:f.context.now});await reject(lifecycle.retire({requestId:crypto.randomUUID(),reason:'operator-erasure',eligibleAt:at,legalHold:false,externalWorkStopped:true}),'invalid-record'); await f.storage.put(key,original);const manifest=await f.storage.get(f.base+'confirmation/manifest');await f.storage.delete(f.base+'confirmation/manifest');await reject(readPaidOrderConfirmation(f.storage,f.context),'invalid-record');await f.storage.put(f.base+'confirmation/manifest',manifest);const other=await fixture({storage:f.storage,checkoutId:'another'});assert.equal(await readPaidOrderConfirmation(f.storage,other.context),undefined); }); test('admin confirmation access is separate, reauthorized on HEAD, escaped and downloaded as inert exact attachments',async()=>{ const f=await fixture();f.bundle.files[0].bytes=encode(f.body+'');await preparePaidOrderConfirmation(f.storage,f.bundle,f.context);let allowed=false,calls=0;const worker=createAdminWorker({basePath:'/admin/orders',authorize:()=>{calls++;return {store:f.store(),allowOrderArchive:true,allowExport:true,allowOrderConfirmation:allowed};}}),url='https://example.test/admin/orders/order-confirmation?id='+f.receipt.id; assert.equal((await worker.fetch(new Request(url),{})).status,403);allowed=true;let response=await worker.fetch(new Request(url),{}),html=await response.text();assert.equal(response.status,200);assert.match(html,/Alex Morgan/);assert.match(html,/<script>attack/);assert.doesNotMatch(html,/