fix: exercise strict release checks in CI

This commit is contained in:
jb
2026-09-19 10:01:23 -04:00
parent 197e6a3a70
commit b13df3fa34
8 changed files with 46 additions and 11 deletions
+6 -5
View File
@@ -19,6 +19,7 @@ const configuredPort = process.env.SMOKE_WEB_PORT;
const port = configuredPort === undefined ? await freePort() : Number(configuredPort);
if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error('SMOKE_WEB_PORT must be a valid TCP port');
const base = `http://127.0.0.1:${port}`;
const releaseId = `fixture-${crypto.randomUUID()}`;
const root = await mkdtemp(join(tmpdir(), 'eden-fixture-'));
let server: Bun.Subprocess | undefined;
// Never pass ambient vault/session/provider variables into repository-controlled
@@ -38,12 +39,12 @@ async function compileFixture(directory: string): Promise<void> {
if (code !== 0) throw new Error('Fixture reference compilation failed');
}
async function waitForServer(): Promise<void> {
async function waitForServer(expectedReleaseId: string): Promise<void> {
const deadline = Date.now() + 30_000;
while (Date.now() < deadline) {
try {
const response = await fetch(base);
if (response.status === 200 && response.headers.get('x-application-release') === 'fixture') return;
if (response.status === 200 && response.headers.get('x-application-release') === expectedReleaseId) return;
} catch { /* startup race */ }
await Bun.sleep(250);
}
@@ -58,15 +59,15 @@ try {
...safeEnv,
APP_MODE: 'demo', APP_ORIGIN: base, ORIGIN: base,
HOST: '127.0.0.1', PORT: String(port), REFERENCE_DIR: root,
APP_RELEASE_ID: 'fixture',
APP_RELEASE_ID: releaseId,
APP_CAPABILITY_ALLOWLIST: 'asset_search,asset_valuation,asset_sync,assistant_dispatch,session_token,identity,subscriptions',
},
});
await waitForServer();
await waitForServer(releaseId);
const rootResponse = await fetch(base);
if (!(await rootResponse.text()).includes('FIXTURE DEMO')) throw new Error('Fixture label missing');
if (rootResponse.headers.get('x-application-release') !== 'fixture') throw new Error('Release identifier header missing');
if (rootResponse.headers.get('x-application-release') !== releaseId) throw new Error('Release identifier header missing');
const assets = await fetch(`${base}/assets`);
if (assets.status !== 200 || !(await assets.text()).includes('2,086,000.00')) throw new Error('Fixture asset report failed');
+7 -2
View File
@@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { parseCapabilityAllowlist, releaseIdIsImmutable } from '../src/lib/server/release-gate';
const RELEASE_ENVIRONMENTS = new Set(['fixture', 'development', 'staging', 'production']);
@@ -15,6 +15,7 @@ type ReleaseCheckInput = {
capabilityAllowlist: string | undefined;
trackedFiles: readonly string[];
browserBundleFiles?: readonly string[];
browserBundlePresent?: boolean;
};
export function releaseSafetyErrors(input: ReleaseCheckInput): string[] {
@@ -35,6 +36,9 @@ export function releaseSafetyErrors(input: ReleaseCheckInput): string[] {
if (strictEnvironment && !DIGEST_IMAGE.test(input.convexImage)) {
errors.push('CONVEX_IMAGE must be digest-pinned for staging/production');
}
if (strictEnvironment && input.browserBundlePresent === false) {
errors.push('built browser client artifacts are required for staging/production release checks');
}
if (strictEnvironment) {
const baseImages = [...input.dockerfile.matchAll(/^FROM\s+([^\s]+)(?:\s+AS\s+[^\s]+)?$/gim)].map((match) => match[1]);
if (baseImages.length === 0 || baseImages.some((image) => !DIGEST_IMAGE.test(image))) {
@@ -45,7 +49,7 @@ export function releaseSafetyErrors(input: ReleaseCheckInput): string[] {
errors.push('APP_MODE=demo is not permitted for a staging/production release');
}
for (const file of input.trackedFiles) {
if (/(^|\/)(?:\.env(?:\.|$)|.*\.pem$|.*\.key$)/i.test(file) && file !== '.env.example') {
if (/(^|\/)(?:\.env(?:\.|$)|.*\.(?:pem|key|p12|pfx)$|(?:credentials|secrets)(?:\.(?:json|ya?ml|env|txt)|$)|\.npmrc$)/i.test(file) && file !== '.env.example') {
errors.push(`secret-like file is tracked: ${file}`);
}
}
@@ -91,6 +95,7 @@ if (import.meta.main) {
capabilityAllowlist: process.env.APP_CAPABILITY_ALLOWLIST,
trackedFiles: trackedFiles(),
browserBundleFiles: browserBundleFiles(),
browserBundlePresent: existsSync('build/client'),
});
if (errors.length) {
console.error(`Release safety check failed (${errors.length} issue${errors.length === 1 ? '' : 's'})`);