refresh-round-artifacts-smoke.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const ROOT = path.resolve(__dirname, '..');
  7. function main() {
  8. const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-round-refresh-'));
  9. const outputRoot = path.join(temp, 'outputs');
  10. seedRequiredOutputs(outputRoot);
  11. const result = spawnSync(process.execPath, ['scripts/refresh-round-artifacts.js', '--output-root', outputRoot, '--strict'], {
  12. cwd: ROOT,
  13. encoding: 'utf8',
  14. shell: false
  15. });
  16. if (result.stdout) process.stdout.write(result.stdout);
  17. if (result.stderr) process.stderr.write(result.stderr);
  18. assert(result.status === 0, 'round refresh command should pass');
  19. const refresh = readJson(path.join(outputRoot, 'round-refresh-summary.json'));
  20. const form = readJson(path.join(outputRoot, 'tihao-proof-gap-software-form-latest', 'tihao-proof-gap-software-form-summary.json'));
  21. const proofGap = readJson(path.join(outputRoot, 'proof-gap-request-latest', 'proof-gap-request-summary.json'));
  22. const closure = readJson(path.join(outputRoot, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'));
  23. const feedbackClosure = readJson(path.join(outputRoot, 'feedback-loop-closure-latest', 'feedback-loop-closure-summary.json'));
  24. const homepageReadiness = readJson(path.join(outputRoot, 'homepage-evidence-readiness-latest', 'homepage-evidence-readiness-summary.json'));
  25. const intakeReadiness = readJson(path.join(outputRoot, 'intake-readiness-latest', 'intake-readiness-summary.json'));
  26. const videoReadiness = readJson(path.join(outputRoot, 'video-resource-readiness-latest', 'video-resource-readiness-summary.json'));
  27. const livePreflight = readJson(path.join(outputRoot, 'live-preflight-latest', 'live-preflight-summary.json'));
  28. const longRunReadiness = readJson(path.join(outputRoot, 'long-run-readiness-latest', 'long-run-readiness-summary.json'));
  29. const fieldChecklist = readJson(path.join(outputRoot, 'intake-field-checklist-latest', 'intake-field-checklist-summary.json'));
  30. const manualReviewLabelGuide = readJson(path.join(outputRoot, 'manual-review-label-guide-latest', 'manual-review-label-guide-summary.json'));
  31. const progress = readJson(path.join(outputRoot, 'business-proof-progress-latest', 'business-proof-progress-summary.json'));
  32. const nextActions = readJson(path.join(outputRoot, 'business-proof-next-actions-latest', 'business-proof-next-actions-summary.json'));
  33. const businessExecution = readJson(path.join(outputRoot, 'business-execution-index-latest', 'business-execution-index-summary.json'));
  34. const status = readJson(path.join(outputRoot, 'optimization-status-latest', 'optimization-status-summary.json'));
  35. const evidence = readJson(path.join(outputRoot, 'evidence-index-latest', 'evidence-index-summary.json'));
  36. const handoff = readJson(path.join(outputRoot, 'optimization-handoff-latest', 'optimization-handoff-summary.json'));
  37. const completion = readJson(path.join(outputRoot, 'optimization-completion-latest', 'optimization-completion-summary.json'));
  38. const planExecution = readJson(path.join(outputRoot, 'plan-execution-status-latest', 'plan-execution-status-summary.json'));
  39. const operatorPack = readJson(path.join(outputRoot, 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json'));
  40. const intakeBundle = readJson(path.join(outputRoot, 'real-proof-intake-bundle-latest', 'real-proof-intake-bundle-summary.json'));
  41. const softwareRefresh = readJson(path.join(outputRoot, 'software-client-latest', 'software-client-refresh-summary.json'));
  42. const softwareClient = readJson(path.join(outputRoot, 'software-client-latest', 'software-client-list.final.summary.json'));
  43. const latestForm = readJson(path.join(outputRoot, 'latest-form-index-latest', 'latest-form-index-summary.json'));
  44. const localSeed = readJson(path.join(outputRoot, 'local-seed-material-index-latest', 'local-seed-material-index-summary.json'));
  45. const seedWorklist = readJson(path.join(outputRoot, 'local-seed-to-intake-worklist-latest', 'local-seed-to-intake-worklist-summary.json'));
  46. const realGapMaterialAudit = readJson(path.join(outputRoot, 'real-gap-material-audit-latest', 'real-gap-material-audit-summary.json'));
  47. const realProofClosureWorkOrder = readJson(path.join(outputRoot, 'real-proof-closure-work-order-latest', 'real-proof-closure-work-order-summary.json'));
  48. const experienceTranscript = readJson(path.join(outputRoot, 'experience-transcript-index-latest', 'experience-transcript-index-summary.json'));
  49. const round = readJson(path.join(outputRoot, 'round-deposition-latest', 'round-deposition-summary.json'));
  50. const expectedOwnerArtifactCount = Number(operatorPack.ownerArtifactCount || operatorPack.ownerArtifacts?.length || 0);
  51. const expectedBusinessOwnerArtifactRows = Number(businessExecution.operatorPackOwnerArtifactRowCount || expectedOwnerArtifactCount);
  52. const expectedWorkOrderCount = Number(realProofClosureWorkOrder.workOrderCount || 0);
  53. const expectedProofGapOpenCount = Number(closure.openCount || 0);
  54. const expectedIntakeFailureCount = Number(intakeReadiness.failureCount || 0);
  55. const expectedRealCandidateRows = Number(videoReadiness.counts?.realCandidateRows || 0);
  56. assert(refresh.passed === true, 'refresh summary should pass');
  57. assert(refresh.steps.length === 29, 'refresh should run twenty-nine ordered steps');
  58. assert(refresh.steps.map(item => item.id).join(',') === 'proof-gap-software-form,proof-gap-request,proof-gap-closure,feedback-loop-closure,homepage-evidence-readiness,intake-readiness,video-resource-readiness,live-preflight,longrun-readiness,intake-field-checklist,manual-review-label-guide,proof-gap-operator-pack,business-proof-progress,business-proof-next-actions,optimization-completion,business-execution-index,optimization-status,plan-execution-status,real-proof-intake-bundle,software-client-list,local-seed-material-index,local-seed-to-intake-worklist,real-gap-material-audit,real-proof-closure-work-order,latest-form-index,optimization-handoff,experience-transcript-index,evidence-index,round-deposition', 'refresh order should be stable');
  59. assert(form.passed === true && form.rowCount === 5 && form.duplicateIdCount === 0, 'software proof-gap form should pass');
  60. assert(proofGap.unresolvedCount >= 1, 'proof gap request should refresh unresolved gaps');
  61. assert(closure.openCount >= 1 && closure.complete === false, 'proof gap closure should refresh open gaps');
  62. assert(feedbackClosure.complete === false && feedbackClosure.directCustomerProof === false, 'feedback loop closure should keep proof boundary');
  63. assert(homepageReadiness.directCustomerProof === false, 'homepage readiness should keep proof boundary');
  64. assert(homepageReadiness.proofLevel === 'not_business_proof' || homepageReadiness.proofLevel === 'smoke_or_local', 'homepage readiness should classify proof level');
  65. assert(intakeReadiness.acceptance?.overallReady === false && intakeReadiness.failureCount >= 1, 'intake readiness should refresh current incomplete real-material state');
  66. assertVideoResourceReadiness(videoReadiness);
  67. assert(typeof livePreflight.readyForLiveAcceptance === 'boolean', 'live preflight should refresh live acceptance readiness');
  68. assert(typeof livePreflight.readyForVideoAb === 'boolean', 'live preflight should refresh video A/B readiness');
  69. assert(livePreflight.strict === true && livePreflight.allowFailure === true, 'round refresh live preflight should run strict checks without failing the refresh');
  70. assert(longRunReadiness.ready === false && Number(longRunReadiness.counts?.fail || 0) >= 1, 'longrun readiness should refresh blocked full-matrix gate');
  71. assert(refresh.artifacts.intakeReadiness?.includes('intake-readiness-summary.json'), 'refresh artifacts should expose intake readiness summary');
  72. assert(refresh.artifacts.videoResourceReadiness?.includes('video-resource-readiness-summary.json'), 'refresh artifacts should expose video resource readiness summary');
  73. assert(refresh.artifacts.livePreflight?.includes('live-preflight-summary.json'), 'refresh artifacts should expose live preflight summary');
  74. assert(refresh.artifacts.longRunReadiness?.includes('long-run-readiness-summary.json'), 'refresh artifacts should expose longrun readiness summary');
  75. assert(fieldChecklist.passed === true && fieldChecklist.itemCount >= 1, 'field checklist should refresh field-level intake gaps');
  76. assert(fieldChecklist.directCustomerProof === false, 'field checklist should keep proof boundary');
  77. assert(manualReviewLabelGuide.passed === true, 'manual review label guide should refresh');
  78. assert(manualReviewLabelGuide.directCustomerProof === false, 'manual review label guide should keep proof boundary');
  79. assert(manualReviewLabelGuide.negativeLabelsRequireAttribution === true, 'manual review label guide should require negative attribution');
  80. assert(manualReviewLabelGuide.attributionTypeCount >= 8, 'manual review label guide should expose attribution types');
  81. assert(progress.complete === false && progress.counts.blocked_by_external_data >= 1, 'business proof progress should refresh incomplete proof state');
  82. assert(progress.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'business proof progress should expose operator owner attachments');
  83. assert(progress.operatorPackOwnerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'business proof progress should expose business operator owner csv');
  84. assert(nextActions.complete === false && nextActions.actions.length >= 1, 'business proof next actions should refresh actionable queue');
  85. assert(businessExecution.passed === true && businessExecution.rowCount >= 1, 'business execution index should refresh executable business table');
  86. assert(businessExecution.directCustomerProof === false, 'business execution index should keep proof boundary');
  87. assert(businessExecution.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'business execution index should expose operator pack owner attachments');
  88. assert(businessExecution.sourceFiles?.longRunReadiness?.includes('long-run-readiness-summary.json'), 'business execution index should expose longrun readiness source');
  89. assert(businessExecution.rows?.some(row => row.title?.includes('长跑前置门禁') && row.mainFile?.includes('long-run-readiness-report.md')), 'business execution index should expose longrun readiness report row');
  90. assert(businessExecution.sourceFiles?.optimizationCompletion?.includes('optimization-completion-summary.json'), 'business execution index should expose optimization completion source');
  91. assert(businessExecution.rows?.some(row => row.title?.includes('优化完成度审计') && row.mainFile?.includes('optimization-completion-report.md')), 'business execution index should expose optimization completion report row');
  92. assert(status.complete === false && status.counts.ready_not_proven >= 1, 'status should preserve unfinished boundary');
  93. assert(completion.complete === false && completion.readyForClaim === false, 'completion audit should preserve unfinished boundary');
  94. assert(completion.proofGapOpenCount >= 1, 'completion audit should expose open proof gaps');
  95. assert(completion.canClaim.tihaoRateImproved === false, 'completion audit should forbid tihao-rate claim');
  96. assert(completion.canClaim.customerEffectAchieved === false, 'completion audit should forbid customer-effect claim');
  97. assert(completion.canClaim.manualSupplementReduced === false, 'completion audit should forbid manual-supplement claim');
  98. assert(evidence.byType['proof-gap-software-form'] === 1, 'evidence index should classify latest software form');
  99. assert(evidence.byType['optimization-completion'] === 1, 'evidence index should classify completion audit');
  100. assert(evidence.byType['proof-gap-operator-pack'] === 1, 'evidence index should classify proof gap operator pack');
  101. assert(evidence.byType['real-proof-intake-bundle'] === 1, 'evidence index should classify real proof intake bundle');
  102. assert(evidence.byType['local-seed-material-index'] === 1, 'evidence index should classify local seed material index');
  103. assert(evidence.byType['local-seed-to-intake-worklist'] === 1, 'evidence index should classify seed-to-intake worklist');
  104. assert(evidence.byType['real-gap-material-audit'] === 1, 'evidence index should classify real gap material audit');
  105. assert(evidence.byType['real-proof-closure-work-order'] === 1, 'evidence index should classify real proof closure work order');
  106. assert(evidence.byType['experience-transcript-index'] === 1, 'evidence index should classify experience transcript index');
  107. assert(evidence.byType['feedback-loop-closure'] === 1, 'evidence index should classify feedback loop closure');
  108. assert(evidence.byType['homepage-evidence-readiness'] === 1, 'evidence index should classify homepage readiness');
  109. assert(evidence.byType['intake-field-checklist'] === 1, 'evidence index should classify field checklist');
  110. assert(evidence.counts.smoke_or_local >= 1, 'evidence index should include smoke/local evidence');
  111. assert(handoff.proofGapSoftwareForm?.passed === true, 'handoff should expose proof-gap software form');
  112. assert(handoff.feedbackLoopClosure?.directCustomerProof === false, 'handoff should expose feedback loop proof boundary');
  113. assert(handoff.homepageEvidenceReadiness?.directCustomerProof === false, 'handoff should expose homepage readiness proof boundary');
  114. assert(handoff.latestFormIndex?.passed === true, 'handoff should expose latest form index');
  115. assert(handoff.supportDocs?.some(item => item.doc?.includes('tihao-handoff-index.md')), 'handoff should expose task handoff index doc');
  116. assert(handoff.latestFormIndex?.manualReviewLabelGuide?.markdown?.includes('manual-review-label-guide.md'), 'handoff should expose manual review label guide');
  117. assert(handoff.latestFormIndex?.planExecutionStatus?.markdown?.includes('plan-execution-status.md'), 'handoff latest form index should expose plan execution status');
  118. assert(handoff.planExecutionStatus?.markdown?.includes('plan-execution-status.md'), 'handoff should expose top-level plan execution status');
  119. assert(handoff.planExecutionStatus?.complete === false, 'handoff plan execution status should keep incomplete boundary');
  120. assert(handoff.planExecutionStatus?.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'handoff plan execution status should expose operator owner attachments');
  121. assert(handoff.latestFormIndex?.planExecutionStatus?.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'handoff latest form plan execution status should expose operator owner attachments');
  122. assert(handoff.latestFormIndex?.proofGapRequest?.markdown?.includes('proof-gap-request-report.md'), 'handoff latest form index should expose proof gap request');
  123. assert(handoff.proofGapRequest?.markdown?.includes('proof-gap-request-report.md'), 'handoff should expose top-level proof gap request');
  124. assert(handoff.latestFormIndex?.proofGapOperatorPack?.markdown?.includes('proof-gap-operator-pack.md'), 'handoff latest form index should expose proof gap operator pack');
  125. assert(handoff.proofGapOperatorPack?.markdown?.includes('proof-gap-operator-pack.md'), 'handoff should expose top-level proof gap operator pack');
  126. assert(handoff.latestFormIndex?.realProofIntakeBundle?.markdown?.includes('real-proof-intake-bundle.md'), 'handoff latest form index should expose real proof intake bundle');
  127. assert(handoff.realProofIntakeBundle?.markdown?.includes('real-proof-intake-bundle.md'), 'handoff should expose top-level real proof intake bundle');
  128. assert(latestForm.realProofClosureWorkOrder?.markdown?.includes('real-proof-closure-work-order.md'), 'latest form index should expose real proof closure work order report');
  129. assert(latestForm.realProofClosureWorkOrder?.csv?.includes('real-proof-closure-work-order.csv'), 'latest form index should expose real proof closure work order csv');
  130. assert(latestForm.realProofClosureWorkOrder?.summary?.includes('real-proof-closure-work-order-summary.json'), 'latest form index should expose real proof closure work order summary');
  131. assert(latestForm.realProofClosureWorkOrder?.directCustomerProof === false, 'latest form real proof closure work order should keep proof boundary');
  132. assert(latestForm.realProofClosureWorkOrder?.proofLevel === 'not_business_proof', 'latest form real proof closure work order should keep proof level');
  133. assert(latestForm.realProofClosureWorkOrder?.materialType === 'real_proof_closure_work_order', 'latest form real proof closure work order should expose material type');
  134. assert(latestForm.realProofClosureWorkOrder?.complete === false, 'latest form real proof closure work order should not claim completion');
  135. assert(latestForm.realProofClosureWorkOrder?.canCloseProofGap === false, 'latest form real proof closure work order should not close proof gaps');
  136. assert(latestForm.realProofClosureWorkOrder?.workOrderCount === expectedWorkOrderCount, 'latest form real proof closure work order should expose work order count');
  137. assert(latestForm.realProofClosureWorkOrder?.ownerArtifacts?.length === expectedOwnerArtifactCount, 'latest form real proof closure work order should expose owner artifacts');
  138. assert(handoff.realProofClosureWorkOrder?.markdown?.includes('real-proof-closure-work-order.md'), 'handoff should expose top-level real proof closure work order');
  139. assert(handoff.realProofClosureWorkOrder?.workOrderCount === expectedWorkOrderCount, 'handoff real proof closure work order should expose work order count');
  140. assert(handoff.realProofClosureWorkOrder?.canCloseProofGap === false, 'handoff real proof closure work order should not close proof gaps');
  141. assert(handoff.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'handoff real proof closure work order should expose business owner csv');
  142. assert(handoff.latestFormIndex?.realProofClosureWorkOrder?.markdown?.includes('real-proof-closure-work-order.md'), 'handoff latest form index should expose real proof closure work order');
  143. assert(handoff.latestFormIndex?.realProofClosureWorkOrder?.workOrderCount === expectedWorkOrderCount, 'handoff latest form real proof closure work order should expose work order count');
  144. assert(handoff.latestFormIndex?.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.markdown?.includes('by-owner/tech-ai.md')), 'handoff latest form real proof closure work order should expose tech owner markdown');
  145. assert(latestForm.localSeedToIntakeWorklist?.markdown?.includes('local-seed-to-intake-worklist.md'), 'latest form index should expose seed-to-intake worklist report');
  146. assert(latestForm.localSeedToIntakeWorklist?.historyDraft?.includes('history-intake-draft.csv'), 'latest form index should expose seed-to-intake history draft');
  147. assert(latestForm.localSeedToIntakeWorklist?.videoWorklist?.includes('video-resource-worklist.csv'), 'latest form index should expose seed-to-intake video worklist');
  148. assert(latestForm.localSeedToIntakeWorklist?.candidateWorklist?.includes('candidate-seed-worklist.csv'), 'latest form index should expose seed-to-intake candidate worklist');
  149. assert(latestForm.localSeedToIntakeWorklist?.directCustomerProof === false, 'latest form seed-to-intake worklist should keep proof boundary');
  150. assert(latestForm.localSeedToIntakeWorklist?.canCloseProofGap === false, 'latest form seed-to-intake worklist should not close proof gaps');
  151. assert(Number(latestForm.localSeedToIntakeWorklist?.candidateSeedRows || 0) >= 1, 'latest form seed-to-intake worklist should expose candidate seed rows');
  152. assert(latestForm.realGapMaterialAudit?.markdown?.includes('real-gap-material-audit.md'), 'latest form index should expose real gap material audit report');
  153. assert(latestForm.realGapMaterialAudit?.summary?.includes('real-gap-material-audit-summary.json'), 'latest form index should expose real gap material audit summary');
  154. assert(latestForm.realGapMaterialAudit?.materialType === 'three_real_gap_material_audit', 'latest form real gap material audit should classify material type');
  155. assert(latestForm.realGapMaterialAudit?.directCustomerProof === false, 'latest form real gap material audit should keep proof boundary');
  156. assert(latestForm.realGapMaterialAudit?.canCloseProofGap === false, 'latest form real gap material audit should not close proof gaps');
  157. assert(latestForm.realGapMaterialAudit?.materialFoundCount === realGapMaterialAudit.materialFoundCount, 'latest form real gap material audit should expose material coverage count');
  158. assert(handoff.localSeedToIntakeWorklist?.markdown?.includes('local-seed-to-intake-worklist.md'), 'handoff should expose top-level seed-to-intake worklist');
  159. assert(handoff.latestFormIndex?.localSeedToIntakeWorklist?.historyDraft?.includes('history-intake-draft.csv'), 'handoff latest form index should expose seed-to-intake history draft');
  160. assert(handoff.latestFormIndex?.localSeedToIntakeWorklist?.directCustomerProof === false, 'handoff latest form seed-to-intake worklist should keep proof boundary');
  161. assert(handoff.realGapMaterialAudit?.markdown?.includes('real-gap-material-audit.md'), 'handoff should expose top-level real gap material audit');
  162. assert(handoff.realGapMaterialAudit?.materialFoundCount === realGapMaterialAudit.materialFoundCount, 'handoff real gap material audit should expose material coverage count');
  163. assert(handoff.realGapMaterialAudit?.canCloseProofGap === false, 'handoff real gap material audit should not close proof gaps');
  164. assert(handoff.latestFormIndex?.realGapMaterialAudit?.summary?.includes('real-gap-material-audit-summary.json'), 'handoff latest form index should expose real gap material audit');
  165. assert(planExecution.passed === true, 'plan execution status should pass');
  166. assert(planExecution.complete === false, 'plan execution status should not claim completion');
  167. assert(planExecution.rowCount === 8, 'plan execution status should map eight plan sections');
  168. assert(planExecution.proofOpenCount >= 1, 'plan execution status should expose open proof gaps');
  169. assert(planExecution.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'plan execution status should expose operator owner attachments');
  170. assert(planExecution.businessExecutionOperatorPackOwnerArtifactRowCount === expectedBusinessOwnerArtifactRows, 'plan execution status should expose business execution operator rows');
  171. assert(planExecution.rows.some(row => row.section === '关键差距' && row.status === 'open'), 'plan execution status should keep key gaps open');
  172. assert(planExecution.guardrails.some(item => item.includes('不证明客户效果')), 'plan execution status should keep proof boundary');
  173. assert(latestForm.primaryForm?.passed === true, 'latest form index should expose passed proof-gap form');
  174. assert(latestForm.supportDocs?.some(item => item.path?.includes('tihao-handoff-index.md')), 'latest form index should expose task handoff index doc');
  175. assert(operatorPack.passed === true && operatorPack.rowCount === 5 && operatorPack.openCount >= 1, 'proof gap operator pack should pass and keep open gaps');
  176. assert(operatorPack.directCustomerProof === false, 'proof gap operator pack should not be direct customer proof');
  177. assert(intakeBundle.passed === true && intakeBundle.proofOpenCount >= 1 && intakeBundle.fillRowCount >= 3, 'real proof intake bundle should pass and expose fill rows');
  178. assert(intakeBundle.directCustomerProof === false, 'real proof intake bundle should not be direct customer proof');
  179. assert(intakeBundle.templateFiles?.some(item => item.path?.includes('long-run-readiness-report.md')), 'real proof intake bundle should expose longrun readiness report');
  180. assert(intakeBundle.recheckCommands?.some(item => item.command?.includes('longrun:readiness')), 'real proof intake bundle should expose longrun readiness recheck command');
  181. assert(intakeBundle.sourceState?.longRunReadinessReady === false, 'real proof intake bundle should expose longrun ready=false');
  182. assert(Number(intakeBundle.sourceState?.longRunReadinessFailCount || 0) >= 1, 'real proof intake bundle should expose longrun fail count');
  183. assert(softwareRefresh.passed === true && softwareRefresh.skipped === false, 'software client list should refresh from latest manual review sample');
  184. assert(softwareRefresh.sourceDuplicateRemovedCount >= 0, 'software client refresh should expose source duplicate rows removed');
  185. assert(softwareRefresh.finalDuplicateGroupCount === 0, 'software client refresh should keep final duplicate groups at zero');
  186. assert(softwareRefresh.duplicateCountMeaning === 'source_duplicate_removed_count', 'software client refresh should define duplicate count meaning');
  187. assert(softwareRefresh.rankContinuous === true, 'software client refresh should keep rank continuity');
  188. assert(softwareClient.finalDuplicateGroupCount === 0, 'software client summary should keep zero final duplicate groups');
  189. assert(refresh.artifacts.softwareClientList?.includes('software-client-refresh-summary.json'), 'refresh artifacts should expose software client refresh summary');
  190. assert(latestForm.manualReviewLabelGuide?.markdown?.includes('manual-review-label-guide.md'), 'latest form index should expose manual review label guide');
  191. assert(latestForm.planExecutionStatus?.markdown?.includes('plan-execution-status.md'), 'latest form index should expose plan execution status');
  192. assert(latestForm.planExecutionStatus?.rowCount === 8, 'latest form index should expose plan execution row count');
  193. assert(latestForm.planExecutionStatus?.complete === false, 'latest form index should keep plan execution incomplete boundary');
  194. assert(latestForm.planExecutionStatus?.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'latest form index should expose plan execution operator owner attachments');
  195. assert(latestForm.proofGapRequest?.markdown?.includes('proof-gap-request-report.md'), 'latest form index should expose proof gap request');
  196. assert(latestForm.proofGapRequest?.directCustomerProof === false, 'latest form index should keep proof gap request proof boundary');
  197. assert(latestForm.proofGapOperatorPack?.markdown?.includes('proof-gap-operator-pack.md'), 'latest form index should expose proof gap operator pack');
  198. assert(latestForm.proofGapOperatorPack?.directCustomerProof === false, 'latest form index should keep operator pack proof boundary');
  199. assert(latestForm.businessExecutionIndex?.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'latest form index should expose business execution operator owner attachments');
  200. assert(latestForm.realProofIntakeBundle?.markdown?.includes('real-proof-intake-bundle.md'), 'latest form index should expose real proof intake bundle');
  201. assert(latestForm.realProofIntakeBundle?.directCustomerProof === false, 'latest form index should keep real proof intake bundle proof boundary');
  202. assert(localSeed.directCustomerProof === false, 'local seed material index should keep proof boundary');
  203. assert(localSeed.canCloseProofGap === false, 'local seed material index should not close proof gaps');
  204. assert(refresh.artifacts.localSeedMaterialIndex?.includes('local-seed-material-index-summary.json'), 'refresh artifacts should expose local seed material index');
  205. assert(seedWorklist.directCustomerProof === false, 'seed-to-intake worklist should keep proof boundary');
  206. assert(seedWorklist.canCloseProofGap === false, 'seed-to-intake worklist should not close proof gaps');
  207. assert(seedWorklist.materialType === 'seed_to_intake_worklist', 'seed-to-intake worklist should classify material type');
  208. assert(refresh.artifacts.localSeedToIntakeWorklist?.includes('local-seed-to-intake-worklist-summary.json'), 'refresh artifacts should expose seed-to-intake worklist');
  209. assert(realGapMaterialAudit.materialType === 'three_real_gap_material_audit', 'real gap material audit should classify material type');
  210. assert(realGapMaterialAudit.proofLevel === 'not_business_proof', 'real gap material audit should keep proof boundary');
  211. assert(realGapMaterialAudit.directCustomerProof === false, 'real gap material audit should not be direct customer proof');
  212. assert(realGapMaterialAudit.canCloseProofGap === false, 'real gap material audit should not close proof gaps');
  213. assert(realGapMaterialAudit.complete === false, 'real gap material audit should not claim completion');
  214. assert(realGapMaterialAudit.gapCount === 3 && realGapMaterialAudit.materialFoundCount === 3 && realGapMaterialAudit.openGapCount >= 1, 'real gap material audit should expose three covered but still-open gaps');
  215. assert(refresh.artifacts.realGapMaterialAudit?.includes('real-gap-material-audit-summary.json'), 'refresh artifacts should expose real gap material audit');
  216. assert(realProofClosureWorkOrder.passed === true, 'real proof closure work order should pass');
  217. assert(realProofClosureWorkOrder.complete === false, 'real proof closure work order should not claim completion');
  218. assert(realProofClosureWorkOrder.proofLevel === 'not_business_proof', 'real proof closure work order should keep not-business-proof level');
  219. assert(realProofClosureWorkOrder.directCustomerProof === false, 'real proof closure work order should not be direct customer proof');
  220. assert(realProofClosureWorkOrder.canCloseProofGap === false, 'real proof closure work order should not close proof gaps');
  221. assert(realProofClosureWorkOrder.materialType === 'real_proof_closure_work_order', 'real proof closure work order should classify material type');
  222. assert(realProofClosureWorkOrder.workOrderCount === expectedWorkOrderCount, 'real proof closure work order should expose current work orders');
  223. assert(realProofClosureWorkOrder.ownerArtifacts?.length === expectedOwnerArtifactCount, 'real proof closure work order should expose current owner artifacts');
  224. assert(realProofClosureWorkOrder.ownerArtifacts.some(item => item.markdown?.includes('by-owner/business.md') && item.csv?.includes('by-owner/business.csv')), 'real proof closure work order should expose business owner files');
  225. if (realProofClosureWorkOrder.ownerArtifacts.some(item => item.owner === '商务/投放')) {
  226. assert(realProofClosureWorkOrder.ownerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md') && item.csv?.includes('by-owner/business-media.csv')), 'real proof closure work order should expose business/media owner files when present');
  227. }
  228. assert(realProofClosureWorkOrder.ownerArtifacts.some(item => item.markdown?.includes('by-owner/tech-ai.md') && item.csv?.includes('by-owner/tech-ai.csv')), 'real proof closure work order should expose tech/AI owner files');
  229. assert(realProofClosureWorkOrder.sourceState?.proofGapOpenCount === expectedProofGapOpenCount, 'real proof closure work order should expose open proof gap count');
  230. assert(realProofClosureWorkOrder.sourceState?.intakeFailureCount === expectedIntakeFailureCount, 'real proof closure work order should expose intake failures');
  231. assert(realProofClosureWorkOrder.sourceState?.realCandidateRows === expectedRealCandidateRows, 'real proof closure work order should expose real candidate rows');
  232. assert(refresh.artifacts.realProofClosureWorkOrder?.includes('real-proof-closure-work-order-summary.json'), 'refresh artifacts should expose real proof closure work order');
  233. assert(experienceTranscript.directCustomerProof === false, 'experience transcript index should keep proof boundary');
  234. assert(experienceTranscript.canCloseProofGap === false, 'experience transcript index should not close proof gaps');
  235. assert(experienceTranscript.materialType === 'experience_seed', 'experience transcript index should classify as experience seed');
  236. assert(refresh.artifacts.experienceTranscriptIndex?.includes('experience-transcript-index-summary.json'), 'refresh artifacts should expose experience transcript index');
  237. assert(latestForm.manualReviewLabelGuide?.negativeLabelsRequireAttribution === true, 'latest form index should keep negative attribution rule');
  238. assert(latestForm.clientList?.csv?.includes('software-client-list.final.csv'), 'latest form index should expose software client list when present');
  239. assert(latestForm.clientList?.directCustomerProof === false, 'latest form index should keep candidate list proof boundary');
  240. assert(latestForm.clientList?.finalDuplicateGroupCount === 0, 'latest form index should expose zero final duplicate groups');
  241. assert(latestForm.clientList?.duplicateCountMeaning === 'source_duplicate_removed_count', 'latest form index should expose duplicate count meaning');
  242. assert(latestForm.repairActions?.dataIntake?.csv?.includes('intake-readiness-repair-actions.csv'), 'latest form index should expose data intake repair actions');
  243. assert(latestForm.repairActions?.videoResource?.csv?.includes('video-resource-repair-actions.csv'), 'latest form index should expose video resource repair actions');
  244. assert(handoff.latestFormIndex?.repairActions?.dataIntake?.csv?.includes('intake-readiness-repair-actions.csv'), 'handoff should expose data intake repair actions');
  245. assert(handoff.latestFormIndex?.repairActions?.videoResource?.csv?.includes('video-resource-repair-actions.csv'), 'handoff should expose video resource repair actions');
  246. assert(handoff.businessExecutionIndex?.longRunReadiness?.report?.includes('long-run-readiness-report.md'), 'handoff should expose business execution longrun readiness report row');
  247. assert(handoff.businessExecutionIndex?.longRunReadiness?.status === 'blocked', 'handoff should expose business execution longrun blocked status');
  248. assert(handoff.businessExecutionIndex?.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'handoff should expose business execution operator owner attachments');
  249. assert(round.passed === true, 'round deposition should pass');
  250. assert(round.observed.proofGapSoftwareFormPassed === true, 'round deposition should observe software form');
  251. assert(round.observed.manualReviewLabelGuidePassed === true, 'round deposition should observe manual review label guide');
  252. assert(round.observed.latestFormIndexManualReviewLabelGuide?.includes('manual-review-label-guide.md'), 'round deposition should observe indexed manual review label guide');
  253. assert(round.observed.latestFormIndexPlanExecutionStatus?.includes('plan-execution-status.md'), 'round deposition should observe indexed plan execution status');
  254. assert(round.observed.planExecutionStatusRowCount === 8, 'round deposition should observe plan execution row count');
  255. assert(round.observed.planExecutionStatusComplete === false, 'round deposition should observe plan execution incomplete boundary');
  256. assert(round.observed.planExecutionStatusProofOpenCount >= 1, 'round deposition should observe plan execution proof open count');
  257. assert(round.observed.latestFormIndexDataRepairCsv?.includes('intake-readiness-repair-actions.csv'), 'round deposition should observe data intake repair csv');
  258. assert(round.observed.latestFormIndexVideoRepairCsv?.includes('video-resource-repair-actions.csv'), 'round deposition should observe video resource repair csv');
  259. assert(round.observed.latestFormIndexClientListFinalDuplicateGroupCount === 0, 'round deposition should observe zero final duplicate groups');
  260. assert(round.observed.latestFormIndexClientListDuplicateCountMeaning === 'source_duplicate_removed_count', 'round deposition should observe duplicate count meaning');
  261. assert(round.observed.businessExecutionIndexLongRunReadinessReport?.includes('long-run-readiness-report.md'), 'round deposition should observe business execution longrun readiness report');
  262. assert(round.observed.businessExecutionIndexOperatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'round deposition should observe business execution operator owner attachment count');
  263. assert(round.observed.handoffBusinessExecutionIndexLongRunReadinessReport?.includes('long-run-readiness-report.md'), 'round deposition should observe handoff business execution longrun readiness report');
  264. assert(round.observed.handoffBusinessExecutionIndexLongRunReadinessStatus === 'blocked', 'round deposition should observe handoff business execution longrun blocked status');
  265. assert(round.observed.realProofIntakeBundleLongRunReadinessReport?.includes('long-run-readiness-report.md'), 'round deposition should observe real proof bundle longrun readiness report');
  266. assert(round.observed.realProofIntakeBundleLongRunReadinessReady === false, 'round deposition should observe real proof bundle longrun ready=false');
  267. assert(round.observed.realProofClosureWorkOrderWorkOrderCount === expectedWorkOrderCount, 'round deposition should observe real proof closure work order count');
  268. assert(round.observed.realProofClosureWorkOrderCanCloseProofGap === false, 'round deposition should observe real proof closure work order boundary');
  269. assert(round.observed.realProofClosureWorkOrderOwnerArtifactCount === expectedOwnerArtifactCount, 'round deposition should observe real proof closure work order owner artifacts');
  270. assert(round.observed.latestFormIndexRealProofClosureWorkOrderMarkdown?.includes('real-proof-closure-work-order.md'), 'round deposition should observe latest form real proof closure work order');
  271. assert(round.observed.handoffRealProofClosureWorkOrderMarkdown?.includes('real-proof-closure-work-order.md'), 'round deposition should observe handoff real proof closure work order');
  272. assert(round.observed.handoffLatestFormIndexRealProofClosureWorkOrderMarkdown?.includes('real-proof-closure-work-order.md'), 'round deposition should observe handoff latest form real proof closure work order');
  273. assert(round.observed.realGapMaterialAuditMaterialFoundCount === realGapMaterialAudit.materialFoundCount, 'round deposition should observe real gap material audit coverage');
  274. assert(round.observed.realGapMaterialAuditCanCloseProofGap === false, 'round deposition should observe real gap material audit non-closure boundary');
  275. assert(round.observed.latestFormIndexRealGapMaterialAuditMarkdown?.includes('real-gap-material-audit.md'), 'round deposition should observe latest form real gap material audit');
  276. assert(round.observed.handoffRealGapMaterialAuditMarkdown?.includes('real-gap-material-audit.md'), 'round deposition should observe handoff real gap material audit');
  277. assert(round.observed.evidenceByType['proof-gap-software-form'] === 1, 'round deposition should read refreshed evidence index');
  278. assert(round.observed.evidenceByType['optimization-completion'] === 1, 'round deposition should read completion audit evidence index');
  279. assert(round.observed.evidenceByType['proof-gap-operator-pack'] === 1, 'round deposition should read operator pack evidence index');
  280. assert(round.observed.evidenceByType['real-proof-intake-bundle'] === 1, 'round deposition should read real proof intake bundle evidence index');
  281. assert(round.observed.evidenceByType['local-seed-material-index'] === 1, 'round deposition should read local seed material evidence index');
  282. assert(round.observed.evidenceByType['local-seed-to-intake-worklist'] === 1, 'round deposition should read seed-to-intake worklist evidence index');
  283. assert(round.observed.evidenceByType['real-gap-material-audit'] === 1, 'round deposition should read real gap material audit evidence index');
  284. assert(round.observed.evidenceByType['real-proof-closure-work-order'] === 1, 'round deposition should read real proof closure work order evidence index');
  285. assert(round.observed.evidenceByType['experience-transcript-index'] === 1, 'round deposition should read experience transcript evidence index');
  286. assert(round.observed.evidenceByType['feedback-loop-closure'] === 1, 'round deposition should read feedback loop evidence index');
  287. assert(round.observed.evidenceByType['homepage-evidence-readiness'] === 1, 'round deposition should read homepage readiness evidence index');
  288. console.log(JSON.stringify({
  289. ok: true,
  290. outputRoot,
  291. evidenceTotal: evidence.total,
  292. roundPassCount: round.counts.pass
  293. }, null, 2));
  294. }
  295. function readJson(file) {
  296. return JSON.parse(fs.readFileSync(file, 'utf8'));
  297. }
  298. function assertVideoResourceReadiness(videoReadiness) {
  299. const ready = videoReadiness.acceptance?.readyForVideoAbPreflight === true;
  300. const realCandidateRows = Number(videoReadiness.counts?.realCandidateRows || 0);
  301. if (ready) {
  302. assert(Number(videoReadiness.failureCount || 0) === 0, 'ready video resource state should have zero failures');
  303. assert(realCandidateRows >= 1, 'ready video resource state should expose real candidate rows');
  304. return;
  305. }
  306. assert(Number(videoReadiness.failureCount || 0) >= 1, 'blocked video resource state should expose failures');
  307. }
  308. function seedRequiredOutputs(outputRoot) {
  309. fs.mkdirSync(outputRoot, { recursive: true });
  310. [
  311. 'proof-gap-request-latest',
  312. 'proof-gap-closure-latest',
  313. 'intake-readiness-latest',
  314. 'video-resource-readiness-latest',
  315. 'data-intake-pack-latest',
  316. 'video-intake-pack-latest',
  317. 'long-run-readiness-latest',
  318. 'business-proof-progress-latest',
  319. 'business-proof-next-actions-latest',
  320. 'optimization-pipeline-latest',
  321. 'video-hit-rate-preflight-latest',
  322. 'software-client-latest'
  323. ].forEach(name => copyOutputDir(name, outputRoot));
  324. copySoftwareClientSourceSeed(outputRoot);
  325. copyRealEvidenceSeed(outputRoot);
  326. copyVideoAbAuditSeed(outputRoot);
  327. }
  328. function copySoftwareClientSourceSeed(outputRoot) {
  329. const summaryPath = path.join(ROOT, 'outputs', 'software-client-latest', 'software-client-list.final.summary.json');
  330. if (!fs.existsSync(summaryPath)) return;
  331. const summary = readJson(summaryPath);
  332. const inputPath = path.resolve(ROOT, summary.input || '');
  333. if (!fs.existsSync(inputPath)) return;
  334. const sourceDir = path.dirname(inputPath);
  335. const relativeDir = path.relative(path.join(ROOT, 'outputs'), sourceDir);
  336. if (!relativeDir || relativeDir.startsWith('..') || path.isAbsolute(relativeDir)) return;
  337. copyDir(sourceDir, path.join(outputRoot, relativeDir));
  338. }
  339. function copyOutputDir(name, outputRoot) {
  340. const source = path.join(ROOT, 'outputs', name);
  341. if (!fs.existsSync(source)) return;
  342. copyDir(source, path.join(outputRoot, name));
  343. }
  344. function copyRealEvidenceSeed(outputRoot) {
  345. const outputs = path.join(ROOT, 'outputs');
  346. if (!fs.existsSync(outputs)) return;
  347. for (const entry of fs.readdirSync(outputs, { withFileTypes: true })) {
  348. if (!entry.isDirectory() || !entry.name.startsWith('overnight-quality-')) continue;
  349. const dir = path.join(outputs, entry.name);
  350. const aggregatePath = path.join(dir, 'aggregate-summary.json');
  351. if (!fs.existsSync(aggregatePath)) continue;
  352. const aggregate = JSON.parse(fs.readFileSync(aggregatePath, 'utf8'));
  353. const liveProof = Boolean(aggregate.manifest?.liveEnabled) &&
  354. aggregate.acceptance?.overallPass === true &&
  355. Number(aggregate.failureCount || 0) === 0 &&
  356. Number(aggregate.acceptance?.failedGateCount ?? aggregate.failedGateCount ?? 0) === 0;
  357. if (!liveProof) continue;
  358. copyDir(dir, path.join(outputRoot, entry.name));
  359. return;
  360. }
  361. }
  362. function copyVideoAbAuditSeed(outputRoot) {
  363. const outputs = path.join(ROOT, 'outputs');
  364. if (!fs.existsSync(outputs)) return;
  365. for (const entry of fs.readdirSync(outputs, { withFileTypes: true })) {
  366. if (!entry.isDirectory() || !entry.name.startsWith('video-hit-rate-audit-')) continue;
  367. const dir = path.join(outputs, entry.name);
  368. if (!fs.existsSync(path.join(dir, 'video-hit-rate-summary.json'))) continue;
  369. copyDir(dir, path.join(outputRoot, entry.name));
  370. return;
  371. }
  372. }
  373. function copyDir(source, target) {
  374. fs.mkdirSync(target, { recursive: true });
  375. for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
  376. const from = path.join(source, entry.name);
  377. const to = path.join(target, entry.name);
  378. if (entry.isDirectory()) copyDir(from, to);
  379. else if (entry.isFile()) fs.copyFileSync(from, to);
  380. }
  381. }
  382. function assert(condition, message) {
  383. if (!condition) throw new Error(message);
  384. }
  385. if (require.main === module) main();