optimization-completion-smoke.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const {
  6. buildCompletionSummary,
  7. renderReport,
  8. bestHistoryAudit,
  9. bestCustomerEffectSummary
  10. } = require('./optimization-completion-audit');
  11. function main() {
  12. const historyAuditPath = path.join('outputs', 'history-audit-latest', 'historical-dataset-audit.json');
  13. const staleHistoryAuditPath = path.join('outputs', 'stale-history-audit', 'historical-dataset-audit.json');
  14. const sourceFiles = { history: historyAuditPath };
  15. const passingHistory = passingHistoryAudit();
  16. const open = buildCompletionSummary({
  17. root: process.cwd(),
  18. outputsDir: 'outputs',
  19. intakeReadiness: { acceptance: { overallReady: false }, failureCount: 9 },
  20. videoReadiness: { acceptance: { readyForVideoAbPreflight: false }, failureCount: 1, counts: { realCandidateRows: 0 } },
  21. proofGapClosure: { complete: false, openCount: 5, closedCount: 0 },
  22. handoff: { complete: false, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [{ id: 'video-ab-proof' }], externalBlockers: [{ id: 'customer-effect-proof' }] },
  23. businessProgress: { complete: false, counts: { pass: 3, fail: 2, pending: 3, blocked_by_external_data: 7 } },
  24. longRunReadiness: { ready: false, counts: { pass: 6, fail: 7 } },
  25. videoAb: { acceptance: { passed: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } } },
  26. overnight: { manifest: { liveEnabled: false }, acceptance: { overallPass: false, failedGateCount: 0 }, failureCount: 0 },
  27. customerEffect: null,
  28. optimizationPipeline: null
  29. });
  30. assert(open.complete === false, 'open fixture should not be complete');
  31. assert(open.readyForClaim === false, 'open fixture should not be ready for claim');
  32. assert(open.proofGapOpenCount === 5, `proof gap open count should be 5, got ${open.proofGapOpenCount}`);
  33. assert(open.intakeFailureCount === 9, 'should expose intake failure count');
  34. assert(open.videoResourceFailureCount === 1, 'should expose video resource failure count');
  35. assert(open.realCandidateRows === 0, 'should expose zero real candidate rows');
  36. assert(open.materialType === 'optimization_completion_audit', 'should expose completion audit material type');
  37. assert(open.directCustomerProof === false, 'completion audit itself should not be direct customer proof');
  38. assert(open.proofLevel === 'not_business_proof', 'open fixture should stay not_business_proof');
  39. assert(open.blockingReasons.length >= 5, 'open fixture should expose blocking reasons');
  40. assert(open.blockingReasonCount === open.blockingReasons.length, 'should expose top-level blocking reason count');
  41. assert(open.blockingReasonsWithMissingProofRequirements === open.blockingReasons.length, 'each open blocker should carry missing proof requirements');
  42. assert(open.missingProofRequirementCount >= open.blockingReasonCount, 'should expose unique missing proof requirement count');
  43. assert(open.canClaim.tihaoRateImproved === false, 'should not allow tihao-rate claim');
  44. assert(open.canClaim.customerEffectAchieved === false, 'should not allow customer-effect claim');
  45. assert(open.canClaim.manualSupplementReduced === false, 'should not allow manual-supplement claim');
  46. assert(open.forbiddenClaimsWhenIncomplete.includes('提号率已经提升'), 'should list forbidden tihao claim');
  47. assert(open.guardrails.some(item => item.includes('不是业务证明')), 'should keep proof boundary guardrail');
  48. const blockedGates = open.gates.filter(item => item.status === 'blocked');
  49. assert(blockedGates.length === open.blockingReasons.length, 'blocked gates should match blocking reasons');
  50. assert(blockedGates.every(item => Array.isArray(item.missingProofRequirements) && item.missingProofRequirements.length >= 1), 'every blocked gate should explain missing proof requirements');
  51. assert(open.blockingReasons.every(item => Array.isArray(item.missingProofRequirements) && item.missingProofRequirements.length >= 1), 'every blocking reason should carry missing proof requirements');
  52. const proofGapGate = open.gates.find(item => item.id === 'proof-gap-closure');
  53. assert(proofGapGate.missingProofRequirements.includes('proofGapClosure.complete=true'), 'proof-gap gate should require complete=true');
  54. assert(proofGapGate.missingProofRequirements.includes('proofGapClosure.openCount=0'), 'proof-gap gate should require openCount=0');
  55. const fakeVideoGate = open.gates.find(item => item.id === 'video-ab-live-proof');
  56. assert(fakeVideoGate.status === 'blocked', 'video A/B without proof context should stay blocked');
  57. assert(fakeVideoGate.missingProofRequirements.includes('proofContext.mode=live'), 'should explain missing live mode');
  58. const preflightVideo = buildCompletionSummary({
  59. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  60. videoAb: { acceptance: { passed: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } } },
  61. videoAbPreflight: failedVideoAbPreflight()
  62. });
  63. assert(preflightVideo.complete === false, 'video A/B preflight failure context should keep completion blocked until live proof exists');
  64. assert(preflightVideo.videoAbPreflightReadyForVideoAb === false, 'completion should expose video A/B preflight readiness');
  65. assert(preflightVideo.videoAbPreflightFailureCount === 4, 'completion should expose video A/B preflight failure count');
  66. const preflightVideoGate = preflightVideo.gates.find(item => item.id === 'video-ab-live-proof');
  67. assert(preflightVideoGate.status === 'blocked', 'video A/B gate should remain blocked with preflight failure only');
  68. assert(preflightVideoGate.evidence.includes('preflight.readyForVideoAb=false'), 'video A/B gate should show preflight readiness');
  69. assert(preflightVideoGate.evidence.includes('runtimeCredentialPresent=false'), 'video A/B gate should show credential presence only');
  70. assert(preflightVideoGate.missingProofRequirements.includes('videoAbPreflight.readyForVideoAb=true'), 'video A/B gate should require preflight readiness before live proof can run');
  71. assert(preflightVideoGate.missingProofRequirements.includes('videoAbPreflight.company resolved for live run'), 'video A/B gate should include preflight missing company context');
  72. const liveVideoWithStalePreflight = buildCompletionSummary({
  73. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  74. videoAb: passingVideoAb(),
  75. videoAbPreflight: failedVideoAbPreflight()
  76. });
  77. assert(liveVideoWithStalePreflight.complete === true, 'passing live video A/B proof should not be blocked by an older preflight failure');
  78. const liveVideoGate = liveVideoWithStalePreflight.gates.find(item => item.id === 'video-ab-live-proof');
  79. assert(liveVideoGate.status === 'pass', 'passing live video A/B proof should pass');
  80. assert(!liveVideoGate.evidence.includes('preflight.readyForVideoAb=false'), 'passing live video proof should not foreground stale preflight failure context');
  81. const tokenLeakOvernight = buildCompletionSummary({
  82. root: process.cwd(),
  83. outputsDir: 'outputs',
  84. sourceFiles,
  85. history: passingHistory,
  86. intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 },
  87. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 2, videoUrlRows: 2 } },
  88. proofGapClosure: passingProofGapClosure(),
  89. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [], externalBlockers: [] },
  90. businessProgress: passingBusinessProgress(),
  91. longRunReadiness: { ready: true, counts: { pass: 13, fail: 0 } },
  92. videoAb: {
  93. acceptance: { passed: true },
  94. proofContext: {
  95. mode: 'live',
  96. collectionMode: 'live',
  97. generatedBy: 'acceptance:video-ab',
  98. requiresRuntimeCredential: true,
  99. requiresVocSocialProvider: true,
  100. requiresVideoAnalysisProvider: true
  101. },
  102. enhanced: { provider: { evidence: { providerStatus: 'ok' } } }
  103. },
  104. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0, tokenLeak: true }, failureCount: 0 },
  105. customerEffect: passingCustomerEffect(historyAuditPath),
  106. optimizationPipeline: passingOptimizationPipeline()
  107. });
  108. const tokenLeakGate = tokenLeakOvernight.gates.find(item => item.id === 'live-provider-overnight-proof');
  109. assert(tokenLeakOvernight.complete === false, 'token leak overnight fixture should not be complete');
  110. assert(tokenLeakOvernight.readyForClaim === false, 'token leak overnight fixture should not be ready for claim');
  111. assert(tokenLeakGate.status === 'blocked', 'token leak overnight gate should be blocked');
  112. assert(tokenLeakGate.missingProofRequirements.includes('acceptance.tokenLeak=false'), 'token leak overnight gate should explain missing tokenLeak=false');
  113. const stalePipeline = buildCompletionSummary({
  114. root: process.cwd(),
  115. outputsDir: 'outputs',
  116. intakeReadiness: { generatedAt: '2026-06-11T02:48:47.000Z', acceptance: { overallReady: false }, failureCount: 6 },
  117. videoReadiness: { generatedAt: '2026-06-11T02:48:47.000Z', acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 } },
  118. proofGapClosure: { generatedAt: '2026-06-11T02:48:47.000Z', complete: false, openCount: 3, closedCount: 2 },
  119. handoff: { complete: false, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [{ id: 'video-ab-proof' }], externalBlockers: [{ id: 'customer-effect-proof' }] },
  120. businessProgress: { complete: false, counts: { pass: 5, fail: 2, pending: 3, blocked_by_external_data: 5 } },
  121. longRunReadiness: { ready: false, counts: { pass: 3, fail: 6 } },
  122. videoAb: { acceptance: { passed: true }, enhanced: { provider: { evidence: { providerStatus: 'ok' } } } },
  123. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 },
  124. customerEffect: null,
  125. optimizationPipeline: {
  126. generatedAt: '2026-06-07T08:59:39.000Z',
  127. readyForClaim: false,
  128. proofGapClosure: { complete: false, openCount: 5 },
  129. results: [{ id: 'history-audit', status: 'fail' }]
  130. }
  131. });
  132. const stalePipelineGate = stalePipeline.gates.find(item => item.id === 'optimization-pipeline');
  133. assert(stalePipelineGate.status === 'blocked', 'stale optimization pipeline should stay blocked');
  134. assert(stalePipelineGate.evidence.includes('stale=true'), 'stale optimization pipeline should be visible in evidence');
  135. assert(stalePipelineGate.missingProofRequirements.includes('optimizationPipeline.generatedAt>=currentProofSources'), 'stale optimization pipeline should require rerun after current proof sources');
  136. const thinIntake = buildCompletionSummary({
  137. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  138. intakeReadiness: { acceptance: { overallReady: true, historyReady: false, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 }
  139. });
  140. const thinIntakeGate = thinIntake.gates.find(item => item.id === 'intake-readiness');
  141. assert(thinIntake.complete === false, 'thin intake summary should keep completion blocked');
  142. assert(thinIntakeGate.missingProofRequirements.includes('acceptance.historyReady=true'), 'thin intake should require history readiness');
  143. const thinTopLevelHistory = buildCompletionSummary({
  144. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  145. history: { generatedAt: '2026-06-11T02:48:48.000Z', briefCount: 5, acceptance: { readyForCustomerEffectProof: true } }
  146. });
  147. const thinTopLevelHistoryGate = thinTopLevelHistory.gates.find(item => item.id === 'customer-effect-proof');
  148. assert(thinTopLevelHistory.complete === false, 'history audit with only top-level ready flag should keep completion blocked');
  149. assert(thinTopLevelHistoryGate.missingProofRequirements.includes('currentHistoryAudit.acceptance.allHaveManualFinalList=true'), 'thin history should require manual final-list coverage');
  150. assert(thinTopLevelHistoryGate.missingProofRequirements.includes('currentHistoryAudit.items.length>=5'), 'thin history should include auditable item details');
  151. const thinVideoResource = buildCompletionSummary({
  152. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  153. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realCandidateRows: 1 } }
  154. });
  155. const thinVideoResourceGate = thinVideoResource.gates.find(item => item.id === 'video-resource-readiness');
  156. assert(thinVideoResource.complete === false, 'thin video resource summary should keep completion blocked');
  157. assert(thinVideoResourceGate.missingProofRequirements.includes('counts.realReferenceRows>=1'), 'thin video resource should require real reference rows');
  158. assert(thinVideoResourceGate.missingProofRequirements.includes('counts.videoUrlRows>=1'), 'thin video resource should require video URL rows');
  159. const thinProofGapClosure = buildCompletionSummary({
  160. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  161. proofGapClosure: { complete: true, openCount: 0, closedCount: 3 }
  162. });
  163. const thinProofGapClosureGate = thinProofGapClosure.gates.find(item => item.id === 'proof-gap-closure');
  164. assert(thinProofGapClosure.complete === false, 'thin proof-gap closure summary should keep completion blocked');
  165. assert(thinProofGapClosureGate.missingProofRequirements.includes('proofGapClosure.closedCount>=5'), 'thin proof-gap closure should require all 5 business gaps closed');
  166. const thinProofGapRows = buildCompletionSummary({
  167. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  168. proofGapClosure: { complete: true, openCount: 0, closedCount: 5, businessGapCount: 5 }
  169. });
  170. const thinProofGapRowsGate = thinProofGapRows.gates.find(item => item.id === 'proof-gap-closure');
  171. assert(thinProofGapRows.complete === false, 'proof-gap closure without business rows should keep completion blocked');
  172. assert(thinProofGapRowsGate.missingProofRequirements.includes('proofGapClosure.businessRows.length>=5'), 'proof-gap closure should include business row details');
  173. const looseHandoff = buildCompletionSummary({
  174. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  175. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [{ id: 'video-ab-proof' }], externalBlockers: [] }
  176. });
  177. const looseHandoffGate = looseHandoff.gates.find(item => item.id === 'handoff-complete');
  178. assert(looseHandoff.complete === false, 'handoff with ready-not-proven items should keep completion blocked');
  179. assert(looseHandoffGate.missingProofRequirements.includes('handoff.readyNotProven.length=0'), 'handoff should require zero ready-not-proven rows');
  180. const looseBusinessProgress = buildCompletionSummary({
  181. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  182. businessProgress: { complete: true, counts: { pass: 14, fail: 0, pending: 0, blocked_by_external_data: 1 } }
  183. });
  184. const looseBusinessProgressGate = looseBusinessProgress.gates.find(item => item.id === 'business-proof-progress');
  185. assert(looseBusinessProgress.complete === false, 'business progress with blocked rows should keep completion blocked');
  186. assert(looseBusinessProgressGate.missingProofRequirements.includes('businessProofProgress.counts.blocked_by_external_data=0'), 'business progress should require zero blocked rows');
  187. const thinBusinessProgressRows = buildCompletionSummary({
  188. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  189. businessProgress: { complete: true, counts: { pass: 15, fail: 0, pending: 0, blocked_by_external_data: 0 } }
  190. });
  191. const thinBusinessProgressRowsGate = thinBusinessProgressRows.gates.find(item => item.id === 'business-proof-progress');
  192. assert(thinBusinessProgressRows.complete === false, 'business progress without 15 rows should keep completion blocked');
  193. assert(thinBusinessProgressRowsGate.missingProofRequirements.includes('businessProofProgress.rows.length>=15'), 'business progress should include 15 step rows');
  194. const thinOptimizationPipelineProofGap = buildCompletionSummary({
  195. ...passingCompletionBase({ historyAuditPath, sourceFiles, history: passingHistory }),
  196. optimizationPipeline: { readyForClaim: true, generatedAt: '2026-06-11T02:48:49.000Z', proofGapClosure: { complete: true, openCount: 0 } }
  197. });
  198. const thinOptimizationPipelineGate = thinOptimizationPipelineProofGap.gates.find(item => item.id === 'optimization-pipeline');
  199. assert(thinOptimizationPipelineProofGap.complete === false, 'pipeline with thin proof-gap closure should keep completion blocked');
  200. assert(thinOptimizationPipelineGate.missingProofRequirements.includes('optimizationPipeline.proofGapClosure.closedCount>=5'), 'pipeline should require closedCount>=5 from proof-gap closure');
  201. const thinCustomerEffect = buildCompletionSummary({
  202. root: process.cwd(),
  203. outputsDir: 'outputs',
  204. sourceFiles,
  205. history: passingHistory,
  206. intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 },
  207. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 2, videoUrlRows: 2 } },
  208. proofGapClosure: passingProofGapClosure(),
  209. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [], externalBlockers: [] },
  210. businessProgress: passingBusinessProgress(),
  211. longRunReadiness: { ready: true, counts: { pass: 13, fail: 0 } },
  212. videoAb: passingVideoAb(),
  213. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 },
  214. customerEffect: { acceptance: { overallPass: true }, customerSelectedRate: 0.5, manualSupplementReductionRate: 0.5, historyAuditPath },
  215. optimizationPipeline: passingOptimizationPipeline()
  216. });
  217. const thinCustomerEffectGate = thinCustomerEffect.gates.find(item => item.id === 'customer-effect-proof');
  218. assert(thinCustomerEffect.complete === false, 'thin customer-effect summary should keep completion blocked');
  219. assert(thinCustomerEffect.readyForClaim === false, 'thin customer-effect summary should not be ready for claim');
  220. assert(thinCustomerEffectGate.missingProofRequirements.includes('acceptance.reviewMetricsOverallUsable=true'), 'thin customer-effect summary should require review metrics usability');
  221. const mismatchedCustomerEffect = buildCompletionSummary({
  222. root: process.cwd(),
  223. outputsDir: 'outputs',
  224. sourceFiles,
  225. history: passingHistory,
  226. intakeReadiness: { acceptance: { overallReady: true }, failureCount: 0 },
  227. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 2, videoUrlRows: 2 } },
  228. proofGapClosure: passingProofGapClosure(),
  229. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [], externalBlockers: [] },
  230. businessProgress: passingBusinessProgress(),
  231. longRunReadiness: { ready: true, counts: { pass: 13, fail: 0 } },
  232. videoAb: passingVideoAb(),
  233. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 },
  234. customerEffect: passingCustomerEffect(staleHistoryAuditPath),
  235. optimizationPipeline: passingOptimizationPipeline()
  236. });
  237. const mismatchedCustomerEffectGate = mismatchedCustomerEffect.gates.find(item => item.id === 'customer-effect-proof');
  238. assert(mismatchedCustomerEffect.complete === false, 'customer-effect summary bound to stale history should keep completion blocked');
  239. assert(mismatchedCustomerEffectGate.missingProofRequirements.includes('historyAuditPath=current-history-audit'), 'customer-effect summary should bind to current history audit path');
  240. const report = renderReport(open);
  241. assert(report.includes('是否完成:否'), 'report should render incomplete status');
  242. assert(report.includes('是否可声明业务效果:否'), 'report should render claim status');
  243. assert(report.includes('证明等级:not_business_proof'), 'report should render proof level');
  244. assert(report.includes('直接客户证明:否'), 'report should render direct customer proof boundary');
  245. assert(report.includes('缺失证明要求唯一计数'), 'report should render missing proof count');
  246. assert(report.includes('提号率提升:不可声明'), 'report should render forbidden tihao claim');
  247. assert(report.includes('客户效果达标:不可声明'), 'report should render forbidden customer claim');
  248. assert(report.includes('人工补号量下降:不可声明'), 'report should render forbidden supplement claim');
  249. assert(!/sk-[A-Za-z0-9_-]{8,}/.test(report), 'report should not include model token patterns');
  250. assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not include session token patterns');
  251. assert(!/Authorization\s*[:=]\s*Bearer/i.test(report), 'report should not expose authorization values');
  252. const closed = buildCompletionSummary({
  253. root: process.cwd(),
  254. outputsDir: 'outputs',
  255. sourceFiles,
  256. history: passingHistory,
  257. intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 },
  258. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 2, videoUrlRows: 2 } },
  259. proofGapClosure: passingProofGapClosure(),
  260. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [], externalBlockers: [] },
  261. businessProgress: passingBusinessProgress(),
  262. longRunReadiness: { ready: true, counts: { pass: 13, fail: 0 } },
  263. videoAb: {
  264. acceptance: { passed: true },
  265. proofContext: {
  266. mode: 'live',
  267. collectionMode: 'live',
  268. generatedBy: 'acceptance:video-ab',
  269. requiresRuntimeCredential: true,
  270. requiresVocSocialProvider: true,
  271. requiresVideoAnalysisProvider: true
  272. },
  273. enhanced: { provider: { evidence: { providerStatus: 'ok' } } }
  274. },
  275. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 },
  276. customerEffect: passingCustomerEffect(historyAuditPath),
  277. optimizationPipeline: passingOptimizationPipeline()
  278. });
  279. assert(closed.complete === true, 'closed fixture should be complete');
  280. assert(closed.readyForClaim === true, 'closed fixture should be ready for claim');
  281. assert(closed.proofLevel === 'real_evidence', 'closed fixture should be evidence-level claimable');
  282. assert(closed.directCustomerProof === false, 'completion audit remains an audit artifact');
  283. assert(closed.blockingReasons.length === 0, 'closed fixture should have no blockers');
  284. assert(closed.blockingReasonCount === 0, 'closed fixture should expose zero blocker count');
  285. assert(closed.blockingReasonsWithMissingProofRequirements === 0, 'closed fixture should expose zero blockers with missing proof');
  286. assert(closed.missingProofRequirementCount === 0, 'closed fixture should expose zero missing proof requirements');
  287. assert(closed.gates.every(item => Array.isArray(item.missingProofRequirements) && item.missingProofRequirements.length === 0), 'closed gates should not keep stale missing proof requirements');
  288. assert(closed.canClaim.tihaoRateImproved === true, 'closed fixture should allow tihao claim');
  289. assert(closed.canClaim.customerEffectAchieved === true, 'closed fixture should allow customer claim');
  290. assert(closed.canClaim.manualSupplementReduced === true, 'closed fixture should allow supplement claim');
  291. const proofTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-completion-discovery-'));
  292. try {
  293. const realHistoryDir = path.join(proofTmp, 'pipeline-run', 'history-audit');
  294. const smokeHistoryDir = path.join(proofTmp, 'pipeline-smoke-run', 'history-audit');
  295. const newerFailingHistoryDir = path.join(proofTmp, 'latest-real-run', 'history-audit');
  296. fs.mkdirSync(realHistoryDir, { recursive: true });
  297. fs.mkdirSync(smokeHistoryDir, { recursive: true });
  298. fs.mkdirSync(newerFailingHistoryDir, { recursive: true });
  299. const passingHistoryFile = path.join(realHistoryDir, 'historical-dataset-audit.json');
  300. const smokeHistoryFile = path.join(smokeHistoryDir, 'historical-dataset-audit.json');
  301. const failingHistoryFile = path.join(newerFailingHistoryDir, 'historical-dataset-audit.json');
  302. fs.writeFileSync(passingHistoryFile, JSON.stringify(passingHistoryAudit(), null, 2), 'utf8');
  303. fs.writeFileSync(smokeHistoryFile, JSON.stringify(passingHistoryAudit(), null, 2), 'utf8');
  304. fs.writeFileSync(failingHistoryFile, JSON.stringify({ briefCount: 1, acceptance: { readyForCustomerEffectProof: false }, items: [] }, null, 2), 'utf8');
  305. fs.utimesSync(passingHistoryFile, new Date(Date.now() - 20000), new Date(Date.now() - 20000));
  306. fs.utimesSync(smokeHistoryFile, new Date(), new Date());
  307. fs.utimesSync(failingHistoryFile, new Date(Date.now() - 10000), new Date(Date.now() - 10000));
  308. const discoveredHistory = bestHistoryAudit(proofTmp);
  309. assert(path.resolve(discoveredHistory) === path.resolve(passingHistoryFile), 'completion history discovery should recurse and prefer a claimable non-smoke audit');
  310. const currentCustomerDir = path.join(proofTmp, 'latest-real-run', 'customer-effect-audit');
  311. const staleCustomerDir = path.join(proofTmp, 'other-real-run', 'customer-effect-audit');
  312. fs.mkdirSync(currentCustomerDir, { recursive: true });
  313. fs.mkdirSync(staleCustomerDir, { recursive: true });
  314. const currentCustomerFile = path.join(currentCustomerDir, 'customer-effect-summary.json');
  315. const staleCustomerFile = path.join(staleCustomerDir, 'customer-effect-summary.json');
  316. fs.writeFileSync(currentCustomerFile, JSON.stringify({
  317. ...passingCustomerEffect(failingHistoryFile),
  318. acceptance: { overallPass: false, historyReadyForCustomerEffectProof: false },
  319. customerSelectedRate: 0
  320. }, null, 2), 'utf8');
  321. fs.writeFileSync(staleCustomerFile, JSON.stringify(passingCustomerEffect(path.join('outputs', 'stale-history-audit', 'historical-dataset-audit.json')), null, 2), 'utf8');
  322. fs.utimesSync(currentCustomerFile, new Date(Date.now() - 10000), new Date(Date.now() - 10000));
  323. fs.utimesSync(staleCustomerFile, new Date(), new Date());
  324. const discoveredCustomerEffect = bestCustomerEffectSummary(proofTmp, failingHistoryFile, process.cwd());
  325. assert(path.resolve(discoveredCustomerEffect) === path.resolve(currentCustomerFile), 'completion customer-effect discovery should prefer summaries bound to the selected history audit');
  326. } finally {
  327. fs.rmSync(proofTmp, { recursive: true, force: true });
  328. }
  329. console.log(JSON.stringify({
  330. ok: true,
  331. openComplete: open.complete,
  332. openReadyForClaim: open.readyForClaim,
  333. openProofGapOpenCount: open.proofGapOpenCount,
  334. closedComplete: closed.complete
  335. }, null, 2));
  336. }
  337. function assert(condition, message) {
  338. if (!condition) throw new Error(message);
  339. }
  340. function passingHistoryAudit() {
  341. return {
  342. generatedAt: '2026-06-11T02:48:48.000Z',
  343. minBriefs: 5,
  344. briefCount: 5,
  345. categoryCount: 3,
  346. categories: ['skincare', 'mom-baby', 'home'],
  347. withReference: 5,
  348. withManualFinalList: 5,
  349. withCustomerDecision: 5,
  350. withRejectionReason: 2,
  351. withManualSupplementBaseline: 5,
  352. missingCriticalCount: 0,
  353. acceptance: {
  354. parseOk: true,
  355. minBriefsMet: true,
  356. allHaveBriefText: true,
  357. allHaveManualFinalList: true,
  358. allHaveCustomerDecision: true,
  359. allHaveFeedbackReason: true,
  360. allHaveManualSupplementBaseline: true,
  361. categoryCoverageMet: true,
  362. referenceCoverageMeasured: true,
  363. readyForLongRun: true,
  364. readyForCustomerEffectProof: true
  365. },
  366. items: Array.from({ length: 5 }, (_, index) => ({
  367. id: `brief-${index + 1}`,
  368. parseOk: true,
  369. hasBriefText: true,
  370. hasManualFinalList: true,
  371. hasCustomerDecision: true,
  372. hasRejectionReason: index < 2,
  373. hasManualSupplementBaseline: true,
  374. manualSupplementBaseline: 2,
  375. missingCritical: []
  376. }))
  377. };
  378. }
  379. function passingProofGapClosure() {
  380. const businessRows = [
  381. 'historical-dataset',
  382. 'video-real-candidate',
  383. 'video-ab-live-proof',
  384. 'live-provider-overnight-proof',
  385. 'manual-review-and-customer-effect'
  386. ].map(id => ({
  387. id,
  388. title: id,
  389. status: 'closed',
  390. evidence: 'verified',
  391. missingProofRequirements: [],
  392. required: 'verified',
  393. command: 'npm run proof-gap:closure',
  394. expectedArtifact: 'outputs/proof-gap-closure-latest/proof-gap-closure-summary.json'
  395. }));
  396. return {
  397. generatedAt: '2026-06-11T02:48:48.500Z',
  398. complete: true,
  399. intakeReady: true,
  400. openCount: 0,
  401. closedCount: 5,
  402. totalRows: 6,
  403. businessGapCount: 5,
  404. rows: [
  405. {
  406. id: 'intake-readiness',
  407. title: 'intake-readiness',
  408. status: 'closed',
  409. evidence: 'verified',
  410. missingProofRequirements: [],
  411. required: 'verified',
  412. command: 'npm run intake:readiness',
  413. expectedArtifact: 'outputs/intake-readiness-latest/intake-readiness-summary.json'
  414. },
  415. ...businessRows
  416. ]
  417. };
  418. }
  419. function passingBusinessProgress() {
  420. return {
  421. generatedAt: '2026-06-11T02:48:48.750Z',
  422. complete: true,
  423. counts: { pass: 15, fail: 0, pending: 0, blocked_by_external_data: 0 },
  424. rows: Array.from({ length: 15 }, (_, index) => ({
  425. order: index + 1,
  426. owner: index < 5 ? 'business' : 'tech-ai',
  427. title: `step-${index + 1}`,
  428. status: 'pass',
  429. evidence: 'verified',
  430. next: 'keep evidence current'
  431. }))
  432. };
  433. }
  434. function passingOptimizationPipeline() {
  435. return {
  436. readyForClaim: true,
  437. generatedAt: '2026-06-11T02:48:49.000Z',
  438. proofGapClosure: {
  439. complete: true,
  440. openCount: 0,
  441. closedCount: 5,
  442. businessGapCount: 5,
  443. claimable: true
  444. },
  445. results: [
  446. { id: 'history-audit', status: 'pass' },
  447. { id: 'video-resource-readiness', status: 'pass' },
  448. { id: 'customer-effect-audit', status: 'pass' },
  449. { id: 'proof-gap-closure', status: 'pass' }
  450. ]
  451. };
  452. }
  453. function passingVideoAb() {
  454. return {
  455. acceptance: { passed: true },
  456. proofContext: {
  457. mode: 'live',
  458. collectionMode: 'live',
  459. generatedBy: 'acceptance:video-ab',
  460. requiresRuntimeCredential: true,
  461. requiresVocSocialProvider: true,
  462. requiresVideoAnalysisProvider: true
  463. },
  464. enhanced: { provider: { evidence: { providerStatus: 'ok' } } }
  465. };
  466. }
  467. function failedVideoAbPreflight() {
  468. return {
  469. materialType: 'video_ab_runtime_preflight',
  470. directCustomerProof: false,
  471. proofLevel: 'not_business_proof',
  472. canCloseProofGap: false,
  473. readyForVideoAb: false,
  474. failureCount: 4,
  475. proofContext: {
  476. mode: 'not_run',
  477. collectionMode: 'not_run',
  478. generatedBy: 'acceptance:video-ab-preflight',
  479. requiresRuntimeCredential: true,
  480. requiresVocSocialProvider: true,
  481. requiresVideoAnalysisProvider: true
  482. },
  483. runtime: {
  484. runtimeCredentialPresent: false,
  485. companyResolved: false,
  486. vocSocialProviderTokenPresent: false,
  487. videoAnalysisTokenPresent: false
  488. },
  489. missingProofRequirements: [
  490. 'proofContext.requiresRuntimeCredential=true',
  491. 'company resolved for live run',
  492. 'proofContext.requiresVocSocialProvider=true',
  493. 'proofContext.requiresVideoAnalysisProvider=true'
  494. ]
  495. };
  496. }
  497. function passingCustomerEffect(historyAuditPath) {
  498. return {
  499. reviewPath: path.join('outputs', 'review-metrics-latest', 'reviewed.csv'),
  500. historyAuditPath,
  501. customerSelectedRate: 0.5,
  502. referenceCustomerSelectedRate: 0.5,
  503. manualSupplementBaseline: { available: true, source: 'history-audit', total: 10, byBrief: [{ briefId: 'brief-a', count: 10 }] },
  504. currentManualSupplement: { available: true, source: 'cli-arg', total: 5, byBrief: [] },
  505. manualSupplementReductionRate: 0.5,
  506. acceptance: {
  507. overallPass: true,
  508. reviewMetricsOverallUsable: true,
  509. customerSelectedRateMeasured: true,
  510. customerSelectedRate30Pass: true,
  511. referenceCustomerSelectedRateMeasured: true,
  512. referenceCustomerSelectedRate40Pass: true,
  513. historyReadyForCustomerEffectProof: true,
  514. manualSupplementBaselineAvailable: true,
  515. currentManualSupplementAvailable: true,
  516. manualSupplementReduction50Pass: true
  517. },
  518. missingEvidence: []
  519. };
  520. }
  521. function passingCompletionBase({ historyAuditPath, sourceFiles, history }) {
  522. return {
  523. root: process.cwd(),
  524. outputsDir: 'outputs',
  525. sourceFiles,
  526. history,
  527. intakeReadiness: { acceptance: { overallReady: true, historyReady: true, videoReady: true, reviewMetricsReady: true, customerEffectReady: true }, failureCount: 0 },
  528. videoReadiness: { acceptance: { readyForVideoAbPreflight: true }, failureCount: 0, counts: { realReferenceRows: 1, realCandidateRows: 2, videoUrlRows: 2 } },
  529. proofGapClosure: passingProofGapClosure(),
  530. handoff: { complete: true, readyCapabilities: [{ id: 'release-chain' }], readyNotProven: [], externalBlockers: [] },
  531. businessProgress: passingBusinessProgress(),
  532. longRunReadiness: { ready: true, counts: { pass: 13, fail: 0 } },
  533. videoAb: passingVideoAb(),
  534. overnight: { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0 },
  535. customerEffect: passingCustomerEffect(historyAuditPath),
  536. optimizationPipeline: passingOptimizationPipeline()
  537. };
  538. }
  539. main();