| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const root = path.resolve(__dirname, '..');
- function main() {
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-next-actions-missing-proof-'));
- const outputs = path.join(tmp, 'outputs');
- fs.mkdirSync(outputs, { recursive: true });
- writeJson(path.join(outputs, 'business-proof-progress-latest', 'business-proof-progress-summary.json'), {
- complete: false,
- counts: { pass: 0, blocked_by_external_data: 1, fail: 0, pending: 0 },
- rows: []
- });
- writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
- complete: false,
- openCount: 1,
- rows: [
- {
- id: 'video-ab-live-proof',
- title: '真实视频 A/B 验收',
- status: 'open',
- evidence: 'passed=true, mode=unknown, generatedBy=unknown, provider=ok',
- command: 'npm run acceptance:video-ab',
- expectedArtifact: '<视频A/B输出目录>/video-hit-rate-summary.json',
- required: 'acceptance:video-ab 必须在真实 provider 和真实视频资源下通过。',
- missingProofRequirements: [
- 'proofContext.mode=live',
- 'proofContext.collectionMode=live',
- 'proofContext.generatedBy=acceptance:video-ab'
- ]
- }
- ]
- });
- writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), {
- generatedAt: '2026-06-07T08:59:39.964Z',
- readyForClaim: false,
- counts: { fail: 1 },
- nextActions: [
- {
- priority: 1,
- owner: '技术/AI',
- title: '过期 pipeline 视频 A/B 动作',
- step: 'video-ab-proof',
- command: 'stale pipeline command should not dispatch',
- expectedArtifact: 'stale/video-hit-rate-summary.json',
- acceptance: 'stale pipeline should be rerun first'
- }
- ]
- });
- writeJson(path.join(outputs, 'optimization-completion-latest', 'optimization-completion-summary.json'), {
- complete: false,
- readyForClaim: false,
- proofGapOpenCount: 1,
- blockingReasons: [
- {
- id: 'optimization-pipeline',
- 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',
- missingProofRequirements: [
- 'optimizationPipeline.generatedAt>=currentProofSources',
- 'optimizationPipeline.readyForClaim=true'
- ],
- 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'
- }
- ]
- });
- const outputDir = path.join(outputs, 'business-proof-next-actions-latest');
- const result = spawnSync(process.execPath, [
- path.join(root, 'scripts', 'business-proof-next-actions.js'),
- '--outputs',
- outputs,
- '--output',
- outputDir
- ], { cwd: root, encoding: 'utf8' });
- if (result.status !== 0) {
- process.stderr.write(result.stderr || result.stdout || '');
- throw new Error('business-proof-next-actions should run');
- }
- const summary = readJson(path.join(outputDir, 'business-proof-next-actions-summary.json'));
- const report = readText(path.join(outputDir, 'business-proof-next-actions-report.md'));
- const csv = readText(path.join(outputDir, 'business-proof-next-actions.csv'));
- const ownerReport = readText(path.join(outputDir, 'by-owner', 'tech-ai.md'));
- const videoAction = summary.actions.find(action => action.id === 'gap-video-ab-live-proof');
- assert(summary.actionCount === 1, 'summary should expose top-level actionCount for stale-pipeline-only fixture');
- assert(Array.isArray(summary.topActions) && summary.topActions.length === 1, 'summary should expose top-level topActions for current dispatch');
- assert(summary.topActions[0].id === 'gap-video-ab-live-proof', 'topActions should keep the current video A/B action');
- assert(summary.sourceState.pipelineStale === true, 'next actions should expose stale pipeline state from completion audit');
- assert(summary.sourceState.pipelineNextActionCount === 0, 'stale pipeline next actions should not count as current dispatch');
- assert(summary.sourceState.pipelineRawNextActionCount === 1, 'next actions should retain raw stale pipeline action count for audit');
- assert(summary.sourceState.pipelineSuppressedNextActionCount === 1, 'next actions should expose suppressed stale pipeline action count');
- assert(String(summary.sourceState.pipelineStaleReason || '').includes('stale=true'), 'next actions should expose stale pipeline reason');
- assert(!summary.actions.some(action => action.command === 'stale pipeline command should not dispatch'), 'stale pipeline next action should not dispatch');
- assert(videoAction, 'video A/B action should exist');
- assert(Array.isArray(videoAction.missingProofRequirements), 'video A/B action should keep missingProofRequirements');
- assert(videoAction.missingProofRequirements.includes('proofContext.mode=live'), 'video A/B action should keep missing mode requirement');
- assert(videoAction.next.includes('proofContext.generatedBy=acceptance:video-ab'), 'video A/B next step should expose generatedBy requirement');
- assert(report.includes('proofContext.collectionMode=live'), 'main report should expose missing collectionMode requirement');
- assert(report.includes('pipeline.stale:true'), 'main report should expose stale pipeline state');
- assert(report.includes('pipeline.suppressedNextActionCount:1'), 'main report should expose suppressed stale pipeline count');
- assert(ownerReport.includes('proofContext.mode=live'), 'owner report should expose missing mode requirement');
- assert(csv.includes('proofContext.generatedBy=acceptance:video-ab'), 'CSV should expose missing generatedBy requirement');
- assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report + csv + ownerReport), 'outputs should not contain model token patterns');
- assert(!/r:[A-Za-z0-9]{20,}/.test(report + csv + ownerReport), 'outputs should not contain Parse sessionToken patterns');
- console.log(JSON.stringify({ ok: true, outputDir, missingCount: videoAction.missingProofRequirements.length }, null, 2));
- }
- function writeJson(file, value) {
- fs.mkdirSync(path.dirname(file), { recursive: true });
- fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
- }
- function readJson(file) {
- return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- }
- function readText(file) {
- return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- main();
|