import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import crypto from 'node:crypto'; import { gateChecks, parseArgs, productionUrlFailure, requiredAttestations, validateEvidence, weappAttestations, weappGateChecks, } from './production-launch-gate.js'; const repoRoot = process.cwd(); const scriptPath = path.join(repoRoot, 'scripts', 'production-launch-gate.js'); function isoNow() { return new Date().toISOString(); } function sampleSummaryValue(expectedValue) { if (!expectedValue || typeof expectedValue !== 'object' || Array.isArray(expectedValue)) return expectedValue; if (Object.prototype.hasOwnProperty.call(expectedValue, 'eq')) return expectedValue.eq; if (Object.prototype.hasOwnProperty.call(expectedValue, 'oneOf')) return expectedValue.oneOf?.[0]; if (Object.prototype.hasOwnProperty.call(expectedValue, 'truthy')) return expectedValue.truthy ? 'sample-truthy-value' : ''; if (Object.prototype.hasOwnProperty.call(expectedValue, 'lte')) return expectedValue.lte; if (Object.prototype.hasOwnProperty.call(expectedValue, 'lt')) return Number(expectedValue.lt) - 1; if (Object.prototype.hasOwnProperty.call(expectedValue, 'gte')) return expectedValue.gte; if (Object.prototype.hasOwnProperty.call(expectedValue, 'gt')) return Number(expectedValue.gt) + 1; return expectedValue; } function sampleSummary(summarySpec) { const result = {}; for (const [key, value] of Object.entries(summarySpec || {})) { const parts = key.split('.'); let cursor = result; for (const part of parts.slice(0, -1)) { if (!cursor[part] || typeof cursor[part] !== 'object') cursor[part] = {}; cursor = cursor[part]; } cursor[parts.at(-1)] = sampleSummaryValue(value); } return result; } function nestedSummaryPayload(summary) { return { summary }; } function pocketBaseDryRunPayload(summary) { return { migrationProfile: summary.migrationProfile, summary: { blockers: summary.blocker, warnings: summary.warning, }, migrationReadiness: { requiredCollections: Array.from({ length: 3 }, (_, index) => ({ collection: `required-${index}`, present: true })), criticalFieldCoverage: Array.from({ length: 3 }, (_, index) => ({ collection: `coverage-${index}`, present: true })), }, }; } function productionEvidencePayload(spec, summary) { if (spec.id === 'db.migration-history') return summary; if (spec.id === 'auth.platform-admin-bootstrap') { const { identityMatches: _identityMatches, ...artifact } = summary; return artifact; } if (spec.id === 'backup.restore-drill') return summary; if (spec.id === 'taro.supply-chain') { const reviewedEdges = [ { parent: '@tarojs/components', dependency: 'swiper', declared: '11.1.15' }, { parent: '@tarojs/components-react', dependency: 'swiper', declared: '11.1.15' }, { parent: '@tarojs/plugin-platform-h5', dependency: 'lodash-es', declared: '4.17.21' }, { parent: '@tarojs/taro-h5', dependency: 'lodash-es', declared: '4.17.21' }, ]; return { schemaVersion: summary.schemaVersion, status: summary.status, securedBundleDependencies: summary.securedBundleDependencies, npmLs: { edges: reviewedEdges, }, audit: { counts: summary.audit.counts }, riskBoundary: { appliesTo: 'Taro 4.2.0 CLI and build toolchain', controls: Array.from({ length: summary.riskControlCount }, (_, index) => `control-${index}`), }, }; } return null; } function jsonArtifactPayload(spec, summary) { const productionPayload = productionEvidencePayload(spec, summary); if (productionPayload) return productionPayload; if (spec.id === 'readiness.production.env' || spec.id === 'readiness.production.db') return nestedSummaryPayload(summary); if (spec.id === 'postgres.tuning-evidence' || spec.id === 'auth.sms-pnvs-diagnostics' || spec.id === 'auth.sms-pnvs-remote-smoke') return summary; if (spec.id === 'migration.pb-production-dry-run') return pocketBaseDryRunPayload(summary); if (spec.id === 'api.launch-persona-smoke') return summary; if (spec.id === 'api.dynamic-tenant-cors-smoke' || spec.id === 'db.tenant-foreign-key-audit') return summary; if (spec.id === 'taro.h5-static-smoke' || spec.id === 'taro.h5-release-guardrails' || spec.id === 'security.repo-scan') return nestedSummaryPayload(summary); if (spec.id === 'taro.h5-interaction-smoke') return { summary: { fail: summary.fail, pass: summary.pass }, mockApi: summary.mockApi }; if (spec.id === 'taro.h5-release-manifest') return { ...nestedSummaryPayload(summary), portals: [] }; if (spec.id === 'taro.weapp-release-guardrails') return nestedSummaryPayload(summary); return null; } function writeCheckArtifact(spec, summary, artifactPath) { const jsonPayload = jsonArtifactPayload(spec, summary); if (jsonPayload) { fs.writeFileSync(artifactPath, `${JSON.stringify(jsonPayload, null, 2)}\n`, 'utf8'); return; } fs.writeFileSync(artifactPath, `TIKU_LAUNCH_GATE_SUCCESS:${spec.id}\n`, 'utf8'); } function rewriteArtifact(tempDir, item, payload) { const artifactPath = path.join(tempDir, item.artifact); fs.writeFileSync(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); } function createEvidence(tempDir, overrides = {}) { const artifactDir = path.join(tempDir, 'launch-artifacts'); fs.mkdirSync(artifactDir, { recursive: true }); const checks = gateChecks.map(spec => { const artifact = `launch-artifacts/${spec.id}.log`; const artifactPath = path.join(tempDir, artifact); const summary = sampleSummary(spec.summary); if (spec.id === 'db.migration-history') { const migrationFiles = fs.readdirSync(path.join(repoRoot, 'supabase', 'migrations')) .filter(file => file.endsWith('.sql')) .sort(); const latestMigration = /^(\d+)_/.exec(migrationFiles.at(-1) || '')?.[1]; summary.latestRepositoryMigration = latestMigration; summary.latestAppliedMigration = latestMigration; } if (spec.id === 'auth.platform-admin-bootstrap') { const identityHash = crypto.createHash('sha256').update('platform-admin-auth-user').digest('hex'); summary.adminUserIdSha256 = identityHash; summary.authSmokeExpectedUserIdSha256 = identityHash; summary.dryRunArtifactSha256 = crypto.createHash('sha256').update('platform-admin-dry-run-artifact').digest('hex'); summary.applyArtifactSha256 = crypto.createHash('sha256').update('platform-admin-apply-artifact').digest('hex'); summary.authSmokeArtifactSha256 = crypto.createHash('sha256').update('platform-admin-auth-smoke-artifact').digest('hex'); summary.auditArtifactSha256 = crypto.createHash('sha256').update('platform-admin-audit-artifact').digest('hex'); } if (spec.id === 'backup.restore-drill') { summary.verificationArtifactSha256 = crypto.createHash('sha256').update('backup-restore-verification-artifact').digest('hex'); } if (spec.id === 'taro.h5-release-manifest') { const releaseRoot = path.join(tempDir, 'candidate'); const portalDirs = [ ['student', 'apps/taro/dist/h5-student'], ['tenant-admin', 'apps/taro/dist/h5-tenant-admin'], ['platform-admin', 'apps/taro/dist/h5-platform-admin'], ]; const portals = portalDirs.map(([portal, relativeDir]) => { const dir = path.join(releaseRoot, relativeDir); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, 'index.html'), `
${portal}
\n`, 'utf8'); const content = fs.readFileSync(path.join(dir, 'index.html')); const treeHash = crypto.createHash('sha256') .update('index.html\0') .update(String(content.length)) .update('\0') .update(content) .update('\0') .digest('hex'); return { portal, dist: { treeSha256: treeHash } }; }); fs.writeFileSync(artifactPath, `${JSON.stringify({ summary, portals }, null, 2)}\n`, 'utf8'); } else if (spec.id === 'performance.tenant-students-100k') { const caseItem = (id, p95, indexes) => ({ id, latencyMs: { p95 }, explain: { summary: { indexes } }, }); fs.writeFileSync(artifactPath, `${JSON.stringify({ schemaVersion: 1, kind: 'tenant-student-capacity', safety: { databaseEnvironment: summary.databaseEnvironment }, fixture: summary.fixture, config: { deepCursorApproximateOffset: summary.deepCursorApproximateOffset }, cases: [ caseItem('first-page', summary.firstPageP95Ms, ['idx_memberships_student_keyset_page']), caseItem('deep-cursor', summary.deepCursorP95Ms, ['idx_memberships_student_keyset_page']), caseItem('name-substring', summary.searchP95MaxMs, ['idx_platform_users_identity_search_trgm']), caseItem('phone-substring', summary.searchP95MaxMs, ['idx_platform_users_identity_search_trgm']), caseItem('email-substring', summary.searchP95MaxMs, ['idx_platform_users_identity_search_trgm']), ], cleanup: { cleanupVerified: summary.cleanupVerified, remaining: { tenants: 0, platformUsers: 0, memberships: 0, profiles: 0 }, }, }, null, 2)}\n`, 'utf8'); } else { writeCheckArtifact(spec, summary, artifactPath); } return { id: spec.id, status: 'pass', command: `npm run ${spec.commandIncludes} -- recorded-for-launch-gate`, completedAt: isoNow(), artifact, artifactSha256: crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'), summary, }; }); const readinessDbCheck = checks.find(item => item.id === 'readiness.production.db'); const migrationHistoryCheck = checks.find(item => item.id === 'db.migration-history'); const readinessDbArtifactPath = path.join(tempDir, readinessDbCheck.artifact); migrationHistoryCheck.summary.readinessArtifactSha256 = crypto.createHash('sha256') .update(fs.readFileSync(readinessDbArtifactPath)) .digest('hex'); const migrationHistoryArtifactPath = path.join(tempDir, migrationHistoryCheck.artifact); writeCheckArtifact( gateChecks.find(spec => spec.id === migrationHistoryCheck.id), migrationHistoryCheck.summary, migrationHistoryArtifactPath, ); migrationHistoryCheck.artifactSha256 = crypto.createHash('sha256') .update(fs.readFileSync(migrationHistoryArtifactPath)) .digest('hex'); const attestations = requiredAttestations.map(spec => ({ id: spec.id, status: 'approved', approver: 'test-owner', approvedAt: isoNow(), notes: spec.label, })); return { schemaVersion: 1, environment: 'production', releaseTargets: ['h5'], commit: '52cef9fabcd1234567890abcdef1234567890abc', target: { apiBaseUrl: 'https://api.gongxue100.com', studentH5Url: 'https://www.gongxue100.com', tenantAdminH5Url: 'https://admin.gongxue100.com', platformAdminH5Url: 'https://console.gongxue100.com', }, checks, attestations, ...overrides, }; } function runGate(evidence, options = {}) { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-launch-gate-')); const evidencePath = path.join(tempDir, 'evidence.json'); const finalEvidence = typeof evidence === 'function' ? evidence(tempDir) : evidence; const deployReleaseRoot = options.deployReleaseRoot === '__TEMP_CANDIDATE__' ? path.join(tempDir, 'candidate') : (options.deployReleaseRoot || ''); fs.writeFileSync(evidencePath, JSON.stringify(finalEvidence, null, 2), 'utf8'); const result = spawnSync(process.execPath, [scriptPath, '--evidence', evidencePath, '--json', ...(options.args || [])], { cwd: repoRoot, encoding: 'utf8', env: { PATH: process.env.PATH || '', Path: process.env.Path || '', SystemRoot: process.env.SystemRoot || '', ComSpec: process.env.ComSpec || '', TEMP: process.env.TEMP || os.tmpdir(), TMP: process.env.TMP || os.tmpdir(), DEPLOY_COMMIT_SHA: options.deployCommitSha || '', DEPLOY_RELEASE_ROOT: deployReleaseRoot, }, }); const payload = JSON.parse(result.stdout || '{}'); fs.rmSync(tempDir, { recursive: true, force: true }); return { ...result, payload }; } function sha256(value) { return crypto.createHash('sha256').update(value).digest('hex'); } function liveH5Fixture(tempDir) { const portalContents = { student: { targetKey: 'studentH5Url', baseUrl: 'https://student.gongxue100.com', appPath: '/js/app.student.js', app: 'console.log("student");\n', }, 'tenant-admin': { targetKey: 'tenantAdminH5Url', baseUrl: 'https://admin.gongxue100.com', appPath: '/js/app.tenant.js', app: 'console.log("tenant-admin");\n', }, 'platform-admin': { targetKey: 'platformAdminH5Url', baseUrl: 'https://console.gongxue100.com', appPath: '/js/app.platform.js', app: 'console.log("platform-admin");\n', }, }; const evidence = createEvidence(tempDir); evidence.liveH5 = { portals: [] }; const responses = new Map(); for (const [portal, fixture] of Object.entries(portalContents)) { const index = `
`; const runtime = `${JSON.stringify({ portal, apiBaseUrl: evidence.target.apiBaseUrl })}\n`; evidence.target[fixture.targetKey] = fixture.baseUrl; evidence.liveH5.portals.push({ portal, indexSha256: sha256(index), appPath: fixture.appPath, appSha256: sha256(fixture.app), }); responses.set(`${fixture.baseUrl}/index.html`, { body: index, contentType: 'text/html; charset=utf-8' }); responses.set(`${fixture.baseUrl}/runtime-config.json`, { body: runtime, contentType: 'application/json' }); responses.set(`${fixture.baseUrl}${fixture.appPath}`, { body: fixture.app, contentType: 'application/javascript' }); } return { evidence, responses }; } function liveH5ManifestFixture(tempDir) { const fixture = liveH5Fixture(tempDir); const candidateRoot = path.join(tempDir, 'candidate'); const manifest = { schemaVersion: 1, portals: [], }; for (const portalItem of fixture.evidence.liveH5.portals) { const portalDir = path.join( candidateRoot, 'apps', 'taro', 'dist', portalItem.portal === 'student' ? 'h5-student' : `h5-${portalItem.portal}`, ); const baseUrl = portalItem.portal === 'student' ? 'https://student.gongxue100.com' : portalItem.portal === 'tenant-admin' ? 'https://admin.gongxue100.com' : 'https://console.gongxue100.com'; const index = fixture.responses.get(`${baseUrl}/index.html`).body; const app = fixture.responses.get(`${baseUrl}${portalItem.appPath}`).body; const appFile = path.join(portalDir, `.${portalItem.appPath}`); fs.mkdirSync(path.dirname(appFile), { recursive: true }); fs.writeFileSync(path.join(portalDir, 'index.html'), index, 'utf8'); fs.writeFileSync(appFile, app, 'utf8'); manifest.portals.push({ portal: portalItem.portal, dist: { dir: '../../must-not-be-used', indexSha256: sha256(index), }, }); } const manifestPath = path.join(tempDir, 'launch-artifacts', 'taro-h5-release-manifest.json'); fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); fixture.evidence.liveH5 = { releaseManifestArtifact: path.relative(tempDir, manifestPath), releaseManifestSha256: sha256(fs.readFileSync(manifestPath)), }; return fixture; } function fixtureFetch(responses) { return async url => { const key = String(url); const item = responses.get(key); if (!item) return new Response('not found', { status: 404, headers: { 'content-type': 'text/plain' } }); return new Response(item.body, { status: 200, headers: { 'content-type': item.contentType }, }); }; } function removeTempDir(tempDir) { fs.rmSync(tempDir, { recursive: true, force: true }); } const safe = runGate(tempDir => createEvidence(tempDir)); assert.equal(safe.status, 0, `complete launch evidence should pass: ${safe.stdout} ${safe.stderr}`); assert.equal(safe.payload.summary?.blocker, 0, 'complete launch evidence should have no blockers'); const evidenceTemplate = JSON.parse(fs.readFileSync(path.join(repoRoot, 'docs', 'refactor', 'production-launch-evidence.template.json'), 'utf8')); const templateCheckIds = evidenceTemplate.checks.map(item => item.id); const coreGateCheckIds = gateChecks.map(item => item.id); assert.deepEqual(templateCheckIds, coreGateCheckIds, 'template core check IDs must exactly match gateChecks in order'); assert.equal(new Set(templateCheckIds).size, templateCheckIds.length, 'template core check IDs must be unique'); assert.ok(coreGateCheckIds.includes('worker.platform-billing'), 'platform billing worker must be a core launch check'); assert.ok(coreGateCheckIds.includes('worker.platform-dunning'), 'platform dunning worker must be a core launch check'); for (const id of ['db.migration-history', 'auth.platform-admin-bootstrap', 'backup.restore-drill', 'taro.supply-chain']) { assert.ok(coreGateCheckIds.includes(id), `${id} must be a core launch check`); } for (const item of evidenceTemplate.checks) { const spec = gateChecks.find(candidate => candidate.id === item.id); if (typeof spec?.artifactSummary === 'function') continue; assert.ok( String(item.command || '').includes(`TIKU_LAUNCH_GATE_SUCCESS:${item.id}`), `log-backed template check ${item.id} must append its explicit success sentinel`, ); } assert.equal( evidenceTemplate.checks.filter(item => item.artifact && !item.artifactSha256).length, 0, 'every template check with an artifact must include artifactSha256', ); assert.ok(evidenceTemplate.liveH5?.releaseManifestSha256, 'template must document the strict live H5 release manifest hash'); for (const rejectedUrl of [ 'http://api.gongxue100.com', 'https://localhost', 'https://127.0.0.1', 'https://portal.test', 'https://portal.example', 'https://portal.example.com', 'https://replace-with-domain.invalid', 'https://replace-with-real-domain.com', ]) { assert.ok(productionUrlFailure(rejectedUrl), `production URL validation must reject ${rejectedUrl}`); } assert.equal(productionUrlFailure('https://student.gongxue100.com'), '', 'real HTTPS production URL should be accepted'); assert.equal(parseArgs(['node', 'gate', '--verify-live-h5']).verifyLiveH5, true, 'CLI should explicitly enable strict live H5 validation'); assert.equal( parseArgs(['node', 'gate', '--verify-live-h5', '--no-verify-live-h5']).verifyLiveH5, false, 'CLI should allow deployment wrappers to explicitly disable inherited live validation', ); const placeholderTarget = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.target.studentH5Url = 'https://student.example.com'; return evidence; }); assert.notEqual(placeholderTarget.status, 0, 'placeholder target URL should fail launch gate'); assert.ok(placeholderTarget.payload.checks?.some(item => item.id === 'target.studentH5Url' && item.status === 'blocker')); { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-launch-gate-live-')); try { const fixture = liveH5Fixture(tempDir); const checks = await validateEvidence(fixture.evidence, { evidencePath: path.join(tempDir, 'evidence.json'), allowStale: false, maxAgeDays: 14, verifyLiveH5: true, liveTimeoutMs: 5_000, fetchImpl: fixtureFetch(fixture.responses), deployReleaseRoot: path.join(tempDir, 'candidate'), }); assert.equal(checks.some(item => item.status === 'blocker'), false, JSON.stringify(checks.filter(item => item.status === 'blocker'), null, 2)); assert.equal(checks.filter(item => /^live_h5\..+\.app_hash$/.test(item.id) && item.status === 'pass').length, 3); } finally { removeTempDir(tempDir); } } { const tempDir = fs.mkdtempSync(path.join(repoRoot, '.tmp-launch-gate-manifest-')); try { const fixture = liveH5ManifestFixture(tempDir); const checks = await validateEvidence(fixture.evidence, { evidencePath: path.join(tempDir, 'evidence.json'), allowStale: false, maxAgeDays: 14, verifyLiveH5: true, liveTimeoutMs: 5_000, fetchImpl: fixtureFetch(fixture.responses), deployReleaseRoot: path.join(tempDir, 'candidate'), }); assert.equal(checks.some(item => item.status === 'blocker'), false, JSON.stringify(checks.filter(item => item.status === 'blocker'), null, 2)); assert.equal(checks.filter(item => /^live_h5\..+\.candidate_files$/.test(item.id) && item.status === 'pass').length, 3); } finally { removeTempDir(tempDir); } } { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-launch-gate-live-')); try { const fixture = liveH5Fixture(tempDir); const studentApp = fixture.evidence.liveH5.portals.find(item => item.portal === 'student'); fixture.responses.set('https://student.gongxue100.com/js/app.student.js', { body: 'console.log("stale-production-bundle");\n', contentType: 'application/javascript', }); const checks = await validateEvidence(fixture.evidence, { evidencePath: path.join(tempDir, 'evidence.json'), allowStale: false, maxAgeDays: 14, verifyLiveH5: true, liveTimeoutMs: 5_000, fetchImpl: fixtureFetch(fixture.responses), }); assert.ok(studentApp.appSha256); assert.ok(checks.some(item => item.id === 'live_h5.student.app_hash' && item.status === 'blocker')); } finally { removeTempDir(tempDir); } } { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-launch-gate-live-')); try { const fixture = liveH5Fixture(tempDir); fixture.responses.set('https://admin.gongxue100.com/runtime-config.json', { body: JSON.stringify({ portal: 'student', apiBaseUrl: 'https://api.other-domain.com' }), contentType: 'application/json', }); const checks = await validateEvidence(fixture.evidence, { evidencePath: path.join(tempDir, 'evidence.json'), allowStale: false, maxAgeDays: 14, verifyLiveH5: true, liveTimeoutMs: 5_000, fetchImpl: fixtureFetch(fixture.responses), }); assert.ok(checks.some(item => item.id === 'live_h5.tenant-admin.runtime_portal' && item.status === 'blocker')); assert.ok(checks.some(item => item.id === 'live_h5.tenant-admin.runtime_api' && item.status === 'blocker')); } finally { removeTempDir(tempDir); } } const wrongCommit = runGate(tempDir => createEvidence(tempDir), { deployCommitSha: 'aaaaaaaaaaaa', deployReleaseRoot: path.join(os.tmpdir(), 'missing-release'), }); assert.notEqual(wrongCommit.status, 0, 'evidence for another commit should fail launch gate'); assert.ok(wrongCommit.payload.checks?.some(item => item.id === 'evidence.commit_match' && item.status === 'blocker')); const matchingCandidate = runGate(tempDir => createEvidence(tempDir), { deployCommitSha: '52cef9fabcd1', deployReleaseRoot: '', }); assert.notEqual(matchingCandidate.status, 0, 'deployment gate without a candidate release root should fail'); assert.ok(matchingCandidate.payload.checks?.some(item => item.id === 'evidence.release_root' && item.status === 'blocker')); const candidateTreeMatch = runGate(tempDir => createEvidence(tempDir), { deployCommitSha: '52cef9fabcd1', deployReleaseRoot: '__TEMP_CANDIDATE__', }); assert.equal(candidateTreeMatch.status, 0, `matching candidate release should pass: ${candidateTreeMatch.stdout}`); const candidateTreeMismatch = runGate(tempDir => { const evidence = createEvidence(tempDir); fs.appendFileSync(path.join(tempDir, 'candidate/apps/taro/dist/h5-student/index.html'), 'tampered\n', 'utf8'); return evidence; }, { deployCommitSha: '52cef9fabcd1', deployReleaseRoot: '__TEMP_CANDIDATE__', }); assert.notEqual(candidateTreeMismatch.status, 0, 'candidate release that differs from the manifest should fail'); assert.ok(candidateTreeMismatch.payload.checks?.some(item => item.id === 'check.taro.h5-release-manifest.release_tree' && item.status === 'blocker')); const tamperedArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.remote-smoke'); fs.appendFileSync(path.join(tempDir, item.artifact), 'tampered\n', 'utf8'); return evidence; }); assert.notEqual(tamperedArtifact.status, 0, 'tampered artifact should fail launch gate'); assert.ok(tamperedArtifact.payload.checks?.some(item => item.id === 'check.auth.remote-smoke.artifact_hash' && item.status === 'blocker')); const missingArtifactHash = runGate(tempDir => { const evidence = createEvidence(tempDir); delete evidence.checks.find(check => check.id === 'auth.remote-smoke').artifactSha256; return evidence; }); assert.notEqual(missingArtifactHash.status, 0, 'missing artifact hash should fail launch gate'); assert.ok(missingArtifactHash.payload.checks?.some(item => item.id === 'check.auth.remote-smoke.artifact_hash' && item.status === 'blocker')); const forgedPassLog = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'worker.platform-billing'); const artifactPath = path.join(tempDir, item.artifact); fs.writeFileSync(artifactPath, '[PASS] worker.platform-billing\n', 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(forgedPassLog.status, 0, 'a forged generic PASS log must not satisfy a log-backed check'); assert.ok( forgedPassLog.payload.checks?.some(item => item.id === 'check.worker.platform-billing.artifact_success' && item.status === 'blocker'), 'missing check-specific log success sentinel must be reported as a blocker', ); const forgedJsonSummary = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'readiness.production.env'); const artifactPath = path.join(tempDir, item.artifact); const payload = JSON.parse(fs.readFileSync(artifactPath, 'utf8')); payload.summary.blocker = 1; fs.writeFileSync(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(forgedJsonSummary.status, 0, 'evidence summary must not override a failing structured artifact'); assert.ok( forgedJsonSummary.payload.checks?.some(item => item.id === 'check.readiness.production.env.artifact_summary' && item.status === 'blocker'), 'structured artifact mismatch must be reported as a blocker', ); const invalidJsonArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'readiness.production.db'); const artifactPath = path.join(tempDir, item.artifact); fs.writeFileSync(artifactPath, '{not-json}\n', 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(invalidJsonArtifact.status, 0, 'invalid JSON artifact must not satisfy a structured check'); assert.ok( invalidJsonArtifact.payload.checks?.some(item => item.id === 'check.readiness.production.db.artifact_json' && item.status === 'blocker'), 'invalid structured artifact must be reported as a JSON blocker', ); const unverifiedJsonSummaryField = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'readiness.production.env'); item.summary.operatorAssertion = 'pass'; return evidence; }); assert.notEqual(unverifiedJsonSummaryField.status, 0, 'structured checks must reject summary fields that are not derived from the artifact gate contract'); assert.ok( unverifiedJsonSummaryField.payload.checks?.some(item => item.id === 'check.readiness.production.env.artifact_summary' && item.status === 'blocker'), 'unverified structured summary fields must be reported as artifact summary blockers', ); const staleMigrationArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'db.migration-history'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.latestAppliedMigration = '202607120018'; rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(staleMigrationArtifact.status, 0, 'migration evidence older than the repository latest migration must fail'); assert.ok(staleMigrationArtifact.payload.checks?.some(item => item.id === 'check.db.migration-history.artifact_summary' && item.status === 'blocker')); const forgedMigrationReadinessHash = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'db.migration-history'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.readinessArtifactSha256 = 'not-a-sha256'; rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(forgedMigrationReadinessHash.status, 0, 'migration evidence must bind the readiness artifact by SHA-256'); assert.ok(forgedMigrationReadinessHash.payload.checks?.some(item => item.id === 'check.db.migration-history.artifact_summary' && item.status === 'blocker')); const mismatchedMigrationReadinessArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'readiness.production.db'); const artifactPath = path.join(tempDir, item.artifact); fs.appendFileSync(artifactPath, '\nchanged-after-migration-summary\n', 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(mismatchedMigrationReadinessArtifact.status, 0, 'migration summary must fail when the bound readiness artifact changes'); assert.ok(mismatchedMigrationReadinessArtifact.payload.checks?.some(item => item.id === 'check.db.migration-history.artifact_summary' && item.status === 'blocker')); const mismatchedPlatformAdminIdentity = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.platform-admin-bootstrap'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.authSmokeExpectedUserIdSha256 = crypto.createHash('sha256').update('different-auth-user').digest('hex'); rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(mismatchedPlatformAdminIdentity.status, 0, 'platform admin bootstrap and Auth smoke identities must match'); assert.ok(mismatchedPlatformAdminIdentity.payload.checks?.some(item => item.id === 'check.auth.platform-admin-bootstrap.artifact_summary' && item.status === 'blocker')); const unverifiedRestoreDrill = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'backup.restore-drill'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.integrityVerified = false; rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(unverifiedRestoreDrill.status, 0, 'restore drill without verified integrity must fail'); assert.ok(unverifiedRestoreDrill.payload.checks?.some(item => item.id === 'check.backup.restore-drill.artifact_summary' && item.status === 'blocker')); const forgedRestoreVerificationHash = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'backup.restore-drill'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.verificationArtifactSha256 = 'not-a-sha256'; rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(forgedRestoreVerificationHash.status, 0, 'restore drill must bind its verification output by SHA-256'); assert.ok(forgedRestoreVerificationHash.payload.checks?.some(item => item.id === 'check.backup.restore-drill.artifact_summary' && item.status === 'blocker')); const regressedTaroSupplyChain = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'taro.supply-chain'); const payload = jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary); payload.securedBundleDependencies.swiper = '11.1.15'; payload.audit.counts.critical = 4; rewriteArtifact(tempDir, item, payload); return evidence; }); assert.notEqual(regressedTaroSupplyChain.status, 0, 'Taro supply-chain bundle or vulnerability regression must fail'); assert.ok(regressedTaroSupplyChain.payload.checks?.some(item => item.id === 'check.taro.supply-chain.artifact_summary' && item.status === 'blocker')); const missingWeappEvidence = runGate(tempDir => createEvidence(tempDir, { releaseTargets: ['h5', 'weapp'] })); assert.notEqual(missingWeappEvidence.status, 0, 'WeApp release target without production evidence should fail launch gate'); assert.ok(missingWeappEvidence.payload.checks?.some(item => item.id === 'check.taro.build.weapp-student' && item.status === 'blocker')); const completeWeappEvidence = runGate(tempDir => { const evidence = createEvidence(tempDir, { releaseTargets: ['h5', 'weapp'] }); for (const spec of weappGateChecks) { const artifact = `launch-artifacts/${spec.id}.log`; const artifactPath = path.join(tempDir, artifact); const summary = sampleSummary(spec.summary); writeCheckArtifact(spec, summary, artifactPath); evidence.checks.push({ id: spec.id, status: 'pass', command: `npm run ${spec.commandIncludes}`, completedAt: isoNow(), artifact, artifactSha256: crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'), summary, }); } evidence.attestations.push(...weappAttestations.map(spec => ({ id: spec.id, status: 'approved', approver: 'test-owner', approvedAt: isoNow(), notes: spec.label, }))); return evidence; }); assert.equal(completeWeappEvidence.status, 0, `complete WeApp evidence should pass: ${completeWeappEvidence.stdout}`); const missingArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.remote-smoke'); fs.rmSync(path.join(tempDir, item.artifact), { force: true }); return evidence; }); assert.notEqual(missingArtifact.status, 0, 'missing artifact should fail launch gate'); assert.ok( missingArtifact.payload.checks?.some(item => item.id === 'check.auth.remote-smoke.artifact' && item.status === 'blocker'), 'missing artifact should be reported as a blocker', ); const missingSmsPnvsSmoke = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'auth.sms-pnvs-remote-smoke'); return evidence; }); assert.notEqual(missingSmsPnvsSmoke.status, 0, 'missing PNVS SMS smoke should fail launch gate'); assert.ok( missingSmsPnvsSmoke.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-remote-smoke' && item.status === 'blocker'), 'missing PNVS SMS smoke should be reported as a blocker', ); const missingSmsPnvsDiagnostics = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'auth.sms-pnvs-diagnostics'); return evidence; }); assert.notEqual(missingSmsPnvsDiagnostics.status, 0, 'missing PNVS diagnostics should fail launch gate'); assert.ok( missingSmsPnvsDiagnostics.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-diagnostics' && item.status === 'blocker'), 'missing PNVS diagnostics should be reported as a blocker', ); const wrongSmsPnvsDiagnostics = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.sms-pnvs-diagnostics'); item.summary.env.providerMatchesPnvs = false; item.summary.provider.provider = 'aliyun'; rewriteArtifact(tempDir, item, item.summary); return evidence; }); assert.notEqual(wrongSmsPnvsDiagnostics.status, 0, 'non-PNVS diagnostics should fail launch gate'); assert.ok( wrongSmsPnvsDiagnostics.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-diagnostics.summary' && item.status === 'blocker'), 'non-PNVS diagnostics should be reported as a blocker', ); const wrongSmsProviderSmoke = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.sms-pnvs-remote-smoke'); item.summary.provider = 'aliyun'; rewriteArtifact(tempDir, item, item.summary); return evidence; }); assert.notEqual(wrongSmsProviderSmoke.status, 0, 'non-PNVS SMS smoke should fail launch gate'); assert.ok( wrongSmsProviderSmoke.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-remote-smoke.summary' && item.status === 'blocker'), 'non-PNVS SMS smoke should be reported as a blocker', ); const wrongMigrationProfile = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'migration.pb-production-dry-run'); item.summary.migrationProfile = 'development'; rewriteArtifact(tempDir, item, pocketBaseDryRunPayload(item.summary)); return evidence; }); assert.notEqual(wrongMigrationProfile.status, 0, 'development dry-run evidence should fail launch gate'); assert.ok( wrongMigrationProfile.payload.checks?.some(item => item.id === 'check.migration.pb-production-dry-run.summary' && item.status === 'blocker'), 'migration profile mismatch should be reported as a blocker', ); const missingPostgresTuning = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'postgres.tuning-evidence'); return evidence; }); assert.notEqual(missingPostgresTuning.status, 0, 'missing PostgreSQL tuning evidence should fail launch gate'); assert.ok( missingPostgresTuning.payload.checks?.some(item => item.id === 'check.postgres.tuning-evidence' && item.status === 'blocker'), 'missing PostgreSQL tuning evidence should be reported as a blocker', ); const slowBenchmark = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'performance.api-real-data-read'); item.summary.p95Ms = 301; return evidence; }); assert.notEqual(slowBenchmark.status, 0, 'slow API benchmark should fail launch gate'); assert.ok( slowBenchmark.payload.checks?.some(item => item.id === 'check.performance.api-real-data-read.summary' && item.status === 'blocker'), 'slow API benchmark should be reported as a blocker', ); const missingMixedBenchmark = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'performance.api-real-data-mixed'); return evidence; }); assert.notEqual(missingMixedBenchmark.status, 0, 'missing mixed read/write benchmark should fail launch gate'); assert.ok( missingMixedBenchmark.payload.checks?.some(item => item.id === 'check.performance.api-real-data-mixed' && item.status === 'blocker'), 'missing mixed benchmark should be reported as a blocker', ); const slowMixedBenchmark = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'performance.api-real-data-mixed'); item.summary.p95Ms = 501; return evidence; }); assert.notEqual(slowMixedBenchmark.status, 0, 'slow mixed API benchmark should fail launch gate'); assert.ok( slowMixedBenchmark.payload.checks?.some(item => item.id === 'check.performance.api-real-data-mixed.summary' && item.status === 'blocker'), 'slow mixed benchmark should be reported as a blocker', ); const capacityCleanupMismatch = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'performance.tenant-students-100k'); const artifactPath = path.join(tempDir, item.artifact); const payload = JSON.parse(fs.readFileSync(artifactPath, 'utf8')); payload.cleanup.remaining.platformUsers = 1; fs.writeFileSync(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(capacityCleanupMismatch.status, 0, 'capacity artifact with managed rows after cleanup should fail'); assert.ok( capacityCleanupMismatch.payload.checks?.some(item => item.id === 'check.performance.tenant-students-100k.artifact_summary' && item.status === 'blocker'), 'capacity cleanup mismatch should be reported from the artifact', ); const tenantForeignKeyArtifactMismatch = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'db.tenant-foreign-key-audit'); const artifactPath = path.join(tempDir, item.artifact); const payload = JSON.parse(fs.readFileSync(artifactPath, 'utf8')); payload.data.invalidRelations = 1; fs.writeFileSync(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(tenantForeignKeyArtifactMismatch.status, 0, 'tenant foreign key artifact with invalid data relations should fail'); assert.ok( tenantForeignKeyArtifactMismatch.payload.checks?.some(item => item.id === 'check.db.tenant-foreign-key-audit.artifact_summary' && item.status === 'blocker'), 'tenant foreign key mismatch must be reported from the raw artifact', ); const corsArtifactMismatch = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'api.dynamic-tenant-cors-smoke'); const artifactPath = path.join(tempDir, item.artifact); const payload = JSON.parse(fs.readFileSync(artifactPath, 'utf8')); payload.unknownOriginDenied = false; fs.writeFileSync(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); item.artifactSha256 = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex'); return evidence; }); assert.notEqual(corsArtifactMismatch.status, 0, 'dynamic CORS artifact mismatch should fail'); assert.ok( corsArtifactMismatch.payload.checks?.some(item => item.id === 'check.api.dynamic-tenant-cors-smoke.artifact_summary' && item.status === 'blocker'), 'dynamic CORS mismatch should be reported from the artifact', ); const missingLaunchPersonaSmoke = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'api.launch-persona-smoke'); return evidence; }); assert.notEqual(missingLaunchPersonaSmoke.status, 0, 'missing launch persona smoke should fail launch gate'); assert.ok( missingLaunchPersonaSmoke.payload.checks?.some(item => item.id === 'check.api.launch-persona-smoke' && item.status === 'blocker'), 'missing launch persona smoke should be reported as a blocker', ); const launchPersonaWithoutSvip = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'api.launch-persona-smoke'); item.summary.student.result.entitlement.isSvip = false; rewriteArtifact(tempDir, item, item.summary); return evidence; }); assert.notEqual(launchPersonaWithoutSvip.status, 0, 'launch persona smoke without SVIP should fail launch gate'); assert.ok( launchPersonaWithoutSvip.payload.checks?.some(item => item.id === 'check.api.launch-persona-smoke.summary' && item.status === 'blocker'), 'launch persona SVIP failure should be reported as a blocker', ); const launchPersonaLegacyAuth = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'api.launch-persona-smoke'); item.summary.authMode = 'legacy'; rewriteArtifact(tempDir, item, item.summary); return evidence; }); assert.notEqual(launchPersonaLegacyAuth.status, 0, 'launch persona smoke with legacy auth should fail launch gate'); assert.ok( launchPersonaLegacyAuth.payload.checks?.some(item => item.id === 'check.api.launch-persona-smoke.summary' && item.status === 'blocker'), 'launch persona legacy auth should be reported as a blocker', ); const missingBusinessSampling = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'migration.pb-import-sample'); return evidence; }); assert.notEqual(missingBusinessSampling.status, 0, 'missing import business sampling should fail launch gate'); assert.ok( missingBusinessSampling.payload.checks?.some(item => item.id === 'check.migration.pb-import-sample' && item.status === 'blocker'), 'missing import business sampling should be reported as a blocker', ); const missingTaroStaticSmoke = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'taro.h5-static-smoke'); return evidence; }); assert.notEqual(missingTaroStaticSmoke.status, 0, 'missing H5 static smoke should fail launch gate'); assert.ok( missingTaroStaticSmoke.payload.checks?.some(item => item.id === 'check.taro.h5-static-smoke' && item.status === 'blocker'), 'missing H5 static smoke should be reported as a blocker', ); const missingTaroInteractionSmoke = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'taro.h5-interaction-smoke'); return evidence; }); assert.notEqual(missingTaroInteractionSmoke.status, 0, 'missing H5 interaction smoke should fail launch gate'); assert.ok( missingTaroInteractionSmoke.payload.checks?.some(item => item.id === 'check.taro.h5-interaction-smoke' && item.status === 'blocker'), 'missing H5 interaction smoke should be reported as a blocker', ); const oldTaroInteractionCoverage = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'taro.h5-interaction-smoke'); item.summary.pass = 26; rewriteArtifact(tempDir, item, jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary)); return evidence; }); assert.notEqual(oldTaroInteractionCoverage.status, 0, 'old 26-check H5 interaction evidence should fail launch gate'); assert.ok( oldTaroInteractionCoverage.payload.checks?.some(item => item.id === 'check.taro.h5-interaction-smoke.summary' && item.status === 'blocker'), 'old H5 interaction coverage should be reported as a blocker', ); const missingAdminWriteCoverage = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'taro.h5-interaction-smoke'); item.summary.mockApi.keyRequests.platformAdminWrites = 0; rewriteArtifact(tempDir, item, jsonArtifactPayload(gateChecks.find(spec => spec.id === item.id), item.summary)); return evidence; }); assert.notEqual(missingAdminWriteCoverage.status, 0, 'H5 interaction evidence without platform admin writes should fail launch gate'); assert.ok( missingAdminWriteCoverage.payload.checks?.some(item => item.id === 'check.taro.h5-interaction-smoke.summary' && item.status === 'blocker'), 'missing platform admin write coverage should be reported as a blocker', ); const missingTaroReleaseManifest = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'taro.h5-release-manifest'); return evidence; }); assert.notEqual(missingTaroReleaseManifest.status, 0, 'missing H5 release manifest should fail launch gate'); assert.ok( missingTaroReleaseManifest.payload.checks?.some(item => item.id === 'check.taro.h5-release-manifest' && item.status === 'blocker'), 'missing H5 release manifest should be reported as a blocker', ); const missingRepoSecurityScan = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.checks = evidence.checks.filter(item => item.id !== 'security.repo-scan'); return evidence; }); assert.notEqual(missingRepoSecurityScan.status, 0, 'missing repository security scan should fail launch gate'); assert.ok( missingRepoSecurityScan.payload.checks?.some(item => item.id === 'check.security.repo-scan' && item.status === 'blocker'), 'missing repository security scan should be reported as a blocker', ); const missingAttestation = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.attestations = evidence.attestations.filter(item => item.id !== 'backup.snapshot'); return evidence; }); assert.notEqual(missingAttestation.status, 0, 'missing manual attestation should fail launch gate'); assert.ok( missingAttestation.payload.checks?.some(item => item.id === 'attestation.backup.snapshot' && item.status === 'blocker'), 'missing attestation should be reported as a blocker', ); console.log('[PASS] production launch gate');