// @requirements REQ-PRODUCT-RIGHTS-CENTER // @requirements REQ-PRODUCT-SCOPE-GUARDS // @requirements REQ-PRODUCT-EVIDENCE-LIFECYCLE // @requirements REQ-PRODUCT-PROCESSING-CHOICES REQ-PRODUCT-RETAINED-RELEASES // @requirements REQ-PRODUCT-CF-HOST REQ-PRODUCT-CONSENT-EVIDENCE REQ-PRODUCT-AUTHORING // Local integration check of the HTTP/DO boundary; never prints the host key. import assert from 'node:assert/strict'; import { readFile, writeFile } from 'node:fs/promises'; const runtimePort=process.env.NOTABENDA_CHECK_PORT??'4179';assert.ok(['4178','4179'].includes(runtimePort)); const origin='http://127.0.0.1:'+runtimePort; const auth={Authorization:'Basic '+Buffer.from('admin:admin').toString('base64')}; const nativeFetch=globalThis.fetch;const demoStart=await nativeFetch(origin+'/demo/admin');assert.equal(demoStart.status,200); assert.doesNotMatch(demoStart.headers.get('Set-Cookie'),/Max-Age|Expires/i); const demoCookie=demoStart.headers.get('Set-Cookie').split(';')[0];const demoGeneration=Buffer.from(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(demoCookie.split('=')[1].split('.')[0]))).toString('hex'); const fetch=(input,options={})=>nativeFetch(input,{...options,headers:{...options.headers,...(new URL(input).pathname.startsWith('/demo/')?{Cookie:demoCookie,'X-Notabenda-Demo-Generation':demoGeneration}:{})}}); const checkFile=new URL('../.local/site-runtime-'+demoGeneration+'.json',import.meta.url); const data=JSON.parse(await readFile(new URL('../site/dist/server-data.json',import.meta.url),'utf8')); const retainedPair=data.releases.map(release=>release.artifact.artifact??release.artifact).filter(artifact=>artifact.manifest.siteId==='juniper-releases');assert.equal(retainedPair.length,2); for(const artifact of retainedPair){const pinned=await fetch(`${origin}/artifacts/${artifact.manifest.artifactId}/terms.html`);assert.equal(pinned.status,200);assert.equal(await pinned.text(),artifact.pages['releases/terms/index.html']);assert.doesNotMatch(pinned.headers.get('Content-Security-Policy'),/script-src/);} const latestTerms=await fetch(origin+'/releases/terms/');assert.equal(latestTerms.status,200);assert.equal(await latestTerms.text(),retainedPair[1].pages['releases/terms/index.html']); const releaseDiff=await fetch(`${origin}/backoffice/history/diff?before=${retainedPair[0].manifest.artifactId}&after=${retainedPair[1].manifest.artifactId}&kind=terms`,{headers:auth});assert.equal(releaseDiff.status,200);assert.match(await releaseDiff.text(),/export your notes before leaving/); console.log('Local Workers retained releases: exact prior/current public HTML and protected terms comparison verified.'); const offer=data.agreements.find(entry=>entry.theme==='light').offer; let saved; try { saved=JSON.parse(await readFile(checkFile,'utf8')); } catch(error) { if(error.code!=='ENOENT') throw error; } if(saved && saved.body.offerId !== offer.offerId) saved=undefined; const body=saved?.body ?? {schemaVersion:1,offerId:offer.offerId,requestId:crypto.randomUUID(),accepted:true}; for(const path of ['/backoffice/history']) { const denied=await fetch(origin+path); assert.equal(denied.status,401); assert.equal(denied.headers.get('Cache-Control'),'private, no-store'); } const invalid=await fetch(origin+'/demo/agreements/light',{method:'POST',headers:{...auth,Origin:'https://attacker.test','Content-Type':'application/json','X-Notabenda-Demo':'1'},body:JSON.stringify(body)}); assert.equal(invalid.status,400); const response=await fetch(origin+'/demo/agreements/light',{method:'POST',headers:{...auth,Origin:origin,'Content-Type':'application/json','X-Notabenda-Demo':'1'},body:JSON.stringify(body)}); assert.equal(response.status,200); const receipt=await response.json(); if(saved) assert.deepEqual(receipt,saved.receipt); else await writeFile(checkFile,JSON.stringify({body,receipt})+'\n',{mode:0o600}); const detail=await fetch(origin+'/demo/admin/agreements/record?id='+receipt.id,{headers:auth}); assert.equal(detail.status,200); assert.match(await detail.text(),/Recorded agreement context/); const history=await fetch(origin+'/backoffice/history',{headers:auth}); assert.equal(history.status,200); assert.match(await history.text(),/Compare document releases/); const authored=data.releases.filter(release=>release.artifact.sourceAttachments); assert.equal(authored.length,2); const authoredIds=authored.map(release=>release.artifact.artifact.manifest.artifactId); const sourcePath='/backoffice/history/source?id='+authoredIds[1]+'&kind=terms'; assert.equal((await fetch(origin+sourcePath)).status,401); const sourceResponse=await fetch(origin+sourcePath,{headers:auth});assert.equal(sourceResponse.status,200);assert.match(sourceResponse.headers.get('Cache-Control'),/private, no-store/);assert.match(await sourceResponse.text(),/<PolicyNote/); const sourceComparison=await fetch(origin+`/backoffice/history/source-diff?before=${authoredIds[0]}&after=${authoredIds[1]}&kind=terms`,{headers:auth});assert.equal(sourceComparison.status,200);assert.match(await sourceComparison.text(),/Authored source changed\. Rendered document body changed\./); const authoredPage=await fetch(origin+'/authored/terms');assert.equal(authoredPage.status,200);assert.match(await authoredPage.text(),/class="policy-note"/); console.log('Local Workers authoring: private source inspection and comparison verified against persisted SQLite archives.'); const choiceDenied=await fetch(origin+'/backoffice/choices'); assert.equal(choiceDenied.status,401); const choices=await fetch(origin+'/backoffice/choices',{headers:auth}); assert.equal(choices.status,200); const html=await choices.text(); const ids=[...html.matchAll(/href="\/backoffice\/choices\/record\?id=([a-f0-9]{64})"/gu)].map(m=>m[1]).sort(); assert.equal(ids.length,3); const choiceFile=new URL('../.local/choice-runtime-check.json',import.meta.url); let priorChoices; try { priorChoices=JSON.parse(await readFile(choiceFile,'utf8')); } catch(error) { if(error.code!=='ENOENT') throw error; } if(priorChoices?.noticeId===data.consent.notice.noticeId) assert.deepEqual(ids,priorChoices.ids); else await writeFile(choiceFile,JSON.stringify({noticeId:data.consent.notice.noticeId,ids})+'\n',{mode:0o600}); const choice=await fetch(origin+'/backoffice/choices/record?id='+ids[0],{headers:auth}); assert.equal(choice.status,200); const choiceHtml=await choice.text(); assert.match(choiceHtml,/Notice wording/); assert.match(choiceHtml,/Revision superseded/); assert.match(choiceHtml,/Historical browser report/); console.log('Local Workers cookie evidence: protected SQLite-backed fictional ledger, three stable events and readable pinned context verified.'); const recordedFile=new URL('../.local/recorded-runtime-'+demoGeneration+'.json',import.meta.url); let recorded; try{recorded=JSON.parse(await readFile(recordedFile,'utf8'));}catch(error){if(error.code!=='ENOENT')throw error;} const notice=data.recordedConsent.notice; if(recorded?.body.noticeId!==notice.noticeId)recorded=undefined; const currentResponse=await fetch(origin+'/demo/choices/head',{headers:auth});assert.equal(currentResponse.status,200);const current=await currentResponse.json(); const recordedBody=recorded?.body??{schemaVersion:1,noticeId:notice.noticeId,requestId:crypto.randomUUID(),expectedRevision:current.revision,action:'revoke-all',choices:Object.fromEntries(Object.keys(notice.scopes).map(id=>[id,false])),clientAt:new Date().toISOString()}; const recordResponse=await fetch(origin+'/demo/choices/record',{method:'POST',headers:{...auth,Origin:origin,'Content-Type':'application/json','X-Notabenda-Demo':'1'},body:JSON.stringify(recordedBody)});assert.equal(recordResponse.status,200);const result=await recordResponse.json(); if(recorded)assert.deepEqual(result.receipt,recorded.receipt);else await writeFile(recordedFile,JSON.stringify({body:recordedBody,receipt:result.receipt})+'\n',{mode:0o600}); assert.ok(result.head.revision>=result.receipt.revision);assert.equal(Object.values(result.receipt.choices).some(Boolean),false); const liveDetail=await fetch(origin+'/demo/admin/recorded/record?id='+result.receipt.id,{headers:auth});assert.equal(liveDetail.status,200);assert.match(await liveDetail.text(),/Revoke all optional purposes/); console.log(recorded?'Local Workers recorded-choice endpoint: original receipt and current head recovered.':'Local Workers recorded-choice endpoint: fixed-stream refusal recorded; rerun after restart to verify recovery.'); const processingFile=new URL('../.local/processing-runtime-'+demoGeneration+'.json',import.meta.url); let processingSaved; try{processingSaved=JSON.parse(await readFile(processingFile,'utf8'));}catch(error){if(error.code!=='ENOENT')throw error;} const processingNotice=data.processing.notice; if(processingSaved?.body.noticeId!==processingNotice.noticeId)processingSaved=undefined; for(const path of ['/backoffice/processing'])assert.equal((await fetch(origin+path)).status,401); const processingBody=processingSaved?.body??{schemaVersion:1,noticeId:processingNotice.noticeId,requestId:crypto.randomUUID(),activities:['sale']}; const processingPost=body=>fetch(origin+'/demo/processing/record',{method:'POST',headers:{...auth,Origin:origin,'Content-Type':'application/json','X-Notabenda-Demo':'1'},body:JSON.stringify(body)}); assert.equal((await processingPost({...processingBody,streamId:'cannot-select-context'})).status,400); const processingResponse=await processingPost(processingBody);assert.equal(processingResponse.status,200);const processingResult=await processingResponse.json(); if(processingSaved)assert.deepEqual(processingResult.receipt,processingSaved.receipt);else await writeFile(processingFile,JSON.stringify({body:processingBody,receipt:processingResult.receipt})+'\n',{mode:0o600}); assert.equal(processingResult.receipt.provenance,'explicit-opt-out');assert.ok(processingResult.head.event.restrictions.includes('sale')); const processingHead=await (await fetch(origin+'/demo/processing/head',{headers:auth})).json();assert.deepEqual(processingHead,processingResult.head); const processingDetail=await fetch(origin+'/demo/admin/processing/record?id='+processingResult.receipt.id,{headers:auth});assert.equal(processingDetail.status,200);const processingHtml=await processingDetail.text();assert.match(processingHtml,/Requested in this event/);assert.match(processingHtml,/Current retained state at read time/);assert.match(processingHtml,/Notice wording/); console.log(processingSaved?'Local Workers processing opt-out: original receipt, monotonic head and pinned context recovered from SQLite.':'Local Workers processing opt-out: host-bound restriction recorded; rerun after restart to verify SQLite recovery.'); console.log(saved ? 'Local Workers runtime: auth/routes, origin rejection, stored receipt recovery and document history verified.' : 'Local Workers runtime: auth/routes, origin rejection, stored acceptance and document history verified; rerun after restart to verify recovery.'); // @requirements REQ-PRODUCT-SETUP REQ-PRODUCT-STORAGE-CLEANUP const setupDenied=await fetch(origin+'/backoffice/setup');assert.equal(setupDenied.status,401); const setupReview=await fetch(origin+'/backoffice/setup',{headers:auth});assert.equal(setupReview.status,200);assert.match(setupReview.headers.get('Cache-Control'),/private, no-store/);assert.match(await setupReview.text(),/data-notabenda-setup/);assert.doesNotMatch(setupReview.headers.get('Content-Security-Policy'),/connect-src/); assert.equal((await fetch(origin+'/backoffice/setup',{method:'POST',headers:auth})).status,405); const cleanupDemo=await fetch(origin+'/storage-cleanup/');assert.equal(cleanupDemo.status,200);assert.match(cleanupDemo.headers.get('Content-Security-Policy'),/script-src 'self'/);assert.match(await cleanupDemo.text(),/Leave a little less/); const cleanupPolicy=await fetch(origin+'/storage-cleanup/cookies/');assert.equal(cleanupPolicy.status,200);assert.doesNotMatch(cleanupPolicy.headers.get('Content-Security-Policy'),/script-src/); console.log('Local Workers setup and storage cleanup: host-protected local questionnaire, write refusal and script-free policy verified.'); // @requirements REQ-PRODUCT-COLLECTION-NOTICE const collectionPage=await fetch(origin+'/collection/');assert.equal(collectionPage.status,200);assert.doesNotMatch(collectionPage.headers.get('Content-Security-Policy'),/script-src/);assert.doesNotMatch(await collectionPage.text(),/