// @requirements REQ-PRODUCT-MOBILE // @requirements REQ-PRODUCT-A11Y // @requirements REQ-PRODUCT-CONTRACT-ACTIONS // @requirements REQ-EU-CONSENT REQ-CA-MEANINGFUL REQ-EU-MARKETING REQ-CA-CASL-OPTIN // @requirements REQ-PRODUCT-SCOPE-GUARDS import {adminCss} from './admin-style.js'; // @requirements REQ-EU-ORDER-ACTION REQ-PRODUCT-CF-ADMIN import {paidOrderCard,paidOrderDetail} from './order-render.js'; import {orderAdminCss} from './order-admin-style.js'; import {normalizeAdminPresentation} from './presentation.js'; import type {AdminPresentation} from './types.js'; import {scopeGuardFields} from './scope-guard-policy.js'; // @requirements REQ-EU-RETENTION-ERASURE // @requirements REQ-PRODUCT-EVIDENCE-LIFECYCLE import {recordQueryParams} from './export.js'; import type {EvidenceExportSegment} from './export.js'; import { boundedText, recordFacts } from './records.js'; import { documentSections,canonicalJson } from './history.js'; import { sourceDiff } from './source-diff.js'; import type { EvidenceRecord, JsonValue, RecordPage, RecordQuery } from './types.js'; export const escapeHtml = (value: string): string => value.replace(/[&<>"']/gu, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char]!); const clip = (value: string, length = 180) => value.length > length ? `${value.slice(0, length)}…` : value; const display = (value: JsonValue) => typeof value === 'string' ? value : JSON.stringify(value, null, 2); const storedText = (value: JsonValue | undefined, fallback = 'Not recorded') => value === undefined ? fallback : display(value); export function readableTime(value:JsonValue|undefined):string { if(typeof value!=='string'||!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u.test(value)||!Number.isFinite(Date.parse(value))) return escapeHtml(storedText(value)); const label=new Intl.DateTimeFormat('en-GB',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false,timeZone:'UTC'}).format(new Date(value)); return ``; } export interface RenderOptions extends AdminPresentation { basePath: string; title: string; scopeLabel: string; nonce: string; exportEnabled?:boolean; sourceEnabled?:boolean; catalogReviewEnabled?:boolean; orderArchiveEnabled?:boolean; orderConfirmationEnabled?:boolean } export function renderWorkspaceShell(body: string, options: RenderOptions, extraHead = '', bodyClass = ''): string { if (!/^\/[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*$/u.test(options.basePath) || !/^[a-f0-9]{32}$/u.test(options.nonce) || !boundedText(options.title, 120) || !boundedText(options.scopeLabel, 200)) throw new Error('Invalid presentation options.'); const e = escapeHtml, presentation=normalizeAdminPresentation(options); const links=presentation.navigation?.length?presentation.navigation:[{label:'Evidence search',href:options.basePath}]; const navIcon=(label:string)=>{const key=label.toLowerCase();const path=key.includes('setup')?'M4 6h16M4 12h16M4 18h16M8 3v6M16 9v6M10 15v6':key.includes('histor')||key.includes('release')||key.includes('revision')?'M3 11a9 9 0 1 1 2.5 7M3 4v7h7M12 7v5l3 2':key.includes('agreement')||key.includes('terms')?'M7 3h7l4 4v14H6V3h1m7 0v5h4M9 12h6M9 16h4':key.includes('export')?'M12 3v12m-4-4 4 4 4-4M5 15v6h14v-6':key.includes('evidence')||key.includes('search')?'m15 15 6 6M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0':key.includes('choice')||key.includes('permission')?'M9 12l2 2 4-4M12 3 4 6v6c0 5 8 9 8 9s8-4 8-9V6l-8-3Z':'M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4zM14 14h6v6h-6z';return ``;}; const navigation=links.map(link=>`${e(link.label)}`).join(''); return `${e(options.title)}${extraHead}
Workspace${e(options.title)}
${bodyClass==='workspace-setup'?'Local draft · No uploads':'Read-only workspace'}
${body}
`; } const shell=renderWorkspaceShell; const recordKindLabels=new Map([['terms-acceptance','Terms agreement'],['consent-choice','Cookie choice'],['document-release','Document release'],['form-permission','Form permission'],['processing-opt-out','Processing opt-out'],['contract-declaration','Contract declaration'],['evidence-lifecycle','Evidence lifecycle'],['paid-order-admission','Order request']]); const recordKindLabel=(kind:string)=>recordKindLabels.get(kind)??kind; function recordUrl(basePath: string, id: string): string { return `${basePath}/record?${new URLSearchParams({ id })}`; } const objectData = (value: JsonValue | undefined): Record => value && typeof value === 'object' && !Array.isArray(value) ? value : {}; function releaseSummary(record: EvidenceRecord): string | undefined { const data = objectData(record.data); if (data.kind !== 'document-release') return undefined; const documents = objectData(data.documents); return Object.entries(documents).map(([kind, value]) => { const version = objectData(value).version; return `${kind} · ${typeof version === 'string' ? version : 'version unspecified'}`; }).join(' / '); } /** @requirements REQ-PRODUCT-TERMS-ACCEPTANCE: show the pinned wording and workflow as evidence, not a validity conclusion. */ function agreementSummary(record: EvidenceRecord): string | undefined { const data = objectData(record.data); if (data.kind !== 'terms-acceptance') return undefined; const offer = objectData(data.offer); const document = objectData(offer.document); return `Terms agreement · ${typeof document.version === 'string' ? clip(document.version,80) : 'version not recorded'}`; } /** @requirements REQ-PRODUCT-CONSENT-EVIDENCE: historical browser reports, never a current-permission conclusion. */ function noticeScopeBlocked(notice:Record,id:string):boolean { const declared=objectData(objectData(objectData(notice.purposeDetails)[id]).scopeGuard);if(declared.facts!==undefined)return scopeGuardFields.some(field=>objectData(declared.facts)[field]==='present'||objectData(declared.facts)[field]==='unknown'); const raw=objectData(notice.scopes)[id];if(typeof raw!=='string')return false; try{const facts=objectData(objectData(JSON.parse(raw) as JsonValue).scopeGuard).facts;return scopeGuardFields.some(field=>objectData(facts)[field]==='present'||objectData(facts)[field]==='unknown');}catch{return false;} } function consentSummary(record:EvidenceRecord):string|undefined { const data=objectData(record.data); if(data.kind!=='consent-choice'||typeof data.noticeId!=='string'||!data.notice) return undefined; const entries=Object.entries(objectData(data.purposeChoices)),notice=objectData(data.notice);const held=entries.filter(([id,v])=>v===false&¬iceScopeBlocked(notice,id)).length; return `Cookie choices · ${entries.filter(([,v])=>v===true).length} allowed · ${entries.filter(([,v])=>v===false).length-held} declined${held?' · '+held+' held by scope guard':''}`; } function consentPanel(record:EvidenceRecord):string { const data=objectData(record.data); if(!consentSummary(record)) return ''; const e=escapeHtml; const notice=objectData(data.notice); const wording=objectData(notice.wording); const purposes=objectData(notice.purposes); const expiry=objectData(data.expiresAt); const documents=objectData(notice.documents); const references:[string,JsonValue|undefined][]=[['Record ID',data.id],['Notice ID',data.noticeId],['Release artifact',data.artifactId],['Material scope digest',data.scopeDigest],['Opaque stream digest',data.streamRef],['Request ID',data.requestId]]; const action=data.action==='revoke-all'?'Revoke all optional purposes':data.action==='accept-all'?'Accept all declared purposes':data.action==='save'?'Save granular choices':'Action not recorded'; return `

Reported purposes

${e(action)} · revision ${e(storedText(data.revision))}${notice.mode==='preview'?' · Preview record':''}

Historical browser report. Later choices, expiry or scope changes may supersede it.

${Object.entries(objectData(data.purposeChoices)).map(([id,value])=>`

${e(storedText(purposes[id],id))}

${value===true?'Reported allow':value===false?(noticeScopeBlocked(notice,id)?'Kept off by scope guard':'Reported decline'):'Unrecognized value'}

Choice expires: ${readableTime(expiry[id])}

${purposeDisclosure(notice,id)}${data.action==='revoke-all'&&!Object.hasOwn(purposes,id)?'

Cleared by revoke-all; this purpose was added after the submitted notice.

':''}
`).join('')}

Notice wording

${e(storedText(wording.title))}

${e(storedText(wording.summary))}

Preferences wording

${e(storedText(wording.preferencesIntro))}

${e(storedText(wording.essentialNote))}

Action labels

${['rejectLabel','acceptLabel','saveLabel'].map(key=>e(storedText(wording[key]))).join(' · ')}

${[['Client-reported action time',data.clientAt],['Host-received time',data.occurredAt],['Submitted revision',data.expectedRevision],['Revision superseded',data.supersededRevision]].map(([label,value])=>`
${e(String(label))}
${e(storedText(value))}
`).join('')}

Choice expiry is distinct from evidence retention. The host configures retention and deletion separately. Delayed first delivery of revoke-all can conservatively revoke a newer grant.

Notice, document and evidence references
${references.map(([label,value])=>`
${e(label)}
${e(storedText(value))}
`).join('')}
${Object.entries(documents).map(([kind,value])=>{const d=objectData(value);return `

${e(storedText(d.title,kind))}

${[['Version',d.version],['Effective date',d.effectiveDate],['Pinned document path',d.url],['HTML SHA-256',d.sha256],['Content SHA-256',d.contentSha256]].map(([label,v])=>`
${e(String(label))}
${e(storedText(v))}
`).join('')}
`;}).join('')}
`; } const activityLabels:Record={sale:'Sale',sharing:'Sharing','targeted-advertising':'Targeted advertising'}; const processingActivityList=(value:JsonValue|undefined)=>Array.isArray(value)?value.map(a=>activityLabels[String(a)]??storedText(a)).join(' · '):'Not recorded'; const processingSource=(value:JsonValue|undefined)=>({['explicit-opt-out']:'Explicit opt-out',['gpc-header']:'Observed GPC header',['browser-report']:'Browser report',['trusted-application']:'Trusted application'}[String(value)]??storedText(value)); const familyLabel=(value:JsonValue|undefined)=>({consent:'Cookie-choice evidence',processing:'Processing opt-out evidence',terms:'Terms agreement evidence',permissions:'Form permission evidence',contracts:'Contract declaration evidence',orders:'Paid-order evidence'}[String(value)]??storedText(value)); const facts=(entries:[string,JsonValue|undefined][])=>`
${entries.filter(([,value])=>value!==undefined&&value!==null).map(([label,value])=>`
${escapeHtml(label)}
${escapeHtml(storedText(value))}
`).join('')}
`; function lifecycleStatus(data:Record):string {return objectData(data.hold).active===true?'On hold':data.phase==='complete'?'Purge recorded complete':data.phase==='purging'?'Retired · purge pending':'Retained';} function lifecycleTone(data:Record):string {return objectData(data.hold).active===true?'held':data.phase==='complete'?'complete':'pending';} function lifecycleSummary(record:EvidenceRecord):string|undefined { const data=objectData(record.data);if(data.kind!=='evidence-lifecycle')return undefined; const hold=objectData(data.hold);return `${familyLabel(data.family)} · ${hold.active===true?'on hold':data.phase==='complete'?'purge recorded complete':data.phase==='purging'?'retired, purge pending':'retained'}`; } function lifecyclePanel(record:EvidenceRecord):string { if(!lifecycleSummary(record))return ''; const e=escapeHtml,data=objectData(record.data),hold=objectData(data.hold),progress=objectData(data.progress),retirement=objectData(data.retirement); const retired=data.phase!=='retained'; return `

Evidence lifecycle

${retired?'Context retired':hold.active===true?'Legal hold active':'Evidence retained'}

${e(lifecycleStatus(data))}

${retired?'This context rejects new commands and historical retries. Purge removes only its matching ledger payloads.':'This context remains live. A hold protects its payloads from the lifecycle purge.'}

  1. Retained
  2. Retired
  3. Payload purge

Legal hold

${hold.active===true?'Active — further purge steps blocked':'No active stored hold'}

${hold.active===true?`Review: ${readableTime(hold.reviewAt)}. Reaching this date does not release the hold.`:'The host must reassess eligibility before each purge step.'}

Matching events removed

${e(storedText(progress.deleted))}

${e(storedText(progress.scanned))} key${progress.scanned===1?'':'s'} examined across ${e(storedText(progress.batches))} committed batch${progress.batches===1?'':'es'}. These counts describe this scan, not all personal data.

${[['Lifecycle created',data.occurredAt],['Retirement began',retirement.startedAt],['Eligibility declared by host',retirement.eligibleAt],['Manifest reapplied after restore',data.restoredAt],['Purge completion recorded',progress.completedAt]].filter(([,value])=>value!==undefined&&value!==null).map(([label,value])=>`
${e(String(label))}
${readableTime(value)}
`).join('')}
${data.family==='processing'&&retired?'

Retired processing contexts remain blocked. They do not become empty live opt-out streams.

':''}
Context, decision and continuation references${facts([['Context digest',data.contextRef],['Evidence family',data.family],['Decision request',retirement.requestId],['Host reason',retirement.reason],['Hold revision',hold.revision],['Last examined key',progress.after]])}

Completion applies to the scoped scan. Minimal lifecycle metadata and shared policy archives remain; backups, exports and host copies need separate handling. Reapply the authoritative retirement manifest and restart scanning before reopening restored storage.

`; } function retiredSummary(record:EvidenceRecord):string|undefined { const data=objectData(record.data);return data.kind==='retired-evidence'?`Retired ${familyLabel(data.family).toLocaleLowerCase('en')} context`:undefined; } function retiredPanel(record:EvidenceRecord):string { const title=retiredSummary(record);if(!title)return ''; const data=objectData(record.data);return `

${escapeHtml(title)}

${escapeHtml(storedText(data.description))}

${facts([['Context digest',data.contextRef],['Recorded lifecycle state',data.status]])}${typeof data.processing==='string'?`

${escapeHtml(data.processing)}

`:''}

This is a minimized inspection projection, not the original event or a historical receipt. The lifecycle view records purge progress separately.

`; } /** @requirements REQ-PRODUCT-CONTRACT-ACTIONS Receipt, provider acceptance and fulfillment stay distinct. */ function contractSummary(record:EvidenceRecord):string|undefined { const d=objectData(record.data);if(d.kind!=='contract-declaration')return undefined; return storedText(objectData(objectData(d.definition).wording).title,'Contract declaration')+' · '+storedText(objectData(objectData(d.request).fields).name,'Visitor-provided name'); } function contractDeliveryStatus(value:JsonValue|undefined):string {return ({pending:'Pending dispatch',leased:'Runner lease active',uncertain:'Outcome uncertain',accepted:'Provider accepted',cancelled:'Host cancelled delivery'} as Record)[storedText(objectData(value).status)]??'Not recorded';} function contractProgress(record:EvidenceRecord):string {const jobs=objectData(objectData(record.data).delivery);return 'Confirmation: '+contractDeliveryStatus(jobs.acknowledgement)+' · Application: '+contractDeliveryStatus(jobs.application);} function contractPanel(record:EvidenceRecord):string { if(!contractSummary(record))return '';const d=objectData(record.data),definition=objectData(d.definition),wording=objectData(definition.wording),request=objectData(d.request),f=objectData(request.fields),c=objectData(f.cancellation),receipt=objectData(d.receipt),ack=objectData(receipt.acknowledgement),jobs=objectData(d.delivery); return `

Declaration details

Visitor-provided claims, received by the host. Receipt, confirmation delivery and application fulfillment are separate.

${[['acknowledgement','Electronic acknowledgement'],['application','Application handoff']].map(([channel,title])=>{const job=objectData(jobs[channel!]);return `

${escapeHtml(title!)}

${escapeHtml(contractDeliveryStatus(job))}

${escapeHtml(storedText(job.attempts))} attempt${job.attempts===1?'':'s'} · ${readableTime(job.updatedAt)}

${channel==='acknowledgement'?'Must run independently of contract matching. Provider acceptance does not prove recipient delivery.':'No cancellation, refund or effective end date is inferred from this handoff.'}

`;}).join('')}

Exact declaration

${escapeHtml(storedText(wording.statement))}

Final action shown

${escapeHtml(storedText(wording.confirmLabel))}

${facts([['Visitor-provided name',f.name],['Contract or part identified',f.contract],['Confirmation address',f.email],['Cancellation type',c.kind],['Reason supplied',c.reason],['Requested ending',f.cancellation===undefined?undefined:c.ending||'Earliest possible'],['Host receipt time',d.occurredAt],['Browser-reported time (unverified)',request.clientAt]])}
Exact savable acknowledgement and references
${escapeHtml(storedText(ack.text))}
${facts([['Action definition',definition.definitionId],['Form revision',definition.revision],['Profile',definition.profile],['Request ID',request.requestId],['Request SHA-256',receipt.requestSha256],['Acknowledgement SHA-256',ack.sha256],['Application delivery ID',objectData(jobs.application).deliveryId],['Confirmation delivery ID',objectData(jobs.acknowledgement).deliveryId]])}

Archived form documents do not establish the terms governing the original contract. The host owns admission, prompt dispatch, contract/identity matching, eligibility, fulfillment, retention and external copies.

`; } /** @requirements REQ-PRODUCT-FORM-PERMISSIONS Historical assertion and evaluated current permission stay distinct. */ function permissionSummary(record:EvidenceRecord):string|undefined { const d=objectData(record.data);if(d.kind!=='form-permission')return undefined; const offer=objectData(d.offer),purpose=objectData(objectData(offer.facts).purpose),channel=objectData(offer.channel); return `${storedText(purpose.name,'Form permission')} · ${storedText(channel.label,'Channel not recorded')}`; } function permissionStateLabel(value:JsonValue|undefined):string {const state=objectData(value);if(state.permitted===true)return 'Allowed';return ({withdrawn:'Withdrawn','offer-changed':'Needs a new choice',inactive:'Unavailable','scope-restricted':'Separate process required','not-granted':'Not granted'} as Record)[String(state.reason)]??'Not evaluated';} function permissionHistoryLink(record:EvidenceRecord,basePath:string):string { const d=objectData(record.data);if(d.kind!=='form-permission'||typeof d.streamRef!=='string'||!/^[a-f0-9]{64}$/u.test(d.streamRef))return ''; return `Follow this permission history `; } function permissionPanel(record:EvidenceRecord,basePath:string):string { if(!permissionSummary(record))return ''; const e=escapeHtml,d=objectData(record.data),offer=objectData(d.offer),wording=objectData(offer.wording),document=objectData(offer.document),state=objectData(d.currentState),purpose=objectData(objectData(offer.facts).purpose),channel=objectData(offer.channel); return `

Permission record

${offer.mode==='preview'?'Preview record · ':''}One purpose and channel. The recorded action is shown alongside the permission state evaluated when this page was read.

Historical action

${d.action==='grant'?'Grant recorded':d.action==='withdraw'?'Withdrawal recorded':'Action not recorded'}

Event revision ${e(storedText(d.revision))}

Evaluated current state at read time

${state.permitted===true?'Permission currently recorded':state.permitted===false?'No current permission':'Evaluation not available'}

${e(({granted:'The grant matches the current permission offer.',withdrawn:'The current recorded choice is a withdrawal.','offer-changed':'The permission information changed after this grant.',inactive:'No permission offer is currently active.','scope-restricted':'This context needs a separate supported process.','not-granted':'No grant is recorded for this contact generation.'} as Record)[String(state.reason)]??'A current evaluation is not available.')}

${permissionHistoryLink(record,basePath)}

${e(storedText(purpose.name))} · ${e(storedText(channel.label))}

${e(storedText(purpose.description))}

Exact permission statement

${e(storedText(wording.statement))}

Optionality disclosed

${e(storedText(wording.optionality))}

Withdrawal disclosed

${e(storedText(wording.withdrawalExplanation))}

Host-received time
${readableTime(d.occurredAt)}
Browser-reported time
${readableTime(d.clientAt)}
Material offer revision
${e(storedText(offer.revision))}
Purpose, channel, document and stream references${facts([['Stable permission',d.permissionId],['Purpose',d.purposeId],['Channel',channel.id],['Opaque person/contact generation',d.streamRef],['Request ID',d.requestId],['Offer ID',d.offerId],['Pinned privacy path',document.url],['Privacy version',document.version],['Privacy HTML SHA-256',document.sha256],['Current event revision',state.revision],['Current activation revision',state.activeRevision],['Current active offer',state.activeOfferId]])}

The host must evaluate the state again at downstream use and apply its reviewed rules. A receipt, page snapshot or asserted grant does not establish identity, address ownership, lawful marketing, message delivery, or fulfillment of withdrawal.

`; } /** @requirements REQ-PRODUCT-PROCESSING-CHOICES Historical intent and read-time state have distinct labels. */ function processingSummary(record:EvidenceRecord):string|undefined { const data=objectData(record.data);if(data.kind!=='processing-restriction'||!data.notice)return undefined; return `Processing opt-out · revision ${storedText(data.revision)}`; } function processingPanel(record:EvidenceRecord):string { if(!processingSummary(record))return ''; const e=escapeHtml,data=objectData(record.data),notice=objectData(data.notice),wording=objectData(notice.wording),document=objectData(notice.document),head=objectData(data.currentHead),latest=objectData(head.event); const list=processingActivityList; const facts=(values:[string,JsonValue|undefined][])=>`
${values.map(([label,value])=>`
${e(label)}
${e(storedText(value))}
`).join('')}
`; return `

Processing restrictions

${notice.mode==='preview'?'Preview record · ':''}Separate from cookie permission. These records add restrictions; they never grant processing.

Requested in this event

${e(list(data.activities))}

Retained after this event

${e(list(data.restrictions))}

Historical revision ${e(storedText(data.revision))}

Current retained state at read time

${e(list(latest.restrictions))}

Revision ${e(storedText(head.revision))}. Later events may change this view.

Notice wording

${e(storedText(wording.title))}

${e(storedText(wording.description))}

Action label

${e(storedText(wording.actionLabel))}

${facts([['Host-received time',data.occurredAt],['Host-assigned provenance',processingSource(data.provenance)],['Site',data.siteId],['Workflow scope',data.scopeId],['Privacy version',document.version],['Privacy effective date',document.effectiveDate]])}
Context, document and evidence references${facts([['Opaque stream digest',data.streamRef],['Request ID',data.requestId],['Notice ID',data.noticeId],['Release artifact',data.artifactId],['Pinned privacy path',document.url],['Privacy HTML SHA-256',document.sha256],['Privacy content SHA-256',document.contentSha256],['Current head record',latest.id]])}

The host assigns context and provenance. A stored event does not prove a human click, complete identity matching, or downstream cessation and notifications. The host supplies those operations, retention and erasure.

`; } function purposeDisclosure(notice:Record,id:string):string { const details=objectData(objectData(notice.purposeDetails)[id]); if(!Object.keys(details).length) return ''; const services=Array.isArray(details.services)?details.services:[]; return `
Purpose disclosed

${escapeHtml(storedText(details.description))}

${services.length?`

Services: ${services.map(value=>{const s=objectData(value);return `${escapeHtml(storedText(s.name))} (${escapeHtml(storedText(s.provider))})`;}).join(', ')}

`:''}
`; } function releaseComparisonForm(records: EvidenceRecord[], options: RenderOptions): string { const releases = records.filter(record => objectData(record.data).kind === 'document-release'); if (releases.length < 2) return ''; const dated = releases.map(record => ({ record, time: Date.parse(recordFacts(record).occurredAt ?? '') })).filter(entry => Number.isFinite(entry.time)).sort((a, b) => b.time - a.time); // A key or version string does not establish chronology. Require two distinct stored timestamps. const latest = dated[0]; const preceding = latest && dated.find(entry => entry.time < latest.time); const defaults = preceding && latest ? { before: preceding.record.id, after: latest.record.id } : undefined; const label = (record: EvidenceRecord): string => { const data = objectData(record.data); const timestamp = recordFacts(record).occurredAt; const note = typeof data.changeNote === 'string' && data.changeNote ? clip(data.changeNote, 70) : ''; return `${clip(releaseSummary(record) ?? 'Document release', 110)} · ${timestamp ? clip(timestamp, 35) : 'date not recorded'}${note ? ` · ${note}` : ''} · ${clip(record.id, 10)}`; }; const select = (name: 'before' | 'after', heading: string) => `
`; return `

Compare document releases

${select('before', 'Before release')}${select('after', 'After release')}

Choose from releases on this page. ${defaults ? 'Defaults use the two latest distinct recorded timestamps on this page.' : 'Choose the comparison order; recorded timestamps do not establish a default pair.'} Records below remain in key order.

`; } function factsHtml(record: EvidenceRecord): string { const f = recordFacts(record); const data=objectData(record.data); const contact=data.kind==='form-permission'&&typeof data.streamRef==='string'&&/^[a-f0-9]{64}$/u.test(data.streamRef)?data.streamRef.slice(0,12)+'…':undefined; const values = [['Subject', f.subject], ['Contact reference',contact], ['Site', f.site], ['Document version', f.version]]; return `
${values.filter(([, value]) => value !== undefined).map(([label, value]) => `
${label}
${escapeHtml(clip(value!))}
`).join('')}
`; } /** Pure presentation helper. The caller must authorize and validate all data before invoking it. */ export function renderRecordList(page: RecordPage, query: RecordQuery, options: RenderOptions): string { const e = escapeHtml; const field = (name: string, label: string, value: string, type = 'text', placeholder = '') => ``; const activeFilters=[query.kind,query.keyPrefix,query.from,query.to].filter(Boolean).length; const orderView=page.records.length>0&&page.records.every(record=>objectData(record.data).kind==='paid-order-admission')||query.kind==='paid-order-admission'; const filters = `
${field('q', 'Search records', query.query, 'search', orderView?'Search a product, quote, request or review ID…':'Search a subject, version, purpose or reference…')}
Refine your search${activeFilters?' · '+activeFilters+' active':''}
${field('kind', 'Record kind', query.kind, 'text', 'Any kind')}${field('key', 'Record key prefix', query.keyPrefix, 'text', 'Any key')}
${field('from', 'From (UTC)', query.from, 'date')}${field('to', 'To (UTC)', query.to, 'date')}

Search matches all words across keys, JSON values and displayed lifecycle labels. State shortcuts use exact lifecycle or current permission fields. Date filters use recorded event times, in UTC.

Clear filters ↗
`; const lifecycleView=page.records.length>0&&page.records.every(record=>!!lifecycleSummary(record))||query.query.split(/\s+/u).some(term=>term.startsWith('lifecycle:')); const lifecycleFilters=lifecycleView?``:''; const permissionTerms=query.query.split(/\s+/u).filter(Boolean),permissionView=page.records.length>0&&page.records.every(record=>!!permissionSummary(record))||permissionTerms.some(term=>term.toLowerCase().startsWith('permission:')); const permissionSearch=permissionTerms.filter(term=>!term.toLowerCase().startsWith('permission:')).join(' '); const permissionFilters=permissionView?``:''; const contractTerms=query.query.split(/\s+/u).filter(Boolean),contractView=page.records.length>0&&page.records.every(record=>!!contractSummary(record))||contractTerms.some(term=>term.toLowerCase().startsWith('contract:')); const contractSearch=contractTerms.filter(term=>!term.toLowerCase().startsWith('contract:')).join(' '); const contractFilters=contractView?``:''; const permissionTimeline=page.records.length>0&&page.nextAfter===undefined&&query.after===undefined&&/^[a-f0-9]{64}$/u.test(permissionSearch)&&page.records.every(record=>permissionSummary(record)&&objectData(record.data).streamRef===permissionSearch&&Number.isSafeInteger(objectData(record.data).revision)); const displayed=permissionTimeline?[...page.records].sort((a,b)=>Number(objectData(a.data).revision)-Number(objectData(b.data).revision)):page.records; const allAgreements=page.records.length>0&&page.records.every(record=>!!agreementSummary(record)); const cards = displayed.map(record => { const order=paidOrderCard(record,options.basePath);if(order)return order; const f = recordFacts(record); const release = releaseSummary(record); const agreement = agreementSummary(record); const choice = consentSummary(record); const processing = processingSummary(record); const permission=permissionSummary(record),contract=contractSummary(record); const lifecycle=lifecycleSummary(record),retired=retiredSummary(record); const note = objectData(record.data).changeNote; const summary = contract ? contractProgress(record) : permission ? `${objectData(record.data).action==='grant'?'Grant recorded':'Withdrawal recorded'} · Now: ${permissionStateLabel(objectData(record.data).currentState)} · ${storedText(objectData(objectData(objectData(record.data).offer).wording).statement)}` : lifecycle ? `${storedText(objectData(objectData(record.data).progress).deleted)} matching events removed · ${storedText(objectData(objectData(record.data).progress).scanned)} keys examined · shared archives retained` : retired ? 'Original payload withheld from inspection; review lifecycle progress separately.' : processing ? `Requested: ${processingActivityList(objectData(record.data).activities)} · ${processingSource(objectData(record.data).provenance)} · ${storedText(objectData(objectData(objectData(record.data).notice).wording).title)}` : choice ? `Historical browser report · revision ${clip(storedText(objectData(record.data).revision))} · ${clip(storedText(objectData(objectData(objectData(record.data).notice).wording).summary,''))}` : agreement ? `Operation: ${clip(storedText(objectData(record.data).operationId, 'not recorded'))} · ${clip(storedText(objectData(objectData(record.data).offer).statement, ''))}` : release !== undefined ? `Release ${clip(record.id, 16)}${typeof note === 'string' && note ? ` · ${clip(note)}` : ''}` : (f.purposeChoices !== undefined ? `Purpose choices: ${clip(display(f.purposeChoices))}` : clip(JSON.stringify(record.data))); if(agreement){const data=objectData(record.data),offer=objectData(data.offer),document=objectData(offer.document);return `
${allAgreements?'':`Terms agreement`}${e(clip(f.subject??record.id))}

${e(clip(storedText(data.operationId,'Operation not recorded')))}

${e(clip(storedText(document.title,'Terms')))}${e(clip(storedText(document.version,'Version not recorded'),80))}
Received${f.occurredAt?readableTime(f.occurredAt):'Time not recorded'}

${e(clip(storedText(offer.statement,''),240))}

`;} const heading=agreement&&f.subject?f.subject:lifecycle || retired || release || agreement || choice || processing || permission || contract || record.id; return `
${lifecycle?`${e(lifecycleStatus(objectData(record.data)))}`:`${e(clip(recordKindLabel(f.kind ?? 'Unclassified record'), 80))}`}${f.occurredAt ? `${readableTime(f.occurredAt)}` : ''}
${e(heading)}${agreement?`

${e(agreement)}

`:''}${factsHtml(record)}

${e(summary)}

${permissionHistoryLink(record,options.basePath)}
`; }).join(''); const params = new URLSearchParams({ q: query.query, kind: query.kind, key: query.keyPrefix, from: query.from, to: query.to, limit: String(query.limit) }); if (page.nextAfter !== undefined) params.set('after', page.nextAfter); const next = page.nextAfter === undefined ? 'End of this search' : `Continue search `; return shell(`

Evidence search

${e(options.title)}

${e(contractView?'Review the exact declaration and its two independent handoffs. A recorded request does not mean a contract has ended.':orderView?'Inspect received requests, their retained prices and the information supplied before the order action.':page.records.length&&page.records.every(record=>agreementSummary(record))?'Inspect the statement and document version attached to each agreement.':'Search recorded choices, agreements and document releases.')}

${page.records.length}Records in this segment

Authorized scope: ${e(options.scopeLabel)}

${filters}

${permissionTimeline?'Permission history':allAgreements?'Agreements on this page':orderView?'Order requests':'Stored evidence'}

${options.exportEnabled?`Export this segment `:''}${page.records.length} record${page.records.length === 1 ? '' : 's'} on this page${page.scanned === undefined ? '' : ` · ${page.scanned} examined`}
${releaseComparisonForm(page.records, options)}${lifecycleFilters}${permissionFilters}${contractFilters}
${cards || `

No matches in this segment

${page.nextAfter === undefined ? 'Try another search or clear your filters.' : 'More records remain to examine. Continue this search to inspect the next segment.'}

`}

Results come from the authorized store, ${permissionTimeline?'shown in recorded event order for this contact reference':'ordered by record key'}. Counts describe this page, not the full store.

`, options); } export function renderRecordDetail(record: EvidenceRecord, options: RenderOptions): string { const e = escapeHtml; const order=paidOrderDetail(record,options.basePath,options.orderArchiveEnabled===true,options.orderConfirmationEnabled===true);if(order)return shell(order,options); const f = recordFacts(record); const sections: [string, JsonValue | undefined][] = [ ['Recorded event', f.occurredAt], ['Record kind', f.kind], ['Subject reference', f.subject], ['Site reference', f.site], ['Document version', f.version], ['Purpose choices', f.purposeChoices], ['Provenance', f.provenance], ['Retention', f.retention], ]; const data = objectData(record.data); const agreement = agreementSummary(record); const choice = consentSummary(record); const processing = processingSummary(record); const permission=permissionSummary(record),contract=contractSummary(record); const lifecycle=lifecycleSummary(record),retired=retiredSummary(record); const offer = objectData(data.offer); const document = objectData(offer.document); if(agreement){ const keyFacts:[string,JsonValue|undefined][]=[['Host-received time',f.occurredAt],['Workflow scope',offer.scopeId],['Operation',data.operationId],['Site',f.site]]; const references:[string,JsonValue|undefined][]=[['Record ID',record.id],['Offer ID',offer.offerId],['Release artifact',offer.artifactId],['Terms HTML SHA-256',document.sha256],['Content SHA-256',document.contentSha256],['Document path',document.url],['Provenance',f.provenance],['Retention',f.retention],['Purpose choices',f.purposeChoices]]; return shell(`← Back to agreements

Agreement record

${e(storedText(f.subject,'Terms agreement'))}

Authorized scope: ${e(options.scopeLabel)}

${keyFacts.filter(([,value])=>value!==undefined).map(([label,value])=>`
${e(label)}
${label==='Host-received time'?readableTime(value):e(storedText(value))}
`).join('')}

Statement shown

${e(storedText(offer.statement))}

Action label shown

${e(storedText(offer.actionLabel))}

This records an assertion received by the host. It does not prove a human click, identity, enforceability, delivery, or completion of the related application operation.

Version and evidence references${facts(references)}
Inspect complete stored JSON
${e(JSON.stringify(record.data,null,2))}
`,options,'','workspace-agreement-detail'); } const isRelease = data.kind === 'document-release'; const catalogReview=isRelease&&data.hasCatalogReview===true&&options.catalogReviewEnabled?`

Retained service references

Inspect the selected catalog and site-use facts bound to this release.

Inspect catalog review →
`:''; const sources = isRelease && options.sourceEnabled!==false && Array.isArray(data.sourceKinds) ? `

Authored source

Inspect the exact Markdown or MDX retained with this release. Source is displayed as text and never evaluated.

${data.sourceKinds.filter(kind => typeof kind === 'string' && ['privacy','cookies','terms'].includes(kind)).map(kind => `${e(String(kind))} source →`).join(' · ')}

` : ''; const release = isRelease ? `

Document release

${e(releaseSummary(record) ?? '')}

${typeof data.changeNote === 'string' ? `

${e(data.changeNote)}

` : ''}${typeof data.actor === 'string' ? `

Recorded actor: ${e(data.actor)}

` : ''}

An archive records supplied content, not proof of publication, delivery, acceptance, or valid consent. Archived HTML is retained in storage and never executed here.

` : ''; return shell(`← Back to evidence search

${isRelease ? 'Immutable release' : 'Record detail'}

${e(lifecycle ? familyLabel(objectData(record.data).family) : retired ?? (contract ? contract : permission ? 'Form permission' : processing ? 'Processing opt-out' : choice ? 'Cookie choices' : agreement ?? record.id))}

${agreement ? `

Record ${e(record.id)}

` : choice ? `

${e(choice.replace('Cookie choices · ',''))}

` : ''}

Authorized scope: ${e(options.scopeLabel)}

${choice||lifecycle||retired ? '' : '

The fields below reflect stored values. Absent fields are omitted; identity, consent, and legal validity are not inferred.

'}${lifecyclePanel(record)}${retiredPanel(record)}${release}${catalogReview}${sources}${consentPanel(record)}${processingPanel(record)}${permissionPanel(record,options.basePath)}${contractPanel(record)}
${sections.filter(([heading, value]) => !contract && !permission && !processing && !lifecycle && !retired && value !== undefined && !(choice && ['Purpose choices','Retention'].includes(heading))).map(([heading, value]) => `

${heading}

${typeof value === 'string' ? `

${e(value)}

` : `
${e(display(value!))}
`}
`).join('')}
${isRelease ? 'Inspect release metadata and structured documents' : 'Inspect complete stored JSON'}
${e(JSON.stringify(record.data, null, 2))}
`, options); } export function renderRecordDiff(before: EvidenceRecord, after: EvidenceRecord, kind: string, options: RenderOptions): string { const e = escapeHtml; const previous = documentSections(before, kind); const next = documentSections(after, kind); const oldById = new Map(previous.map(section => [section.id, section])); const newById = new Map(next.map(section => [section.id, section])); const ids = [...new Set([...previous.map(section => section.id), ...next.map(section => section.id)])]; const changes = ids.filter(id => oldById.get(id)?.comparison !== newById.get(id)?.comparison); const beforeData = objectData(before.data); const afterData = objectData(after.data); if (beforeData.siteId !== afterData.siteId) throw new Error('Compare releases for the same site.'); const beforeDoc = objectData(objectData(beforeData.documents)[kind]); const afterDoc = objectData(objectData(afterData.documents)[kind]); const titleChanged = beforeDoc.title !== afterDoc.title; const reordered = JSON.stringify(previous.map(section => section.id)) !== JSON.stringify(next.map(section => section.id)); const hashes = (data: Record) => { const manifest = objectData(data.manifest); const descriptors = Array.isArray(manifest.documents) ? manifest.documents : []; return objectData(descriptors.find(value => objectData(value).kind === kind)); }; const oldHash = hashes(beforeData); const newHash = hashes(afterData); const oldAuthoring = objectData(oldHash.authoring); const newAuthoring = objectData(newHash.authoring); const oldManifest=objectData(beforeData.manifest),newManifest=objectData(afterData.manifest); const catalogComparison=options.catalogReviewEnabled&&oldManifest.catalogReview!==undefined&&newManifest.catalogReview!==undefined?`

Catalog review

${canonicalJson(oldManifest.catalogReview)===canonicalJson(newManifest.catalogReview)?'Selected catalog review unchanged.':'Selected catalog review changed; document text and rendering are compared separately.'}

Compare selected service references →
`:''; const sourceComparison = options.sourceEnabled!==false && typeof oldAuthoring.sourceSha256 === 'string' && typeof newAuthoring.sourceSha256 === 'string' ? `

Source and rendered body

${oldAuthoring.sourceSha256 === newAuthoring.sourceSha256 ? 'Authored source unchanged.' : 'Authored source changed.'} ${oldAuthoring.bodySha256 === newAuthoring.bodySha256 ? 'Rendered document body unchanged.' : 'Rendered document body changed.'}

Compare retained source →
` : ''; const blocks = changes.map(id => { const old = oldById.get(id); const current = newById.get(id); return `

${e(current?.heading ?? old!.heading)}

Before · ${old ? 'removed or replaced' : 'not present'}

${e(old?.text ?? '—')}

After · ${current ? 'added or replacement' : 'removed'}

${e(current?.text ?? '—')}
`; }).join(''); return shell(`← Back to release

Document comparison

${e(kind)} · what changed

Authorized scope: ${e(options.scopeLabel)}

Text comparison supports human review. It does not decide legal materiality, whether new acceptance is needed, or whether consent remains valid.

Before · ${e(String(beforeDoc.version ?? ''))}

${e(before.id)}

After · ${e(String(afterDoc.version ?? ''))}

${e(after.id)}

${oldHash.contentSha256 === newHash.contentSha256 ? 'Structured content hash unchanged.' : 'Structured content hash changed.'} ${oldHash.sha256 === newHash.sha256 ? 'Rendered HTML hash unchanged.' : 'Rendered HTML hash changed (includes presentation and document metadata).'}

${catalogComparison}${sourceComparison}${titleChanged ? `

Document title

Before: ${e(String(beforeDoc.title))}

After: ${e(String(afterDoc.title))}

` : ''}${reordered ? '

Section order or membership changed. Review the complete structured documents for their full order.

' : ''}
${blocks || '

No section text changes.

'}
Inspect compared structured documents

Before

${e(JSON.stringify(beforeDoc,null,2))}

After

${e(JSON.stringify(afterDoc,null,2))}
`, options); } /** @requirements REQ-PRODUCT-AUTHORING REQ-PRODUCT-CF-HISTORY: source is escaped text, never executable MDX. */ export function renderDocumentSource(before: EvidenceRecord, after: EvidenceRecord | undefined, options: RenderOptions): string { const e = escapeHtml; const inspect = (record: EvidenceRecord) => { const data = objectData(record.data); if (data.kind !== 'document-source' || typeof data.source !== 'string' || new TextEncoder().encode(data.source).length > 24576 || !['md','mdx'].includes(String(data.format)) || typeof data.artifactId !== 'string' || !/^[a-f0-9]{64}$/u.test(data.artifactId) || !['privacy','cookies','terms'].includes(String(data.documentKind)) || record.id !== `${data.artifactId}/${data.documentKind}`) throw new Error('Invalid source record.'); return {data, source:data.source, authoring:objectData(data.authoring)}; }; const first = inspect(before); const next = after ? inspect(after) : undefined; if (next && (first.data.siteId !== next.data.siteId || first.data.documentKind !== next.data.documentKind)) throw new Error('Compare the same site and document kind.'); const current = next ?? first; const metadata = (entry: typeof first, label: string) => `

${label} · ${e(storedText(entry.data.documentVersion))}

${e(String(entry.data.format).toUpperCase())} · ${e(storedText(entry.authoring.sourceBytes))} UTF-8 bytes

${e(storedText(entry.data.title))} →
Build references
${['sourceSha256','bodySha256','renderer','policy','componentsRevision'].map(key=>`
${e(key)}
${e(storedText(entry.authoring[key]))}
`).join('')}
`; let comparison = ''; if (next) { const diff=sourceDiff(first.source,next.source); const changed = first.source !== next.source; comparison = `

Source comparison

${changed ? 'Authored source changed.' : 'Authored source unchanged.'} ${first.authoring.bodySha256===next.authoring.bodySha256 ? 'Rendered document body unchanged.' : 'Rendered document body changed.'}

${diff.precise ? 'Changed ranges include two surrounding lines for context.' : 'This large comparison uses one bounded range, with surrounding context.'} Newline encoding and byte-only differences remain visible through the source hashes; complete source is retained below.

${diff.ranges.map(range=>`

Before · from line ${range.beforeStart}

${e(range.before.join('\n') || 'No lines in this range.')}

After · from line ${range.afterStart}

${e(range.after.join('\n') || 'No lines in this range.')}
`).join('')}
`; } const complete = (entry:typeof first,label:string) => `
${label} complete source · ${e(String(entry.data.format).toUpperCase())}
${e(entry.source)}
`; return shell(`← Back to release

Private authoring archive

${e(storedText(current.data.documentKind))} · ${next ? 'source changes' : 'authored source'}

Authorized scope: ${e(options.scopeLabel)}

Exact build input, displayed as text. MDX expressions, imports and components are never executed in this workspace.

${metadata(first,next ? 'Before' : 'Release')}${next ? metadata(next,'After') : ''}
${comparison}${complete(first,next ? 'Before' : 'Inspect')}${next ? complete(next,'After') : ''}`,options); } /** @requirements REQ-PRODUCT-EVIDENCE-EXPORT A live preview never implies a frozen or complete export. */ export function renderEvidenceExport(segment:EvidenceExportSegment,options:RenderOptions):string { const e=escapeHtml,query=segment.selection; const search=`${options.basePath}?${recordQueryParams(query)}`; const download=`${options.basePath}/export.json?${recordQueryParams(query)}`; const next=segment.segment.nextAfter; const beginning={...query};delete beginning.after; const nextReview=next===undefined?undefined:`${options.basePath}/export?${recordQueryParams({...query,after:next})}`; const facts:[string,string][]=[['Search words',query.query||'Any words'],['Record kind',query.kind||'Any kind'],['Key prefix',query.keyPrefix||'Any key'],['Recorded date · UTC',query.from||query.to?`${query.from||'Any start'} → ${query.to||'Any end'}`:'Any date'],['Incoming cursor',query.after??'Beginning of this search'],['Maximum records',String(query.limit)]]; return shell(`← Back to these search filters

Evidence export

Export evidence

Download one JSON segment using the current search filters. The file includes those filters and any continuation cursor.

Authorized scope: ${e(segment.scopeLabel)}

Selection preview

Current search segment

Matches in this preview
${segment.segment.returned}
Keys examined${segment.segment.examined===undefined?' · not reported':''}
${segment.segment.examined??'—'}
${next===undefined?'No continuation reported':'Continue after this segment'}
${next===undefined?'End':'→'}
${facts.map(([label,value])=>`
${e(label)}
${e(value)}
`).join('')}

Preview read completed: ${readableTime(segment.read.completedAt)}

Download JSON segment

Downloads a fresh read of this segment. Records may differ from this preview. Every download is authorized again by your host.

${next===undefined?'This read reported no continuation.':'Continue the export'}

${next===undefined?'This is the end reported for the current filtered read. Records can change between reads; it is not proof that all evidence or application data has been exported.':'More keys may remain to examine, even when this segment has no matches. Follow the download’s nextReview or nextDownload link to continue from its actual cursor. Save each segment separately.'}

${nextReview?'

The next preview link uses this preview’s cursor. Use the JSON file’s continuation links when assembling a download sequence.

':''}
`,options); }