kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
83 lines (74 loc) • 3.24 kB
text/typescript
/**
* [NEEDS-LIVE-GATEWAY] Owner smoke for the IB Gateway TWS-socket transport (kestrel-7o2.5).
*
* This is the acceptance the OWNER runs against a RUNNING IB PAPER gateway. It is NOT a unit test
* and is NEVER exercised by `bun test` (it lives under `src/`, not `*.test.ts`, and only runs when
* invoked directly with the env gate set), so CI stays green with no gateway.
*
* It PLACES NO ORDERS. It only: connects, completes the handshake, prints the REDACTED account id +
* the server time, shows a live heartbeat, and then waits for the operator to KILL the gateway to
* demonstrate the fail-closed degraded-on-kill behavior (status flips `degraded`, use throws the
* typed error). Ctrl-C or a detected degradation exits cleanly.
*
* Run (paper only):
* KESTREL_IBKR_SMOKE=1 KESTREL_IBKR_MODE=paper KESTREL_IBKR_PORT=4002 \
* bun run src/adapters/broker/ibkr/smoke.ts
*/
import { describeIbkrConfig, resolveIbkrConfig } from "./config.ts";
import { loadEnvFallback } from "../../../cli/credentials.ts";
import { IbkrTransport } from "./transport.ts";
async function runSmoke(): Promise<void> {
const config = resolveIbkrConfig();
// eslint-disable-next-line no-console
console.log(`[smoke] resolving transport → ${describeIbkrConfig(config)} (PLACES NO ORDERS)`);
const transport = new IbkrTransport(config, {
heartbeatIntervalMs: 3_000,
heartbeatStalenessMs: 12_000,
log: (line) => console.log(`[smoke] ${line}`),
});
const status = await transport.connect();
console.log(
`[smoke] connected: account=${status.account} serverTime=${
status.serverTime === undefined ? "?" : new Date(status.serverTime).toISOString()
} heartbeat=${status.lastHeartbeat ?? "?"}`,
);
console.log("[smoke] heartbeat is live. Now KILL the IB Gateway to prove degraded-on-kill…");
await new Promise<void>((resolve) => {
const timer = setInterval(() => {
transport.pulse();
const s = transport.status();
console.log(
`[smoke] status connected=${s.connected} degraded=${s.degraded} serverTime=${
s.serverTime === undefined ? "?" : new Date(s.serverTime).toISOString()
}`,
);
if (s.degraded) {
console.log(`[smoke] DEGRADED (fail-closed): ${s.reason}`);
try {
transport.client(); // must throw the typed IbkrConnectionError
} catch (e) {
console.log(`[smoke] use-after-degrade threw as required: ${(e as Error).name}: ${(e as Error).message}`);
}
clearInterval(timer);
resolve();
}
}, 3_000);
});
transport.disconnect();
console.log("[smoke] done — transport closed cleanly. No orders were placed.");
}
if (import.meta.main) {
loadEnvFallback(); // KESTREL_IBKR_* identifiers from the shared secrets home (OSS-ADR-0054)
if (process.env.KESTREL_IBKR_SMOKE !== "1") {
console.log(
"[smoke] skipped: set KESTREL_IBKR_SMOKE=1 (and run a paper IB Gateway) to exercise this. Places no orders.",
);
} else {
runSmoke().catch((e: unknown) => {
const err = e as Error;
console.error(`[smoke] FAILED (fail-closed): ${err.name}: ${err.message}`);
process.exitCode = 1;
});
}
}
export { runSmoke };