import { createHash } from 'node:crypto' import { readFile } from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const rootDir = path.resolve(scriptDir, '..') const defaultApprovalPath = path.join( rootDir, 'docs', 'content-candidates', 'aug1-release-approval.json', ) let strict = false let scenarioSnapshots = false const positional = [] for (const argument of process.argv.slice(2)) { if (argument === '--strict') { strict = true } else if (argument === '--scenario-snapshots') { strict = true scenarioSnapshots = true } else if (argument.startsWith('-')) { console.error(`Unknown option: ${argument}`) process.exit(2) } else { positional.push(argument) } } if (positional.length > 1) { console.error('Usage: node verify-aug1-formal-content.mjs [--strict] [--scenario-snapshots] [approval-json]') process.exit(2) } const approvalPath = positional[0] ? path.resolve(positional[0]) : defaultApprovalPath const approval = JSON.parse(await readFile(approvalPath, 'utf8')) const failures = [] const check = (condition, message) => { if (!condition) failures.push(message) } const nonBlank = (value) => typeof value === 'string' && value.trim().length > 0 const sha256Pattern = /^[0-9a-f]{64}$/ const reviewedAtPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$/ const sourceIdPattern = /^[A-Z0-9][A-Z0-9._-]{1,79}$/ const scenarioCodePattern = /^[a-z0-9][a-z0-9-]{1,79}$/ const placeholderPattern = /(?:待补|待定|待填写|待签|TBD|TODO)/i check(approval.schemaVersion === '1.0', 'approval schemaVersion must be 1.0') check(approval.releaseTarget === '2026-08-01', 'release target must be 2026-08-01') check(/^[0-9A-Za-z_-]{1,20}$/.test(approval.tenantId ?? ''), 'tenantId is invalid') const candidateFileValid = nonBlank(approval.candidateFile) && path.basename(approval.candidateFile) === approval.candidateFile && approval.candidateFile.endsWith('.json') check(candidateFileValid, 'candidateFile must be a JSON basename beside the approval file') check(sha256Pattern.test(approval.candidateSha256 ?? ''), 'candidateSha256 must be lowercase SHA-256') check( sha256Pattern.test(approval.expectedApkSignerSha256 ?? ''), 'expectedApkSignerSha256 must be lowercase SHA-256', ) if (!candidateFileValid) { for (const failure of failures) console.error(`FAIL: ${failure}`) process.exit(1) } const candidatePath = path.resolve(path.dirname(approvalPath), approval.candidateFile ?? '') let candidateBytes try { candidateBytes = await readFile(candidatePath) } catch { console.error('FAIL: candidate file cannot be read beside the approval manifest') process.exit(1) } const candidateHash = createHash('sha256').update(candidateBytes).digest('hex') check(candidateHash === approval.candidateSha256, 'candidate file SHA-256 does not match approval manifest') const candidate = JSON.parse(candidateBytes.toString('utf8')) check(candidate.candidateStatus === 'PENDING_BUSINESS_REVIEW', 'candidate source must remain pending review') check(candidate.publishable === false, 'candidate source must remain non-publishable') check(Array.isArray(candidate.scenarios) && candidate.scenarios.length === 5, 'candidate source must contain five scenarios') check( Array.isArray(candidate.policyQuestionCandidates) && candidate.policyQuestionCandidates.length === 30, 'candidate source must contain thirty policy questions', ) const expectedScenarios = new Map() for (const item of candidate.scenarios ?? []) { check(nonBlank(item.candidateId), 'candidate scenario ID is required') check(nonBlank(item.scenarioDraft?.id), `${item.candidateId ?? 'unknown scenario'}: scenario code is required`) check(!expectedScenarios.has(item.candidateId), `${item.candidateId}: duplicate candidate scenario ID`) expectedScenarios.set(item.candidateId, item) } const expectedQuestions = new Map() for (const item of candidate.policyQuestionCandidates ?? []) { check(nonBlank(item.id), 'candidate question ID is required') check(!expectedQuestions.has(item.id), `${item.id}: duplicate candidate question ID`) expectedQuestions.set(item.id, item) } const sourceRegistry = Array.isArray(approval.sourceRegistry) ? approval.sourceRegistry : [] check(Array.isArray(approval.sourceRegistry), 'sourceRegistry must be an array') const sourceIds = new Set() for (const source of sourceRegistry) { const label = source.sourceId ?? 'unknown source' check(sourceIdPattern.test(source.sourceId ?? ''), `${label}: sourceId is invalid`) check(!sourceIds.has(source.sourceId), `${label}: duplicate sourceId`) sourceIds.add(source.sourceId) check(nonBlank(source.title) && !placeholderPattern.test(source.title), `${label}: formal source title is required`) check(nonBlank(source.version) && !placeholderPattern.test(source.version), `${label}: formal source version is required`) check(/^\d{4}-\d{2}-\d{2}$/.test(source.effectiveDate ?? ''), `${label}: effectiveDate must be YYYY-MM-DD`) check(nonBlank(source.scope) && !placeholderPattern.test(source.scope), `${label}: source scope is required`) check(sha256Pattern.test(source.sha256 ?? ''), `${label}: source SHA-256 is required`) check(source.formalPolicy === true, `${label}: source must be marked formalPolicy=true`) } const scenarioApprovals = approval.scenarioApprovals const questionApprovals = approval.policyQuestionApprovals const channelApprovals = approval.channelApprovals check( scenarioApprovals && typeof scenarioApprovals === 'object' && !Array.isArray(scenarioApprovals), 'scenarioApprovals must be an object keyed by candidate ID', ) check( questionApprovals && typeof questionApprovals === 'object' && !Array.isArray(questionApprovals), 'policyQuestionApprovals must be an object keyed by question ID', ) check( channelApprovals && typeof channelApprovals === 'object' && !Array.isArray(channelApprovals), 'channelApprovals must be an object keyed by direct-channel role', ) const scenarioEntries = Object.entries(scenarioApprovals ?? {}) const questionEntries = Object.entries(questionApprovals ?? {}) const channelEntries = Object.entries(channelApprovals ?? {}) for (const [candidateId] of scenarioEntries) { check(expectedScenarios.has(candidateId), `${candidateId}: approval references an unknown scenario`) } for (const [questionId] of questionEntries) { check(expectedQuestions.has(questionId), `${questionId}: approval references an unknown question`) } const requiredChannels = [ 'direct_president', 'direct_finance', 'direct_hr', 'direct_audit', 'direct_operations', ] for (const [roleKey] of channelEntries) { check(requiredChannels.includes(roleKey), `${roleKey}: approval references an unknown direct channel`) } const resolveSourceRefs = (refs, label) => { check(Array.isArray(refs) && refs.length > 0, `${label}: at least one formal source reference is required`) for (const reference of refs ?? []) { check(nonBlank(reference), `${label}: source reference must be non-empty`) const sourceId = typeof reference === 'string' ? reference.split(':', 1)[0] : '' check(sourceIds.has(sourceId), `${label}: source reference ${reference} is not in sourceRegistry`) } } const requireReview = (entry, label, prefix = '') => { const reviewedBy = prefix ? entry[`${prefix}ReviewedBy`] : entry.reviewedBy const reviewedAt = prefix ? entry[`${prefix}ReviewedAt`] : entry.reviewedAt check(nonBlank(reviewedBy) && !placeholderPattern.test(reviewedBy), `${label}: ${prefix || 'first'} reviewer is required`) check(reviewedAtPattern.test(reviewedAt ?? ''), `${label}: ${prefix || 'first'} review time must include timezone`) } if (strict) { check(approval.releaseStatus === 'APPROVED', 'releaseStatus must be APPROVED') check( nonBlank(approval.formalContentVersion) && !placeholderPattern.test(approval.formalContentVersion), 'formalContentVersion is required', ) check(sourceRegistry.length > 0, 'at least one formal source is required') check(scenarioEntries.length === expectedScenarios.size, 'scenario approvals must cover exactly 5/5 candidates') check(questionEntries.length === expectedQuestions.size, 'question approvals must cover exactly 30/30 candidates') for (const [candidateId, candidateItem] of expectedScenarios) { const entry = scenarioApprovals?.[candidateId] const label = candidateId check(Boolean(entry), `${label}: scenario approval is missing`) if (!entry) continue check(entry.status === 'APPROVED', `${label}: scenario status must be APPROVED`) check(entry.scenarioCode === candidateItem.scenarioDraft.id, `${label}: production scenario code does not match candidate`) check(scenarioCodePattern.test(entry.scenarioCode ?? ''), `${label}: production scenario code is invalid`) check( ['常规', '高风险'].includes(entry.riskLevel) && entry.riskLevel === candidateItem.proposedRiskLevel, `${label}: risk level must match the reviewed candidate proposal`, ) resolveSourceRefs(entry.sourceRefs, label) requireReview(entry, label) if (entry.riskLevel === '高风险') { requireReview(entry, label, 'second') check(entry.secondReviewedBy !== entry.reviewedBy, `${label}: high-risk reviewers must be different people`) } check(nonBlank(entry.businessEvidenceRef), `${label}: business evidence reference is required`) check(sha256Pattern.test(entry.businessEvidenceSha256 ?? ''), `${label}: business evidence SHA-256 is required`) check(nonBlank(entry.productionContentVersion), `${label}: production content version is required`) check(sha256Pattern.test(entry.productionContentHash ?? ''), `${label}: production content hash is required`) } const dispositions = new Set([ 'FORMALLY_SOURCED', 'FORMAL_NO_EVIDENCE_BOUNDARY', 'FORMAL_UNAUTHORIZED_BOUNDARY', ]) for (const [questionId, candidateItem] of expectedQuestions) { const entry = questionApprovals?.[questionId] const label = questionId check(Boolean(entry), `${label}: question approval is missing`) if (!entry) continue check(entry.status === 'APPROVED', `${label}: question status must be APPROVED`) check(dispositions.has(entry.answerDisposition), `${label}: answerDisposition is invalid`) if (candidateItem.category === 'NO_EVIDENCE') { check( entry.answerDisposition === 'FORMAL_NO_EVIDENCE_BOUNDARY', `${label}: no-evidence question must preserve the formal refusal boundary`, ) } else if (candidateItem.category === 'UNAUTHORIZED') { check( entry.answerDisposition === 'FORMAL_UNAUTHORIZED_BOUNDARY', `${label}: unauthorized question must preserve the permission boundary`, ) } else { check(entry.answerDisposition === 'FORMALLY_SOURCED', `${label}: answer must be formally sourced`) } resolveSourceRefs(entry.sourceRefs, label) check(nonBlank(entry.expectedBehavior), `${label}: expected behavior is required`) check(nonBlank(entry.observedBehavior), `${label}: observed behavior is required`) check(nonBlank(entry.evidenceRef), `${label}: acceptance evidence reference is required`) check(sha256Pattern.test(entry.evidenceSha256 ?? ''), `${label}: acceptance evidence SHA-256 is required`) requireReview(entry, label) } check(channelEntries.length === requiredChannels.length, 'channel approvals must cover exactly 5/5 roles') for (const roleKey of requiredChannels) { const entry = channelApprovals?.[roleKey] const label = roleKey check(Boolean(entry), `${label}: channel approval is missing`) if (!entry) continue check(entry.status === 'APPROVED', `${label}: channel status must be APPROVED`) check(entry.minimumActiveHandlers === 2, `${label}: primary and backup requirement must remain 2`) for (const evidenceName of [ 'bindingEvidence', 'positiveAccessEvidence', 'crossChannelDenialEvidence', 'singleReplyEvidence', ]) { check(nonBlank(entry[`${evidenceName}Ref`]), `${label}: ${evidenceName} reference is required`) check( sha256Pattern.test(entry[`${evidenceName}Sha256`] ?? ''), `${label}: ${evidenceName} SHA-256 is required`, ) } requireReview(entry, label) } const requiredSignoffs = [ 'businessOwner', 'knowledgeOwner', 'trainingOwner', 'channelOwner', 'releaseOwner', ] check( approval.signoffs && typeof approval.signoffs === 'object' && !Array.isArray(approval.signoffs), 'signoffs must be an object', ) for (const role of requiredSignoffs) { const signoff = approval.signoffs?.[role] check(signoff?.status === 'APPROVED', `${role}: sign-off must be APPROVED`) if (signoff) requireReview(signoff, role) } const distribution = approval.distributionApproval check( distribution && typeof distribution === 'object' && !Array.isArray(distribution), 'distributionApproval must be an object', ) if (distribution) { check(distribution.status === 'APPROVED', 'distribution approval must be APPROVED') check(distribution.scope === 'CONTROLLED_INTERNAL_TEST', 'distribution scope must remain controlled internal test') check( ['LEGAL_APPROVED', 'CONTROLLED_INTERNAL_TEST_EXCEPTION_ACCEPTED'].includes(distribution.legalDecision), 'distribution legalDecision is not approved', ) check( [ 'ENTERPRISE_SIGNER_APPROVED', 'DCLOUD_TEST_SIGNER_ACCEPTED_FOR_CONTROLLED_INTERNAL_TEST', ].includes(distribution.signerDecision), 'distribution signerDecision is not approved', ) check( distribution.signerSha256 === approval.expectedApkSignerSha256, 'distribution signer SHA-256 must match the expected APK signer', ) check(nonBlank(distribution.distributionEvidenceRef), 'controlled-distribution evidence reference is required') check( sha256Pattern.test(distribution.distributionEvidenceSha256 ?? ''), 'controlled-distribution evidence SHA-256 is required', ) requireReview(distribution, 'distributionApproval') } } if (failures.length > 0) { for (const failure of failures) console.error(`FAIL: ${failure}`) process.exit(1) } if (scenarioSnapshots) { for (const [candidateId, candidateItem] of expectedScenarios) { const entry = scenarioApprovals[candidateId] console.log([ candidateItem.scenarioDraft.id, entry.productionContentVersion, entry.productionContentHash, ].join('|')) } } else { const approvedScenarios = scenarioEntries.filter(([, value]) => value?.status === 'APPROVED').length const approvedQuestions = questionEntries.filter(([, value]) => value?.status === 'APPROVED').length const approvedChannels = channelEntries.filter(([, value]) => value?.status === 'APPROVED').length const approvedSignoffs = Object.values(approval.signoffs ?? {}).filter((value) => value?.status === 'APPROVED').length console.log('August 1 formal content approval audit passed') console.log(`- releaseStatus: ${approval.releaseStatus}`) console.log(`- formal sources: ${sourceRegistry.length}`) console.log(`- approved scenarios: ${approvedScenarios}/5`) console.log(`- approved policy questions: ${approvedQuestions}/30`) console.log(`- approved direct channels: ${approvedChannels}/5`) console.log(`- approved owner sign-offs: ${approvedSignoffs}/5`) console.log(`- controlled distribution: ${approval.distributionApproval?.status ?? 'MISSING'}`) console.log(`- strict release ready: ${strict ? 'true' : 'not requested'}`) }