import { readFileSync, writeFileSync } from 'node:fs' import { createHash } from 'node:crypto' import { resolve, dirname } from 'node:path' import { pathToFileURL } from 'node:url' const levels = new Set(['unit', 'contract', 'http', 'browser', 'device']) const digest = bytes => createHash('sha256').update(bytes).digest('hex') const nonempty = value => typeof value === 'string' && value.trim().length > 0 // The reviewed manifest defines scope. Counts and free-text evidence never prove a requirement. export function auditEvidence(manifest, load) { if (!manifest || manifest.version !== 1 || !nonempty(manifest.scope) || !nonempty(manifest.revision) || !Array.isArray(manifest.requirements) || manifest.requirements.length === 0) throw new Error('Invalid requirement manifest') const ids = new Set() const results = manifest.requirements.map(requirement => { const { id, domain, level, checks, evidence } = requirement if (!nonempty(id) || ids.has(id) || !nonempty(domain) || !levels.has(level) || !Array.isArray(checks) || checks.length === 0 || checks.some(x => !nonempty(x)) || new Set(checks).size !== checks.length) throw new Error('Invalid or duplicate requirement') ids.add(id) const reasons = [] if (!evidence || !nonempty(evidence.path) || !/^[a-f0-9]{64}$/.test(evidence.sha256 || '')) { return { id, domain, level, passed: false, reasons: ['MISSING_EVIDENCE'] } } let report try { const bytes = load(evidence.path) if (digest(bytes) !== evidence.sha256) reasons.push('HASH_MISMATCH') report = JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/, '')) } catch { return { id, domain, level, passed: false, reasons: ['UNREADABLE_EVIDENCE'] } } if (report.level !== level) reasons.push('EVIDENCE_LEVEL_MISMATCH') if (report.domain !== domain) reasons.push('BUSINESS_DOMAIN_MISMATCH') if (report.revision !== manifest.revision) reasons.push('REVISION_MISMATCH') if (!nonempty(report.runId)) reasons.push('MISSING_RUN_ID') if (!checkLegacyConsistency(report).passed) reasons.push('CONTRADICTORY_REPORT') if (report.success !== true || report.fatal || report.completionClaim === false || report.summary?.allConditionsComplete === false || Number(report.summary?.failed || 0) > 0 || Number(report.summary?.pendingConditions || 0) > 0 || Number(report.summary?.skippedModules || 0) > 0) reasons.push('REPORT_NOT_COMPLETE') if ((report.coverageGaps || []).some(gap => gap.blocking !== false) || Number(report.summary?.coverageGaps?.blocking || 0) > 0 || (report.deploymentBlockers || []).length > 0) reasons.push('BLOCKING_GAP') const steps = Array.isArray(report.steps) ? report.steps : [] if (steps.some(step => step.passed !== true || step.skipped)) { reasons.push('FAILED_OR_SKIPPED_STEP') } for (const name of checks) { const matches = steps.filter(step => step.name === name && step.requirementId === id) if (matches.length !== 1 || matches[0].passed !== true) reasons.push(`CHECK_NOT_PROVEN:${name}`) else if (['http', 'browser'].includes(level) && (!nonempty(matches[0].requestId) || !nonempty(matches[0].marker) || matches[0].marker !== report.runId)) { reasons.push(`MISSING_RUNTIME_IDENTITY:${name}`) } } return { id, domain, level, passed: reasons.length === 0, reasons } }) const passed = results.filter(row => row.passed).length return { version: 1, scope: manifest.scope, revision: manifest.revision, generatedAt: new Date().toISOString(), completionClaim: passed === results.length, summary: { requirements: results.length, passed, pending: results.length - passed }, results } } export function checkLegacyConsistency(report) { const reasons = [] if (report.summary?.allConditionsComplete === true && report.completionClaim !== true) reasons.push('CONTRADICTORY_COMPLETION') if (report.summary?.allConditionsComplete === true && report.summary?.checkedScopeEvidence !== report.summary?.requirements) reasons.push('INCOMPLETE_CHECKED_SCOPE') if (report.summary?.allConditionsComplete === true && (report.conditions || []).some(row => row.remaining)) reasons.push('REMAINING_REQUIREMENTS') return { passed: reasons.length === 0, reasons, scope: 'Consistency only; legacy report is not promoted to verified evidence' } } if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { try { const [mode, input, output] = process.argv.slice(2) if (!['audit', 'check-legacy'].includes(mode) || !input || !output) throw new Error('Usage: acceptance-evidence-audit.mjs audit|check-legacy ') const source = JSON.parse(readFileSync(input, 'utf8').replace(/^\uFEFF/, '')) const result = mode === 'audit' ? auditEvidence(source, file => readFileSync(resolve(dirname(input), file))) : checkLegacyConsistency(source) writeFileSync(output, JSON.stringify(result, null, 2) + '\n', { flag: 'wx' }) console.log(JSON.stringify(result.summary || { passed: result.passed, reasons: result.reasons })) process.exitCode = (mode === 'audit' ? result.completionClaim : result.passed) ? 0 : 1 } catch (error) { console.error(error.code === 'EEXIST' ? 'Output already exists; preserve prior evidence' : 'Evidence audit failed: invalid arguments, manifest or file') process.exitCode = 2 } }