Testing a Jev (TypeSafe AI) integration without an API key: mocks, simulators and failure branches
You can test most of a Jev integration before you have a key. You do this by replacing one layer of the call with something you control. This page lists twelve ways to do that and what each proves and cannot prove. It then gives ten failure branches to drive and a tested example of a client run against a local mock server.
Replace one layer at a time: the HTTP transport (the official SDKs accept a custom fetch or transport), the model (the AI SDK mock model), or the whole server (a local mock). Then drive each of the ten failure branches below before you have a key. None of these proves how Jev itself answers. A no-key "mock mode" left on in production is the most common way a Jev check silently disappears. Documented and Tested (offline) 29 Sep 2026
No key yet? Access status shows whether you can get one today.
What runs for real under each approach
Only the layers marked "real" are tested by a given approach. Everything to the right of the first scripted layer is whatever you told it to say.
Layers of one Jev call, and which ones each test approach replaces
| Approach | Your code | Client library (retries, error classes) | HTTP over a socket | API server | Jev model |
|---|---|---|---|---|---|
Official SDK with a custom fetch (JS) or transport (Python) | Real | Real | Your script | Your script | Your script |
AI SDK Experimental_EvaluationMockModelV4 | Real | AI SDK core only; provider mapping replaced | Your script | Your script | Your script |
| Local mock server (pool tests T01, T04) | Real | Real | Real | Your script | Your script |
| System One-compatible local server (Ollaya, Kev) | Real | Real | Real | Different server | Different model |
| Real key against the live API (not done by the pool) | Real | Real | Real | Real | Real |
Twelve test facilities: what each proves
"Silent-bypass risk" asks whether, in production, a missing key or a failure could skip Jev without anyone noticing.
| Facility (owner · language) | Replaces | Can emit 429 / 529 / timeout? | Realistic probabilities? | Proves | Cannot prove | Silent-bypass risk | Source, label, date |
|---|---|---|---|---|---|---|---|
Official JS SDK fetch optionOfficial · TS/JS | HTTP transport | Yes: you script responses, delays and aborts; the SDK's real retry and error mapping run | Only if you script them | Your handling of the SDK's real error classes and retries | Model behaviour, real latency, route-specific errors | Low the no-key check still throws at construction | TypeSafeClientConfig Documented 29 Sep |
Official Python SDK transport / http_client (httpx2)Official · Python | HTTP transport | Yes, for example with an httpx mock transport | Only if scripted | The same for Python, including the 30 s retry budget | Model behaviour, real latency, route-specific errors | Low | Sync client docs Documented 29 Sep |
AI SDK Experimental_EvaluationMockModelV4 (ai/test)Route owner (Vercel) · TS | Whole model (provider and HTTP) | Yes, by throwing from doEvaluate; HTTP status mapping is not exercised | Only if scripted | Your threshold, fallback and error branches around experimental_evaluate | Gateway auth, provider error mapping, retry timing on real statuses, model output | Low if production passes the real model | AI SDK evaluation guide; ai@7.0.122 test types Documented 29 Sep |
| Local mock server (pool tests T01 Python, T04 Node) Pool · Python, Node | Remote API, over real HTTP | Yes: 429 with Retry-After, 529, HTML 403, hangs | Only if scripted | Retry, timeout, status mapping and parsing over a real socket | Model behaviour; real limits | None by itself | T01 (Python page, 28 Sep); T04 (below) Tested offline |
jev-claude fakeJev test serverCommunity project · Node | Remote API | Not in the tests read | Scripted | That plugin's fail-open and "a 200 with no answers is a failure" branches, in enforce mode | Its shipped shadow default; the real API | High plugin default is shadow: nothing is enforced | test/core.test.mjs L21–41 Documented 29 Sep |
LangChain AutoModeMiddleware unit testsFramework vendor · Python | HTTP responses | A 500 is used in a test | Scripted | A failure ends the run and the tool is not executed | The real API; the tests assert the criteria default bug rather than flag it | None no key raises | sdist tests L317–341 Documented 29 Sep |
QuantDinger monkeypatched requests.post testsProject · Python | HTTP call | Errors and malformed responses | Scripted | LLM fallback and allow-on-failure branches | Live orders, real Jev | High no key → LLM if configured, else allow | 28 Sep audit; behaviour re-read at a5a9f4c, 4 Oct: unchanged Documented |
Jevmail /previewProject · TS | Whole app path (in-memory simulator) | No | No (simulated) | UI interaction without credentials | Gateway entitlement, any Jev answer, accuracy, cost | Medium the simulator is separate; classifyMessage has no no-key branch | 28 Sep audit (carried); README Reported |
TypeSafeAI mockCallJevUnofficial community organisation (not TypeSafe) · TS | Whole client | Not established | Mocked | The demo flow runs without a key | Anything about the API | Demo-only path | 25 Sep report (carried) Documented 25 Sep |
| Jev Workbench seeded demo Project | Whole app path | Not established | Seeded | Local UI and function registry | Real calls; the project states it has no retry protection | Explicitly simulated | 25 Sep report (carried) Reported |
jev-trader and Jev Trade MODEL=mockProjects | Whole decision model (a heuristic) | No | No | Pipeline wiring | Any Jev behaviour; the default is the mock, not Jev | High the default is a mock heuristic; with a wallet key set, the mock still sends real orders | Source at b587759 and a3f2f83, 4 Oct Documented (25–26 Sep reports were Reported) |
System One-compatible local servers (Ollaya /v1/systemone, Kev, kev-onnx)Alternative projects | Model and server (same request shape) | Not documented as fault injection | Real, from a different model | Request shape and parsing against a live local server | Jev's answers, calibration, limits, error codes | Medium configs that default to a local server can reach production pointed at the wrong model | Authors' docs (alternatives, 28 Sep) Reported |
How a Jev check silently disappears
In the projects the pool has audited, one way to lose a Jev check in production is a no-key or test mode that is still switched on. Examples:
- QuantDinger (trade entry gate): with no key, entry goes to an LLM if one is configured, otherwise it is allowed. Documented 28 Sep audit; re-read at a5a9f4c, 4 Oct
- jev-trader and Jev Trade:
MODEL=mockis the default, so a fresh deployment trades on a heuristic, not on Jev. "Mock" means no Jev, not no trading: with a wallet key set, the mock model still sends real orders (Jev Trade to testnet by default). Documented source at pinned commits, 4 Oct; details on trading and fraud gates - danna-zhou/jev-mcp hooks: any failure, including an unreachable endpoint, lets the command proceed. Tested offline 29 Sep, see Claude Code and MCP
- jev-claude: installs in
shadowmode, so every decision becomes "allow" with a note. Documented 29 Sep
To guard against this, make the no-key path fail loudly at start-up, and log every time a decision is skipped. The first row of the checklist below tests for this.
Ten failure branches to drive before you have a key
One row per branch. The status codes are explained on Errors and rate limits; this table covers only how to simulate each branch and what your code must do. Tick a row when your tests cover it. The ticks are not saved.
| Branch | How to simulate it without a key | What your code must do |
|---|---|---|
| Unset the key variable and construct the client | Fail loudly at start-up, or take an explicit, logged fallback. Never skip the decision silently. | |
| Mock returns 401 with a JSON body | Stop retrying, alert, and treat it as a configuration failure. | |
Mock returns 403 with Content-Type: text/html | Check the content type before rotating keys, then route to your fallback. | |
Send state: null to a mock that returns 422, or assert that your own validator rejects it first | Do not retry; log the request shape. | |
Mock returns 429 with Retry-After: 1, then 200 | Wait as told (bounded), retry, and succeed on the next attempt. | |
| Mock returns 529 three times | Retry within your budget, then take your chosen failure policy. | |
| Mock never responds | Abort at your deadline, which must be shorter than your caller's, then take the failure policy. | |
| Mock returns 200 with a truncated body | Treat it as a failure, not as "no risk found". | |
Mock returns 200 with an unknown choice | Reject the answer. Do not map it to a default category (Jevmail maps unknown answers to "promotional"). | |
| Mock returns a valid answer below your threshold | Take your low-confidence route: a person, another model, or abstain. See Confidence thresholds for choosing the cutoff. |
Documented method assembled from the SDK sources, audited projects and the pool's offline tests, 29 Sep 2026
Worked example: a Node client against a local mock server (T04)
Tested (offline, local mock server, no Jev call) Node v24.20.0, 29 Sep 2026, about 07:16 UTC.
The client is pool-authored. It mirrors the defaults of @typesafe-ai/sdk 0.6.0: 2 retries, retrying 408, 429, 5xx and timeouts, honouring Retry-After, and backoff starting at 0.5 s and doubling. It is not the SDK itself; the SDK was not installed. The per-attempt timeout was cut to 800 ms so the hang case finishes quickly. It uses only node:http and the built-in fetch, and it ran in a temporary directory that was then deleted. The Python counterpart (T01, 28 Sep) is the retry client on the Python page.
| Mock server script | Attempts | Time | Result |
|---|---|---|---|
429 with Retry-After: 1, then 200 | 2 | 1,053 ms | success billing, confidence 0.85, on attempt 2 |
| 529 three times | 3 | 1,519 ms | gave up HTTP 529 after 3 attempts |
| 403 with an HTML body (Cloudflare-style) | 1 | 5 ms | failed at once 403, flagged as HTML |
| 401 | 1 | 4 ms | failed at once no retry |
| 422 | 1 | 3 ms | failed at once no retry |
| 200 with malformed JSON | 1 | 4 ms | failed at once malformed-json |
200 with choice: "legal" (not an option) | 1 | 5 ms | rejected invalid-answer |
| No response, three times | 3 | 3,908 ms | timed out 3 × 800 ms plus 0.5 s and 1 s backoff |
Reading. The client honoured Retry-After, gave up on repeated 529s, and did not retry 401, 403, 422, malformed JSON or an out-of-set answer. With the SDK's 10 s default timeout, the hang case would take about 31.5 s (derived arithmetic). What T04 does not prove: TypeSafe's real headers and bodies, whether 529 occurs in practice, or the SDK's own internal behaviour.
T04 script and raw output
// T04 (pool-authored, offline): raw-fetch Jev client vs a local mock server. No Jev call.
import http from 'node:http';
const script = []; let hits = 0;
const server = http.createServer((req, res) => {
hits++; const step = script.shift() ?? { status: 500, body: '{}' };
if (step.hang) return;
res.writeHead(step.status, { 'content-type': step.ctype ?? 'application/json', ...(step.headers ?? {}) });
res.end(step.body);
});
await new Promise(r => server.listen(0, '127.0.0.1', r));
const url = `http://127.0.0.1:${server.address().port}/v1/systemone`;
const OPTIONS = ['billing', 'technical', 'other'];
const RETRY = new Set([408, 429, ...Array.from({ length: 100 }, (_, i) => 500 + i)]);
async function ask({ timeoutMs = 800, maxRetries = 2 } = {}) {
for (let attempt = 0; ; attempt++) {
const ctl = new AbortController(); const t = setTimeout(() => ctl.abort(), timeoutMs);
let res;
try {
res = await fetch(url, { method: 'POST', signal: ctl.signal, headers: { 'content-type': 'application/json', authorization: 'Bearer test-not-a-key' },
body: JSON.stringify({ model: 'jev-1.13.0', state: 'I was charged twice', questions: { category: { type: 'choice', instructions: 'Team?', criteria: { billing: null, technical: null, other: null } } } }) });
var text = await res.text();
} catch (e) {
clearTimeout(t);
const kind = ctl.signal.aborted ? 'timeout' : 'connection';
if (attempt >= maxRetries) return { ok: false, kind, attempts: attempt + 1 };
await new Promise(r => setTimeout(r, 500 * 2 ** attempt)); continue;
}
clearTimeout(t);
if (!res.ok) {
const html = !(res.headers.get('content-type') ?? '').includes('json');
if (attempt < maxRetries && RETRY.has(res.status)) {
const ra = Number(res.headers.get('retry-after'));
await new Promise(r => setTimeout(r, Number.isFinite(ra) && ra >= 0 && res.headers.has('retry-after') ? ra * 1000 : 500 * 2 ** attempt)); continue;
}
return { ok: false, kind: 'http', status: res.status, html, attempts: attempt + 1 };
}
let body; try { body = JSON.parse(text); } catch { return { ok: false, kind: 'malformed-json', attempts: attempt + 1 }; }
const a = body?.answers?.category;
if (!a || !OPTIONS.includes(a.choice) || typeof a.confidence !== 'number') return { ok: false, kind: 'invalid-answer', got: a ?? null, attempts: attempt + 1 };
return { ok: true, choice: a.choice, confidence: a.confidence, attempts: attempt + 1 };
}
}
const good = JSON.stringify({ model: 'jev-1.13.0', answers: { category: { type: 'choice', choice: 'billing', probabilities: { billing: 0.9, technical: 0.06, other: 0.04 }, confidence: 0.85 } }, usage: { input_tokens: 300, output_tokens: 20 } });
const cases = [
['429 Retry-After:1 then 200', [{ status: 429, headers: { 'retry-after': '1' }, body: '{"error":"rate"}' }, { status: 200, body: good }]],
['529 x3', [{ status: 529, body: '{}' }, { status: 529, body: '{}' }, { status: 529, body: '{}' }]],
['HTML 403 (Cloudflare-style)', [{ status: 403, ctype: 'text/html', body: '<html>blocked</html>' }]],
['401', [{ status: 401, body: '{"error":"bad key"}' }]],
['422', [{ status: 422, body: '{"error":"state null"}' }]],
['200 malformed JSON', [{ status: 200, body: '{"answers": ' }]],
['200 choice not among options', [{ status: 200, body: good.replace('"choice":"billing"', '"choice":"legal"') }]],
['hang past client timeout x3', [{ hang: true }, { hang: true }, { hang: true }]],
];
for (const [name, steps] of cases) {
script.length = 0; script.push(...steps); hits = 0;
const t0 = Date.now(); const r = await ask();
console.log(JSON.stringify({ case: name, result: r, server_hits: hits, ms: Date.now() - t0 }));
}
server.closeAllConnections?.(); server.close();
{"case":"429 Retry-After:1 then 200","result":{"ok":true,"choice":"billing","confidence":0.85,"attempts":2},"server_hits":2,"ms":1053}
{"case":"529 x3","result":{"ok":false,"kind":"http","status":529,"html":false,"attempts":3},"server_hits":3,"ms":1519}
{"case":"HTML 403 (Cloudflare-style)","result":{"ok":false,"kind":"http","status":403,"html":true,"attempts":1},"server_hits":1,"ms":5}
{"case":"401","result":{"ok":false,"kind":"http","status":401,"html":false,"attempts":1},"server_hits":1,"ms":4}
{"case":"422","result":{"ok":false,"kind":"http","status":422,"html":false,"attempts":1},"server_hits":1,"ms":3}
{"case":"200 malformed JSON","result":{"ok":false,"kind":"malformed-json","attempts":1},"server_hits":1,"ms":4}
{"case":"200 choice not among options","result":{"ok":false,"kind":"invalid-answer","got":{"type":"choice","choice":"legal","probabilities":{"billing":0.9,"technical":0.06,"other":0.04},"confidence":0.85},"attempts":1},"server_hits":1,"ms":5}
{"case":"hang past client timeout x3","result":{"ok":false,"kind":"timeout","attempts":3},"server_hits":3,"ms":3908}
What was not verified
- No test on this page used the real Jev API; the pool has no key. Real headers, error bodies, latency and model answers are not covered.
- The official JS SDK was not installed; T04 mirrors its defaults in pool code. The Python SDK install (planned test T03) has still not been done.
- Rows marked "carried" (Jevmail,
mockCallJev, Jev Workbench, Ollaya and Kev) keep their 25–28 Sep check dates and were not rechecked. The QuantDinger, jev-trader and Jev Trade rows were re-read at pinned commits on 4 Oct 2026; no test was run. - The project test suites listed above were read, not run.