#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { buildClosureSummary, renderReport, bestHistoryAudit, bestCustomerEffectSummary, bestOvernightAggregate, isClosableOvernightAggregate } = require('./proof-gap-closure-audit'); function main() { const historyAuditPath = path.join('outputs', 'history-audit-latest', 'historical-dataset-audit.json'); const staleHistoryAuditPath = path.join('outputs', 'stale-history-audit', 'historical-dataset-audit.json'); const sourceFiles = { history: historyAuditPath }; const passingHistory = passingHistoryAudit(); const open = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: false, historyReady: false, videoReady: false, reviewMetricsReady: false, customerEffectReady: false }, failureCount: 9 }, history: null, videoReadiness: { acceptance: { readyForVideoAbPreflight: false }, counts: { realReferenceRows: 1, realCandidateRows: 0 }, failureCount: 1 }, videoAb: { acceptance: { passed: false }, delta: { strong: 1, evidenceCards: 2 } }, overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: false, failedGateCount: 1 }, failureCount: 0 }, customerEffect: { acceptance: { overallPass: false }, customerSelectedRate: 0.2 } }); assert(!open.complete, 'open summary should not be complete'); assert(open.openCount === 5, `openCount should be 5, got ${open.openCount}`); assert(open.businessOpenCount === 5, `businessOpenCount should be 5, got ${open.businessOpenCount}`); assert(open.openCount === open.businessOpenCount, 'openCount should remain a business-open alias for compatibility'); assert(open.businessClosedCount === 0, `businessClosedCount should be 0, got ${open.businessClosedCount}`); assert(open.businessGapCount === 5, `businessGapCount should be 5, got ${open.businessGapCount}`); assert(open.precheckRowCount === 1, `precheckRowCount should be 1, got ${open.precheckRowCount}`); assert(open.precheckOpenCount === 1, `precheckOpenCount should be 1, got ${open.precheckOpenCount}`); assert(open.totalRows === 6, `totalRows should include intake precheck row, got ${open.totalRows}`); assert(open.countSemantics?.openCount === 'business_gap_open_count_excludes_precheck_rows', 'summary should document openCount count semantics'); assert(open.intakeReady === false, 'open summary should record intake readiness as false'); assert(open.rows.every(row => row.status === 'open'), 'all open fixture rows should be open'); assert(open.rows.some(row => row.id === 'intake-readiness'), 'summary should include intake readiness row'); assert(open.rows.every(row => row.command && row.expectedArtifact), 'all rows should include command and expected artifact'); assert(open.rows.every(row => Array.isArray(row.missingProofRequirements) && row.missingProofRequirements.length >= 1), 'all open fixture rows should explain missing proof requirements'); assert(open.rows.find(row => row.id === 'intake-readiness').missingProofRequirements.includes('acceptance.overallReady=true'), 'intake row should explain missing overall readiness'); assert(open.rows.find(row => row.id === 'historical-dataset').missingProofRequirements.includes('missing historical-dataset-audit.json'), 'history row should explain missing history audit'); assert(open.rows.find(row => row.id === 'video-real-candidate').missingProofRequirements.includes('counts.realCandidateRows>=1'), 'video resource row should explain missing real candidate rows'); assert(open.rows.find(row => row.id === 'live-provider-overnight-proof').missingProofRequirements.includes('acceptance.overallPass=true'), 'overnight row should explain missing overall pass'); assert(open.rows.find(row => row.id === 'manual-review-and-customer-effect').missingProofRequirements.includes('acceptance.overallPass=true'), 'customer effect row should explain missing customer effect pass'); const closed = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: { acceptance: { passed: true }, proofContext: { mode: 'live', collectionMode: 'live', generatedBy: 'acceptance:video-ab', requiresRuntimeCredential: true, requiresVocSocialProvider: true, requiresVideoAnalysisProvider: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } }, delta: { strong: 1, evidenceCards: 3 } }, overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); assert(closed.complete, 'closed summary should be complete'); assert(closed.closedCount === 5, `closedCount should be 5, got ${closed.closedCount}`); assert(closed.businessClosedCount === 5, `businessClosedCount should be 5, got ${closed.businessClosedCount}`); assert(closed.closedCount === closed.businessClosedCount, 'closedCount should remain a business-closed alias for compatibility'); assert(closed.businessOpenCount === 0, `businessOpenCount should be 0, got ${closed.businessOpenCount}`); assert(closed.precheckOpenCount === 0, `precheckOpenCount should be 0, got ${closed.precheckOpenCount}`); assert(closed.precheckClosedCount === 1, `precheckClosedCount should be 1, got ${closed.precheckClosedCount}`); assert(closed.intakeReady === true, 'closed summary should record intake readiness as true'); assert(closed.rows.every(row => Array.isArray(row.missingProofRequirements) && row.missingProofRequirements.length === 0), 'closed rows should not keep stale missing proof requirements'); const report = renderReport(closed); assert(report.includes('是否全部关闭:是'), 'report should render completion'); assert(report.includes('Intake 预审是否就绪:是'), 'report should render intake readiness'); assert(report.includes('业务证明缺口未关闭:0'), 'report should render business open proof gap count'); assert(report.includes('预审未就绪行:0'), 'report should render precheck open row count separately'); assert(report.includes('缺失证明'), 'report should render missing proof column'); assert(report.includes('验证命令'), 'report should render command column'); assert(report.includes('期望产物'), 'report should render expected artifact column'); assert(!/sk-[A-Za-z0-9_-]{8,}/.test(report), 'report should not include secret-like values'); const thinIntake = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: false, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: passingVideoReadiness(), videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); const thinIntakeRow = thinIntake.rows.find(row => row.id === 'intake-readiness'); assert(thinIntakeRow.status === 'open', 'intake with top-level overallReady but missing child readiness should remain open'); assert(thinIntakeRow.missingProofRequirements.includes('acceptance.historyReady=true'), 'thin intake should require history readiness'); const thinHistory = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: { briefCount: 3, acceptance: { readyForCustomerEffectProof: true } }, videoReadiness: passingVideoReadiness(), videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); const thinHistoryRow = thinHistory.rows.find(row => row.id === 'historical-dataset'); assert(thinHistoryRow.status === 'open', 'history audit with fewer than 5 briefs should remain open'); assert(thinHistoryRow.missingProofRequirements.includes('briefCount>=5'), 'thin history should require at least 5 briefs'); const thinTopLevelHistory = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: { briefCount: 5, acceptance: { readyForCustomerEffectProof: true } }, videoReadiness: passingVideoReadiness(), videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); const thinTopLevelHistoryRow = thinTopLevelHistory.rows.find(row => row.id === 'historical-dataset'); assert(thinTopLevelHistoryRow.status === 'open', 'history audit with only top-level ready flag should remain open'); assert(thinTopLevelHistoryRow.missingProofRequirements.includes('acceptance.allHaveManualFinalList=true'), 'thin history should require manual final-list coverage'); assert(thinTopLevelHistoryRow.missingProofRequirements.includes('items.length>=5'), 'thin history should include auditable item details'); const thinVideoResource = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1 }, failureCount: 0 }, videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); const thinVideoResourceRow = thinVideoResource.rows.find(row => row.id === 'video-real-candidate'); assert(thinVideoResourceRow.status === 'open', 'video readiness without video URL rows should remain open'); assert(thinVideoResourceRow.missingProofRequirements.includes('counts.videoUrlRows>=1'), 'thin video readiness should require video URL rows'); const thinCustomerEffect = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: { acceptance: { overallPass: true }, customerSelectedRate: 0.42, manualSupplementReductionRate: 0.5, historyAuditPath }, sourceFiles }); const thinCustomerEffectRow = thinCustomerEffect.rows.find(row => row.id === 'manual-review-and-customer-effect'); assert(thinCustomerEffectRow.status === 'open', 'thin customer-effect summary should not close customer proof'); assert(thinCustomerEffectRow.missingProofRequirements.includes('acceptance.reviewMetricsOverallUsable=true'), 'thin customer-effect summary should require review metrics usability'); assert(thinCustomerEffectRow.missingProofRequirements.includes('referenceCustomerSelectedRate=number'), 'thin customer-effect summary should require reference customer selected rate'); const mismatchedCustomerEffect = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: passingVideoAb(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(staleHistoryAuditPath), sourceFiles }); const mismatchedCustomerEffectRow = mismatchedCustomerEffect.rows.find(row => row.id === 'manual-review-and-customer-effect'); assert(mismatchedCustomerEffectRow.status === 'open', 'customer-effect summary bound to stale history audit should stay open'); assert(mismatchedCustomerEffectRow.missingProofRequirements.includes('historyAuditPath=current-history-audit'), 'customer-effect summary should bind to current history audit path'); const fakeVideo = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: { acceptance: { passed: true }, delta: { strong: 1, evidenceCards: 3 } }, overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); assert(!fakeVideo.complete, 'passed video A/B without live proofContext should not close all proof gaps'); const fakeVideoRow = fakeVideo.rows.find(row => row.id === 'video-ab-live-proof'); assert(fakeVideoRow.status === 'open', 'video A/B without live proofContext should remain open'); assert(fakeVideoRow.missingProofRequirements.includes('proofContext.mode=live'), 'video A/B row should explain missing live mode'); assert(fakeVideoRow.missingProofRequirements.includes('proofContext.generatedBy=acceptance:video-ab'), 'video A/B row should explain missing generatedBy'); const preflightVideo = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: { acceptance: { passed: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } }, delta: { strong: 1, evidenceCards: 3 } }, videoAbPreflight: failedVideoAbPreflight(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); assert(!preflightVideo.complete, 'video A/B preflight failure context should not close live proof'); assert(preflightVideo.videoAbPreflightReadyForVideoAb === false, 'summary should expose video A/B preflight readiness'); assert(preflightVideo.videoAbPreflightFailureCount === 4, 'summary should expose video A/B preflight failure count'); const preflightVideoRow = preflightVideo.rows.find(row => row.id === 'video-ab-live-proof'); assert(preflightVideoRow.status === 'open', 'video A/B row should remain open when only preflight failed'); assert(preflightVideoRow.evidence.includes('preflight.readyForVideoAb=false'), 'video A/B evidence should include preflight readiness'); assert(preflightVideoRow.evidence.includes('runtimeCredentialPresent=false'), 'video A/B evidence should include runtime credential presence only'); assert(preflightVideoRow.missingProofRequirements.includes('videoAbPreflight.readyForVideoAb=true'), 'video A/B row should require preflight readiness before live proof can run'); assert(preflightVideoRow.missingProofRequirements.includes('videoAbPreflight.company resolved for live run'), 'video A/B row should include preflight missing company context'); const liveVideoWithStalePreflight = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: passingVideoAb(), videoAbPreflight: failedVideoAbPreflight(), overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); assert(liveVideoWithStalePreflight.complete, 'passing live video A/B proof should not be blocked by an older preflight failure'); const liveVideoRow = liveVideoWithStalePreflight.rows.find(row => row.id === 'video-ab-live-proof'); assert(liveVideoRow.status === 'closed', 'passing live video A/B row should close'); assert(!liveVideoRow.evidence.includes('preflight.readyForVideoAb=false'), 'closed live video proof should not foreground stale preflight failure context'); const tokenLeak = buildClosureSummary({ root: process.cwd(), intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }, history: passingHistory, videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }, videoAb: { acceptance: { passed: true }, proofContext: { mode: 'live', collectionMode: 'live', generatedBy: 'acceptance:video-ab', requiresRuntimeCredential: true, requiresVocSocialProvider: true, requiresVideoAnalysisProvider: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } }, delta: { strong: 1, evidenceCards: 3 } }, overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0, tokenLeak: true }, failureCount: 0 }, customerEffect: passingCustomerEffect(historyAuditPath), sourceFiles }); const tokenLeakRow = tokenLeak.rows.find(row => row.id === 'live-provider-overnight-proof'); assert(tokenLeakRow.status === 'open', 'overnight aggregate with token leak should remain open'); assert(tokenLeakRow.missingProofRequirements.includes('acceptance.tokenLeak=false'), 'overnight token leak should be listed as a missing requirement'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-proof-gap-')); try { const closableDir = path.join(tmp, 'older-closable'); const badDir = path.join(tmp, 'newer-bad'); fs.mkdirSync(closableDir, { recursive: true }); fs.mkdirSync(badDir, { recursive: true }); const closableFile = path.join(closableDir, 'aggregate-summary.json'); const badFile = path.join(badDir, 'aggregate-summary.json'); fs.writeFileSync(closableFile, JSON.stringify({ manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0, tokenLeak: false }, failureCount: 0 }), 'utf8'); fs.writeFileSync(badFile, JSON.stringify({ manifest: { liveEnabled: false }, acceptance: { overallPass: false, failedGateCount: 1, tokenLeak: false }, failureCount: 0 }), 'utf8'); fs.utimesSync(closableFile, new Date(Date.now() - 10000), new Date(Date.now() - 10000)); fs.utimesSync(badFile, new Date(), new Date()); const best = bestOvernightAggregate(tmp); assert(path.resolve(best) === path.resolve(closableFile), 'best overnight aggregate should prefer an older closable aggregate over a newer bad aggregate'); assert(isClosableOvernightAggregate(JSON.parse(fs.readFileSync(best, 'utf8'))), 'selected overnight aggregate should be closable'); } finally { fs.rmSync(tmp, { recursive: true, force: true }); } const proofTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-proof-discovery-')); try { const realHistoryDir = path.join(proofTmp, 'pipeline-run', 'history-audit'); const smokeHistoryDir = path.join(proofTmp, 'pipeline-smoke-run', 'history-audit'); const newerFailingHistoryDir = path.join(proofTmp, 'latest-real-run', 'history-audit'); fs.mkdirSync(realHistoryDir, { recursive: true }); fs.mkdirSync(smokeHistoryDir, { recursive: true }); fs.mkdirSync(newerFailingHistoryDir, { recursive: true }); const passingHistoryFile = path.join(realHistoryDir, 'historical-dataset-audit.json'); const smokeHistoryFile = path.join(smokeHistoryDir, 'historical-dataset-audit.json'); const failingHistoryFile = path.join(newerFailingHistoryDir, 'historical-dataset-audit.json'); fs.writeFileSync(passingHistoryFile, JSON.stringify(passingHistoryAudit(), null, 2), 'utf8'); fs.writeFileSync(smokeHistoryFile, JSON.stringify(passingHistoryAudit(), null, 2), 'utf8'); fs.writeFileSync(failingHistoryFile, JSON.stringify({ briefCount: 1, acceptance: { readyForCustomerEffectProof: false }, items: [] }, null, 2), 'utf8'); fs.utimesSync(passingHistoryFile, new Date(Date.now() - 20000), new Date(Date.now() - 20000)); fs.utimesSync(smokeHistoryFile, new Date(), new Date()); fs.utimesSync(failingHistoryFile, new Date(Date.now() - 10000), new Date(Date.now() - 10000)); const discoveredHistory = bestHistoryAudit(proofTmp); assert(path.resolve(discoveredHistory) === path.resolve(passingHistoryFile), 'history discovery should recurse and prefer a claimable non-smoke audit'); const currentCustomerDir = path.join(proofTmp, 'latest-real-run', 'customer-effect-audit'); const staleCustomerDir = path.join(proofTmp, 'other-real-run', 'customer-effect-audit'); fs.mkdirSync(currentCustomerDir, { recursive: true }); fs.mkdirSync(staleCustomerDir, { recursive: true }); const currentCustomerFile = path.join(currentCustomerDir, 'customer-effect-summary.json'); const staleCustomerFile = path.join(staleCustomerDir, 'customer-effect-summary.json'); fs.writeFileSync(currentCustomerFile, JSON.stringify({ ...passingCustomerEffect(failingHistoryFile), acceptance: { overallPass: false, historyReadyForCustomerEffectProof: false }, customerSelectedRate: 0 }, null, 2), 'utf8'); fs.writeFileSync(staleCustomerFile, JSON.stringify(passingCustomerEffect(path.join('outputs', 'stale-history-audit', 'historical-dataset-audit.json')), null, 2), 'utf8'); fs.utimesSync(currentCustomerFile, new Date(Date.now() - 10000), new Date(Date.now() - 10000)); fs.utimesSync(staleCustomerFile, new Date(), new Date()); const discoveredCustomerEffect = bestCustomerEffectSummary(proofTmp, failingHistoryFile, process.cwd()); assert(path.resolve(discoveredCustomerEffect) === path.resolve(currentCustomerFile), 'customer-effect discovery should prefer summaries bound to the selected history audit'); } finally { fs.rmSync(proofTmp, { recursive: true, force: true }); } console.log(JSON.stringify({ ok: true, openCount: open.openCount, closedCount: closed.closedCount }, null, 2)); } function assert(condition, message) { if (!condition) throw new Error(message); } function passingVideoAb() { return { acceptance: { passed: true }, proofContext: { mode: 'live', collectionMode: 'live', generatedBy: 'acceptance:video-ab', requiresRuntimeCredential: true, requiresVocSocialProvider: true, requiresVideoAnalysisProvider: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } }, delta: { strong: 1, evidenceCards: 3 } }; } function passingVideoReadiness() { return { acceptance: { readyForVideoAbPreflight: true }, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }, failureCount: 0 }; } function failedVideoAbPreflight() { return { materialType: 'video_ab_runtime_preflight', directCustomerProof: false, proofLevel: 'not_business_proof', canCloseProofGap: false, readyForVideoAb: false, failureCount: 4, proofContext: { mode: 'not_run', collectionMode: 'not_run', generatedBy: 'acceptance:video-ab-preflight', requiresRuntimeCredential: true, requiresVocSocialProvider: true, requiresVideoAnalysisProvider: true }, runtime: { runtimeCredentialPresent: false, companyResolved: false, vocSocialProviderTokenPresent: false, videoAnalysisTokenPresent: false }, missingProofRequirements: [ 'proofContext.requiresRuntimeCredential=true', 'company resolved for live run', 'proofContext.requiresVocSocialProvider=true', 'proofContext.requiresVideoAnalysisProvider=true' ] }; } function passingCustomerEffect(historyAuditPath) { return { reviewPath: path.join('outputs', 'review-metrics-latest', 'reviewed.csv'), historyAuditPath, customerSelectedRate: 0.5, referenceCustomerSelectedRate: 0.5, manualSupplementBaseline: { available: true, source: 'history-audit', total: 10, byBrief: [{ briefId: 'brief-a', count: 10 }] }, currentManualSupplement: { available: true, source: 'cli-arg', total: 5, byBrief: [] }, manualSupplementReductionRate: 0.5, acceptance: { overallPass: true, reviewMetricsOverallUsable: true, customerSelectedRateMeasured: true, customerSelectedRate30Pass: true, referenceCustomerSelectedRateMeasured: true, referenceCustomerSelectedRate40Pass: true, historyReadyForCustomerEffectProof: true, manualSupplementBaselineAvailable: true, currentManualSupplementAvailable: true, manualSupplementReduction50Pass: true }, missingEvidence: [] }; } function passingHistoryAudit() { return { generatedAt: '2026-06-11T02:48:48.000Z', minBriefs: 5, briefCount: 5, categoryCount: 3, categories: ['skincare', 'mom-baby', 'home'], withReference: 5, withManualFinalList: 5, withCustomerDecision: 5, withRejectionReason: 2, withManualSupplementBaseline: 5, missingCriticalCount: 0, acceptance: { parseOk: true, minBriefsMet: true, allHaveBriefText: true, allHaveManualFinalList: true, allHaveCustomerDecision: true, allHaveFeedbackReason: true, allHaveManualSupplementBaseline: true, categoryCoverageMet: true, referenceCoverageMeasured: true, readyForLongRun: true, readyForCustomerEffectProof: true }, items: Array.from({ length: 5 }, (_, index) => ({ id: `brief-${index + 1}`, parseOk: true, hasBriefText: true, hasManualFinalList: true, hasCustomerDecision: true, hasRejectionReason: index < 2, hasManualSupplementBaseline: true, manualSupplementBaseline: 2, missingCritical: [] })) }; } main();