Files
gongxue-base/scripts/production-launch-gate-test.js
2026-07-12 19:26:57 +08:00

1051 lines
49 KiB
JavaScript

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'), `<div id="app">${portal}</div>\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 = `<!doctype html><html><head><script defer src="${fixture.appPath}"></script></head><body><div id="app"></div></body></html>`;
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');