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.
1,025 lines (1,018 loc) • 42.6 kB
JavaScript
import {
demoLessonObject,
neverFiredLine,
paidBoundaryNudgeObject,
renderDemoLesson,
renderPaidBoundaryNudge,
renderSimMenu,
renderSimStory,
renderStandDownLesson,
standDownLessonObject
} from "./bin-awvz75fn.js";
import {
appendHostedRun,
defaultHostedStorePath
} from "./bin-m9a64j9t.js";
import {
DEFAULT_API,
idemKey
} from "./bin-qewrvqtv.js";
import {
renderOffer,
renderSettlementRequired
} from "./bin-x2cbtyjz.js";
import {
parseArgs
} from "./bin-1gc4zavq.js";
import {
CliError,
EXIT,
asConnectionError
} from "./bin-t9ggwnv5.js";
import {
canonicalPlansText
} from "./bin-29be75ss.js";
import {
sha256
} from "./bin-8pchjxn2.js";
import {
KestrelParseError,
VERIFY_KEYS_PATH,
asOfferResponse,
asSettlementAffordance,
asSettlementResume,
asWireOffer,
parse,
parseMarkdown
} from "./bin-2ywrx58g.js";
// src/cli/commands/sim.ts
import { readFileSync } from "node:fs";
// src/cli/backend/hosted.ts
var PROOF_WEB_BASE = "https://kestrel.markets";
function slugifyTitle(title) {
return title.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
var SCENARIO_ALIASES = {
"wsb-gme-meme-short-squeeze": "meme-stock-short-squeeze",
"wsb-gme-short-squeeze": "meme-stock-short-squeeze",
"covid-crash-march-2020": "pandemic-volatility-crash",
"chip-earnings-blowout": "ai-chip-earnings-blowout",
"intraday-opening-drive": "intraday-opening-drive-momentum"
};
function resolveScenarioSlug(slug, entries) {
const canonical = SCENARIO_ALIASES[slug] ?? slug;
const viaAlias = canonical !== slug;
for (const entry of entries) {
if (slugifyTitle(entry.title) === canonical) {
return { entry, canonicalSlug: canonical, requestedSlug: slug, viaAlias };
}
}
return null;
}
function catalogSlugs(entries) {
return entries.map((e) => slugifyTitle(e.title));
}
function nearMissSlugs(input, entries, limit = 3) {
const inputTokens = new Set(input.split("-").filter(Boolean));
const scored = catalogSlugs(entries).map((slug) => {
const shared = slug.split("-").filter((t) => inputTokens.has(t)).length;
let prefix = 0;
while (prefix < slug.length && prefix < input.length && slug[prefix] === input[prefix])
prefix += 1;
return { slug, shared, prefix };
});
scored.sort((a, b) => b.shared - a.shared || b.prefix - a.prefix || (a.slug < b.slug ? -1 : 1));
return scored.slice(0, limit).map((s) => s.slug);
}
class HostedFunnel {
base;
fetch;
constructor(opts) {
this.base = opts.baseUrl.replace(/\/+$/, "");
const f = opts.fetch ?? globalThis.fetch;
if (f === undefined)
throw new CliError({
code: "RUNTIME_UNAVAILABLE",
exit: EXIT.RUNTIME_UNAVAILABLE,
message: "global fetch unavailable — need node ≥18 or bun for `kestrel sim`"
});
this.fetch = f;
}
get verifyKeysUrl() {
return `${this.base}${VERIFY_KEYS_PATH}`;
}
async catalog() {
const res = await this.fetch(`${this.base}/catalog`, { headers: { accept: "application/json" } });
if (!res.ok)
throw httpErr(res.status, "GET /catalog failed");
const body = await res.json();
const entries = Array.isArray(body) ? body : body?.entries;
if (!Array.isArray(entries))
throw httpErr(502, "GET /catalog returned no entries[]");
return { entries: verifiedCatalogEntries(entries) };
}
async mintTrial(opts = {}) {
const headers = { "content-type": "application/json", accept: "application/json" };
if (opts.copyToken !== undefined && opts.copyToken !== "")
headers["x-kestrel-copy-token"] = opts.copyToken;
const res = await this.fetch(`${this.base}/capabilities/trial`, {
method: "POST",
headers,
body: JSON.stringify({})
});
if (!res.ok)
throw httpErr(res.status, "trial capability mint failed");
return verifiedHostedTrial(await safeJson(res));
}
async verifyKeys(opts = {}) {
const headers = { accept: "application/json" };
if (opts.copyToken !== undefined && opts.copyToken !== "")
headers["x-kestrel-copy-token"] = opts.copyToken;
const res = await this.fetch(`${this.base}${VERIFY_KEYS_PATH}`, { headers });
if (!res.ok)
throw httpErr(res.status, `GET ${VERIFY_KEYS_PATH} failed`);
return verifiedVerifyKeys(await safeJson(res));
}
async publishedProof(id, opts = {}) {
const headers = { accept: "application/json" };
if (opts.copyToken !== undefined && opts.copyToken !== "")
headers["x-kestrel-copy-token"] = opts.copyToken;
const res = await this.fetch(`${this.base}/proof/${encodeURIComponent(id)}`, { headers });
if (res.status === 404)
return null;
if (!res.ok)
throw httpErr(res.status, `GET /proof/${id} failed`);
return verifiedPublishedProof(await safeJson(res), id);
}
async evidenceBundle(id, opts = {}) {
const headers = { accept: "application/json" };
if (opts.copyToken !== undefined && opts.copyToken !== "")
headers["x-kestrel-copy-token"] = opts.copyToken;
const res = await this.fetch(`${this.base}/proof/${encodeURIComponent(id)}/evidence`, { headers });
if (res.status === 404)
return null;
if (!res.ok)
throw httpErr(res.status, `GET /proof/${id}/evidence failed`);
return verifiedEvidenceBundle(await safeJson(res), id);
}
async simulate(req, bearer) {
const body = {
source: req.source,
dataset: { artifact_id: req.artifactId },
...req.spend !== undefined ? { spend: req.spend } : {}
};
const encoded = JSON.stringify(body);
const idempotencyKey = idemKey("POST", "/simulate", body);
let pendingOperationId;
for (let attempt = 0;attempt < 2; attempt += 1) {
const res = await this.fetch(`${this.base}/simulate`, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
authorization: `Bearer ${bearer}`,
"idempotency-key": idempotencyKey
},
body: encoded
});
if (res.status === 402) {
if (pendingOperationId !== undefined)
throw evidenceError("evidence-finalize replay crossed an unexpected payment boundary");
const offer = asOfferResponse(await safeJson(res));
if (offer === null)
throw httpErr(402, "402 body was not a structured OfferResponse");
return { gated: true, payment: offer };
}
if (!res.ok)
throw await httpErrFrom(res, "POST /simulate failed");
const value = await safeJson(res);
const settlement = settlementRequiredOperation(value);
if (settlement !== null) {
if (pendingOperationId !== undefined)
throw evidenceError("evidence-finalize replay crossed an unexpected settlement boundary");
return { settlementRequired: settlement };
}
const pending = evidencePendingOperation(value);
if (pending !== null) {
if (pendingOperationId !== undefined && pending.operation_id !== pendingOperationId)
throw evidenceError("evidence-finalize replay changed Operation identity");
pendingOperationId = pending.operation_id;
if (attempt === 1)
throw evidenceError(`R2 evidence remained pending after one effect-once finalize-only retry: ${pending.evidence_status.detail}`);
continue;
}
const op = verifiedHostedOperation(value);
if (pendingOperationId !== undefined && op.operation_id !== pendingOperationId)
throw evidenceError("evidence-finalize replay changed Operation identity");
return { gated: false, value: op };
}
throw evidenceError("R2 evidence finalize retry exhausted");
}
async proof(artifact, receipt, operationId, bearer) {
const headers = { accept: "application/json" };
if (bearer !== undefined)
headers["authorization"] = `Bearer ${bearer}`;
const res = await this.fetch(`${this.base}/proof/${encodeURIComponent(artifact.artifact_id)}`, { headers });
if (!res.ok)
throw httpErr(res.status, `GET /proof/${artifact.artifact_id} failed`);
return verifiedHostedProof(await safeJson(res), artifact, receipt, operationId);
}
}
function summarizeReceipts(receipts) {
let meteredCount = 0;
let budgetRemaining = null;
for (const r of receipts) {
if (r.kind === "metered") {
meteredCount += 1;
if (typeof r.budget_remaining === "number")
budgetRemaining = r.budget_remaining;
}
}
return { meteredCount, budgetRemaining };
}
function neverFiredReasonOf(proof) {
const reason = proof?.result.diagnostics?.never_fired_reason;
return typeof reason === "string" && reason.trim().length > 0 ? reason : undefined;
}
function gradedMetrics(metrics) {
const m = metrics ?? {};
return {
orderCount: numOr0(m["order_count"]),
fillCount: numOr0(m["fill_count"]),
realizedPnl: numOr0(m["realized_pnl"])
};
}
function gradeArtifactOf(op) {
return op.artifacts.find((artifact) => artifact.kind === "grade");
}
function gradeReceiptOf(op) {
return op.receipts.find((receipt) => receipt.kind === "grade");
}
function proofWebUrl(artifactId) {
return `${PROOF_WEB_BASE}/proof/${artifactId}`;
}
function numOr0(v) {
return typeof v === "number" && Number.isFinite(v) ? v : 0;
}
var ARTIFACT_ID_RE = /^art_[0-9a-f]{24}$/;
var CONTENT_HASH_RE = /^sha256:[0-9a-f]{64}$/;
var OPERATION_ID_RE = /^op_[A-Za-z0-9][A-Za-z0-9_-]*$/;
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function evidenceError(detail) {
return httpErr(502, `POST /simulate did not return verified R2 evidence: ${detail}`);
}
function operationError(detail) {
return httpErr(502, `POST /simulate returned no verified public Grade artifact: ${detail}`);
}
function catalogError(detail) {
return httpErr(502, `GET /catalog returned invalid curated scenarios: ${detail}`);
}
function nonEmptyString(value) {
return typeof value === "string" && value.trim().length > 0;
}
function verifiedCatalogEntries(values) {
const entries = values.map((value, index) => {
if (!isRecord(value))
throw catalogError(`entries[${index}] is not an object`);
if (!nonEmptyString(value["artifact_id"]))
throw catalogError(`entries[${index}].artifact_id is empty`);
if (!nonEmptyString(value["title"]))
throw catalogError(`entries[${index}].title is empty`);
if (slugifyTitle(value["title"]).length === 0)
throw catalogError(`entries[${index}].title does not produce a canonical slug`);
if (!nonEmptyString(value["instrument"]))
throw catalogError(`entries[${index}].instrument is empty`);
const period = value["period"];
if (!isRecord(period) || !nonEmptyString(period["start"]) || !nonEmptyString(period["end"]))
throw catalogError(`entries[${index}].period must name start and end`);
if (typeof value["free"] !== "boolean")
throw catalogError(`entries[${index}].free must be boolean`);
if (typeof value["description"] !== "string")
throw catalogError(`entries[${index}].description must be string`);
if (!Number.isSafeInteger(value["frame_count"]) || value["frame_count"] <= 0)
throw catalogError(`entries[${index}].frame_count must be a positive integer`);
if (value["content_hash"] !== undefined && typeof value["content_hash"] !== "string")
throw catalogError(`entries[${index}].content_hash must be string when present`);
return value;
});
const slugs = new Set;
const artifacts = new Set;
for (const entry of entries) {
const slug = slugifyTitle(entry.title);
if (slugs.has(slug))
throw catalogError(`duplicate catalog canonical slug ${JSON.stringify(slug)}`);
if (artifacts.has(entry.artifact_id))
throw catalogError(`duplicate catalog artifact id ${JSON.stringify(entry.artifact_id)}`);
slugs.add(slug);
artifacts.add(entry.artifact_id);
}
return entries;
}
function trialError(detail) {
return httpErr(502, `POST /capabilities/trial returned an unusable trial capability: ${detail}`);
}
var REQUIRED_TRIAL_SCOPES = ["sim", "grade"];
var FORBIDDEN_TRIAL_SCOPES = ["paper", "broker", "live"];
function verifiedHostedTrial(value) {
if (!isRecord(value))
throw trialError("body is missing");
if (!nonEmptyString(value["capability"]))
throw trialError("capability token is empty");
const scopes = value["scopes"];
if (scopes === undefined)
return value;
if (!Array.isArray(scopes) || !scopes.every((s) => typeof s === "string"))
throw trialError("scopes must be an array of strings when advertised");
for (const required of REQUIRED_TRIAL_SCOPES)
if (!scopes.includes(required))
throw trialError(`advertised scopes lack the required Simulation scope ${JSON.stringify(required)}`);
for (const forbidden of FORBIDDEN_TRIAL_SCOPES)
if (scopes.includes(forbidden))
throw trialError(`advertised scopes carry the forbidden authority scope ${JSON.stringify(forbidden)}`);
return value;
}
function proofError(detail) {
return httpErr(502, `GET /proof returned no verified Grade proof: ${detail}`);
}
function evidencePendingOperation(value) {
if (!isRecord(value) || value["status"] !== "running" || !OPERATION_ID_RE.test(String(value["operation_id"] ?? "")))
return null;
const status = value["evidence_status"];
if (!isRecord(status) || status["state"] !== "pending" || typeof status["detail"] !== "string")
return null;
if (!Array.isArray(value["artifacts"]) || value["artifacts"].length !== 0)
return null;
if (!Array.isArray(value["receipts"]) || value["evidence"] !== undefined)
return null;
if (value["offer"] !== null)
return null;
const resume = value["resume"];
if (!isRecord(resume) || resume["method"] !== "POST" || resume["path"] !== "/api/simulate" || resume["operation_id"] !== value["operation_id"] || typeof value["cursor"] !== "string" || resume["cursor"] !== value["cursor"] || resume["requires_settlement"] !== false || typeof resume["detail"] !== "string")
return null;
return value;
}
function settlementRequiredOperation(value) {
if (!isRecord(value) || value["status"] !== "suspended")
return null;
const opId = value["operation_id"];
if (typeof opId !== "string" || !OPERATION_ID_RE.test(opId))
return null;
const offer = asWireOffer(value["offer"]);
if (offer === null || offer.operation_id !== opId)
return null;
const resume = asSettlementResume(value["resume"], opId);
if (resume === null)
return null;
if (typeof value["cursor"] === "string" && resume.cursor !== value["cursor"])
return null;
const settlement = asSettlementAffordance(value["settlement"]);
return { operationId: opId, offer, resume, ...settlement !== null ? { settlement } : {} };
}
function gradeArtifact(value) {
if (!isRecord(value) || value["kind"] !== "grade")
throw operationError("kind must be grade");
if (typeof value["artifact_id"] !== "string" || !ARTIFACT_ID_RE.test(value["artifact_id"]))
throw operationError("artifact_id is malformed");
if (typeof value["content_hash"] !== "string" || !CONTENT_HASH_RE.test(value["content_hash"]))
throw operationError("content_hash is malformed");
if (value["proof_url"] !== undefined && value["proof_url"] !== `/proof/${value["artifact_id"]}`)
throw operationError("proof_url does not name the Grade artifact");
return value;
}
function gradeReceipt(value, artifact) {
if (!isRecord(value) || value["kind"] !== "grade")
throw operationError("Grade receipt kind must be grade");
if (value["root"] !== artifact.content_hash)
throw operationError("Grade receipt root does not match the Grade artifact");
if (typeof value["signature"] !== "string" || value["signature"].length === 0)
throw operationError("Grade receipt signature is missing");
return value;
}
function evidenceRef(value, expectedKind, field) {
if (!isRecord(value))
throw evidenceError(`${field} is missing`);
if (value["kind"] !== expectedKind)
throw evidenceError(`${field}.kind must be ${expectedKind}`);
if (typeof value["artifact_id"] !== "string" || !ARTIFACT_ID_RE.test(value["artifact_id"]))
throw evidenceError(`${field}.artifact_id is malformed`);
if (typeof value["content_hash"] !== "string" || !CONTENT_HASH_RE.test(value["content_hash"]))
throw evidenceError(`${field}.content_hash is malformed`);
if (!Number.isSafeInteger(value["byte_length"]) || value["byte_length"] <= 0)
throw evidenceError(`${field}.byte_length must be a positive integer`);
if (value["classification"] !== "private")
throw evidenceError(`${field}.classification must be private`);
return value;
}
function verifiedHostedOperation(value) {
if (!isRecord(value) || !OPERATION_ID_RE.test(String(value["operation_id"] ?? "")))
throw httpErr(502, "POST /simulate returned malformed Operation id");
if (value["status"] !== "completed")
throw evidenceError("operation is not completed");
if ("evidence_status" in value)
throw evidenceError("legacy evidence_status is not durable proof");
const raw = value["evidence"];
if (!isRecord(raw))
throw evidenceError("bundle is missing");
const calls = raw["callRefs"];
if (!Array.isArray(calls))
throw evidenceError("callRefs must be an array");
if (calls.length !== 0)
throw evidenceError(`inference-free sim unexpectedly captured ${calls.length} model call(s)`);
const artifacts = value["artifacts"];
if (!Array.isArray(artifacts))
throw operationError("artifacts must be an array");
const grades = artifacts.filter((artifact) => isRecord(artifact) && artifact["kind"] === "grade");
if (grades.length !== 1)
throw operationError(`expected exactly one Grade artifact, received ${grades.length}`);
const grade = gradeArtifact(grades[0]);
if (!Array.isArray(value["receipts"]))
throw operationError("receipts must be an array");
const gradeReceipts = value["receipts"].filter((receipt) => isRecord(receipt) && receipt["kind"] === "grade");
if (gradeReceipts.length !== 1)
throw operationError(`expected exactly one Grade receipt, received ${gradeReceipts.length}`);
gradeReceipt(gradeReceipts[0], grade);
const evidence = {
busRef: evidenceRef(raw["busRef"], "session_bus", "busRef"),
gradeRef: evidenceRef(raw["gradeRef"], "certified_grade", "gradeRef"),
callRefs: calls.map((ref, index) => evidenceRef(ref, "agent_call", `callRefs[${index}]`)),
manifestRef: evidenceRef(raw["manifestRef"], "manifest", "manifestRef")
};
return { ...value, status: "completed", evidence };
}
function verifiedHostedProof(value, artifact, receipt, operationId) {
if (!isRecord(value))
throw proofError("body is missing");
if (value["proof_id"] !== artifact.artifact_id)
throw proofError("proof_id does not match the Grade artifact");
if (value["kind"] !== "grade")
throw proofError("kind must be grade");
if (value["root"] !== artifact.content_hash)
throw proofError("root does not match the Grade artifact");
if (value["signature"] !== receipt.signature)
throw proofError("signature does not match the Grade receipt");
const verification = value["verification"];
if (!isRecord(verification) || verification["verified"] !== true || verification["status"] !== "verified" || verification["algorithm"] !== "ed25519" || typeof verification["kid"] !== "string" || !Number.isSafeInteger(verification["epoch"]))
throw proofError("signature did not re-verify");
const result = value["result"];
if (!isRecord(result) || result["subjectSessionId"] !== operationId)
throw proofError("subjectSessionId does not match the Operation");
const metrics = result["metrics"];
if (!isRecord(metrics))
throw proofError("metrics are missing");
for (const name of ["order_count", "fill_count"]) {
if (!Number.isSafeInteger(metrics[name]) || metrics[name] < 0)
throw proofError(`metric ${name} must be a nonnegative integer`);
}
if (typeof metrics["realized_pnl"] !== "number" || !Number.isFinite(metrics["realized_pnl"]))
throw proofError("metric realized_pnl is missing or non-finite");
return value;
}
function verifyKeysError(detail) {
return httpErr(502, `GET ${VERIFY_KEYS_PATH} returned an unusable verify-set: ${detail}`);
}
function verifiedVerifyKeys(value) {
const set = isRecord(value) ? value["grade_verify_keys"] : undefined;
if (!Array.isArray(set))
throw verifyKeysError("grade_verify_keys is not an array");
const out = [];
for (const k of set) {
if (!isRecord(k))
continue;
if (!nonEmptyString(k["kid"]) || k["algorithm"] !== "ed25519")
continue;
if (k["status"] !== "active" && k["status"] !== "accept-only")
continue;
const jwk = k["public_jwk"];
if (!isRecord(jwk) || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519" || !nonEmptyString(jwk["x"]))
continue;
if (!Number.isSafeInteger(k["epoch"]))
continue;
out.push({
kid: k["kid"],
epoch: k["epoch"],
algorithm: "ed25519",
public_jwk: { kty: "OKP", crv: "Ed25519", x: jwk["x"] },
status: k["status"]
});
}
return out;
}
function publishedProofError(detail) {
return httpErr(502, `GET /proof returned an unverifiable proof body: ${detail}`);
}
function asCertifiedCoveredFields(value) {
if (!isRecord(value))
return;
if (value["kind"] !== "grade")
return;
if (!isRecord(value["result"]))
return;
if (!nonEmptyString(value["evView"]))
return;
if (!isRecord(value["supportPartition"]))
return;
if (!nonEmptyString(value["pinnedDataRef"]))
return;
if (!nonEmptyString(value["pinnedFillModelVersion"]))
return;
if (!nonEmptyString(value["judge"]))
return;
if (!nonEmptyString(value["issuedAt"]))
return;
if (!Array.isArray(value["boundRoots"]) || !value["boundRoots"].every((r) => typeof r === "string"))
return;
if (!nonEmptyString(value["kid"]))
return;
if (!Number.isSafeInteger(value["epoch"]))
return;
if (typeof value["replayable"] !== "boolean")
return;
if (value["pinnedProvenanceRef"] !== undefined && !nonEmptyString(value["pinnedProvenanceRef"]))
return;
if (value["pinnedAuthorProvenanceRef"] !== undefined && !nonEmptyString(value["pinnedAuthorProvenanceRef"]))
return;
return {
kind: "grade",
result: value["result"],
evView: value["evView"],
supportPartition: value["supportPartition"],
pinnedDataRef: value["pinnedDataRef"],
...value["pinnedProvenanceRef"] !== undefined ? { pinnedProvenanceRef: value["pinnedProvenanceRef"] } : {},
...value["pinnedAuthorProvenanceRef"] !== undefined ? { pinnedAuthorProvenanceRef: value["pinnedAuthorProvenanceRef"] } : {},
pinnedFillModelVersion: value["pinnedFillModelVersion"],
judge: value["judge"],
issuedAt: value["issuedAt"],
boundRoots: value["boundRoots"],
...value["counterfactual"] !== undefined ? { counterfactual: value["counterfactual"] } : {},
kid: value["kid"],
epoch: value["epoch"],
replayable: value["replayable"]
};
}
function verifiedPublishedProof(value, id) {
if (!isRecord(value))
throw publishedProofError("body is missing");
if (value["proof_id"] !== undefined && value["proof_id"] !== id)
throw publishedProofError("proof_id does not match the requested id");
if (!nonEmptyString(value["root"]))
throw publishedProofError("root is missing");
if (!nonEmptyString(value["signature"]))
throw publishedProofError("signature is missing");
const verification = value["verification"];
if (!isRecord(verification))
throw publishedProofError("verification block is missing");
if (!nonEmptyString(verification["kid"]))
throw publishedProofError("verification.kid is missing");
if (!Number.isSafeInteger(verification["epoch"]))
throw publishedProofError("verification.epoch is missing");
const coveredFields = asCertifiedCoveredFields(value["coveredFields"]);
return {
proof_id: id,
root: value["root"],
signature: value["signature"],
kid: verification["kid"],
epoch: verification["epoch"],
verificationClaim: verification["verified"] === true,
...coveredFields !== undefined ? { coveredFields } : {}
};
}
function evidenceBundleError(detail) {
return httpErr(502, `GET /proof/{id}/evidence returned an unusable evidence bundle: ${detail}`);
}
function asPublishedGrade(value) {
if (!isRecord(value))
return;
if (!nonEmptyString(value["subjectSessionId"]))
return;
const metrics = value["metrics"];
if (!isRecord(metrics))
return;
for (const v of Object.values(metrics))
if (typeof v !== "number" || !Number.isFinite(v))
return;
return { subjectSessionId: value["subjectSessionId"], metrics };
}
function verifiedEvidenceBundle(value, id) {
if (!isRecord(value))
throw evidenceBundleError("body is missing");
if (value["schema"] !== "kestrel.evidence-bundle/v1")
throw evidenceBundleError(`schema must be "kestrel.evidence-bundle/v1"`);
if (value["proof_id"] !== undefined && value["proof_id"] !== id)
throw evidenceBundleError("proof_id does not match the requested id");
if (!nonEmptyString(value["bus"]))
throw evidenceBundleError("bus (session-Bus JSONL text) is missing");
if (!nonEmptyString(value["blotter"]))
throw evidenceBundleError("blotter (published Blotter bytes) is missing");
if (value["engine_version"] !== undefined && typeof value["engine_version"] !== "string")
throw evidenceBundleError("engine_version must be a string when present");
if (value["fill_model"] !== undefined && typeof value["fill_model"] !== "string")
throw evidenceBundleError("fill_model must be a string when present");
const publishedGrade = asPublishedGrade(value["publishedGrade"]);
const coveredFields = asCertifiedCoveredFields(value["coveredFields"]);
return {
proof_id: id,
bus: value["bus"],
blotter: value["blotter"],
...typeof value["engine_version"] === "string" ? { engine_version: value["engine_version"] } : {},
...typeof value["fill_model"] === "string" ? { fill_model: value["fill_model"] } : {},
...publishedGrade !== undefined ? { publishedGrade } : {},
...coveredFields !== undefined ? { coveredFields } : {},
...nonEmptyString(value["root"]) ? { root: value["root"] } : {}
};
}
async function safeJson(res) {
try {
return await res.json();
} catch {
return null;
}
}
function httpErr(status, m) {
const exit = status === 404 ? EXIT.NOT_FOUND : status >= 500 ? EXIT.RUNTIME_UNAVAILABLE : EXIT.GENERIC;
return new CliError({ code: `HTTP_${status}`, exit, message: `${m} (HTTP ${status})` });
}
var MAX_PROBLEM_FIELD = 2048;
async function readProblem(res) {
try {
const body = await res.json();
if (body === null || typeof body !== "object")
return;
const b = body;
const str = (v) => {
if (typeof v !== "string" || v.length === 0)
return;
return v.length > MAX_PROBLEM_FIELD ? `${v.slice(0, MAX_PROBLEM_FIELD)}… [truncated]` : v;
};
const title = str(b["title"]);
const detail = str(b["detail"]);
const code = str(b["code"]);
const remediation = str(b["remediation"]) ?? str(b["remedy"]) ?? str(b["fix"]);
if (title === undefined && detail === undefined && code === undefined && remediation === undefined) {
return;
}
return {
...title !== undefined ? { title } : {},
...detail !== undefined ? { detail } : {},
...code !== undefined ? { code } : {},
...remediation !== undefined ? { remediation } : {},
status: res.status
};
} catch {
return;
}
}
async function httpErrFrom(res, m) {
const problem = await readProblem(res);
const base = httpErr(res.status, m);
if (problem === undefined)
return base;
const parts = [problem.title, problem.detail].filter((s) => s !== undefined);
const suffix = parts.length > 0 ? `: ${parts.join(" — ")}` : "";
return new CliError({
code: base.code,
exit: base.exit,
message: `${base.message}${suffix}`,
...problem.remediation !== undefined ? { hint: problem.remediation } : {},
problem
});
}
// src/cli/render/residency.ts
var MCP_ENDPOINT = "https://mcp.kestrel.markets/mcp";
var PROVE_ONE_LINER = "npx kestrel.markets prove";
function residencyObject(proofUrl) {
return {
mcpServers: { "kestrel-markets": { type: "streamable-http", url: MCP_ENDPOINT } },
agentsMdLine: `- kestrel.markets: reproduce/extend this proof — ${proofUrl}`,
cli: PROVE_ONE_LINER
};
}
function renderResidencyOneLine(proofUrl) {
return `keep this proof URL — the shareable durable handle for this run; \`kestrel runs list\` also finds it from any directory (recorded to your home ledger ~/.kestrel, not just this folder): ${proofUrl}`;
}
function renderResidencySnippet(proofUrl) {
const snippet = residencyObject(proofUrl);
const mcpJson = JSON.stringify({ mcpServers: snippet.mcpServers }, null, 2);
const lines = [];
lines.push("");
lines.push("keep this — reproduce or extend this proof:");
lines.push(" 1) add the Kestrel MCP server to your agent config:");
for (const l of mcpJson.split(`
`))
lines.push(` ${l}`);
lines.push(" 2) add to your AGENTS.md:");
lines.push(` ${snippet.agentsMdLine}`);
lines.push(" 3) or just run it again, no credential:");
lines.push(` ${snippet.cli}`);
return lines.join(`
`);
}
// src/cli/commands/sim.ts
var STAND_DOWN_DOC = `# demo: stand-down baseline — supply --plans <file> to author a strategy
`;
var STAND_DOWN_EXAMPLE_PLAN = `PLAN starter budget 1R ttl +24h
WHEN spot > 0
DO buy 1 shares @ spot`;
function usageErr(message, hint) {
return new CliError({ code: "USAGE", exit: EXIT.USAGE, message, ...hint !== undefined ? { hint } : {} });
}
function classifySelector(token) {
const hasUpper = /[A-Z]/.test(token);
const hasLower = /[a-z]/.test(token);
if (hasUpper && hasLower)
return "mixed";
if (hasUpper)
return "tickers";
return "slug";
}
var DATE_RE = /^\d{4}-\d{2}(-\d{2})?$/;
var RANGE_RE = /^\d{4}-\d{2}(-\d{2})?\.\.\d{4}-\d{2}(-\d{2})?$/;
var REL_WHEN = new Set(["yesterday", "last-week", "last-month"]);
function looksLikeWhen(token) {
return DATE_RE.test(token) || RANGE_RE.test(token) || REL_WHEN.has(token);
}
function parseSimSelector(positionals) {
const first = positionals[0];
if (positionals.length > 1) {
const when = positionals[1];
throw usageErr(`date-window selectors are reserved — \`kestrel sim ${first} ${when}\` is not available yet`, "this PR ships curated scenario slugs only; ticker × <when> selectors are coming (kestrel-do4). Run `kestrel sim` for the menu.");
}
const kind = classifySelector(first);
if (kind === "mixed") {
throw usageErr(`"${first}" is ambiguous: a lowercase scenario slug or an UPPERCASE ticker selector? — mixed case is reserved`, "use an all-lowercase scenario slug (e.g. meme-stock-short-squeeze) or, once shipped, all-UPPERCASE tickers (e.g. SPY). Run `kestrel sim` for the menu.");
}
if (kind === "tickers") {
throw usageErr(`ticker selectors are reserved — \`kestrel sim ${first} <when> --plans <file>\` is not available yet`, "this PR ships curated lowercase scenario slugs only; UPPERCASE ticker × <when> selectors are coming (kestrel-do4). Run `kestrel sim` for the menu.");
}
if (looksLikeWhen(first)) {
throw usageErr(`"${first}" is a <when> with no scenario or ticker — reserved`, "name a scenario slug (kestrel sim <slug>); a standalone market window is coming (kestrel-do4). Run `kestrel sim` for the menu.");
}
return { scenarioSlug: first };
}
function asParseError(e) {
if (e instanceof KestrelParseError)
return new CliError({ code: "PARSE", exit: EXIT.USAGE, message: e.message });
return usageErr(e instanceof Error ? e.message : String(e));
}
function loadStrategy(plansPath, bundledDefault) {
if (plansPath === undefined) {
const doc = bundledDefault ?? {
source: STAND_DOWN_DOC,
label: "stand-down baseline (demo) — supply --plans <file> to author a strategy"
};
const documents2 = [parse(doc.source)];
const source2 = canonicalPlansText(documents2);
return { source: source2, label: doc.label, digest: sha256(source2) };
}
let text;
try {
text = plansPath === "-" ? readFileSync(0, "utf8") : readFileSync(plansPath, "utf8");
} catch (e) {
throw new CliError({
code: "NOT_FOUND",
exit: EXIT.NOT_FOUND,
message: `cannot read --plans ${JSON.stringify(plansPath)}: ${e instanceof Error ? e.message : String(e)}`
});
}
let documents;
try {
documents = plansPath.endsWith(".md") ? parseMarkdown(text) : [parse(text)];
} catch (e) {
throw asParseError(e);
}
const source = canonicalPlansText(documents);
return { source, label: plansPath, digest: sha256(source) };
}
function resolveSimBase(globals, env) {
const raw = globals.api ?? env.KESTREL_API;
if (raw === undefined || raw === "" || raw === "default")
return DEFAULT_API;
return raw;
}
function resolveCopyToken(flags, env) {
const raw = flags.get("copy-token") ?? env.KESTREL_COPY_TOKEN;
return raw === undefined || raw === "" ? undefined : raw;
}
function resolveSpend(flags) {
const raw = flags.get("budget");
if (raw === undefined)
return;
const budget = Number(raw);
if (raw.trim() === "" || !Number.isFinite(budget) || budget < 0)
throw usageErr(`--budget must be a finite, non-negative number (got ${JSON.stringify(raw)})`, "declare a spend ceiling in USD, e.g. `--budget 0.50`; use `--budget 0` to pause at the first metered draw");
return { budget };
}
async function simCommand(argv, ctx, globals, deps = {}) {
const { positionals, flags } = parseArgs(argv, new Set, new Set(["plans", "copy-token", "budget"]));
const env = deps.env ?? process.env;
const base = resolveSimBase(globals, env);
const client = new HostedFunnel({ baseUrl: base, ...deps.fetch !== undefined ? { fetch: deps.fetch } : {} });
try {
return await runSimFunnel(client, base, positionals, flags, ctx, deps, env);
} catch (e) {
throw asConnectionError(e, base) ?? e;
}
}
async function runSimFunnel(client, base, positionals, flags, ctx, deps, env) {
if (positionals.length === 0) {
const { entries: entries2 } = await client.catalog();
return renderMenu(entries2, ctx);
}
const { scenarioSlug } = parseSimSelector(positionals);
const { entries } = await client.catalog();
const resolved = resolveScenarioSlug(scenarioSlug, entries);
if (resolved === null) {
const near = nearMissSlugs(scenarioSlug, entries);
throw new CliError({
code: "NOT_FOUND",
exit: EXIT.NOT_FOUND,
message: `unknown scenario slug ${JSON.stringify(scenarioSlug)}`,
hint: near.length > 0 ? `did you mean: ${near.join(", ")}? — run \`kestrel sim\` for the full menu` : "run `kestrel sim` for the menu"
});
}
const plansPath = flags.get("plans");
const strategy = loadStrategy(plansPath);
const copyToken = resolveCopyToken(flags, env);
const spend = resolveSpend(flags);
return runHostedProof({
client,
resolved,
strategy,
ctx,
hostedStorePath: deps.hostedStorePath ?? defaultHostedStorePath(env),
residencyOneLine: true,
authoredPlan: plansPath !== undefined,
...plansPath === undefined ? {
standDownLesson: {
scenarioSlug: resolved.canonicalSlug,
examplePlanSource: STAND_DOWN_EXAMPLE_PLAN,
swapInCli: `npx kestrel.markets sim ${resolved.canonicalSlug} --plans <your-plan.kestrel>`,
proveCli: "npx kestrel.markets prove"
}
} : {},
...spend === undefined ? { paidBoundaryNudge: { scenarioSlug: resolved.canonicalSlug } } : {},
...copyToken !== undefined ? { copyToken } : {},
...spend !== undefined ? { spend } : {}
});
}
async function runHostedProof(run) {
const { client, resolved, strategy, ctx } = run;
const trial = await client.mintTrial(run.copyToken !== undefined ? { copyToken: run.copyToken } : {});
const g = await client.simulate({ source: strategy.source, artifactId: resolved.entry.artifact_id, ...run.spend !== undefined ? { spend: run.spend } : {} }, trial.capability);
if ("settlementRequired" in g) {
renderSettlementRequired(g.settlementRequired, ctx);
return EXIT.PAYMENT_REQUIRED;
}
if (g.gated) {
renderOffer(g.payment, ctx);
return EXIT.PAYMENT_REQUIRED;
}
const op = g.value;
const gradeArtifact2 = gradeArtifactOf(op);
const gradeReceipt2 = gradeReceiptOf(op);
const proofArtifactId = gradeArtifact2.artifact_id;
const proofUrl = proofWebUrl(proofArtifactId);
let proof = null;
try {
proof = await client.proof(gradeArtifact2, gradeReceipt2, op.operation_id, trial.capability);
} catch (e) {
if (!isProofSettling(e))
throw e;
process.stderr.write(`warning: the grade proof for ${op.operation_id} is not yet readable (settling — retry shortly); ` + `metrics are pending and this run is not yet in \`kestrel runs list --hosted\`: ${e.message}
`);
}
const metrics = proof === null ? null : gradedMetrics(proof.result.metrics);
const neverFiredReason = metrics !== null && metrics.orderCount === 0 ? neverFiredReasonOf(proof) : undefined;
if (proof !== null && metrics !== null) {
run.onProof?.({
proofUrl,
proofId: proofArtifactId,
root: proof.root,
signature: proof.signature,
kid: proof.verification.kid,
epoch: proof.verification.epoch,
metrics
});
}
if (proof !== null && metrics !== null) {
recordHostedRun(run.hostedStorePath, {
source: "hosted",
slug: resolved.canonicalSlug,
requested_slug: resolved.requestedSlug,
operation_id: op.operation_id,
grade_artifact_id: proofArtifactId,
bus_artifact_id: op.evidence.busRef.artifact_id,
manifest_artifact_id: op.evidence.manifestRef.artifact_id,
order_count: metrics.orderCount,
fill_count: metrics.fillCount,
realized_pnl: metrics.realizedPnl,
proof_url: proofUrl,
issued_at: proof.issuedAt ?? "",
strategy: strategy.label,
strategy_digest: strategy.digest,
...run.copyToken !== undefined ? { copy_token: run.copyToken } : {},
...neverFiredReason !== undefined ? { never_fired_reason: neverFiredReason } : {}
});
}
if (!run.quiet && run.copyToken !== undefined && run.authoredPlan !== true) {
process.stderr.write(`notice: --copy-token continues a proof, but the origin plan is NOT recoverable from the opaque token — ` + `this run used the bundled/stand-down baseline (${strategy.label}), not the strategy you are continuing. ` + `Supply \`--plans <your-plan>\` to author the continuation; the lineage back to the origin is recorded on the receipt.
`);
}
if (run.quiet)
return 0;
if (ctx.mode === "json") {
process.stdout.write(JSON.stringify({
schema: "kestrel.sim/v1",
scenario: resolved.canonicalSlug,
requested: resolved.requestedSlug,
operation: op,
metrics: proof === null ? null : proof.result.metrics,
...neverFiredReason !== undefined ? { never_fired_reason: neverFiredReason } : {},
proof_url: proofUrl,
...proof === null ? { proof_settling: true } : {},
...run.emitResidency ? { residency: residencyObject(proofUrl) } : {},
...run.residencyOneLine && !run.emitResidency ? { residency_guidance: renderResidencyOneLine(proofUrl) } : {},
...run.demoLesson !== undefined ? { demo: demoLessonObject(run.demoLesson) } : {},
...run.standDownLesson !== undefined && metrics !== null && metrics.orderCount === 0 ? { stand_down: standDownLessonObject(run.standDownLesson) } : {},
...run.paidBoundaryNudge !== undefined && metrics !== null ? { paid_nudge: paidBoundaryNudgeObject(run.paidBoundaryNudge) } : {}
}) + `
`);
return 0;
}
const story = {
canonicalSlug: resolved.canonicalSlug,
requestedSlug: resolved.requestedSlug,
viaAlias: resolved.viaAlias,
title: resolved.entry.title,
instrument: resolved.entry.instrument,
period: resolved.entry.period,
frameCount: resolved.entry.frame_count,
strategyLabel: strategy.label,
operation: { id: op.operation_id, status: op.status },
receipts: summarizeReceipts(op.receipts),
evidence: op.evidence,
metrics,
proofUrl
};
renderSimStory(story, ctx);
if (neverFiredReason !== undefined)
process.stdout.write(neverFiredLine(neverFiredReason) + `
`);
if (run.demoLesson !== undefined)
process.stdout.write(renderDemoLesson(run.demoLesson) + `
`);
if (run.standDownLesson !== undefined && metrics !== null && metrics.orderCount === 0)
process.stdout.write(renderStandDownLesson(run.standDownLesson) + `
`);
if (run.paidBoundaryNudge !== undefined && metrics !== null)
process.stdout.write(renderPaidBoundaryNudge(run.paidBoundaryNudge) + `
`);
if (run.emitResidency)
process.stdout.write(renderResidencySnippet(proofUrl) + `
`);
if (run.residencyOneLine && !run.emitResidency)
process.stdout.write(renderResidencyOneLine(proofUrl) + `
`);
return 0;
}
function isProofSettling(e) {
return e instanceof CliError && e.code === "HTTP_404";
}
function recordHostedRun(storePath, row) {
try {
appendHostedRun(storePath, row);
} catch (e) {
process.stderr.write(`warning: could not append the hosted-run receipt to ${JSON.stringify(storePath)} — ` + `\`kestrel runs list --hosted\` will not show this run: ${e instanceof Error ? e.message : String(e)}
`);
}
}
function renderMenu(entries, ctx) {
const scenarios = entries.map((e) => ({
slug: slugifyTitle(e.title),
title: e.title,
instrument: e.instrument,
artifactId: e.artifact_id,
free: e.free
}));
const menu = { scenarios, aliases: SCENARIO_ALIASES };
if (ctx.mode === "json") {
process.stdout.write(JSON.stringify({
schema: "kestrel.sim.menu/v1",
scenarios: scenarios.map((s) => ({
slug: s.slug,
title: s.title,
instrument: s.instrument,
artifact_id: s.artifactId,
free: s.free
})),
aliases: SCENARIO_ALIASES,
usage: "kestrel sim <scenario-slug> [--plans <file>]"
}) + `
`);
return 0;
}
renderSimMenu(menu, ctx);
return 0;
}
export { slugifyTitle, resolveScenarioSlug, nearMissSlugs, HostedFunnel, STAND_DOWN_DOC, STAND_DOWN_EXAMPLE_PLAN, classifySelector, parseSimSelector, loadStrategy, resolveSimBase, resolveCopyToken, resolveSpend, simCommand, runHostedProof };