| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const { buildRealProofClosureWorkOrder } = require('./real-proof-closure-work-order');
- 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 summary = buildRealProofClosureWorkOrder({
- root: ROOT,
- outputsDir: path.join(ROOT, 'outputs')
- });
- assert(summary.passed === true, 'work order should pass against current outputs');
- assert(summary.complete === false, 'work order should keep incomplete state while proof gaps remain');
- assert(summary.proofLevel === 'not_business_proof', 'work order should be not business proof');
- assert(summary.directCustomerProof === false, 'work order must not be direct customer proof');
- assert(summary.canCloseProofGap === false, 'work order must not close proof gaps');
- assert(summary.requiresHumanRealMaterial === true, 'work order should require human real material');
- assert(summary.sourceState.proofGapOpenCount >= 1, 'work order should expose open proof gaps');
- assert(summary.sourceState.intakeFailureCount >= 1, 'work order should expose intake failures');
- if (summary.sourceState.videoReadyForAbPreflight) {
- assert(summary.sourceState.realCandidateRows >= 1, 'ready video state should expose real candidate video rows');
- } else {
- assert(summary.sourceState.realCandidateRows === 0, 'blocked video state should expose missing real candidate video');
- }
- assert(summary.workOrderCount >= 5, 'work order should contain actionable rows');
- assert(Array.isArray(summary.ownerGroups) && summary.ownerGroups.length >= 1, 'work order should expose current owner groups');
- assert(summary.ownerGroups.some(item => item.owner === '商务' && item.workOrderCount >= 1), 'business owner group should exist');
- assert(summary.ownerGroups.some(item => item.owner === '技术/AI' && item.workOrderCount >= 1), 'tech/AI owner group should exist');
- if (summary.ownerGroups.some(item => item.owner === '商务/投放')) {
- assert(summary.ownerGroups.some(item => item.owner === '商务/投放' && item.workOrderCount >= 1), 'business/media owner group should exist when current gaps require it');
- }
- assert(summary.workOrders.every(item => item.requiresHumanRealMaterial === true), 'all rows should require real material');
- assert(summary.workOrders.every(item => item.directCustomerProof === false), 'all rows should keep proof boundary');
- assert(summary.workOrders.every(item => item.canCloseProofGap === false), 'all rows should not close proof gap');
- assert(summary.workOrders.every(item => item.targetFile && item.targetFields.length && item.requiredAction && item.acceptanceCommand), 'all rows should be actionable');
- assert(summary.workOrders.some(item => item.gapId === 'historical-dataset' && item.targetFile.includes('history-data-template.csv')), 'history gap should target history template');
- if (summary.sourceState.videoReadyForAbPreflight) {
- assert(!summary.workOrders.some(item => item.gapId === 'video-real-candidate'), 'closed video candidate gap should not produce stale work order');
- } else {
- assert(summary.workOrders.some(item => item.gapId === 'video-real-candidate' && item.targetFile.includes('video-resource-template.csv')), 'video candidate gap should target video template');
- }
- assert(summary.workOrders.some(item => item.gapId === 'manual-review-and-customer-effect' && item.targetFile.includes('manual-review-template.csv')), 'customer effect gap should target review template');
- const videoAbWorkOrder = summary.workOrders.find(item => item.gapId === 'video-ab-live-proof');
- assert(videoAbWorkOrder, 'work order should include video A/B live proof gap');
- assertVideoAbPreflightWorkOrder(videoAbWorkOrder, 'video A/B live proof work order');
- assert(summary.workOrders.some(item => item.seedSuggestion && item.seedSuggestion.includes('DHA')), 'work order should carry seed hints without treating them as proof');
- assert(summary.guardrails.some(item => item.includes('不写入正式 intake 模板')), 'guardrails should prevent template mutation');
- assert(summary.guardrails.some(item => item.includes('不得宣称提号率提升')), 'guardrails should prevent false business claims');
- const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-real-proof-work-order-'));
- const result = spawnSync(process.execPath, [
- 'scripts/real-proof-closure-work-order.js',
- '--outputs',
- 'outputs',
- '--output',
- temp,
- '--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, 'CLI should pass');
- const cliSummary = JSON.parse(fs.readFileSync(path.join(temp, 'real-proof-closure-work-order-summary.json'), 'utf8'));
- const report = fs.readFileSync(path.join(temp, 'real-proof-closure-work-order.md'), 'utf8').replace(/^\uFEFF/, '');
- const csv = fs.readFileSync(path.join(temp, 'real-proof-closure-work-order.csv'), 'utf8').replace(/^\uFEFF/, '');
- const cliVideoAbWorkOrder = cliSummary.workOrders.find(item => item.gapId === 'video-ab-live-proof');
- assert(cliSummary.passed === true, 'CLI summary should pass');
- assert(cliSummary.canCloseProofGap === false, 'CLI summary should not close proof gap');
- assert(cliVideoAbWorkOrder, 'CLI summary should include video A/B live proof work order');
- assertVideoAbPreflightWorkOrder(cliVideoAbWorkOrder, 'CLI video A/B live proof work order');
- assert(cliSummary.ownerArtifacts.length === summary.ownerGroups.length, 'CLI should write current owner artifacts');
- for (const item of cliSummary.ownerArtifacts) {
- const slug = ownerSlug(item.owner);
- assert(fs.existsSync(path.join(temp, 'by-owner', `${slug}.md`)), `${item.owner} owner md should exist`);
- assert(fs.existsSync(path.join(temp, 'by-owner', `${slug}.csv`)), `${item.owner} owner csv should exist`);
- }
- const techOwnerReport = fs.readFileSync(path.join(temp, 'by-owner', 'tech-ai.md'), 'utf8').replace(/^\uFEFF/, '');
- assert(report.includes('# 真实证明闭环工单'), 'report should render title');
- assert(report.includes('proofGapOpenCount'), 'report should expose open proof gap count');
- assert(report.includes('history-data-template.csv'), 'report should include history template');
- assert(report.includes('video-resource-template.csv'), 'report should include video template');
- assert(report.includes('manual-review-template.csv'), 'report should include review template');
- assert(report.includes('不证明客户效果'), 'report should keep proof boundary');
- assert(csv.includes('需要人工真实材料'), 'csv should expose real-material flag');
- assert(csv.includes('可关闭proof-gap'), 'csv should expose proof-gap boundary');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'work order report should preserve video A/B preflight evidence context');
- assertTextIncludesAll(report, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'work order report should preserve video A/B preflight missing requirements');
- assertTextIncludesAll(csv, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'work order csv should preserve video A/B preflight evidence context');
- assertTextIncludesAll(csv, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'work order csv should preserve video A/B preflight missing requirements');
- assertTextIncludesAll(techOwnerReport, VIDEO_AB_PREFLIGHT_EVIDENCE_TOKENS, 'tech/AI work order report should preserve video A/B preflight evidence context');
- assertTextIncludesAll(techOwnerReport, VIDEO_AB_PREFLIGHT_REQUIREMENT_TOKENS, 'tech/AI work order report 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,
- workOrderCount: cliSummary.workOrderCount,
- ownerArtifactCount: cliSummary.ownerArtifacts.length,
- proofGapOpenCount: cliSummary.sourceState.proofGapOpenCount
- }, null, 2));
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- function assertVideoAbPreflightWorkOrder(row, label) {
- assert(row.source === 'proof-gap-closure', `${label} should come from proof-gap closure`);
- assert(row.requiresHumanRealMaterial === true, `${label} should require real human material`);
- assert(row.directCustomerProof === false, `${label} should not be direct customer proof`);
- assert(row.canCloseProofGap === false, `${label} should not close proof gap`);
- assert(row.acceptanceCommand === 'npm run acceptance:video-ab', `${label} should keep live video A/B acceptance command`);
- assertTextIncludesAll(row.currentEvidence || '', 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(', ')}`);
- }
- function ownerSlug(owner) {
- if (owner === '商务') return 'business';
- if (owner === '商务/投放') return 'business-media';
- if (owner === '技术/AI') return 'tech-ai';
- return String(owner || '').replace(/[^\w]+/g, '-').replace(/^-|-$/g, '').toLowerCase();
- }
- if (require.main === module) main();
|