| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- #!/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, '..');
- const VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS = [
- 'mode=unknown',
- 'collectionMode=unknown',
- 'generatedBy=unknown',
- 'preflight.readyForVideoAb=false',
- 'preflight.failureCount=4',
- 'preflight.generatedBy=acceptance:video-ab-preflight',
- 'preflight.proofLevel=not_business_proof',
- 'preflight.canCloseProofGap=false',
- 'runtimeCredentialPresent=false',
- 'companyResolved=false',
- 'vocSocialProviderTokenPresent=false',
- 'videoAnalysisTokenPresent=false'
- ];
- const VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS = [
- 'proofContext.mode=live',
- 'proofContext.collectionMode=live',
- 'proofContext.generatedBy=acceptance:video-ab',
- 'proofContext.requiresRuntimeCredential=true',
- 'proofContext.requiresVocSocialProvider=true',
- 'proofContext.requiresVideoAnalysisProvider=true',
- 'videoAbPreflight.readyForVideoAb=true',
- 'videoAbPreflight.proofContext.requiresRuntimeCredential=true',
- 'videoAbPreflight.company resolved for live run',
- 'videoAbPreflight.proofContext.requiresVocSocialProvider=true',
- 'videoAbPreflight.proofContext.requiresVideoAnalysisProvider=true'
- ];
- function main() {
- const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-real-proof-intake-bundle-'));
- const result = spawnSync(process.execPath, [
- 'scripts/real-proof-intake-bundle.js',
- '--output',
- outputDir,
- '--strict'
- ], {
- cwd: ROOT,
- encoding: 'utf8',
- shell: false
- });
- if (result.stdout) process.stdout.write(result.stdout);
- if (result.stderr) process.stderr.write(result.stderr);
- assert(result.status === 0, 'real proof intake bundle should pass against current outputs');
- const summaryPath = path.join(outputDir, 'real-proof-intake-bundle-summary.json');
- const reportPath = path.join(outputDir, 'real-proof-intake-bundle.md');
- const csvPath = path.join(outputDir, 'real-proof-intake-bundle.csv');
- assert(fs.existsSync(summaryPath), 'summary should exist');
- assert(fs.existsSync(reportPath), 'report should exist');
- assert(fs.existsSync(csvPath), 'csv should exist');
- const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
- const report = fs.readFileSync(reportPath, 'utf8').replace(/^\uFEFF/, '');
- const csv = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '');
- const expectedOwnerArtifactCount = Number(summary.expectedOperatorPackOwnerArtifactCount || summary.operatorPackOwnerArtifactCount || 0);
- assert(summary.passed === true, 'summary should pass');
- assert(summary.complete === false, 'bundle should keep incomplete state while proof gaps remain');
- assert(summary.directCustomerProof === false, 'bundle must not be direct customer proof');
- assert(summary.proofLevel === 'not_business_proof', 'bundle should classify as not business proof');
- assert(summary.proofOpenCount >= 1, 'bundle should expose open proof gap count');
- assert(summary.fillRowCount >= 3, 'bundle should expose history/review/video fill rows');
- assert(summary.proofGapRowCount === 5, 'bundle should expose five proof gap rows');
- assert(summary.recheckCommandCount >= 5, 'bundle should expose recheck commands');
- assert(summary.exportRowCount >= summary.fillRowCount + summary.proofGapRowCount, 'csv export should include fill and proof rows');
- assert(summary.operatorPackEvidenceRowCount === 5, 'bundle should expose operator pack evidence row count');
- assert(summary.operatorPackNextActionCount === 5, 'bundle should expose operator pack next action count');
- assert(summary.operatorPackMissingProofRequirementCount >= 1, 'bundle should expose missing proof requirement count');
- assert(summary.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'bundle should expose operator pack owner artifact count');
- assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === expectedOwnerArtifactCount, 'bundle should expose operator pack owner artifacts');
- assert(summary.operatorPackOwnerArtifacts.some(item => item.owner === '商务' && item.markdown?.includes('by-owner/business.md') && item.csv?.includes('by-owner/business.csv')), 'bundle should expose business owner operator files');
- assert(summary.operatorPackOwnerArtifacts.some(item => item.owner === '技术/AI' && item.markdown?.includes('by-owner/tech-ai.md') && item.csv?.includes('by-owner/tech-ai.csv')), 'bundle should expose tech/AI owner operator files');
- if (summary.operatorPackOwnerArtifacts.some(item => item.owner === '商务/投放')) {
- assert(summary.operatorPackOwnerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md') && item.csv?.includes('by-owner/business-media.csv')), 'bundle should expose business/media owner operator files when that owner has open actions');
- }
- assert(summary.operatorPackOwnerArtifacts.every(item => Number(item.evidenceRowCount || 0) >= 1 && Number(item.nextActionCount || 0) >= 1), 'bundle owner artifacts should preserve evidence and next action counts');
- assert(summary.proofGapRows.every(item => item.evidence), 'every proof gap row should carry current evidence');
- assert(summary.proofGapRows.every(item => item.nextAction), 'every proof gap row should carry next action');
- assert(summary.proofGapRows.some(item => Array.isArray(item.missingProofRequirements) && item.missingProofRequirements.length >= 1), 'bundle should carry missing proof requirements from operator pack');
- assert(summary.exportRows.filter(item => item.type === '证明缺口').every(item => item.evidence && item.nextAction), 'proof gap export rows should carry evidence and next action');
- const videoAbProofGap = summary.proofGapRows.find(item => item.gapId === 'video-ab-live-proof');
- assert(videoAbProofGap, 'bundle should include video A/B live proof gap');
- assertVideoAbPreflightContext(videoAbProofGap, 'bundle video A/B proof gap row');
- const videoAbExportRow = summary.exportRows.find(item => item.type === '证明缺口' && item.evidence?.includes('preflight.readyForVideoAb'));
- assert(videoAbExportRow, 'bundle csv export rows should include video A/B preflight evidence row');
- assertTextIncludesAll(videoAbExportRow.evidence, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'bundle export row should preserve video A/B preflight evidence context');
- assertTextIncludesAll(videoAbExportRow.nextAction, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'bundle export row should preserve video A/B preflight missing requirements');
- assert(summary.templateFiles.some(item => item.path?.includes('history-data-template.csv')), 'history template should be included');
- assert(summary.templateFiles.some(item => item.path?.includes('manual-review-template.csv')), 'manual review template should be included');
- assert(summary.templateFiles.some(item => item.path?.includes('video-resource-template.csv')), 'video template should be included');
- assert(summary.templateFiles.some(item => item.path?.includes('intake-field-checklist.md')), 'field checklist should be included');
- assert(summary.templateFiles.some(item => item.path?.includes('proof-gap-operator-pack.md')), 'operator pack should be included');
- assert(summary.templateFiles.some(item => item.path?.includes('optimization-completion-report.md')), 'optimization completion report should be included');
- assert(summary.templateFiles.some(item => item.status?.includes('readyForClaim=false')), 'optimization completion template status should expose readyForClaim=false');
- assert(summary.templateFiles.some(item => item.path?.includes('long-run-readiness-report.md')), 'long-run readiness report should be included');
- assert(summary.templateFiles.some(item => item.status?.includes('ready=false')), 'long-run readiness template status should expose ready=false');
- assert(summary.fillRows.some(item => item.section === 'history' && item.owner === '商务'), 'history fill row should belong to business');
- assert(summary.fillRows.some(item => item.section === 'video' && item.owner === '商务/投放'), 'video fill row should belong to business/media');
- assert(summary.proofGapRows.some(item => item.gapId === 'manual-review-and-customer-effect'), 'customer effect proof gap should be included');
- assert(summary.repairRows.some(item => item.csv?.includes('intake-readiness-repair-actions.csv')), 'data repair csv should be included');
- assert(summary.repairRows.some(item => item.csv?.includes('video-resource-repair-actions.csv')), 'video repair csv should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('intake:readiness')), 'intake readiness recheck should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('video:resource-readiness')), 'video readiness recheck should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('customer-effect:audit')), 'customer effect audit should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('acceptance:video-ab')), 'video A/B acceptance should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('longrun:readiness')), 'long-run readiness recheck should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('optimization:completion')), 'optimization completion recheck should be included');
- assert(summary.recheckCommands.some(item => item.command.includes('round:refresh')), 'round refresh should be included');
- assert(summary.guardrails.some(item => item.includes('proofOpenCount')), 'guardrails should mention proofOpenCount');
- assert(summary.guardrails.some(item => item.includes('readyForClaim=false')), 'guardrails should mention readyForClaim=false');
- assert(summary.guardrails.some(item => item.includes('longrun:readiness.ready=false')), 'guardrails should mention longrun readiness false');
- assert(summary.sourceState.operatorPackEvidenceRowCount === 5, 'source state should expose operator pack evidence row count');
- assert(summary.sourceState.operatorPackNextActionCount === 5, 'source state should expose operator pack next action count');
- assert(summary.sourceState.operatorPackMissingProofRequirementCount >= 1, 'source state should expose missing proof requirement count');
- assert(summary.sourceState.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'source state should expose operator pack owner artifact count');
- assert(summary.sourceState.optimizationCompletionComplete === false, 'source state should expose optimization completion incomplete');
- assert(summary.sourceState.optimizationCompletionReadyForClaim === false, 'source state should expose optimization completion not ready for claim');
- assert(summary.sourceState.longRunReadinessReady === false, 'source state should expose long-run readiness not ready');
- assert(summary.sourceState.longRunReadinessFailCount >= 1, 'source state should expose long-run readiness fail count');
- assert(summary.sourceState.longRunReadinessProofLevel === 'not_business_proof', 'source state should classify long-run readiness as not business proof');
- assert(summary.sourceState.longRunReadinessDirectCustomerProof === false, 'source state should not treat long-run readiness as customer proof');
- assert(report.includes('# 真实验收材料收集总包'), 'report should be Chinese');
- assert(report.includes('history-data-template.csv'), 'report should include history template path');
- assert(report.includes('manual-review-template.csv'), 'report should include manual review template path');
- assert(report.includes('video-resource-template.csv'), 'report should include video template path');
- assert(report.includes('proof-gap-operator-pack.md'), 'report should include operator pack path');
- assert(report.includes('intake-field-checklist.md'), 'report should include field checklist path');
- assert(report.includes('optimization-completion-report.md'), 'report should include optimization completion path');
- assert(report.includes('readyForClaim=false'), 'report should include optimization completion boundary');
- assert(report.includes('long-run-readiness-report.md'), 'report should include long-run readiness path');
- assert(report.includes('ready=false'), 'report should include long-run readiness boundary');
- assert(report.includes('## 补齐后复验命令'), 'report should include recheck command section');
- assert(report.includes('operatorPackEvidenceRowCount:5'), 'report should include operator pack evidence row count');
- assert(report.includes('operatorPackNextActionCount:5'), 'report should include operator pack next action count');
- assert(report.includes(`operatorPackOwnerArtifactCount:${expectedOwnerArtifactCount}`), 'report should include operator pack owner artifact count');
- assert(report.includes('## 操作包负责人附件'), 'report should include operator owner artifact section');
- assert(report.includes('by-owner/business.md'), 'report should include business owner operator markdown');
- if (summary.operatorPackOwnerArtifacts.some(item => item.owner === '商务/投放')) {
- assert(report.includes('by-owner/business-media.csv'), 'report should include business/media owner operator csv when present');
- }
- assert(report.includes('by-owner/tech-ai.md'), 'report should include tech/AI owner operator markdown');
- assert(report.includes('当前证据'), 'report should include current evidence column');
- assert(report.includes('下一步'), 'report should include next action column');
- assert(report.includes('briefCount=1'), 'report should include concrete history audit evidence');
- assert(report.includes('briefCount>=5'), 'report should include concrete history missing proof requirement');
- assert(report.includes('acceptance.customerSelectedRateMeasured=true'), 'report should include concrete customer-effect missing proof requirement');
- assert(report.includes('补 5-10 个真实 Brief'), 'report should include next action text');
- assert(report.includes('npm run longrun:readiness'), 'report should include long-run readiness recheck command');
- assert(report.includes('npm run optimization:completion'), 'report should include optimization completion recheck command');
- assert(report.includes('不证明客户效果'), 'report should keep proof boundary');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'bundle report should preserve video A/B preflight evidence context');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'bundle report should preserve video A/B preflight missing requirements');
- assert(csv.includes('类型,顺序,负责人,标题,文件或命令'), 'csv should include Chinese header');
- assert(csv.includes('当前证据'), 'csv should include current evidence column');
- assert(csv.includes('下一步'), 'csv should include next action column');
- assert(csv.includes('填表顺序'), 'csv should include fill rows');
- assert(csv.includes('证明缺口'), 'csv should include proof gap rows');
- assert(csv.includes('负责人附件'), 'csv should include owner artifact rows');
- assert(csv.includes('by-owner/business.csv'), 'csv should include business owner operator csv');
- if (summary.operatorPackOwnerArtifacts.some(item => item.owner === '商务/投放')) {
- assert(csv.includes('by-owner/business-media.md'), 'csv should include business/media owner operator markdown when present');
- }
- assert(csv.includes('by-owner/tech-ai.csv'), 'csv should include tech/AI owner operator csv');
- assertTextIncludesAll(csv, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'bundle csv should preserve video A/B preflight evidence context');
- assertTextIncludesAll(csv, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'bundle csv should preserve video A/B preflight missing requirements');
- assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'report should not contain model token patterns');
- assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not contain Parse sessionToken patterns');
- assert(!/Authorization/i.test(report), 'report should not expose Authorization text');
- console.log(JSON.stringify({
- ok: true,
- outputDir,
- proofOpenCount: summary.proofOpenCount,
- exportRowCount: summary.exportRowCount
- }, null, 2));
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- function assertVideoAbPreflightContext(row, label) {
- assert(row.status === 'open', `${label} should remain open until live proof exists`);
- assert(row.recheckCommands.some(command => command.includes('acceptance:video-ab')), `${label} should keep live video A/B acceptance command`);
- assertTextIncludesAll(row.evidence || '', VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, `${label} should preserve non-proof preflight evidence context`);
- assertTextIncludesAll(row.missingProofRequirements || [], VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, `${label} should preserve live proof requirements`);
- assertTextIncludesAll(row.nextAction || '', VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, `${label} next action should preserve live proof requirements`);
- }
- function assertTextIncludesAll(value, tokens, message) {
- const text = Array.isArray(value) ? value.join('\n') : String(value || '');
- const missing = tokens.filter(token => !text.includes(token));
- assert(missing.length === 0, `${message}: missing ${missing.join(', ')}`);
- }
- if (require.main === module) main();
|