Initial Commit

This commit is contained in:
jb
2026-09-18 23:15:05 -04:00
commit fdcc4863f5
118 changed files with 16868 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import { createLocalJWKSet, jwtVerify } from 'jose';
import { configuration, requireLiveConfig } from '../src/lib/server/config';
import { applicationJwt, TokenCipher } from '../src/lib/server/security';
let stage = 'required live settings';
try {
const config = configuration();
requireLiveConfig(config);
stage = 'encryption-key round trip';
const cipher = new TokenCipher(config.encryptionKey);
const encrypted = await cipher.seal({ sentinel: true }, 'configuration-check');
const decrypted = await cipher.open<{ sentinel: boolean }>(encrypted, 'configuration-check');
if (decrypted.sentinel !== true) throw new Error();
stage = 'application signing key / public JWKS match';
const jwksValue = process.env.APP_AUTH_JWKS ?? '';
const prefix = 'data:application/json;base64,';
if (!jwksValue.startsWith(prefix)) throw new Error();
const jwks = createLocalJWKSet(JSON.parse(Buffer.from(jwksValue.slice(prefix.length), 'base64').toString('utf8')));
const jwt = await applicationJwt({ userId: 'configuration-check', sessionId: 'configuration-check', issuer: config.issuer, privateKey: config.privateKey });
await jwtVerify(jwt, jwks, { issuer: config.issuer, audience: 'eve-intelligence', algorithms: ['RS256'] });
console.log('Live configuration passed: required settings, encryption round trip, and matching application JWT keys. No secret values displayed.');
} catch {
console.error(`Live configuration failed at: ${stage}. Values and raw errors suppressed.`);
process.exitCode = 1;
}
+12
View File
@@ -0,0 +1,12 @@
export {};
// Run only against your intended self-hosted deployment. Values are never printed.
const names = ['CONVEX_BRIDGE_SECRET', 'APP_AUTH_ISSUER', 'APP_AUTH_JWKS'] as const;
if (!process.env.CONVEX_SELF_HOSTED_URL || !process.env.CONVEX_SELF_HOSTED_ADMIN_KEY) throw new Error('Set self-hosted URL and admin key before configuring Convex.');
for (const name of names) {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
// Pipe values rather than exposing them in process arguments or shell history.
const child = Bun.spawn(['bun', 'node_modules/convex/bin/main.js', 'env', 'set', name], { stdin: new Blob([value]), stdout: 'pipe', stderr: 'pipe' });
if (await child.exited !== 0) throw new Error(`Could not configure ${name}; check deployment URL/admin key (output suppressed to protect secrets).`);
console.log(`Configured ${name}`);
}
+13
View File
@@ -0,0 +1,13 @@
import { generateKeyPair, exportPKCS8, exportJWK } from 'jose';
import { randomBytes } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
const { privateKey, publicKey } = await generateKeyPair('RS256', { extractable: true, modulusLength: 2048 });
const publicJwk = { ...await exportJWK(publicKey), kid: 'app-v1', alg: 'RS256', use: 'sig' };
const values = {
TOKEN_ENCRYPTION_KEY: randomBytes(32).toString('base64'),
CONVEX_BRIDGE_SECRET: randomBytes(32).toString('base64url'),
APP_AUTH_PRIVATE_KEY: Buffer.from(await exportPKCS8(privateKey)).toString('base64'),
APP_AUTH_JWKS: `data:application/json;base64,${Buffer.from(JSON.stringify({ keys: [publicJwk] })).toString('base64')}`
};
await writeFile('.env.keys', Object.entries(values).map(([k, v]) => `${k}='${v}'`).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
console.log('Wrote .env.keys (0600). Import its four values into your Infisical project (dev, /), or a private .env for standalone setup. Never commit it. Existing files are not overwritten.');
+174
View File
@@ -0,0 +1,174 @@
import { constants } from 'node:fs';
import { open } from 'node:fs/promises';
import { parseEnv } from 'node:util';
import { fileURLToPath } from 'node:url';
export interface VaultProject {
workspaceId: string;
defaultEnvironment: string;
domain: string;
secretPath: string;
}
export const BOOTSTRAP_KEYS = ['INFISICAL_UNIVERSAL_AUTH_CLIENT_ID', 'INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET'] as const;
export const APPLICATION_KEYS = [
'EVE_CLIENT_ID', 'EVE_CLIENT_SECRET', 'TYPESAFE_API_KEY', 'TOKEN_ENCRYPTION_KEY',
'CONVEX_BRIDGE_SECRET', 'APP_AUTH_PRIVATE_KEY', 'APP_AUTH_JWKS',
] as const;
export const REQUIRED_KEYS = ['EVE_CLIENT_ID', 'TOKEN_ENCRYPTION_KEY', 'CONVEX_BRIDGE_SECRET', 'APP_AUTH_PRIVATE_KEY', 'APP_AUTH_JWKS'] as const;
export const ADMIN_KEY = 'CONVEX_SELF_HOSTED_ADMIN_KEY';
export type Environment = Record<string, string | undefined>;
export type CliRunner = (args: string[], env: Environment) => Promise<string>;
export function validateProject(input: unknown): VaultProject {
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Invalid Infisical project configuration');
const project = input as Record<string, unknown>;
if (typeof project.workspaceId !== 'string' || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(project.workspaceId)) throw new Error('Invalid Infisical project ID');
if (typeof project.defaultEnvironment !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(project.defaultEnvironment)) throw new Error('Invalid Infisical environment');
// This repository is deliberately pinned to the operator-authorized vault origin.
// A repository config change alone must never send the bootstrap credential elsewhere.
if (project.domain !== 'https://secrets.jb.codes') throw new Error('Infisical origin is not the approved instance');
if (typeof project.secretPath !== 'string' || !/^\/(?:[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*)?$/.test(project.secretPath)) throw new Error('Invalid Infisical secret path');
return project as unknown as VaultProject;
}
export async function readCredentials(path: string): Promise<Record<string, string>> {
let handle;
try {
// Do not follow a symlink or read a world/group-accessible bootstrap secret.
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
const stat = await handle.stat();
if (!stat.isFile() || (stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.() || stat.size > 16_384) {
throw new Error('Invalid credentials file');
}
const parsed = parseEnv(await handle.readFile('utf8'));
const selected: Record<string, string> = {};
for (const name of BOOTSTRAP_KEYS) {
if (!parsed[name]?.trim()) throw new Error('Missing bootstrap credential');
selected[name] = parsed[name];
}
return selected;
} catch {
throw new Error('Cannot read Universal Auth credentials: use a regular, owner-owned 0600 file containing the two documented variables');
} finally {
await handle?.close();
}
}
async function boundedOutput(stream: ReadableStream<Uint8Array>, limit: number): Promise<string> {
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
let bytes = 0;
try {
for (;;) {
const { value, done } = await reader.read();
if (done) break;
bytes += value.byteLength;
if (bytes > limit) throw new Error('Infisical output exceeded its safety limit');
chunks.push(value);
}
} finally { reader.releaseLock(); }
return Buffer.concat(chunks).toString('utf8');
}
async function captureSecretCommand(argv: string[], env: Environment): Promise<string> {
const child = Bun.spawn(argv, { env, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
const deadline = setTimeout(() => child.kill('SIGKILL'), 30_000);
deadline.unref();
try {
const [output, , code] = await Promise.all([
boundedOutput(child.stdout, 2 * 1024 * 1024),
boundedOutput(child.stderr, 64 * 1024),
child.exited,
]);
if (code !== 0) throw new Error('CLI failed');
return output;
} catch {
// CLI errors can embed request details. Never echo raw stdout/stderr or credentials.
throw new Error('Credential command failed; check permissions and connectivity (raw output suppressed)');
} finally {
clearTimeout(deadline);
if (child.exitCode === null) child.kill('SIGKILL');
}
}
export const runCli: CliRunner = async (args, env) => {
const executable = fileURLToPath(new URL('../../node_modules/.bin/infisical', import.meta.url));
return captureSecretCommand([executable, ...args], env);
};
export async function localDeploymentAdmin(env: Environment, runner = captureSecretCommand): Promise<string> {
if (!['http://127.0.0.1:3210', 'http://localhost:3210'].includes(env.CONVEX_SELF_HOSTED_URL ?? '')) {
throw new Error('Local admin derivation is restricted to this host’s Convex deployment');
}
// The backend derives its admin credential from its persistent random instance
// secret. Docker-host access is already administrative; no second secret file
// or write-enabled Infisical identity is required. Only explicit --deployment
// commands receive this key; Compose never forwards it to the web container.
const key = (await runner(['docker', 'compose', 'exec', '-T', 'backend', './generate_admin_key.sh'], cleanEnvironment(env))).trim();
if (!key || /\s/.test(key) || key.length > 16_384) throw new Error('Unexpected local admin response; output suppressed');
return key;
}
export function parseExport(text: string): Record<string, string> {
try {
const parsed: unknown = JSON.parse(text);
const values: Record<string, string> = Object.create(null);
const entries: unknown[] = Array.isArray(parsed)
? parsed
: parsed && typeof parsed === 'object'
? Object.entries(parsed).map(([key, value]) => ({ key, value }))
: [];
if (!parsed || typeof parsed !== 'object') throw new Error();
for (const entry of entries) {
if (!entry || typeof entry !== 'object') throw new Error();
const row = entry as Record<string, unknown>;
const key = row.key ?? row.secretKey;
const value = row.value ?? row.secretValue;
if (typeof key !== 'string' || !/^[A-Z][A-Z0-9_]*$/.test(key) || typeof value !== 'string' || Object.hasOwn(values, key)) throw new Error();
values[key] = value;
}
return values;
} catch { throw new Error('Invalid Infisical export; values suppressed'); }
}
export async function fetchSecrets(project: VaultProject, credentials: Record<string, string>, runner: CliRunner = runCli, base: Environment = process.env): Promise<Record<string, string>> {
validateProject(project);
for (const name of BOOTSTRAP_KEYS) if (!credentials[name]) throw new Error(`Missing ${name}`);
const env = cleanEnvironment(base);
const common = ['--domain', project.domain, '--silent', '--telemetry=false'];
const bootstrap = Object.fromEntries(BOOTSTRAP_KEYS.map(key => [key, credentials[key]]));
const token = (await runner(['login', '--method=universal-auth', '--plain', ...common], { ...env, ...bootstrap })).trim();
if (!token || /\s/.test(token) || token.length > 16_384) throw new Error('Unexpected Infisical login response; values suppressed');
const output = await runner([
'export', '--projectId', project.workspaceId, '--env', project.defaultEnvironment, '--path', project.secretPath,
'--format=json', '--expand=false', '--include-imports=false', '--secret-overriding=false', ...common,
], { ...env, INFISICAL_TOKEN: token });
return parseExport(output);
}
export function cleanEnvironment(base: Environment): Environment {
const managed = new Set<string>([ADMIN_KEY, ...APPLICATION_KEYS]);
return Object.fromEntries(Object.entries(base).filter(([key]) => !key.startsWith('INFISICAL_') && !managed.has(key)));
}
const PUBLIC_KEYS = new Set([
'APP_MODE', 'APP_ORIGIN', 'APP_AUTH_ISSUER', 'EVE_CALLBACK_URL', 'PUBLIC_CONVEX_URL',
'CONVEX_URL', 'CONVEX_SELF_HOSTED_URL', 'CONVEX_SITE_ORIGIN', 'REFERENCE_DIR', 'ESI_USER_AGENT',
'WEB_BIND_ADDRESS', 'CONVEX_BIND_ADDRESS', 'WEB_PORT', 'CONVEX_PORT', 'CONVEX_SITE_PORT',
'CONVEX_IMAGE', 'CONVEX_DASHBOARD_IMAGE',
]);
export function applicationEnvironment(base: Environment, secrets: Record<string, string>, publicConfig: Environment, deployment = false): Environment {
const env = cleanEnvironment(base);
// Public file is the configured deployment, not defaults inherited from Bun's .env.local.
for (const [key, value] of Object.entries(publicConfig)) {
if (!PUBLIC_KEYS.has(key) || typeof value !== 'string') throw new Error('Unexpected public deployment configuration key');
env[key] = value;
}
// Managed app keys must come from the selected vault, never a stale ambient .env.
for (const key of APPLICATION_KEYS) {
delete env[key];
if (secrets[key] !== undefined) env[key] = secrets[key];
}
if (deployment && secrets[ADMIN_KEY]) env[ADMIN_KEY] = secrets[ADMIN_KEY];
return env;
}
+78
View File
@@ -0,0 +1,78 @@
import { homedir } from 'node:os';
import { join, resolve } from 'node:path';
import { readFile } from 'node:fs/promises';
import { parseEnv } from 'node:util';
import { ADMIN_KEY, applicationEnvironment, fetchSecrets, localDeploymentAdmin, readCredentials, REQUIRED_KEYS, validateProject } from './lib/infisical';
const HELP = `Infisical-backed development deployment (secret values are never printed by this helper)
bun run secrets:status
bun run secrets:run -- <trusted command> [arguments...]
bun run secrets:run --deployment -- <trusted deployment command> [arguments...]
Optional: --credentials-file PATH (default ~/.config/evebot/infisical.env)
Project metadata: .infisical.json. Public deployment configuration: config/dev.env.
Only allowlisted app secrets are injected. --deployment additionally permits the Convex admin key
and, when absent from the vault, derives it from the local persistent backend without printing it.
Never use this wrapper with printenv, env, shell tracing, or docker compose config without --quiet.
`;
export function parseArguments(args: string[]) {
const [action, ...rest] = args;
if (action !== 'status' && action !== 'run') throw new Error('Use status or run');
let command: string[] = [];
let credentialsFile = join(homedir(), '.config/evebot/infisical.env');
let deployment = false;
for (let i = 0; i < rest.length; i++) {
if (rest[i] === '--') { command = rest.slice(i + 1); break; }
if (rest[i] === '--credentials-file' && rest[i + 1] && !rest[i + 1].startsWith('--')) credentialsFile = resolve(rest[++i]);
else if (rest[i] === '--deployment') deployment = true;
// `bun run` consumes its own -- separator before forwarding script arguments.
else if (action === 'run' && !rest[i].startsWith('-')) { command = rest.slice(i); break; }
else throw new Error('Unknown or incomplete secrets helper option');
}
if (action === 'run' && !command.length) throw new Error('Supply a trusted command after --');
if (action === 'status' && command.length) throw new Error('status does not execute a command');
return { action, command, credentialsFile, deployment };
}
async function main(args: string[]): Promise<void> {
if (!args.length || args[0] === '--help') { console.log(HELP); return; }
const { action, command, credentialsFile, deployment } = parseArguments(args);
const project = validateProject(JSON.parse(await readFile(new URL('../.infisical.json', import.meta.url), 'utf8')));
const credentials = await readCredentials(credentialsFile);
const secrets = await fetchSecrets(project, credentials);
if (action === 'status') {
console.log(JSON.stringify({
authenticated: true,
domain: project.domain, projectId: project.workspaceId,
environment: project.defaultEnvironment, secretPath: project.secretPath,
secretNames: Object.keys(secrets).sort(),
missingRequiredKeys: REQUIRED_KEYS.filter((key) => !secrets[key]?.trim()),
values: 'not displayed',
}, null, 2));
return;
}
const publicConfig = parseEnv(await readFile(new URL('../config/dev.env', import.meta.url), 'utf8'));
const env = applicationEnvironment(process.env, secrets, publicConfig, deployment);
if (deployment && !env[ADMIN_KEY]) env[ADMIN_KEY] = await localDeploymentAdmin(env);
const child = Bun.spawn(command, { env, stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' });
const interrupt = () => child.kill('SIGINT');
const terminate = () => child.kill('SIGTERM');
process.on('SIGINT', interrupt);
process.on('SIGTERM', terminate);
try { process.exitCode = await child.exited; }
finally {
process.off('SIGINT', interrupt);
process.off('SIGTERM', terminate);
}
}
if (import.meta.main) {
try { await main(process.argv.slice(2)); }
catch (error) {
// Never print stack/source excerpts or raw CLI errors around the secret boundary.
console.error(error instanceof Error ? error.message : 'Secret configuration failed');
process.exitCode = 1;
}
}
+42
View File
@@ -0,0 +1,42 @@
import { ConvexHttpClient } from 'convex/browser';
import { makeFunctionReference } from 'convex/server';
import { ConvexStore } from '../src/lib/server/store';
import { applicationJwt, hashSecret, randomSecret } from '../src/lib/server/security';
import assert from 'node:assert/strict';
import { SignJWT, importPKCS8 } from 'jose';
if (process.env.SMOKE_ALLOW_WRITE !== '1') throw new Error('This smoke test writes synthetic records. Set SMOKE_ALLOW_WRITE=1 only for a disposable deployment.');
const url = process.env.CONVEX_SELF_HOSTED_URL!;
const store = new ConvexStore(url, process.env.CONVEX_BRIDGE_SECRET!);
const now = Date.now(); const hash = hashSecret(randomSecret());
const characterId = 90_000_000 + Math.floor(Math.random() * 1_000_000);
const user = await store.completeLogin({ characterId, name: 'Disposable Test Pilot', ownerHash: hashSecret(randomSecret()), scopes: ['esi-assets.read_assets.v1'], encryptedTokens: 'test-only-encrypted-placeholder', tokenExpiresAt: now + 60_000, sessionHash: hash, sessionExpiresAt: now + 60_000 });
assert.equal((await store.session(hash))?.userId, user.userId);
const generation = await store.beginSync(user.userId, characterId);
await store.stageAssets(user.userId, characterId, generation, 0, [{ itemId: '9223372036854775806', characterId, typeId: 34, quantity: 50, locationId: '60003760', locationType: 'station', locationFlag: 'Hangar' }]);
await store.finishSync(user.userId, characterId, generation, 1, now, now + 3_600_000);
assert.equal((await store.assets(user.userId, characterId))[0]!.quantity, 50);
let isolated = false;
try { isolated = (await store.assets('other-user', characterId)).length === 0; } catch { isolated = true; }
assert(isolated);
const failure = await store.beginSync(user.userId, characterId);
await store.stageAssets(user.userId, characterId, failure, 0, []);
await store.failSync(user.userId, characterId, failure, 'test failure');
assert.equal((await store.assets(user.userId, characterId))[0]!.quantity, 50);
const client = new ConvexHttpClient(url, { skipConvexDeploymentUrlCheck: true, logger: false });
client.setAuth(await applicationJwt({ ...user, issuer: process.env.APP_AUTH_ISSUER!, privateKey: process.env.APP_AUTH_PRIVATE_KEY! }));
const rows = await client.query(makeFunctionReference<'query', Record<string, never>, unknown[] | null>('status:characters'), {});
assert.equal(rows?.length, 1);
assert(!JSON.stringify(rows).includes('encryptedTokens'));
const wrongAudience = new ConvexHttpClient(url, { skipConvexDeploymentUrlCheck: true, logger: false });
const key = await importPKCS8(Buffer.from(process.env.APP_AUTH_PRIVATE_KEY!, 'base64').toString('utf8'), 'RS256');
wrongAudience.setAuth(await new SignJWT({ sid: user.sessionId }).setProtectedHeader({ alg: 'RS256', typ: 'JWT', kid: 'app-v1' }).setIssuer(process.env.APP_AUTH_ISSUER!).setSubject(user.userId).setAudience('not-eve-intelligence').setIssuedAt().setExpirationTime('5m').sign(key));
let wrongAudienceDenied = false;
try { const rows = await wrongAudience.query(makeFunctionReference<'query', Record<string, never>, unknown[] | null>('status:characters'), {}); wrongAudienceDenied = rows === null; } catch { wrongAudienceDenied = true; }
assert(wrongAudienceDenied, 'Convex must reject a correctly signed token for another audience');
await store.deleteSession(hash);
assert.equal(await store.session(hash), null);
let revoked = false;
try { const result = await client.query(makeFunctionReference<'query', Record<string, never>, unknown[] | null>('status:characters'), {}); revoked = result === null || result.length === 0; } catch { revoked = true; }
assert(revoked);
console.log('Real self-hosted Convex smoke passed: login/session, snapshot promotion, failure preservation, tenant isolation, JWT-safe projection and revocation.');
+16
View File
@@ -0,0 +1,16 @@
import { JevIntentRouter, JEV_MODEL } from '../src/lib/server/assistant/intent';
if (process.env.LIVE_API_SMOKE !== '1') throw new Error('Set LIVE_API_SMOKE=1 to authorize one real TypeSafe inference request.');
if (!process.env.TYPESAFE_API_KEY) throw new Error('TYPESAFE_API_KEY is missing.');
const started = performance.now();
const result = await new JevIntentRouter({ apiKey: process.env.TYPESAFE_API_KEY }).classify(
'What is the total estimated reference value of all my assets across all connected characters?',
);
if (result.status !== 'routed' || result.intent !== 'asset_valuation') {
// The router already redacts provider failures. Do not print raw SDK errors.
console.error('Live JEV routing smoke did not return the expected valuation intent.', { status: result.status });
process.exitCode = 1;
} else {
console.log(JSON.stringify({ passed: true, requestedModel: JEV_MODEL, reportedModel: result.model, intent: result.intent, confidence: result.confidence, elapsedMs: Math.round(performance.now() - started) }));
}
+22
View File
@@ -0,0 +1,22 @@
import assert from 'node:assert/strict';
const base = process.env.SMOKE_WEB_BASE ?? 'http://127.0.0.1:5173';
const origin = process.env.APP_ORIGIN ?? 'https://dev.evebot.app';
for (const path of ['/', '/assets', '/characters']) {
const response = await fetch(`${base}${path}`, { redirect: 'manual' });
assert.equal(response.status, 200, `SSR ${path}`);
assert(!(await response.text()).includes('FIXTURE DEMO'), 'live app must not be demo mode');
}
for (const message of ['/assets', '/value']) {
const response = await fetch(`${base}/api/chat`, {
method: 'POST', headers: { origin, 'content-type': 'application/json' },
body: JSON.stringify({ message, requestId: crypto.randomUUID() }),
});
assert.equal(response.status, 200);
const result = await response.json();
assert.equal(result.status, 'authentication_required');
assert.equal(result.operation?.status, 'authentication_required');
}
assert.equal((await fetch(`${base}/api/session/token`)).status, 401);
assert.equal((await fetch(`${base}/api/sync`, { method: 'POST', headers: { origin: 'https://untrusted.example' } })).status, 403);
console.log('Live anonymous web smoke passed: SSR, no demo fallback, asset/chat authentication gates, session-token protection, and CSRF. No EVE login or character data accessed.');
+11
View File
@@ -0,0 +1,11 @@
import assert from 'node:assert/strict';
import { EsiClient, EsiMarketEstimates } from '../src/lib/server/esi';
if (process.env.LIVE_API_SMOKE !== '1') throw new Error('Set LIVE_API_SMOKE=1 to make a real public ESI prices request.');
const userAgent = process.env.ESI_USER_AGENT;
if (!userAgent) throw new Error('Set an identifying ESI_USER_AGENT.');
const snapshot = await new EsiMarketEstimates(new EsiClient({ userAgent })).estimates([34, 587]);
assert.equal(snapshot.prices.length, 2);
assert(snapshot.expiresAt > Date.now(), 'price snapshot must be fresh');
for (const price of snapshot.prices) assert(BigInt(price.unitPrice) > 0n);
console.log(JSON.stringify({ publicEsiPrices: 'passed', source: snapshot.source, asOf: snapshot.asOf, expiresAt: snapshot.expiresAt, prices: snapshot.prices, meaning: 'Global reference estimates, not Jita bids or executable quotes' }));
+20
View File
@@ -0,0 +1,20 @@
import assert from 'node:assert/strict';
const base = process.env.SMOKE_WEB_BASE ?? 'http://localhost:15173';
const root = await fetch(base); assert.equal(root.status, 200);
assert((await root.text()).includes('FIXTURE DEMO'));
const characters = await fetch(`${base}/characters`); assert.equal(characters.status, 200);
assert((await characters.text()).includes('Fixture Pilot 1'));
const assets = await fetch(`${base}/assets`); assert.equal(assets.status, 200);
const html = await assets.text(); assert(html.includes('2,086,000.00'));
assert(html.includes('Unpriced'));
const request = (message: string, origin = base) => fetch(`${base}/api/chat`, { method: 'POST', headers: { origin, 'content-type': 'application/json' }, body: JSON.stringify({ message, requestId: crypto.randomUUID() }) });
for (const command of ['/value', '/assets']) {
const response = await request(command); assert.equal(response.status, 200);
const result = await response.json(); assert.equal(result.status, 'ready');
assert.equal(result.operation.report.total, '208600000');
assert.equal(result.operation.report.characters.length, 2);
assert.equal(result.operation.report.unpricedItems, 2);
}
assert.equal((await request('/value', 'https://untrusted.example')).status, 403);
assert.equal((await request('x'.repeat(20_000))).status, 413);
console.log('Production fixture smoke passed: SSR assistant/characters/assets, shared chat/browse totals, exclusions, CSRF and body limits.');