| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- #!/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-proof-gap-operator-pack-'));
- const result = spawnSync(process.execPath, [
- 'scripts/proof-gap-operator-pack.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, 'proof gap operator pack should pass against current outputs');
- const summaryPath = path.join(outputDir, 'proof-gap-operator-pack-summary.json');
- const reportPath = path.join(outputDir, 'proof-gap-operator-pack.md');
- const csvPath = path.join(outputDir, 'proof-gap-operator-pack.csv');
- const businessOwnerPath = path.join(outputDir, 'by-owner', 'business.md');
- const businessOwnerCsvPath = path.join(outputDir, 'by-owner', 'business.csv');
- const mediaOwnerPath = path.join(outputDir, 'by-owner', 'business-media.md');
- const techOwnerPath = path.join(outputDir, 'by-owner', 'tech-ai.md');
- const techOwnerCsvPath = path.join(outputDir, 'by-owner', 'tech-ai.csv');
- assert(fs.existsSync(summaryPath), 'summary should exist');
- assert(fs.existsSync(reportPath), 'report should exist');
- assert(fs.existsSync(csvPath), 'csv should exist');
- assert(fs.existsSync(businessOwnerPath), 'business owner markdown should exist');
- assert(fs.existsSync(businessOwnerCsvPath), 'business owner csv should exist');
- assert(fs.existsSync(techOwnerPath), 'tech/AI owner markdown should exist');
- assert(fs.existsSync(techOwnerCsvPath), 'tech/AI owner 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 businessOwnerReport = fs.readFileSync(businessOwnerPath, 'utf8').replace(/^\uFEFF/, '');
- const businessOwnerCsv = fs.readFileSync(businessOwnerCsvPath, 'utf8').replace(/^\uFEFF/, '');
- const techOwnerReport = fs.readFileSync(techOwnerPath, 'utf8').replace(/^\uFEFF/, '');
- const techOwnerCsv = fs.readFileSync(techOwnerCsvPath, 'utf8').replace(/^\uFEFF/, '');
- const hasBusinessMediaOwnerArtifact = Array.isArray(summary.ownerArtifacts) &&
- summary.ownerArtifacts.some(item => item.markdown === 'by-owner/business-media.md');
- if (hasBusinessMediaOwnerArtifact) {
- assert(fs.existsSync(mediaOwnerPath), 'business/media owner markdown should exist when generated');
- }
- assert(summary.passed === true, 'summary should pass');
- assert(summary.complete === false, 'summary should keep incomplete state while proof gaps remain');
- assert(summary.directCustomerProof === false, 'operator pack must not be direct customer proof');
- assert(summary.proofLevel === 'not_business_proof', 'operator pack should classify as not business proof');
- assert(summary.rowCount === 5, 'operator pack should expose five business proof gaps');
- assert(summary.openCount >= 1, 'operator pack should expose open proof gaps');
- assert(Array.isArray(summary.gapIds) && summary.gapIds.includes('historical-dataset') && summary.gapIds.includes('manual-review-and-customer-effect'), 'operator pack should expose stable gap ids');
- assert(summary.fillFileCount >= 3, 'operator pack should expose unique fill file count');
- assert(summary.recheckCommandCount >= 5, 'operator pack should expose unique recheck command count');
- assert(summary.ownerGroupCount >= 2, 'operator pack should expose owner group count');
- assert(summary.ownerArtifactCount === summary.ownerGroups.length, 'operator pack should expose one owner artifact per current open owner group');
- assert(Array.isArray(summary.ownerArtifacts) && summary.ownerArtifacts.length === summary.ownerGroups.length, 'operator pack should expose owner artifacts');
- assert(summary.ownerArtifacts.some(item => item.owner === '商务' && item.markdown === 'by-owner/business.md' && item.csv === 'by-owner/business.csv'), 'operator pack should expose business owner artifact');
- if (summary.ownerGroups.some(item => item.owner === '商务/投放')) {
- assert(summary.ownerArtifacts.some(item => item.owner === '商务/投放' && item.markdown === 'by-owner/business-media.md' && item.csv === 'by-owner/business-media.csv'), 'operator pack should expose business/media owner artifact when that owner has open actions');
- }
- assert(summary.ownerArtifacts.some(item => item.owner === '技术/AI' && item.markdown === 'by-owner/tech-ai.md' && item.csv === 'by-owner/tech-ai.csv'), 'operator pack should expose tech/AI owner artifact');
- assert(summary.ownerArtifacts.every(item => item.evidenceRowCount >= 1 && item.nextActionCount >= 1), 'every owner artifact should expose evidence and next action counts');
- assert(summary.evidenceRowCount === 5, 'operator pack should expose current evidence for every gap row');
- assert(summary.nextActionCount === 5, 'operator pack should expose next action for every gap row');
- assert(summary.missingProofRequirementCount >= 1, 'operator pack should expose missing proof requirement count');
- assert(summary.rows.some(row => row.gapId === 'historical-dataset' && row.owner === '商务'), 'history gap should be assigned to business');
- assert(summary.rows.some(row => row.gapId === 'video-real-candidate' && row.owner === '商务/投放'), 'video resource gap should be assigned to business/media');
- assert(summary.rows.some(row => row.gapId === 'video-ab-live-proof' && row.owner === '技术/AI'), 'video A/B gap should be assigned to tech/AI');
- const videoAbRow = summary.rows.find(row => row.gapId === 'video-ab-live-proof');
- assert(videoAbRow, 'video A/B gap row should exist');
- assertVideoAbPreflightContext(videoAbRow, 'operator pack video A/B row');
- assert(summary.rows.some(row => row.gapId === 'manual-review-and-customer-effect' && row.fillFiles.some(file => file.includes('manual-review-template.csv'))), 'customer effect gap should point to manual review template');
- assert(summary.rows.every(row => row.fillFiles.length >= 1), 'every row should expose fill files');
- assert(summary.rows.every(row => row.recheckCommands.length >= 1), 'every row should expose recheck commands');
- assert(summary.rows.every(row => row.primaryFillFile && row.primaryRecheckCommand), 'every row should expose primary fill/recheck fields');
- assert(summary.rows.every(row => row.evidence && row.nextAction), 'every row should expose evidence and next action');
- assert(summary.rows.every(row => /不.*证明|不得宣称|不等同于/.test(row.boundary || '')), 'every row should keep proof boundary');
- assert(summary.ownerGroups.length >= 2, 'operator pack should expose owner groups');
- assert(summary.guardrails.some(item => item.includes('proofOpenCount')), 'guardrails should mention proofOpenCount');
- assert(report.includes('# 提号补证操作包'), 'report should be Chinese');
- assert(report.includes('缺口 ID'), 'report should include gap id summary');
- assert(report.includes('复验命令数'), 'report should include recheck command count');
- assert(report.includes('## 负责人文件'), 'report should include owner artifact section');
- assert(report.includes('by-owner/business.md'), 'report should include business owner markdown');
- if (hasBusinessMediaOwnerArtifact) {
- assert(report.includes('by-owner/business-media.csv'), 'report should include business/media owner csv when generated');
- }
- assert(report.includes('by-owner/tech-ai.md'), 'report should include tech/AI owner markdown');
- assert(report.includes('当前证据行数'), 'report should include evidence row count');
- assert(report.includes('下一步动作数'), 'report should include next action count');
- assert(report.includes('当前证据'), 'report should include current evidence column');
- assert(report.includes('下一步'), 'report should include next action column');
- assert(report.includes('history-data-template.csv'), 'report should include history template path');
- assert(report.includes('video-resource-template.csv'), 'report should include video template path');
- assert(report.includes('customer-effect:audit'), 'report should include customer effect audit command');
- assert(csv.includes('缺口ID'), 'csv should include Chinese header');
- assert(csv.includes('主填写文件'), 'csv should include primary fill file header');
- assert(csv.includes('首个复验命令'), 'csv should include primary recheck command header');
- assert(csv.includes('当前证据'), 'csv should include current evidence header');
- assert(csv.includes('下一步'), 'csv should include next action header');
- assert(businessOwnerReport.includes('# 商务提号补证操作包'), 'business owner report should have owner title');
- assert(businessOwnerReport.includes('missing historical-dataset-audit.json'), 'business owner report should include current evidence');
- assert(businessOwnerReport.includes('补 5-10 个真实 Brief'), 'business owner report should include next action');
- assert(businessOwnerCsv.includes('当前证据'), 'business owner csv should include current evidence header');
- assert(businessOwnerCsv.includes('下一步'), 'business owner csv should include next action header');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'operator pack report should preserve video A/B preflight evidence context');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'operator pack report should preserve video A/B preflight missing requirements');
- assertTextIncludesAll(csv, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'operator pack csv should preserve video A/B preflight evidence context');
- assertTextIncludesAll(techOwnerReport, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'tech/AI owner report should preserve video A/B preflight evidence context');
- assertTextIncludesAll(techOwnerReport, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'tech/AI owner report should preserve video A/B preflight missing requirements');
- assertTextIncludesAll(techOwnerCsv, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'tech/AI owner csv should preserve video A/B preflight evidence context');
- 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,
- rowCount: summary.rowCount,
- openCount: summary.openCount
- }, 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.directCustomerProof !== true, `${label} should not be direct customer proof`);
- assert(Array.isArray(row.recheckCommands) && row.recheckCommands.includes('npm run 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`);
- }
- 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();
|