scripts/smoke-matrix.mjs
Rama master · réplica de lectura del repositorio original.
#!/usr/bin/env node
/**
* Smoke matrix — every user-visible feature, success + failure/edge, one boot.
*
* Broad companion to scripts/dashboard-smoke.mjs. It REUSES that suite's fake
* upstream (test/helpers/dashboard-smoke-upstream.mjs: same scripted
* login/REST/catalog/fetch transports, same synthetic credentials) and adds the
* two seams the dashboard suite never needed:
*
* - a scripted DEVIN_CONNECT event stream (__setStreamChatForTest), so the
* REAL adapters in src/devin-connect-openai.js serve the four public
* protocol fronts (OpenAI chat/completions, Anthropic Messages, OpenAI
* Responses, Gemini) from deterministic bytes;
* - a fake `devin acp` stdio server, so the special-agent backend runs its
* REAL runner (handshake, credential injection, SSE re-framing, error
* mapping) without the real CLI or an account.
*
* Every flow is one user-visible behaviour with at least one success path and
* one failure/edge path. Families are declared in MATRIX_FAMILIES and each
* family must contribute at least one flow; the manifest flow enforces both
* directions so a silently dropped registration cannot pass.
*
* Isolation: throwaway DATA_DIR, ephemeral loopback port, no dotenv, and
* scripts/mutation-network-deny.mjs installed as an independent egress
* barrier. A seam the suite forgot fails with NETWORK_STUB_MISS instead of
* dialling a real provider.
*
* Usage: node scripts/smoke-matrix.mjs
* Exit 0 = every flow passed. Exit 1 = at least one failed (the FAIL line names
* the family and the flow).
* Last stdout line:
* SMOKE-MATRIX SUMMARY: <passed>/<total> flows passed, <failed> failed, <f>/<F> families green
*/
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { once } from 'node:events';
import { setTimeout as delay } from 'node:timers/promises';
import {
SMOKE,
MATRIX,
MATRIX_SECRETS,
MATRIX_FAMILIES,
scriptedLoginTransport,
scriptedPostJson,
scriptedCatalogTransport,
scriptedFetch,
scriptedConnectEvents,
FAKE_ACP_SOURCE,
} from '../test/helpers/smoke-matrix-upstream.mjs';
import { registerCoreFlows } from './smoke-matrix/flows-core.mjs';
import { registerLoginFlows } from './smoke-matrix/flows-login.mjs';
import { registerProtocolFlows } from './smoke-matrix/flows-protocol.mjs';
import { registerLiveFlows } from './smoke-matrix/flows-live.mjs';
import { registerReauthFlows } from './smoke-matrix/flows-reauth.mjs';
import { registerAcuFlows } from './smoke-matrix/flows-acu.mjs';
import { registerAccountDrawerFlows } from './smoke-matrix/flows-account-drawer.mjs';
import { registerRuntimeFlows } from './smoke-matrix/flows-runtime.mjs';
import { registerFleetFlows } from './smoke-matrix/flows-fleet.mjs';
import { registerDetachFlows } from './smoke-matrix/flows-detach.mjs';
import { registerDocsFlows } from './smoke-matrix/flows-docs.mjs';
const DATA_DIR = mkdtempSync(join(tmpdir(), 'windsurfapi-smoke-matrix-'));
const ACP_DIR = mkdtempSync(join(tmpdir(), 'windsurfapi-smoke-matrix-acp-'));
const ACP_SCRIPT = join(ACP_DIR, 'fake-acp.mjs');
writeFileSync(ACP_SCRIPT, FAKE_ACP_SOURCE, 'utf8');
// Everything the product reads at import time is set BEFORE the dynamic
// imports below. PORT=0 lets the OS pick a free loopback port.
Object.assign(process.env, {
WINDSURFAPI_SKIP_DOTENV: '1',
DATA_DIR,
HOST: '127.0.0.1',
PORT: '0',
API_KEY: SMOKE.apiKey,
DASHBOARD_PASSWORD: SMOKE.dashboardPassword,
DEVIN_CONNECT: '1',
DEVIN_CONNECT_TOKEN: MATRIX.connectToken,
DEVIN_CONNECT_AUTO_RELOGIN: '0',
RELOGIN_LIVE: '0',
WINDSURFAPI_NO_OPEN: '1',
LOG_LEVEL: 'error',
NO_COLOR: '1',
RESPONSE_STORE_ENABLED: '1',
// Deterministic stream shape: no empty-turn retries, no rescue re-issues.
DEVIN_CONNECT_RETRY_ON_EMPTY: '0',
DEVIN_CONNECT_RESCUE_MAX: '0',
// ACP backend is only armed during the acp family (see flows-live.mjs); the
// path/args are inert until WINDSURFAPI_SPECIAL_AGENT_BACKEND=devin-cli.
DEVIN_CLI_PATH: process.execPath,
DEVIN_CLI_ACP_ARGS_JSON: JSON.stringify([ACP_SCRIPT]),
DEVIN_CLI_ENABLED: '1',
});
// Capture everything this process writes (including product logs) so the final
// secret scan covers real stdout, not just our own PASS lines.
const captured = [];
for (const stream of [process.stdout, process.stderr]) {
const original = stream.write.bind(stream);
stream.write = (chunk, encoding, callback) => {
try {
captured.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8'));
} catch { /* capture is best-effort */ }
return original(chunk, encoding, callback);
};
}
const responseTexts = [];
const flows = [];
const familyTotals = new Map(MATRIX_FAMILIES.map((f) => [f, { passed: 0, total: 0 }]));
const state = {};
let server = null;
let base = '';
let passed = 0;
let failed = 0;
let exitCode = 0;
// Seam handles, assigned in main() and cleared in cleanup().
let seams = {};
function out(line) {
process.stdout.write(`${line}\n`);
}
// `process.exit()` does not wait for a piped stdout buffer. Resolve both write
// callbacks first so the summary line cannot be truncated under CI's pipe.
async function flushStdio() {
await Promise.all([process.stdout, process.stderr].map((stream) => new Promise((resolve) => {
try { stream.write('', resolve); } catch { resolve(); }
})));
}
function requireThat(condition, detail) {
if (!condition) throw new Error(detail);
}
/**
* One HTTP call against the real server.
* `auth`: 'dashboard' (default) | 'api' | 'both' | 'none'.
*/
async function http(method, path, { body, rawBody, auth = 'dashboard', headers = {} } = {}) {
const h = { ...headers };
if (body !== undefined || rawBody !== undefined) h['content-type'] = 'application/json';
if (auth === 'dashboard' || auth === 'both') h['x-dashboard-password'] = SMOKE.dashboardPassword;
if (auth === 'api' || auth === 'both') h.authorization = `Bearer ${SMOKE.apiKey}`;
const res = await fetch(base + path, {
method,
headers: h,
body: rawBody !== undefined ? rawBody : (body === undefined ? undefined : JSON.stringify(body)),
signal: AbortSignal.timeout(20_000),
});
const text = await res.text();
responseTexts.push(text);
let json = null;
try { json = text ? JSON.parse(text) : null; } catch { /* non-JSON is checked by callers */ }
return {
status: res.status,
json,
text,
contentType: res.headers.get('content-type') || '',
header: (name) => res.headers.get(name),
};
}
function readPath(value, path) {
return path.split('.').reduce((node, key) => (node == null ? undefined : node[key]), value);
}
async function waitFor(predicate, timeoutMs = 5000) {
const deadline = Date.now() + timeoutMs;
for (;;) {
if (predicate()) return true;
if (Date.now() >= deadline) return false;
await delay(10);
}
}
/** Split an SSE body into `{event, data}` frames; `[DONE]` keeps data as-is. */
function sseEvents(text) {
return text
.split(/\r?\n\r?\n/)
.filter((frame) => frame.trim())
.map((frame) => {
const lines = frame.split(/\r?\n/);
const event = lines.find((l) => l.startsWith('event:'))?.slice(6).trim();
const data = lines
.filter((l) => l.startsWith('data:'))
.map((l) => l.slice(5).trim())
.join('\n');
return { event, data };
})
.filter((frame) => frame.event || frame.data);
}
function addFlow(family, name, fn) {
requireThat(MATRIX_FAMILIES.includes(family), `unknown family ${family} for flow ${name}`);
requireThat(!flows.some((f) => f.name === name), `duplicate flow name ${name}`);
flows.push({ family, name, fn });
}
/**
* Read the first chunk of a long-lived SSE endpoint, then abort. The dashboard
* log view is a stream, so `http()` (which waits for the body to end) cannot
* exercise it.
*/
async function streamHead(path, { timeoutMs = 3000 } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(base + path, {
headers: { 'x-dashboard-password': SMOKE.dashboardPassword },
signal: controller.signal,
});
const reader = res.body.getReader();
const { value } = await reader.read();
const text = value ? Buffer.from(value).toString('utf8') : '';
responseTexts.push(text);
return { status: res.status, contentType: res.headers.get('content-type') || '', text };
} finally {
clearTimeout(timer);
controller.abort();
}
}
const ctx = {
SMOKE,
MATRIX,
state,
http,
streamHead,
sseEvents,
readPath,
waitFor,
requireThat,
addFlow,
base: '',
deps: {},
};
async function runFlow({ family, name, fn }) {
const totals = familyTotals.get(family);
totals.total++;
try {
const detail = await fn();
passed++;
totals.passed++;
out(`PASS [${family}] ${name}${detail ? ` — ${detail}` : ''}`);
return true;
} catch (err) {
failed++;
exitCode = 1;
out(`FAIL [${family}] ${name} — ${err?.message || String(err)}`);
return false;
}
}
function buildCatalogResponse(writeStringField, writeVarintField, writeMessageField) {
const row = Buffer.concat([
writeStringField(1, SMOKE.liveLabel),
writeStringField(22, SMOKE.liveSelector),
writeVarintField(10, 2), // provider: OpenAI
writeMessageField(23, writeStringField(23, SMOKE.liveAlias)),
]);
// The pooled-account entitlement filter only admits a FREE_REACHABLE
// selector when the account's own live catalog lists it. Without this row a
// pooled chat for `swe-1-6-slow` queues for 30 s and falls back to the env
// token, which would make the billed-request history permanently empty.
const freeRow = Buffer.concat([
writeStringField(1, 'Free Smoke'),
writeStringField(22, MATRIX.models.chat),
writeVarintField(10, 2),
writeMessageField(23, writeStringField(23, 'free.smoke')),
]);
return Buffer.concat([writeMessageField(1, row), writeMessageField(1, freeRow)]);
}
function buildStatusResponse(writeStringField, writeVarintField, writeMessageField) {
const plan = writeStringField(2, 'Pro');
const billing = Buffer.concat([
writeMessageField(1, plan),
writeVarintField(16, 12_500_000), // balance, micro-USD → $12.50
writeVarintField(17, 1_790_000_000),
writeVarintField(18, 1_790_604_800),
]);
return writeMessageField(1, writeMessageField(13, billing));
}
const REST_STATUS = {
userStatus: {
planStatus: {
planInfo: { planName: 'Pro' },
dailyQuotaRemainingPercent: 42,
weeklyQuotaRemainingPercent: 88,
dailyQuotaResetAtUnix: 1_790_000_000,
weeklyQuotaResetAtUnix: 1_790_604_800,
usedPromptCredits: 1000,
availablePromptCredits: 4000,
usedFlexCredits: 0,
availableFlexCredits: 0,
},
},
};
const USAGE_QUOTA = {
is_quota_plan: true,
has_quota_allocation: true,
daily_percentage: 42.5,
weekly_percentage: 17,
daily_reset_at: '2026-10-23T00:00:00Z',
weekly_reset_at: '2026-10-27T00:00:00Z',
overage_balance: 0,
hide_daily_quota: false,
};
const USAGE_STATUS = {
plan_slug: 'pro',
is_subscription_valid: true,
available_credits: 1234,
overage_credits: 0,
billing_method: 'card',
};
const USAGE_LIMITS = { max_acu_limit: 100, max_total_session_acu_limit: 50 };
const USAGE_DAILY = {
days: [{ date: '2026-10-22', amount: 5, cumulative: 5 }],
products: [{ view: 'chat', total: 5, days: [{ date: '2026-10-22', amount: 5, cumulative: 5 }] }],
total: 5,
cycle_start: '2026-10-01',
cycle_end: '2026-11-01',
previous_cycle_available: true,
};
async function main() {
// Independent egress barrier. Importing the same URL a CI wrapper preloads is
// a module-cache no-op, so manual runs get the guard too.
await import('../scripts/mutation-network-deny.mjs');
const { startServer } = await import('../src/server.js');
const { initAuth } = await import('../src/auth.js');
const authModule = await import('../src/auth.js');
const loginModule = await import('../src/dashboard/windsurf-login.js');
const otpModule = await import('../src/dashboard/email-otp-login.js');
const windsurfApiModule = await import('../src/windsurf-api.js');
const catalogModule = await import('../src/devin-connect-catalog.js');
const devinReadModule = await import('../src/dashboard/devin-read.js');
const connectOpenAi = await import('../src/devin-connect-openai.js');
const cardsModule = await import('../src/dashboard/devin-cards.js');
const { getLiveCatalog } = await import('../src/devin-connect-models.js');
const { __resetRequestRingForTest } = await import('../src/dashboard/request-ring.js');
const { writeStringField, writeVarintField, writeMessageField } = await import('../src/proto.js');
seams = {
login: loginModule.__setLoginTransportForTests,
otp: otpModule.__setOtpTransportForTests,
postJson: windsurfApiModule.__setWindsurfApiPostJsonForTest,
catalog: catalogModule.__setCatalogRequestImpl,
fetch: devinReadModule.__setFetchImplForTest,
streamChat: connectOpenAi.__setStreamChatForTest,
preflight: cardsModule.__setPreflightDepsForTest,
reloginDeps: authModule.__setReloginDeps,
resetReauthChecks: authModule.__resetReauthChecks,
resetEmailFlows: loginModule._resetEmailFlowsForTests,
resetEmailLockout: loginModule._resetEmailLockoutForTests,
resetReadCache: devinReadModule.__resetReadCache,
};
// SessionReauth's confirmation probe is a free GetUserStatus call. The
// matrix needs to choose the verdict per token (dead / alive), so it injects
// the same seam the unit suite uses. Unlisted tokens are alive.
const liveness = new Map();
const checkSessionLiveness = async ({ token }) => (
liveness.get(String(token || '')) || { alive: true, code: 'OK' }
);
const catalogBytes = buildCatalogResponse(writeStringField, writeVarintField, writeMessageField);
const statusBytes = buildStatusResponse(writeStringField, writeVarintField, writeMessageField);
const loginTransport = scriptedLoginTransport([
// app.devin.ai is the wave-3 fallback/explicit login host. It uses the
// /api/auth1/* paths (not windsurf.com's /_devin-auth/*), so these routes
// can only be reached when the product actually chose that host.
{
match: (url) => url.includes('app.devin.ai/api/auth1/connections'),
reply: (_url, body) => {
if (body?.email === MATRIX.emailDevinOnly) {
return {
json: {
connections: [{ id: 'email', type: 'email', enabled: true }],
auth_method: { method: 'auth1', has_password: true },
},
};
}
return { status: 500, json: { error: 'devin_host_unrouted' } };
},
},
{
match: (url) => url.includes('app.devin.ai/api/auth1/email/start'),
reply: { json: { email_verification_token: MATRIX.verifyTokenDevin } },
},
{
match: (url) => url.includes('app.devin.ai/api/auth1/email/complete'),
reply: (_url, body) => (body?.code === SMOKE.emailCode
? { json: { token: MATRIX.auth1TokenDevin } }
: { status: 400, json: { detail: 'invalid verification code' } }),
},
{
match: (url) => url.includes('/_devin-auth/connections'),
reply: (_url, body) => {
const email = body?.email;
if (email === MATRIX.emailBrokenProbe) return { status: 500, json: { error: 'probe_down' } };
// Host-fallback fixture: windsurf.com cannot answer this address, so
// only the app.devin.ai route above can.
if (email === MATRIX.emailDevinOnly) return { status: 500, json: { error: 'probe_down' } };
// Known non-auth1 method with NO has_password field: the only reason a
// probe can return it is the wave-3 "honor a non-auth1 method" clause.
if (email === MATRIX.emailFirebase) {
return {
json: {
connections: [
{ id: 'email', type: 'email', enabled: true },
{ id: 'google', type: 'google', enabled: true },
],
auth_method: { method: 'firebase' },
},
};
}
if (email === MATRIX.emailUnknown) {
return { json: { auth_method: { method: 'not_found', has_password: false } } };
}
if (email === MATRIX.emailPassword || email === SMOKE.emailHasPassword) {
return {
json: {
connections: [{ id: 'password', type: 'password', enabled: true }],
auth_method: { method: 'auth1', has_password: true },
},
};
}
return {
json: {
connections: [{ id: 'email', type: 'email', enabled: true }],
auth_method: { method: 'auth1', has_password: false },
},
};
},
},
{
// Secondary probe. Only the broken-probe identity reaches it, and it must
// also fail so the route reports "unknown" rather than inventing a method.
match: (url) => url.includes('CheckUserLoginMethod'),
reply: (_url, body) => (body?.email === MATRIX.emailBrokenProbe
? { status: 503, json: { error: 'unavailable' } }
: { json: { userExists: true, hasPassword: false } }),
},
{
match: (url) => url.includes('/_devin-auth/email/start'),
reply: { json: { email_verification_token: SMOKE.verifyToken } },
},
{
match: (url) => url.includes('/_devin-auth/email/complete'),
reply: (_url, body) => (body?.code === SMOKE.emailCode
? { json: { token: SMOKE.auth1Token } }
: { status: 400, json: { detail: 'invalid verification code' } }),
},
{
match: (url) => url.includes('/_devin-auth/password/login'),
reply: (_url, body) => (body?.password === MATRIX.password
? { json: { token: MATRIX.auth1Password } }
// Upstream's documented Auth1 rejection string; createFriendlyAuthError
// maps it to ERR_INVALID_PASSWORD + isAuthFail.
: { status: 401, json: { detail: 'INVALID_PASSWORD' } }),
},
{
match: (url) => url.includes('WindsurfPostAuth'),
reply: (_url, _body, _raw, opts) => {
// PostAuth carries the Auth1 token in a header with an EMPTY body, so
// the header is the only way to tell the password flow from the
// email-code flow.
const headers = opts?.headers || {};
const auth1 = Object.entries(headers)
.find(([key]) => key.toLowerCase() === 'x-devin-auth1-token')?.[1] || '';
const passwordFlow = String(auth1).includes(MATRIX.auth1Password);
const devinFlow = String(auth1).includes(MATRIX.auth1TokenDevin);
return {
buffer: Buffer.from(JSON.stringify({
sessionToken: passwordFlow
? MATRIX.sessionTokenPassword
: (devinFlow ? MATRIX.sessionTokenDevin : SMOKE.sessionTokenEmail),
accountId: passwordFlow
? 'account-smoke-password'
: (devinFlow ? 'account-smoke-devin' : 'account-smoke-email'),
})),
};
},
},
]);
const postJson = scriptedPostJson([
{
match: (path) => path.includes('GetCascadeModelConfigs'),
reply: { json: { clientModelConfigs: [], clientModelSorts: [] } },
},
{ match: (path) => path.includes('GetUserStatus'), reply: { json: REST_STATUS } },
{
match: (path) => path.includes('CheckUserMessageRateLimit'),
reply: { json: { hasCapacity: true, messagesRemaining: 42, maxMessages: 100, resetsInSeconds: 0 } },
},
]);
const catalogTransport = scriptedCatalogTransport([
{ match: (path) => path.includes('GetCliModelConfigs'), reply: { buffer: catalogBytes } },
{ match: (path) => path.includes('GetUserStatus'), reply: { buffer: statusBytes } },
]);
const fetchImpl = scriptedFetch([
{
match: (url) => url.includes('/users/current-membership'),
reply: { json: { org: { org_id: SMOKE.orgId } } },
},
{ match: (url) => url.includes('/billing/quota/usage'), reply: { json: USAGE_QUOTA } },
{ match: (url) => url.includes('/billing/status'), reply: { json: USAGE_STATUS } },
{ match: (url) => url.includes('/billing/usage/limits'), reply: { json: USAGE_LIMITS } },
{ match: (url) => url.includes('/billing/usage/daily-usage'), reply: { json: USAGE_DAILY } },
{
match: (url) => url.includes('/internal-model-catalog'),
reply: { json: { models: [{ model_uid: 'swe-1-6-slow', disabled: false }] } },
},
{ match: (url) => url.includes('/fed-models'), reply: { json: { models: [] } } },
{ match: (url) => url.includes('/toggleable-agent-versions'), reply: { json: { toggleable_versions: [] } } },
{ match: (url) => url.includes('/windsurf/org-configs'), reply: { json: { configs: [] } } },
]);
const connectStream = scriptedConnectEvents();
const otpTransport = async (url) => {
if (url.includes('SendEmailVerification')) return { status: 200, data: {} };
if (url.includes('RegisterUser')) {
return {
status: 200,
data: { api_key: MATRIX.otpKey, name: 'OTP Smoke', api_server_url: '' },
};
}
return { status: 404, data: { error: 'unrouted otp call' } };
};
seams.login(loginTransport);
seams.otp(otpTransport);
seams.postJson(postJson);
seams.catalog(catalogTransport);
seams.fetch(fetchImpl);
seams.streamChat(connectStream);
seams.preflight({
rateLimit: async () => ({ hasCapacity: true, messagesRemaining: 42, maxMessages: 100, retryAfterMs: null }),
});
seams.reloginDeps({ checkSessionLiveness });
seams.resetReauthChecks();
seams.resetReadCache();
seams.resetEmailFlows();
seams.resetEmailLockout();
__resetRequestRingForTest();
await initAuth();
server = startServer();
if (!server.listening) await once(server, 'listening');
base = `http://127.0.0.1:${server.address().port}`;
ctx.base = base;
Object.assign(ctx.deps, {
loginTransport,
postJson,
catalogTransport,
fetchImpl,
connectStream,
otpTransport,
getLiveCatalog,
liveness,
installFetch: seams.fetch,
// The fleet cancel flow needs to stretch one upstream read so a cancel can
// land while an item is genuinely in flight; re-installing is the same seam
// the transport was armed with.
installPostJson: seams.postJson,
resetReadCache: seams.resetReadCache,
resetEmailFlows: seams.resetEmailFlows,
resetEmailLockout: seams.resetEmailLockout,
});
// Order matters: the protocol fronts run BEFORE any account is pooled, so
// the connect client takes the documented env-token fallback instead of
// queueing for a per-account entitlement that a fresh pool cannot satisfy.
// The dashboard families that assert an empty pool/ring therefore also run
// before login.
registerCoreFlows(ctx);
registerProtocolFlows(ctx);
registerLoginFlows(ctx);
// Pooled-account surfaces. The history flow drives one real chat through the
// pool, so it must run before any family that parks an account (reauth).
registerAccountDrawerFlows(ctx);
registerAcuFlows(ctx);
// Fleet + KPI need the pool the login family built and the org identities the
// ACU reader learned, and they must run BEFORE reauth parks an account (a
// parked account is excluded from a fleet run by design).
registerFleetFlows(ctx);
registerReauthFlows(ctx);
registerLiveFlows(ctx);
registerRuntimeFlows(ctx);
// Detach flips its own switch per request and restores it; the docs family
// runs the repo guard in a child process.
registerDetachFlows(ctx);
registerDocsFlows(ctx);
// Hygiene flows are ordinary members of the flow list so the family census
// sees them. The leak scan is last on purpose: it must run even when an
// earlier family failed, because a leak must never be hidden by a red line.
addFlow('hygiene', 'hygiene.flow-manifest', async () => {
requireThat(flows.length >= 130, `only ${flows.length} flows registered`);
const declared = new Set(MATRIX_FAMILIES);
for (const flow of flows) requireThat(declared.has(flow.family), `flow ${flow.name} has an undeclared family`);
for (const family of MATRIX_FAMILIES) {
requireThat(familyTotals.get(family).total > 0, `family ${family} registered no flows`);
}
return `${flows.length} flows across ${MATRIX_FAMILIES.length} families`;
});
addFlow('hygiene', 'hygiene.no-secret-leak', async () => {
const haystack = [...captured, ...responseTexts].join('\n');
for (const secret of MATRIX_SECRETS) {
requireThat(!haystack.includes(secret.value), `${secret.label} appeared in stdout/stderr or an HTTP body`);
}
return `${MATRIX_SECRETS.length} synthetic secrets absent from output and API bodies`;
});
for (const flow of flows) {
await runFlow(flow);
}
// Per-family census. A family with zero flows is a hole in the matrix, not a
// green result, so it fails the run.
let familiesGreen = 0;
for (const family of MATRIX_FAMILIES) {
const totals = familyTotals.get(family);
if (totals.total === 0) {
failed++;
exitCode = 1;
out(`FAIL [${family}] family-manifest — no flow registered for this family`);
continue;
}
if (totals.passed === totals.total) familiesGreen++;
out(`FAMILY ${family} ${totals.passed}/${totals.total}`);
}
out(`SMOKE-MATRIX SUMMARY: ${passed}/${flows.length} flows passed, ${failed} failed, ${familiesGreen}/${MATRIX_FAMILIES.length} families green`);
}
async function cleanup() {
try { seams.login?.(null); } catch { /* best-effort */ }
try { seams.otp?.(null); } catch { /* best-effort */ }
try { seams.postJson?.(null); } catch { /* best-effort */ }
try { seams.catalog?.(null); } catch { /* best-effort */ }
try { seams.fetch?.(null); } catch { /* best-effort */ }
try { seams.streamChat?.(null); } catch { /* best-effort */ }
try { seams.preflight?.({}); } catch { /* best-effort */ }
try { seams.reloginDeps?.(null); } catch { /* best-effort */ }
if (server) {
await new Promise((resolve) => {
let settled = false;
const finish = () => { if (!settled) { settled = true; resolve(); } };
server.close(finish);
server.closeAllConnections?.();
const timer = setTimeout(finish, 2000);
timer.unref?.();
});
}
for (const dir of [DATA_DIR, ACP_DIR]) {
try { rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ }
}
}
try {
await main();
} catch (err) {
failed++;
exitCode = 1;
out(`FAIL [boot] boot — ${err?.message || String(err)}`);
}
await cleanup();
if (!/SMOKE-MATRIX SUMMARY:/.test(captured.join(''))) {
out(`SMOKE-MATRIX SUMMARY: ${passed}/${flows.length} flows passed, ${failed} failed, 0/${MATRIX_FAMILIES.length} families green`);
}
await flushStdio();
process.exit(exitCode);