generate-proof-gap-request-smoke.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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-proof-gap-'));
  9. const handoff = path.join(tmp, 'optimization-handoff-summary.json');
  10. const evidence = path.join(tmp, 'evidence-index-summary.json');
  11. const videoReadiness = path.join(tmp, 'video-resource-readiness-summary.json');
  12. const closure = path.join(tmp, 'proof-gap-closure-summary.json');
  13. const output = path.join(tmp, 'proof-gap-output');
  14. fs.writeFileSync(handoff, JSON.stringify({
  15. complete: false,
  16. statusCounts: { passed: 22, ready_not_proven: 2, blocked_by_external_data: 2 },
  17. evidenceCounts: { real_evidence: 5, smoke_or_local: 22, not_business_proof: 85 },
  18. readyNotProven: [{ id: 'video-ab-proof' }, { id: 'historical-dataset' }],
  19. externalBlockers: [{ id: 'live-provider-overnight-proof' }, { id: 'customer-effect-proof' }]
  20. }, null, 2), 'utf8');
  21. fs.writeFileSync(evidence, JSON.stringify({
  22. counts: { real_evidence: 5, smoke_or_local: 22, not_business_proof: 85 }
  23. }, null, 2), 'utf8');
  24. fs.writeFileSync(videoReadiness, JSON.stringify({
  25. acceptance: { readyForVideoAbPreflight: false },
  26. failureCount: 1,
  27. counts: { realReferenceRows: 1, realCandidateRows: 0, videoUrlRows: 1 }
  28. }, null, 2), 'utf8');
  29. fs.writeFileSync(closure, JSON.stringify({
  30. complete: false,
  31. openCount: 5,
  32. closedCount: 0
  33. }, null, 2), 'utf8');
  34. const result = spawnSync(process.execPath, [
  35. path.join(root, 'scripts', 'generate-proof-gap-request.js'),
  36. '--handoff',
  37. handoff,
  38. '--evidence',
  39. evidence,
  40. '--video-readiness',
  41. videoReadiness,
  42. '--closure',
  43. closure,
  44. '--output',
  45. output
  46. ], { cwd: root, encoding: 'utf8' });
  47. if (result.status !== 0) {
  48. process.stderr.write(result.stderr || result.stdout || '');
  49. throw new Error('generate-proof-gap-request should pass');
  50. }
  51. const summary = JSON.parse(fs.readFileSync(path.join(output, 'proof-gap-request-summary.json'), 'utf8'));
  52. const report = fs.readFileSync(path.join(output, 'proof-gap-request-report.md'), 'utf8').replace(/^\uFEFF/, '');
  53. const csv = fs.readFileSync(path.join(output, 'proof-gap-request-table.csv'), 'utf8').replace(/^\uFEFF/, '');
  54. assert(summary.complete === false, 'proof gap request must not mark long-term goal complete');
  55. assert(summary.unresolvedCount >= 4, 'proof gap request should list unresolved proof gaps');
  56. assert(summary.rows.some(row => row.id === 'video-real-candidate' && row.status === 'missing_real_data'), 'should request real candidate video resources');
  57. assert(summary.rows.some(row => row.id === 'manual-review-and-customer-effect'), 'should request customer effect proof data');
  58. assert(summary.rows.every(row => row.templateCommand && row.templateArtifact), 'every proof gap row should include template command and artifact');
  59. assert(summary.rows.some(row => row.id === 'historical-dataset' && row.templateCommand.includes('data:intake-template')), 'historical dataset gap should point to data intake template');
  60. assert(summary.rows.some(row => row.id === 'video-real-candidate' && row.templateCommand.includes('video:intake-template')), 'video gap should point to video intake template');
  61. assert(summary.observed.proofGapClosureOpenCount === 5, 'proof gap request should expose latest closure open count');
  62. assert(summary.rows.every(row => row.owner !== '技术/投放' && row.owner !== '商务/客户' && row.owner !== '投放/商务'), 'owner labels should be normalized');
  63. assert(report.includes('# 提号真实证明缺口请求包'), 'report should be Chinese');
  64. assert(report.includes('模板命令') && report.includes('模板产物'), 'report should expose intake template columns');
  65. assert(report.includes('closure.openCount:5'), 'report should include closure open count');
  66. assert(csv.startsWith('缺口ID,负责人,状态'), 'CSV should have business-facing Chinese header');
  67. assert(csv.includes('模板命令') && csv.includes('模板产物'), 'CSV should expose intake template columns');
  68. assert(!/(sk-[A-Za-z0-9_-]{20,}|r:[A-Za-z0-9]{20,}|Authorization\s*[:=]\s*Bearer)/i.test(report + csv), 'proof gap request should not contain secrets');
  69. fs.writeFileSync(videoReadiness, JSON.stringify({
  70. acceptance: { readyForVideoAbPreflight: true },
  71. failureCount: 0,
  72. counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 }
  73. }, null, 2), 'utf8');
  74. fs.writeFileSync(closure, JSON.stringify({
  75. complete: false,
  76. openCount: 3,
  77. closedCount: 2,
  78. rows: [
  79. { id: 'video-real-candidate', status: 'closed', evidence: 'realReference=1, realCandidate=1' },
  80. { id: 'live-provider-overnight-proof', status: 'closed', evidence: 'liveEnabled=true, overallPass=true' },
  81. { id: 'historical-dataset', status: 'open' },
  82. { id: 'video-ab-live-proof', status: 'open' },
  83. { id: 'manual-review-and-customer-effect', status: 'open' }
  84. ]
  85. }, null, 2), 'utf8');
  86. const closureAwareOutput = path.join(tmp, 'proof-gap-output-closure-aware');
  87. const closureAware = spawnSync(process.execPath, [
  88. path.join(root, 'scripts', 'generate-proof-gap-request.js'),
  89. '--handoff',
  90. handoff,
  91. '--evidence',
  92. evidence,
  93. '--video-readiness',
  94. videoReadiness,
  95. '--closure',
  96. closure,
  97. '--output',
  98. closureAwareOutput
  99. ], { cwd: root, encoding: 'utf8' });
  100. if (closureAware.status !== 0) {
  101. process.stderr.write(closureAware.stderr || closureAware.stdout || '');
  102. throw new Error('closure-aware generate-proof-gap-request should pass');
  103. }
  104. const closureAwareSummary = JSON.parse(fs.readFileSync(path.join(closureAwareOutput, 'proof-gap-request-summary.json'), 'utf8'));
  105. assert(closureAwareSummary.unresolvedCount === 3, `closure-aware request should follow closure openCount=3, got ${closureAwareSummary.unresolvedCount}`);
  106. assert(closureAwareSummary.rows.some(row => row.id === 'live-provider-overnight-proof' && row.status === 'ready'), 'closed live-provider gap should stay ready in request package');
  107. assert(closureAwareSummary.rows.some(row => row.id === 'video-real-candidate' && row.status === 'ready'), 'closed video resource gap should stay ready in request package');
  108. console.log(JSON.stringify({
  109. ok: true,
  110. output,
  111. gapCount: summary.rows.length,
  112. unresolvedCount: summary.unresolvedCount
  113. }, null, 2));
  114. }
  115. function assert(condition, message) {
  116. if (!condition) throw new Error(message);
  117. }
  118. main();