{"repo": "androoagi/starnet", "files": {"test/sidecar.http.test.js": {"606027f69a39f104774733d291224243a9fff9ce": {"t": 1785745559.914128, "n": 95398, "h": "3d672e6f558b3348", "c": "/* node test/sidecar.http.test.js — boot-level end-to-end test of the real Node host (sidecar/index.js).\n Spawns the actual server as a child process against an ISOLATED temp workspace (SKYNET_WORKSPACES) on an\n ephemeral port, then drives the cost-spine HTTP surface over real sockets:\n - GET /api/health\n - GET /api/budget/status (reads a PRE-SEEDED ledger -> pools reflect persisted spend)\n - POST /api/budget/resume (valid scope lifts the cap; bad scope 400s)\n - POST /api/run (missing key/model 400s — the guard path, zero spend)\n Zero network dependence (the catalog warm fails closed offline) and ZERO model spend — it never sends a key.\n This is the one test that proves the server actually BOOTS and the new routes are wired, not just the units.\n\n NOT in test:fast (a child-process boot test shouldn't gate ~25 other agents' merges); run via `npm run test:http`. */\n'use strict';\nconst A = require('./_assert.js');\nconst { spawn } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\nconst http = require('http');\nconst crypto = require('crypto');\nconst edge = require('../sidecar/edgetts.js'); // V-EDGE: exported WS codec + secMsGec for offline unit + loopback tests\nconst { bootToken } = require('./_httpToken.js');\n\nconst HOST = '127.0.0.1';\nconst INDEX = path.resolve(__dirname, '..', 'sidecar', 'index.js');\nconst IPC_TOKEN = 'ipc-provider-config-test-token';\n\nfunction sleep(ms) { return new Promise(r => setTimeout(r, ms)); }\n\n// spawn the host on a candidate port; resolve once it logs \"listening\", retry the next port on EADDRINUSE.\nfunction boot(port, workspaces, attemptsLeft, extraEnv) {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [INDEX], {\n env: Object.assign({}, process.env, {\n SKYNET_PORT: String(port),\n SKYNET_WORKSPACES: workspaces,\n // keep the managed-credits path OFF for the default boot so /api/credits proves the unconfigured 404 path\n // deterministically (a dev machine with these set in the ambient env would otherwise flip the assertion).\n STARNET_CREDITS_URL: '', SKYNET_CREDITS_URL: '',\n STARNET_CREDITS_API_KEY: '', SKYNET_CREDITS_API_KEY: '',\n STARNET_CREDITS_ACCOUNT: '', SKYNET_CREDITS_ACCOUNT: '',\n SKYNET_IPC_TOKEN: IPC_TOKEN,\n // clear the OpenRouter key so the voice routes' no-key fallback path is deterministic (a dev machine\n // with the key in its ambient env would otherwise hit the network and flip the {text:''} assertions).\n OPENROUTER_KEY: '', STARNET_OPENROUTER_KEY: '', SKYNET_OPENROUTER_KEY: '',\n OPENROUTER_API_KEY: '', STARNET_OPENROUTER_API_KEY: '', SKYNET_OPENROUTER_API_KEY: '',\n ELEVENLABS_API_KEY: '', // same determinism for the ElevenLabs TTS branch's no-key degrade\n // V-EDGE: disable the free Edge neural floor in the DEFAULT boot so the no-key TTS test degrades to\n // {fallback:true} deterministically without any network attempt. The dedicated edge-loopback boot below\n // re-enables it (SKYNET_EDGE_TTS + host/port overrides) to prove the round-trip offline.\n SKYNET_EDGE_TTS: '0', STARNET_EDGE_TTS: '0',\n // /api/tts now resolves a CHAIN of credentials (openrouter → gemini → openai) — clear the Gemini\n // keys too, or an ambient dev key would hit the network and flip the no-key degrade assertions.\n GEMINI_API_KEY: '', STARNET_GEMINI_API_KEY: '', SKYNET_GEMINI_API_KEY: '',\n GOOGLE_API_KEY: '', STARNET_GOOGLE_API_KEY: '', SKYNET_GOOGLE_API_KEY: '',\n GOOGLE_AI_API_KEY: '', STARNET_GOOGLE_AI_API_KEY: '', SKYNET_GOOGLE_AI_API_KEY: '',\n OPENAI_API_KEY: '',\n STARNET_OPENAI_API_KEY: '',\n SKYNET_OPENAI_API_KEY: '',\n CUSTOM_OPENAI_KEY: '',\n STARNET_CUSTOM_OPENAI_KEY: '',\n SKYNET_CUSTOM_OPENAI_KEY: '',\n XAI_API_KEY: '',\n STARNET_XAI_API_KEY: '',\n SKYNET_XAI_API_KEY: '',\n X_AI_API_KEY: '',\n STARNET_X_AI_API_KEY: '',\n SKYNET_X_AI_API_KEY: '',\n GROQ_API_KEY: '',\n STARNET_GROQ_API_KEY: '',\n SKYNET_GROQ_API_KEY: '',\n MISTRAL_API_KEY: '',\n STARNET_MISTRAL_API_KEY: '',\n SKYNET_MISTRAL_API_KEY: '',\n DEEPSEEK_API_KEY: '',\n STARNET_DEEPSEEK_API_KEY: '',\n SKYNET_DEEPSEEK_API_KEY: '',\n TOGETHER_API_KEY: '',\n STARNET_TOGETHER_API_KEY: '',\n SKYNET_TOGETHER_API_KEY: '',\n FIREWORKS_API_KEY: '',\n STARNET_FIREWORKS_API_KEY: '',\n SKYNET_FIREWORKS_API_KEY: '',\n PERPLEXITY_API_KEY: '',\n STARNET_PERPLEXITY_API_KEY: '',\n SKYNET_PERPLEXITY_API_KEY: '',\n CEREBRAS_API_KEY: '',\n STARNET_CEREBRAS_API_KEY: '',\n SKYNET_CEREBRAS_API_KEY: '',\n CUSTOM_OPENAI_BASE_URL: '',\n STARNET_CUSTOM_OPENAI_BASE_URL: '',\n SKYNET_CUSTOM_OPENAI_BASE_URL: '',\n // GROUND_UP_AUDIT P2: the packaged app injects its version via STARNET_APP_VERSION (src-tauri/ isn't a\n // bundled resource, so /api/version's conf lookup returns blank there). Set it so /api/version proves the\n // Budget-legibility pass (2026-07-23): the SHIPPED defaults are now perRun $10 with every cross-run pool\n // UNGOVERNED (0). This suite's budget assertions prove ENFORCEMENT mechanics (status pools, one-click\n // resume), so pin the old governed caps via env — deterministic regardless of shipped defaults or a dev\n // machine's ambient SKYNET_BUDGET_* values.\n SKYNET_BUDGET_PER_RUN: '3', SKYNET_BUDGET_PER_AGENT: '5', SKYNET_BUDGET_PER_DAY: '40', SKYNET_BUDGET_GLOBAL: '100',\n // env-first fallback + honest appSource='env' below.\n STARNET_APP_VERSION: '9.9.9-http-test',\n // Clear any ambient build stamp so the DEFAULT boot deterministically exercises the harness git-describe\n // fallback (harnessSource='git'); a per-boot `extraEnv` (below) sets it to prove the env override wins.\n STARNET_BUILD_DESCRIBE: '', SKYNET_BUILD_DESCRIBE: ''\n }, extraEnv || {}),\n stdio: ['ignore', 'pipe', 'pipe']\n });\n let out = '', settled = false;\n const onData = d => {\n out += d.toString();\n if (!settled && out.indexOf('http://' + HOST + ':' + port) >= 0) { settled = true; resolve({ child, port, output: () => out }); }\n if (!settled && /already in use/i.test(out)) {\n settled = true; try { child.kill(); } catch (_) {}\n if (attemptsLeft > 0) resolve(boot(port + 1, workspaces, attemptsLeft - 1, extraEnv));\n else reject(new Error('no free port'));\n }\n };\n child.stdout.on('data', onData);\n child.stderr.on('data', onData);\n child.on('error', e => { if (!settled) { settled = true; reject(e); } });\n setTimeout(() => { if (!settled) { settled = true; try { child.kill(); } catch (_) {} reject(new Error('boot timeout; output:\\n' + out)); } }, 9000);\n });\n}\n\n(async () => {\n // isolated workspace, PRE-SEEDED with two finished runs inside the trailing-day window (ts ~ now)\n const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'sk-http-'));\n const now = Date.now();\n const seed = [\n { runId: 'r1', agentId: 'alice', turns: 3, usd: 1.25, tokens: 1200, ts: now - 1000 },\n { runId: 'r2', agentId: 'bob', turns: 1, usd: 0.75, tokens: 400, ts: now - 500 }\n ];\n fs.writeFileSync(path.join(ws, 'ledger.jsonl'), seed.map(e => JSON.stringify(e)).join('\\n') + '\\n');\n fs.mkdirSync(path.join(ws, 'channels'), { recursive: true });\n fs.writeFileSync(path.join(ws, 'channels', 'secrets.json'), JSON.stringify({\n version: 1,\n discord: {\n enabled: true,\n token: 'discord-token',\n key: 'sk-or-v1-fake-discord',\n model: 'anthropic/claude-sonnet-4.6',\n provider: 'openrouter',\n agentId: 'agent',\n ownerId: 'discord-owner-42'\n }\n }));\n\n const booted = await boot(8820 + (process.pid % 60), ws, 20);\n const { child, port } = booted;\n const B = 'http://' + HOST + ':' + port;\n let apiToken = '';\n const j = async (m, p, body) => {\n const headers = { 'Content-Type': 'application/json' };\n if (apiToken) headers['X-StarNet-Token'] = apiToken; // hardened: GET data routes need the token too now\n const r = await fetch(B + p, { method: m, headers, body: body ? JSON.stringify(body) : undefined });\n const t = await r.text(); let v; try { v = JSON.parse(t); } catch (_) { v = t; }\n return { status: r.status, body: v };\n };\n\n try {\n // ---- health ----\n const health = await j('GET', '/api/health');\n A.eq(health.status, 200, 'GET /api/health -> 200');\n A.eq(health.body, 'ok', 'health body is ok');\n\n // the served page injects the token for browser mode (the primary delivery path)\n const injected = await (await fetch(B + '/')).text();\n A.ok(/__STARNET_API_TOKEN__/.test(injected), 'served index.html bootstraps the API token for browser mode');\n apiToken = await bootToken(B, B);\n A.ok(apiToken.length >= 32, 'served index.html carries a high-entropy API token');\n const tok = { 'X-StarNet-Token': apiToken };\n const tauriOrigin = 'http://tauri.localhost';\n\n const sessNoToken = await fetch(B + '/api/session', { method: 'POST', headers: { Origin: B } });\n A.eq(sessNoToken.status, 403, 'POST /api/session without X-StarNet-Token -> 403');\n const sess = await fetch(B + '/api/session', { method: 'POST', headers: Object.assign({ Origin: B }, tok) });\n A.eq(sess.status, 200, 'POST /api/session with token -> 200');\n const sessBody = await sess.json();\n A.eq(sessBody.token, undefined, 'POST /api/session never echoes the API token');\n\n // ---- localhost API hardening: CORS mirrors only trusted origins; foreign origins rejected ----\n const sameOriginNoToken = await fetch(B + '/api/budget/status', { headers: { Origin: B } });\n A.eq(sameOriginNoToken.status, 403, 'same-origin sensitive GET without token -> 403');\n const sameOrigin = await fetch(B + '/api/budget/status', { headers: Object.assign({ Origin: B }, tok) });\n A.eq(sameOrigin.status, 200, 'same-origin API request (with token) -> 200');\n A.eq(sameOrigin.headers.get('access-control-allow-origin'), B, 'same-origin CORS mirrors the exact loopback origin');\n\n const tauri = await fetch(B + '/api/budget/status', { headers: Object.assign({ Origin: tauriOrigin }, tok) });\n A.eq(tauri.status, 200, 'Tauri app origin API request -> 200');\n A.eq(tauri.headers.get('access-control-allow-origin'), tauriOrigin, 'Tauri CORS mirrors the trusted app origin');\n\n const badOrigin = await fetch(B + '/api/budget/status', { headers: { Origin: 'https://evil.example' } });\n A.eq(badOrigin.status, 403, 'foreign web origin API request -> 403');\n A.eq(badOrigin.headers.get('access-control-allow-origin'), null, 'foreign origin gets no CORS read access');\n const badOriginWithToken = await fetch(B + '/api/budget/status', { headers: Object.assign({ Origin: 'https://evil.example' }, tok) });\n A.eq(badOriginWithToken.status, 403, 'foreign web origin with a valid token is still blocked');\n\n const preflight = await fetch(B + '/api/run', { method: 'OPTIONS', headers: { Origin: B, 'Access-Control-Request-Method': 'POST' } });\n A.eq(preflight.status, 204, 'trusted API preflight -> 204');\n A.eq(preflight.headers.get('access-control-allow-origin'), B, 'trusted preflight mirrors loopback origin');\n const preflightMethods = preflight.headers.get('access-control-allow-methods') || '';\n A.ok(preflightMethods.split(',').map(s => s.trim()).indexOf('DELETE') >= 0, 'trusted preflight advertises the DELETE routes the API actually serves');\n const badPreflight = await fetch(B + '/api/run', { method: 'OPTIONS', headers: { Origin: 'https://evil.example', 'Access-Control-Request-Method': 'POST' } });\n A.eq(badPreflight.status, 403, 'foreign API preflight -> 403');\n\n // ---- C2 hole closed: GET DATA routes now require the token, not just POSTs ----\n const getNoTok = await fetch(B + '/api/budget/status');\n A.eq(getNoTok.status, 403, 'GET /api/budget/status WITHOUT a token -> 403 (GET data routes are gated now)');\n const getWithTok = await fetch(B + '/api/budget/status', { headers: tok });\n A.eq(getWithTok.status, 200, 'GET /api/budget/status WITH the token -> 200');\n\n // ---- T3.9 DIAGNOSTICS: token-gated, assembled server-side from real state, and SECRET-FREE ----\n const diagNoTok = await fetch(B + '/api/diagnostics');\n A.eq(diagNoTok.status, 403, 'GET /api/diagnostics WITHOUT a token -> 403 (gated like other data routes)');\n const diag = await j('GET', '/api/diagnostics');\n A.eq(diag.status, 200, 'GET /api/diagnostics WITH the token -> 200');\n A.ok(diag.body && typeof diag.body.text === 'string' && diag.body.text.length > 0, 'diagnostics returns a paste-ready text block');\n A.ok(diag.body.report && typeof diag.body.report === 'object', 'diagnostics returns a structured report too');\n const desktopDiagResponse = await fetch(B + '/api/diagnostics', { headers: Object.assign({ Origin: tauriOrigin }, tok) });\n const desktopDiag = await desktopDiagResponse.json();\n A.eq(desktopDiag.report.mode, 'desktop', 'http://tauri.localhost is classified as the packaged desktop origin');\n A.ok(/StarNet diagnostics/.test(diag.body.text), 'the block is clearly fenced');\n A.ok(/no keys, tokens, or message content/.test(diag.body.text), 'the block states it carries no secrets');\n A.eq(typeof diag.body.report.keyPresent, 'boolean', 'keyPresent is a boolean, never the key itself');\n // the pre-seeded discord channel carries a fake OpenRouter key (sk-or-v1-fake-discord) + a bot token — NEITHER\n // may ever appear in the diagnostics payload (the whole point of the sanitization law).\n const diagBlob = JSON.stringify(diag.body);\n A.ok(diagBlob.indexOf('sk-or-v1-fake-discord') < 0, 'diagnostics never leaks the seeded provider key');\n A.ok(diagBlob.indexOf('discord-token') < 0, 'diagnostics never leaks the seeded channel token');\n\n // ---- managed credits: UNCONFIGURED boot exposes ZERO surface (honesty law — no dead STORE, no fake balance) ----\n // This boot sets no STARNET_CREDITS_URL, so /api/credits must 404 with { configured: false } — the frontend\n // reads that and renders no STORE card at all.\n const creditsNoTok = await fetch(B + '/api/credits');\n A.eq(creditsNoTok.status, 403, 'GET /api/credits WITHOUT a token -> 403 (gated like other data routes)');\n const creditsUnconf = await j('GET', '/api/credits');\n A.eq(creditsUnconf.status, 404, 'GET /api/credits with no credits backend configured -> 404 (no surface)');\n A.eq(creditsUnconf.body.configured, false, 'the 404 body honestly reports configured:false');\n\n // ---- provider registry/catalog routes: dynamic provider surface boots and remains token-gated ----\n const providersNoTok = await fetch(B + '/api/providers');\n A.eq(providersNoTok.status, 403, 'GET /api/providers WITHOUT a token -> 403');\n const providers = await j('GET', '/api/providers');\n A.eq(providers.status, 200, 'GET /api/providers -> 200');\n A.ok(Array.isArray(providers.body.providers), 'providers route returns a list');\n A.ok(providers.body.providers.some(p => p.id === 'openrouter'), 'providers include openrouter');\n A.ok(providers.body.providers.some(p => p.id === 'anthropic'), 'providers include anthropic');\n A.ok(providers.body.providers.some(p => p.id === 'gemini'), 'providers include gemini');\n for (const id of ['xai', 'groq', 'mistral', 'deepseek', 'together', 'fireworks', 'perplexity', 'cerebras']) {\n A.ok(providers.body.providers.some(p => p.id === id), 'providers include ' + id);\n }\n A.ok(providers.body.providers.some(p => p.id === 'custom'), 'providers include custom OpenAI-compatible');\n const models = await j('GET', '/api/models/openrouter');\n A.eq(models.status, 200, 'GET /api/models/openrouter -> 200');\n A.ok(Array.isArray(models.body.models), 'provider model route returns a models array');\n\n // PU-07: provider probes distinguish a configured endpoint from a reachable/catalog-backed endpoint.\n const offlineProbe = await j('POST', '/api/providers/probe', { provider: 'custom', baseUrl: 'http://127.0.0.1:65530/v1' });\n A.eq(offlineProbe.status, 200, 'POST /api/providers/probe returns a fact payload even when the endpoint is offline');\n A.eq(offlineProbe.body.reachable, false, 'offline custom endpoint is not reachable');\n A.eq(offlineProbe.body.catalogAvailable, false, 'offline custom endpoint has no proven catalog');\n const probeServer = http.createServer((rq, rs) => {\n if (rq.url.indexOf('/chat/completions') >= 0) {\n if (rq.headers.authorization !== 'Bearer probe-good-key') { rs.writeHead(401); return rs.end('rejected'); }\n rs.writeHead(200, { 'Content-Type': 'text/event-stream' });\n rs.write('data: ' + JSON.stringify({ choices: [{ delta: { content: 'OK' } }] }) + '\\n\\n');\n rs.write('data: ' + JSON.stringify({ choices: [{ delta: {}, finish_reason: 'stop' }] }) + '\\n\\n');\n rs.write('data: [DONE]\\n\\n'); return rs.end();\n }\n rs.writeHead(200, { 'Content-Type': 'application/json' });\n rs.end(JSON.stringify({ data: [{ id: 'local/proven-model' }] }));\n });\n await new Promise(resolve => probeServer.listen(0, HOST, resolve));\n try {\n const liveBase = 'http://' + HOST + ':' + probeServer.address().port + '/v1';\n const liveProbe = await j('POST', '/api/providers/probe', { provider: 'custom', baseUrl: liveBase });\n A.eq(liveProbe.body.reachable, true, 'reachable custom endpoint is proven by a real model-catalog round-trip');\n A.eq(liveProbe.body.catalogAvailable, true, 'reachable custom endpoint reports its real non-empty catalog');\n A.eq(liveProbe.body.credentialVerified, true, 'keyless custom endpoint needs no fabricated credential proof');\n const wrongCandidate = await j('POST', '/api/providers/validate', { provider: 'custom', baseUrl: liveBase, key: 'probe-wrong-key', model: 'local/proven-model' });\n A.eq(wrongCandidate.body.credentialVerified, false, 'candidate-key validation rejects a key that fails the inference wire');\n const goodCandidate = await j('POST', '/api/providers/validate', { provider: 'custom', baseUrl: liveBase, key: 'probe-good-key', model: 'local/proven-model' });\n A.eq(goodCandidate.body.credentialVerified, true, 'candidate-key validation proves the exact key on the inference wire');\n } finally { await new Promise(resolve => probeServer.close(resolve)); }\n\n const pushOpenAi = await fetch(B + '/api/key', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Skynet-Token': IPC_TOKEN },\n body: JSON.stringify({ provider: 'openai', key: 'sk-provider-http-test-secret' })\n });\n A.eq(pushOpenAi.status, 200, 'POST /api/key can push an OpenAI provider key with the IPC token');\n const openAiAck = await pushOpenAi.json();\n A.eq(openAiAck.provider, 'openai', 'provider key ack names the configured provider');\n A.eq(openAiAck.configured, true, 'provider key ack reports configured');\n A.ok(JSON.stringify(openAiAck).indexOf('sk-provider-http-test-secret') < 0, 'provider key ack never echoes the secret');\n\n const pushOpenAiPool = await fetch(B + '/api/key', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Skynet-Token': IPC_TOKEN },\n body: JSON.stringify({ provider: 'openai', keyPool: ['openai-backup-a', 'openai-backup-b'] })\n });\n const openAiPoolAck = await pushOpenAiPool.json();\n A.eq(openAiPoolAck.alternateCount, 2, 'OpenAI alternate pool is accepted only under the OpenAI provider id');\n A.ok(JSON.stringify(openAiPoolAck).indexOf('openai-backup') < 0, 'alternate-pool acknowledgement never echoes a secret');\n\n const pushAnthropic = await fetch(B + '/api/key', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Skynet-Token': IPC_TOKEN },\n body: JSON.stringify({ provider: 'anthropic', key: 'sk-ant-provider-http-test-secret' })\n });\n A.eq(pushAnthropic.status, 200, 'POST /api/key can push an Anthropic provider key with the IPC token');\n const anthropicAck = await pushAnthropic.json();\n A.eq(anthropicAck.provider, 'anthropic', 'Anthropic provider key ack names the configured provider');\n A.eq(anthropicAck.configured, true, 'Anthropic provider key ack reports configured');\n\n const pushXai = await fetch(B + '/api/key', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Skynet-Token': IPC_TOKEN },\n body: JSON.stringify({ provider: 'x-ai', key: 'xai-provider-http-test-secret' })\n });\n A.eq(pushXai.status, 200, 'POST /api/key can push an xAI provider key by alias with the IPC token');\n const xaiAck = await pushXai.json();\n A.eq(xaiAck.provider, 'xai', 'xAI provider key ack names the canonical provider');\n A.eq(xaiAck.configured, true, 'xAI provider key ack reports configured');\n\n const pushCustomBase = await fetch(B + '/api/key', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Skynet-Token': IPC_TOKEN },\n body: JSON.stringify({ provider: 'custom', baseUrl: 'http://127.0.0.1:65530/v1' })\n });\n A.eq(pushCustomBase.status, 200, 'POST /api/key can push custom provider base URL with the IPC token');\n const configuredProviders = await j('GET', '/api/providers');\n const openaiProvider = configuredProviders.body.providers.find(p => p.id === 'openai');\n const xaiProvider = configuredProviders.body.providers.find(p => p.id === 'xai');\n const customProvider = configuredProviders.body.providers.find(p => p.id === 'custom');\n A.eq(!!(openaiProvider && openaiProvider.configured), true, 'OpenAI provider is configured after scoped key push');\n A.eq(!!(xaiProvider && xaiProvider.configured), true, 'xAI provider is configured after scoped key push');\n A.eq(!!(customProvider && customProvider.configured), true, 'Custom provider is configured after scoped base URL push');\n A.ok(JSON.stringify(configuredProviders.body).indexOf('sk-provider-http-test-secret') < 0, 'provider list never leaks the pushed secret');\n A.ok(JSON.stringify(configuredProviders.body).indexOf('xai-provider-http-test-secret') < 0, 'provider list never leaks the pushed xAI secret');\n\n // PU-06: Signal is tokenless. Removing it destroys endpoint/account configuration, not a fictional token.\n const signalConnect = await j('POST', '/api/channels/signal/connect', {\n endpoint: 'http://127.0.0.1:65529', account: '+15551234567', model: 'local/proven-model',\n provider: 'custom', baseUrl: 'http://127.0.0.1:65530/v1', agentId: 'agent', agentName: 'Ultron'\n });\n A.eq(signalConnect.status, 200, 'Signal accepts endpoint/account configuration against an offline bridge');\n const signalSaved = await j('GET', '/api/channels/signal/status');\n A.eq(signalSaved.body.configured, true, 'Signal status is configured from saved endpoint/account');\n const channelsFile = path.join(ws, 'channels', 'secrets.json');\n const signalBeforeMalformed = fs.readFileSync(channelsFile, 'utf8');\n const signalBadJson = await fetch(B + '/api/channels/signal/disconnect', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'X-StarNet-Token': apiToken }, body: '{bad'\n });\n A.eq(signalBadJson.status, 400, 'Signal disconnect with malformed JSON -> 400');\n A.eq(fs.readFileSync(channelsFile, 'utf8'), signalBeforeMalformed, 'malformed Signal disconnect leaves durable configuration byte-identical');\n const signalStillSaved = await j('GET', '/api/channels/signal/status');\n A.eq(signalStillSaved.body.configured, true, 'malformed Signal disconnect does not stop or deconfigure the channel');\n const signalRemove = await j('POST', '/api/channels/signal/disconnect', { purge: true });\n A.eq(signalRemove.body.removedConfiguration, true, 'Signal removal is read-back-proven');\n A.eq(signalRemove.body.purged, false, 'Signal removal never claims token purging');\n const signalGone = await j('GET', '/api/channels/signal/status');\n A.eq(signalGone.body.configured, false, 'Signal becomes unconfigured immediately after removal');\n A.eq(signalGone.body.endpoint, '', 'removed Signal status exposes no stale endpoint');\n A.eq(signalGone.body.account, '', 'removed Signal status exposes no stale account');\n const signalDisk = JSON.parse(fs.readFileSync(path.join(ws, 'channels', 'secrets.json'), 'utf8'));\n A.eq(Object.prototype.hasOwnProperty.call(signalDisk, 'signal'), false, 'Signal record is absent from durable channel configuration');\n const sensitiveGets = [\n ['/api/connectors', 'connectors'],\n ['/api/cron', 'cron'],\n ['/api/checkpoint?agent=agent', 'checkpoint'],\n ['/api/notebook?agent=agent', 'notebook'],\n ['/api/save?agent=agent', 'save'],\n ['/api/runs?agent=agent&limit=5', 'runs'],\n ['/api/transcript?stream=global&agent=agent&limit=5', 'transcript'],\n ['/api/memory/proposals?agent=agent', 'memory proposals'],\n ['/api/memory/records?agent=agent', 'memory records'],\n ['/api/insights?agent=agent', 'insights']\n ];\n for (const [p, label] of sensitiveGets) {\n const noTok = await fetch(B + p);\n A.eq(noTok.status, 403, 'GET ' + label + ' WITHOUT a token -> 403');\n const withTok = await fetch(B + p, { headers: Object.assign({ Origin: B }, tok) });\n A.eq(withTok.status, 200, 'GET ' + label + ' WITH trusted browser token -> 200');\n }\n // Prefix families stop only at a URL segment boundary. Sibling aliases must not inherit a real handler,\n // while the exact/query forms above and the real /api/models/ child route remain live.\n for (const p of ['/api/toolsets-typo', '/api/saveXYZ', '/api/runsXYZ', '/api/subagentsXYZ', '/api/skillsXYZ', '/api/connectors/oauth/callbackevil']) {\n const alias = await fetch(B + p, { headers: tok });\n A.eq(alias.status, 404, 'segment-unsafe route alias ' + p + ' -> 404');\n }\n\n // ---- GROUND_UP_AUDIT P2: /api/version env-first fallback is HONEST (never a silent blank) ----\n // src-tauri/tauri.conf.json isn't a bundled resource in the packaged app, so the desktop shell exports\n // STARNET_APP_VERSION (set in the boot env above). /api/version must surface it with an honest appSource.\n const ver = await j('GET', '/api/version');\n A.eq(ver.status, 200, 'GET /api/version -> 200');\n A.eq(ver.body.app, '9.9.9-http-test', '/api/version app comes from STARNET_APP_VERSION (env-first fallback)');\n A.eq(ver.body.appSource, 'env', '/api/version reports appSource=env — honest about WHERE the version came from');\n A.ok(typeof ver.body.harness === 'string', '/api/version keeps the byte-compatible {harness} field');\n A.ok(typeof ver.body.node === 'string' && ver.body.node.length > 0, '/api/version keeps the node field');\n // LANE V: the harness build id must NOT be the intentional '0.0.0' package placeholder — running from a git\n // checkout it resolves via `git describe` (harnessSource='git'), and harnessSource always names WHERE it came from.\n A.ok(ver.body.harness !== '0.0.0', '/api/version harness is NOT the 0.0.0 placeholder (git-describe truth in a checkout)');\n A.ok(['env', 'git', 'pkg'].includes(ver.body.harnessSource), '/api/version reports an honest harnessSource (env|git|pkg)');\n A.eq(ver.body.harnessSource, 'git', \"/api/version resolves harness via git-describe when no build stamp is set\");\n\n // LANE V: env override wins — a boot with STARNET_BUILD_DESCRIBE set reports that exact string + harnessSource='env'\n // (the release pipeline stamps the real build id this way). Dedicated second boot: the value is fixed at spawn time.\n {\n const bd = 'lane-v-build-2f3a9c1';\n const ws2 = fs.mkdtempSync(path.join(os.tmpdir(), 'sk-http-bd-'));\n const b2 = await boot(port + 200, ws2, 20, { STARNET_BUILD_DESCRIBE: bd });\n const B2 = 'http://' + HOST + ':' + b2.port;\n try {\n const tok2 = await bootToken(B2, B2);\n const r2 = await fetch(B2 + '/api/version', { headers: { 'X-StarNet-Token': tok2, Origin: B2 } });\n const v2 = await r2.json();\n A.eq(r2.status, 200, 'GET /api/version (BUILD_DESCRIBE boot) -> 200');\n A.eq(v2.harness, bd, '/api/version harness = STARNET_BUILD_DESCRIBE (env override wins over git/pkg)');\n A.eq(v2.harnessSource, 'env', '/api/version reports harnessSource=env when the build stamp is set');\n A.eq(v2.app, '9.9.9-http-test', '/api/version app still resolves independently of the harness stamp');\n } finally {\n try { b2.child.kill(); } catch (_) {}\n await sleep(150);\n try { fs.rmSync(ws2, { recursive: true, force: true }); } catch (_) {}\n }\n }\n\n // ---- GROUND_UP_AUDIT P2: a THROWN store read reports 500 {error}, not a 200-empty ----\n // Happy path already proven above (GET /api/runs -> 200 with {runs:[...]}). Force the runStore.list read to\n // THROW by feeding a runId query far past the internal length guard is NOT possible over HTTP; instead we prove\n // the CONTRACT the frontend relies on: the route JSON always carries {runs} on 200, and the source uses\n // json(500,{error}) on a real failure (verified structurally so we don't have to corrupt the live store here).\n const runsOk = await j('GET', '/api/runs?agent=agent&limit=5');\n A.eq(runsOk.status, 200, 'GET /api/runs happy path stays 200');\n A.ok(Array.isArray(runsOk.body.runs), '/api/runs 200 body carries {runs:[...]} (shape byte-compatible)');\n const ckOk = await j('GET', '/api/checkpoint?agent=agent');\n A.eq(ckOk.status, 200, 'GET /api/checkpoint happy path stays 200');\n A.ok(Array.isArray(ckOk.body.snapshots), '/api/checkpoint 200 body carries {snapshots:[...]}');\n // the truthful-failure branch itself (structural: the catch now reports 500, never a masking 200-empty)\n const idxSrcHonesty = fs.readFileSync(INDEX, 'utf8');\n const serveRunsSrc = idxSrcHonesty.slice(idxSrcHonesty.indexOf('function serveRuns'), idxSrcHonesty.indexOf('function serveInsights'));\n A.ok(/catch\\s*\\(e\\)\\s*\\{[\\s\\S]*?json\\(500,\\s*\\{\\s*error/.test(serveRunsSrc), 'serveRuns reports json(500,{error}) on a thrown read, not a 200-empty');\n const ckStart = idxSrcHonesty.indexOf('function handleCheckpointList');\n const serveCkSrc = idxSrcHonesty.slice(ckStart, idxSrcHonesty.indexOf('function ', ckStart + 20));\n A.ok(/catch\\s*\\(e\\)\\s*\\{[\\s\\S]*?json\\(500,\\s*\\{\\s*error/.test(serveCkSrc), 'handleCheckpointList reports json(500,{error}) on a thrown read, not a 200-empty');\n\n // ---- GROUND_UP_AUDIT P2: three more sidecar honesty/reliability fixes (internal behaviors — asserted\n // structurally against the live source, since none is deterministically triggerable over the HTTP surface) ----\n // #3 on-demand model-catalog re-warm: the boot warm's rejection handler no longer no-ops the session; the\n // channel modelCatalog accessor kicks a throttled re-warm when the snapshot is empty.\n A.ok(/function maybeRewarmModelCatalog\\(\\)/.test(idxSrcHonesty), '#3 maybeRewarmModelCatalog exists (on-demand catalog re-warm)');\n A.ok(/modelCatalog:\\s*\\(\\)\\s*=>\\s*\\{\\s*maybeRewarmModelCatalog\\(\\)/.test(idxSrcHonesty), '#3 channel modelCatalog accessor triggers the re-warm');\n A.ok(!/listModels\\(\\)\\.then\\([\\s\\S]{0,120}\\(\\)\\s*=>\\s*\\{\\}\\s*\\)/.test(idxSrcHonesty.slice(idxSrcHonesty.indexOf('server.listen'))), '#3 boot warm no longer has an empty rejection handler');\n // #4 aux-run unmetered parity: skill-review + curator now stamp the ledger with the unmetered flag like\n // reflection/study, so a Codex-only user is not billed phantom metered spend.\n const reviewSrc = idxSrcHonesty.slice(idxSrcHonesty.indexOf('function runBackgroundSkillReview'), idxSrcHonesty.indexOf('async function runSkillCurator'));\n A.ok(/ledger\\.record\\(\\{[^}]*unmetered\\s*\\}\\)/.test(reviewSrc), '#4 runBackgroundSkillReview ledger.record carries unmetered');\n const curatorSrc = idxSrcHonesty.slice(idxSrcHonesty.indexOf('async function runSkillCurator'), idxSrcHonesty.indexOf('async function runSkillCurator') + 4000);\n A.ok(/ledger\\.record\\(\\{[^}]*unmetered\\s*\\}\\)/.test(curatorSrc), '#4 runSkillCurator ledger.record carries unmetered');\n A.ok(/runBackgroundSkillReview\\(\\{[\\s\\S]*?unmetered:\\s*providerUnmetered/.test(idxSrcHonesty), '#4 skill-review invocation threads providerUnmetered');\n A.ok(/runSkillCurator\\(\\{[\\s\\S]*?unmetered:\\s*providerUnmetered/.test(idxSrcHonesty), '#4 skill-curator invocation threads providerUnmetered');\n // #5 steer-drop diagnostics: a non-empty steer buffer discarded at teardown logs a one-line count.\n A.ok(/function dropSteer\\(runId, ctx\\)/.test(idxSrcHonesty), '#5 dropSteer helper exists');\n A.ok(/\\[steer\\] dropped '\\s*\\+\\s*b\\.length/.test(idxSrcHonesty), '#5 dropSteer logs the dropped count');\n A.ok((idxSrcHonesty.match(/dropSteer\\(runId,/g) || []).length >= 3, '#5 all three teardown sites route through dropSteer');\n // #6 _procDiagSeen TTL eviction: the dedupe map now evicts stale entries on every insert (2x the window),\n // not only once it grows past 64 — so a slow trickle of distinct one-shot messages no longer leaks forever.\n const surfaceSrc = idxSrcHonesty.slice(idxSrcHonesty.indexOf('function surfaceProcessError'), idxSrcHonesty.indexOf('function surfaceProcessError') + 1400);\n A.ok(/const ttl = 2 \\* PROC_DIAG_WINDOW_MS/.test(surfaceSrc), '#6 _procDiagSeen uses a 2x-window TTL');\n A.ok(!/_procDiagSeen\\.size > 64/.test(surfaceSrc), '#6 eviction is no longer gated behind size > 64 (runs every insert)');\n\n // ---- SSE telemetry requires the ?token= query (EventSource cannot send a header) ----\n const sseNoTok = await fetch(B + '/api/channels/events');\n A.eq(sseNoTok.status, 403, 'GET /api/channels/events WITHOUT ?token -> 403');\n const sseBadTok = await fetch(B + '/api/channels/events?token=nope');\n A.eq(sseBadTok.status, 403, 'GET /api/channels/events with a WRONG ?token -> 403');\n const sseWithTok = await fetch(B + '/api/channels/events?token=' + encodeURIComponent(apiToken), { headers: { Origin: B } });\n A.eq(sseWithTok.status, 200, 'GET /api/channels/events with the token query -> 200');\n try { if (sseWithTok.body && sseWithTok.body.cancel) await sseWithTok.body.cancel(); } catch (_) {}\n\n // ---- slash command catalog + dispatch seam ----\n const slashCat = await j('GET', '/api/slash/catalog');\n A.eq(slashCat.status, 200, 'GET /api/slash/catalog -> 200');\n A.ok(Array.isArray(slashCat.body.commands), 'slash catalog returns commands');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'retry'), 'slash catalog includes /retry');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'new'), 'slash catalog includes /new');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'branch' && c.aliases && c.aliases.indexOf('fork') >= 0), 'slash catalog includes /branch with /fork alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'queue' && c.aliases && c.aliases.indexOf('q') >= 0), 'slash catalog includes /queue with /q alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'model'), 'slash catalog includes /model');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'skills'), 'slash catalog includes /skills');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'resume' && c.aliases && c.aliases.indexOf('sessions') >= 0), 'slash catalog includes /resume with /sessions alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'background' && c.aliases && c.aliases.indexOf('bg') >= 0), 'slash catalog includes /background with /bg alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'cron'), 'slash catalog includes /cron');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'memory'), 'slash catalog includes /memory');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'reload-mcp' && c.aliases && c.aliases.indexOf('reload_mcp') >= 0), 'slash catalog includes /reload-mcp alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'version' && c.aliases && c.aliases.indexOf('v') >= 0), 'slash catalog includes /version alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'reload-skills' && c.aliases && c.aliases.indexOf('reload_skills') >= 0), 'slash catalog includes /reload-skills alias');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'morning-brief'), 'slash catalog includes built-in recipe commands');\n A.ok(slashCat.body.commands.some(c => c && c.name === 'plan' && c.source === 'skill'), 'slash catalog includes available compute-only skill commands');\n A.ok(!slashCat.body.commands.some(c => c && c.name === 'test-driven-development'), 'slash catalog omits unavailable workbench skill without placed workbench');\n\n const slashBench = await j('GET', '/api/slash/catalog?placed=workbench');\n A.ok(slashBench.body.commands.some(c => c && c.name === 'test-driven-development'), 'slash catalog includes workbench skill when workbench is placed');\n\n const slashRun = await j('POST', '/api/slash/dispatch', { input: '/retry' });\n A.eq(slashRun.status, 200, 'POST /api/slash/dispatch /retry -> 200');\n A.eq(slashRun.body.directive, { type: 'client', action: 'retry', args: '' }, 'slash dispatch returns a client retry directive');\n\n const slashFork = await j('POST', '/api/slash/dispatch', { input: '/fork copy this' });\n A.eq(slashFork.status, 200, 'POST /api/slash/dispatch /fork -> 200');\n A.eq(slashFork.body.command.name, 'branch', '/fork dispatch canonicalizes to branch');\n A.eq(slashFork.body.directive, { type: 'client', action: 'branch', args: 'copy this' }, 'fork dispatch returns branch client directive');\n\n const slashModel = await j('POST', '/api/slash/dispatch', { input: '/model openai/gpt-5' });\n A.eq(slashModel.status, 200, 'POST /api/slash/dispatch /model -> 200');\n A.eq(slashModel.body.directive, { type: 'client', action: 'model', args: 'openai/gpt-5' }, 'model dispatch returns client directive with model id');\n\n const slashReload = await j('POST', '/api/slash/dispatch', { input: '/reload_skills' });\n A.eq(slashReload.status, 200, 'POST /api/slash/dispatch /reload_skills -> 200');\n A.eq(slashReload.body.command.name, 'reload-skills', 'reload_skills dispatch canonicalizes');\n\n const slashSessions = await j('POST', '/api/slash/dispatch', { input: '/sessions 1' });\n A.eq(slashSessions.status, 200, 'POST /api/slash/dispatch /sessions -> 200');\n A.eq(slashSessions.body.directive, { type: 'client', action: 'resume', args: '1' }, 'sessions dispatch returns resume directive');\n\n const slashMcp = await j('POST', '/api/slash/dispatch', { input: '/reload_mcp github' });\n A.eq(slashMcp.status, 200, 'POST /api/slash/dispatch /reload_mcp -> 200');\n A.eq(slashMcp.body.directive, { type: 'client', action: 'reload-mcp', args: 'github' }, 'reload_mcp dispatch returns reload-mcp directive');\n\n const slashRecipe = await j('POST', '/api/slash/dispatch', { input: '/summarize launch notes' });\n A.eq(slashRecipe.status, 200, 'POST /api/slash/dispatch recipe -> 200');\n A.eq(slashRecipe.body.directive.type, 'insert', 'recipe slash dispatch returns insert directive');\n A.ok(String(slashRecipe.body.directive.text || '').indexOf('launch notes') >= 0, 'recipe slash dispatch carries typed args into the draft');\n\n const slashSkill = await j('POST', '/api/slash/dispatch', { input: '/plan refactor commands' });\n A.eq(slashSkill.status, 200, 'POST /api/slash/dispatch skill -> 200');\n A.eq(slashSkill.body.directive.source, 'skill', 'skill slash dispatch returns a skill directive');\n A.ok(String(slashSkill.body.directive.text || '').indexOf('refactor commands') >= 0, 'skill slash dispatch carries typed args into the task');\n\n const slashUnknown = await j('POST', '/api/slash/dispatch', { input: '/unknown' });\n A.eq(slashUnknown.status, 404, 'POST /api/slash/dispatch unknown -> 404');\n\n const noApiToken = await fetch(B + '/api/budget/resume', { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: B }, body: JSON.stringify({ scope: 'day' }) });\n A.eq(noApiToken.status, 403, 'privileged POST without X-StarNet-Token -> 403');\n\n // ---- budget status reflects the PRE-SEEDED ledger (persisted spend survived a fresh boot) ----\n const st = await j('GET', '/api/budget/status');\n A.eq(st.status, 200, 'GET /api/budget/status -> 200');\n A.eq(st.body.runs, 2, 'status counts the two seeded runs');\n A.ok(Math.abs(st.body.totalUsd - 2.0) < 1e-9, 'totalUsd = seeded $1.25 + $0.75');\n A.eq(st.body.perRun, 3, 'perRun cap exposed (Balanced $3)');\n A.ok(st.body.day && Math.abs(st.body.day.cap - 40) < 1e-9, 'day pool base cap is $40');\n A.ok(st.body.global && Math.abs(st.body.global.cap - 100) < 1e-9, 'global pool base cap is $100');\n A.ok(Math.abs(st.body.day.usd - 2.0) < 1e-9, 'day pool reflects seeded spend inside the 24h window');\n A.ok(Math.abs(st.body.global.usd - 2.0) < 1e-9, 'global pool reflects seeded spend');\n A.ok(JSON.stringify(st.body).indexOf('sk-') < 0, 'status leaks no key-shaped secret');\n\n // ---- Discord saved-owner restart guard: a boot from persisted config must restore owner lock\n // before the adapter sees any DM, so the first post-reboot stranger cannot claim the bot.\n for (let i = 0; i < 15 && booted.output().indexOf('discord auto-started from saved config') < 0; i++) await sleep(100);\n A.ok(booted.output().indexOf('discord auto-started from saved config') >= 0, 'Discord auto-started from saved config on boot');\n const dcStatus = await j('GET', '/api/channels/discord/status');\n A.eq(dcStatus.status, 200, 'GET /api/channels/discord/status -> 200');\n A.eq(dcStatus.body.configured, true, 'Discord status sees saved config');\n // `connected` is now the LIVE gateway truth (real WS to Discord), which a sandboxed test can\n // never reach — auto-start itself is proven by the boot log line above. Assert the gateway\n // reported a real state instead of the old adapter-started=connected fiction.\n A.eq(typeof dcStatus.body.connected, 'boolean', 'Discord status reports a truthful gateway connected flag');\n A.ok(typeof dcStatus.body.state === 'string' && dcStatus.body.state.length > 0, 'Discord status reports a gateway state');\n A.eq(dcStatus.body.ownerLocked, true, 'Discord owner lock survived restart');\n A.ok(JSON.stringify(dcStatus.body).indexOf('discord-owner-42') < 0, 'Discord status does not expose ownerId');\n A.ok(JSON.stringify(dcStatus.body).indexOf('discord-token') < 0, 'Discord status does not expose token');\n const savedChannels = JSON.parse(fs.readFileSync(path.join(ws, 'channels', 'secrets.json'), 'utf8'));\n A.eq(savedChannels.discord.ownerId, 'discord-owner-42', 'Discord auto-start preserved saved ownerId on disk');\n\n // ---- resume lifts a pool for the session ----\n const rs = await j('POST', '/api/budget/resume', { scope: 'day' });\n A.eq(rs.status, 200, 'POST /api/budget/resume{day} -> 200');\n A.eq(rs.body.resumed, 'day', 'resume names the scope');\n A.ok(Math.abs(rs.body.cap - 80) < 1e-9, 'resume lifts the day cap to $80 (base + base)');\n\n const st2 = await j('GET', '/api/budget/status');\n A.ok(Math.abs(st2.body.day.cap - 80) < 1e-9, 'status reflects the lifted day cap after resume');\n A.ok(Math.abs(st2.body.overrides.day - 40) < 1e-9, 'override headroom exposed');\n\n // ---- resume rejects an ungoverned / bogus scope ----\n const bad = await j('POST', '/api/budget/resume', { scope: 'bogus' });\n A.eq(bad.status, 400, 'resume of a bogus scope -> 400');\n const runScope = await j('POST', '/api/budget/resume', { scope: 'run' });\n A.eq(runScope.status, 400, 'resume of the per-run scope -> 400 (run is the loop hard cap, not a pool)');\n\n // ---- /api/run guard path (no key -> 400, zero spend) ----\n const noKey = await j('POST', '/api/run', { model: 'anthropic/claude-sonnet-4.6' });\n A.eq(noKey.status, 400, 'POST /api/run without a key -> 400');\n const noModel = await j('POST', '/api/run', { key: 'sk-or-v1-fake' });\n A.eq(noModel.status, 400, 'POST /api/run without a model -> 400');\n const badJson = await fetch(B + '/api/run', { method: 'POST', headers: { 'X-StarNet-Token': apiToken }, body: '{not json' });\n A.eq(badJson.status, 400, 'POST /api/run with malformed JSON -> 400');\n const cancelBadJson = await fetch(B + '/api/cancel', { method: 'POST', headers: { 'X-StarNet-Token': apiToken }, body: '{not json' });\n A.eq(cancelBadJson.status, 400, 'POST /api/cancel with malformed JSON -> 400');\n\n const spotifyStart = await j('POST', '/api/spotify/auth/start', { clientId: 'spotify-http-test-client' });\n A.eq(spotifyStart.status, 200, 'Spotify auth start accepts a valid client id without network I/O');\n const spotifyFile = path.join(ws, '.secrets', 'spotify.json');\n const spotifyBeforeMalformed = fs.readFileSync(spotifyFile, 'utf8');\n const spotifyBadJson = await fetch(B + '/api/spotify/auth/start', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'X-StarNet-Token': apiToken }, body: '{not json'\n });\n A.eq(spotifyBadJson.status, 400, 'Spotify auth start with malformed JSON -> 400 even when a client id is already saved');\n A.eq(fs.readFileSync(spotifyFile, 'utf8'), spotifyBeforeMalformed, 'malformed Spotify auth start leaves durable OAuth configuration byte-identical');\n\n // ---- /api/file media serving: typed content-type + HTTP Range (so COMMS