business-proof-next-actions-missing-proof-smoke.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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 tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-next-actions-missing-proof-'));
  9. const outputs = path.join(tmp, 'outputs');
  10. fs.mkdirSync(outputs, { recursive: true });
  11. writeJson(path.join(outputs, 'business-proof-progress-latest', 'business-proof-progress-summary.json'), {
  12. complete: false,
  13. counts: { pass: 0, blocked_by_external_data: 1, fail: 0, pending: 0 },
  14. rows: []
  15. });
  16. writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
  17. complete: false,
  18. openCount: 1,
  19. rows: [
  20. {
  21. id: 'video-ab-live-proof',
  22. title: '真实视频 A/B 验收',
  23. status: 'open',
  24. evidence: 'passed=true, mode=unknown, generatedBy=unknown, provider=ok',
  25. command: 'npm run acceptance:video-ab',
  26. expectedArtifact: '<视频A/B输出目录>/video-hit-rate-summary.json',
  27. required: 'acceptance:video-ab 必须在真实 provider 和真实视频资源下通过。',
  28. missingProofRequirements: [
  29. 'proofContext.mode=live',
  30. 'proofContext.collectionMode=live',
  31. 'proofContext.generatedBy=acceptance:video-ab'
  32. ]
  33. }
  34. ]
  35. });
  36. writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), {
  37. generatedAt: '2026-06-07T08:59:39.964Z',
  38. readyForClaim: false,
  39. counts: { fail: 1 },
  40. nextActions: [
  41. {
  42. priority: 1,
  43. owner: '技术/AI',
  44. title: '过期 pipeline 视频 A/B 动作',
  45. step: 'video-ab-proof',
  46. command: 'stale pipeline command should not dispatch',
  47. expectedArtifact: 'stale/video-hit-rate-summary.json',
  48. acceptance: 'stale pipeline should be rerun first'
  49. }
  50. ]
  51. });
  52. writeJson(path.join(outputs, 'optimization-completion-latest', 'optimization-completion-summary.json'), {
  53. complete: false,
  54. readyForClaim: false,
  55. proofGapOpenCount: 1,
  56. blockingReasons: [
  57. {
  58. id: 'optimization-pipeline',
  59. evidence: 'readyForClaim=false, stale=true, stale optimization-pipeline-summary.json: generatedAt=2026-06-07T08:59:39.964Z older than latest proof source=2026-06-12T04:21:28.840Z',
  60. missingProofRequirements: [
  61. 'optimizationPipeline.generatedAt>=currentProofSources',
  62. 'optimizationPipeline.readyForClaim=true'
  63. ],
  64. command: 'npm run optimization:pipeline -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict'
  65. }
  66. ]
  67. });
  68. const outputDir = path.join(outputs, 'business-proof-next-actions-latest');
  69. const result = spawnSync(process.execPath, [
  70. path.join(root, 'scripts', 'business-proof-next-actions.js'),
  71. '--outputs',
  72. outputs,
  73. '--output',
  74. outputDir
  75. ], { cwd: root, encoding: 'utf8' });
  76. if (result.status !== 0) {
  77. process.stderr.write(result.stderr || result.stdout || '');
  78. throw new Error('business-proof-next-actions should run');
  79. }
  80. const summary = readJson(path.join(outputDir, 'business-proof-next-actions-summary.json'));
  81. const report = readText(path.join(outputDir, 'business-proof-next-actions-report.md'));
  82. const csv = readText(path.join(outputDir, 'business-proof-next-actions.csv'));
  83. const ownerReport = readText(path.join(outputDir, 'by-owner', 'tech-ai.md'));
  84. const videoAction = summary.actions.find(action => action.id === 'gap-video-ab-live-proof');
  85. assert(summary.actionCount === 1, 'summary should expose top-level actionCount for stale-pipeline-only fixture');
  86. assert(Array.isArray(summary.topActions) && summary.topActions.length === 1, 'summary should expose top-level topActions for current dispatch');
  87. assert(summary.topActions[0].id === 'gap-video-ab-live-proof', 'topActions should keep the current video A/B action');
  88. assert(summary.sourceState.pipelineStale === true, 'next actions should expose stale pipeline state from completion audit');
  89. assert(summary.sourceState.pipelineNextActionCount === 0, 'stale pipeline next actions should not count as current dispatch');
  90. assert(summary.sourceState.pipelineRawNextActionCount === 1, 'next actions should retain raw stale pipeline action count for audit');
  91. assert(summary.sourceState.pipelineSuppressedNextActionCount === 1, 'next actions should expose suppressed stale pipeline action count');
  92. assert(String(summary.sourceState.pipelineStaleReason || '').includes('stale=true'), 'next actions should expose stale pipeline reason');
  93. assert(!summary.actions.some(action => action.command === 'stale pipeline command should not dispatch'), 'stale pipeline next action should not dispatch');
  94. assert(videoAction, 'video A/B action should exist');
  95. assert(Array.isArray(videoAction.missingProofRequirements), 'video A/B action should keep missingProofRequirements');
  96. assert(videoAction.missingProofRequirements.includes('proofContext.mode=live'), 'video A/B action should keep missing mode requirement');
  97. assert(videoAction.next.includes('proofContext.generatedBy=acceptance:video-ab'), 'video A/B next step should expose generatedBy requirement');
  98. assert(report.includes('proofContext.collectionMode=live'), 'main report should expose missing collectionMode requirement');
  99. assert(report.includes('pipeline.stale:true'), 'main report should expose stale pipeline state');
  100. assert(report.includes('pipeline.suppressedNextActionCount:1'), 'main report should expose suppressed stale pipeline count');
  101. assert(ownerReport.includes('proofContext.mode=live'), 'owner report should expose missing mode requirement');
  102. assert(csv.includes('proofContext.generatedBy=acceptance:video-ab'), 'CSV should expose missing generatedBy requirement');
  103. assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report + csv + ownerReport), 'outputs should not contain model token patterns');
  104. assert(!/r:[A-Za-z0-9]{20,}/.test(report + csv + ownerReport), 'outputs should not contain Parse sessionToken patterns');
  105. console.log(JSON.stringify({ ok: true, outputDir, missingCount: videoAction.missingProofRequirements.length }, null, 2));
  106. }
  107. function writeJson(file, value) {
  108. fs.mkdirSync(path.dirname(file), { recursive: true });
  109. fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
  110. }
  111. function readJson(file) {
  112. return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
  113. }
  114. function readText(file) {
  115. return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
  116. }
  117. function assert(condition, message) {
  118. if (!condition) throw new Error(message);
  119. }
  120. main();