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.
555 lines (520 loc) • 31.8 kB
text/typescript
/**
* # cli/render/tables — three render branches for the registry rows (Kestrel CLI v1 §5)
*
* Pure functions of `(data, ctx)`. The data is IDENTICAL across the three branches; only the skin
* changes:
* - **human**: fixed-width glyph tables (`─` separators, right-aligned signed `usd()`, `✓` marks,
* `shortId` truncation), ANSI color gated on `ctx.color`, cells truncated to `ctx.width`.
* - **text**: one record per line, TAB-separated, no glyphs, no color, full precision (grep/LLM).
* - **json**: one deterministic object `{schema, rows:[…]}`, keys sorted, numbers as JSON numbers.
*
* The Unicode/glyph printers live ONLY in the human branch (the machine branches are plain).
*/
import type { OutputCtx } from "../context.ts";
import type { LeaderboardRow, LineageInstance, PlanInstanceRow, RunRow, RunWithPlans } from "../heavy.ts";
import { isPlanDigest, type HostedRunRow } from "../../ledger/hosted.ts";
// ─────────────────────────────────────────────────────────────────────────────
// Formatting primitives (human money glyph + padding) — human branch only
// ─────────────────────────────────────────────────────────────────────────────
const GREEN = "[32m";
const RED = "[31m";
const DIM = "[2m";
const BOLD = "[1m";
const RESET = "[0m";
function pad(s: string, w: number): string {
return s.length >= w ? s : s + " ".repeat(w - s.length);
}
function padL(s: string, w: number): string {
return s.length >= w ? s : " ".repeat(w - s.length) + s;
}
/** Signed dollars, 2dp (`+25.00`, `-270.00`) — the human money glyph. */
function usd(n: number): string {
return (n >= 0 ? "+" : "") + n.toFixed(2);
}
function shortId(id: string): string {
return id.slice(0, 12);
}
/**
* A compact, legible plan/strategy LABEL for the runs table (kestrel-etyu): the `--plans` file's
* BASENAME or the stand-down/bundled default's short head (its trailing "— supply --plans …" hint
* dropped). An absent field (an old receipt written before the field existed) renders `—` — UNKNOWN,
* fail-soft on the display path, never a fault. The label is for READABILITY; two runs of one path
* edited in place share it — the {@link shortDigest} column is what tells them apart. The FULL label
* is preserved verbatim in `runs show` and every json payload; this only trims the one-line list cell.
*/
function compactStrategy(strategy: string | undefined): string {
if (strategy === undefined || strategy === "") return "—";
const head = strategy.split(" — ")[0]!; // drop the demo label's trailing "— supply --plans <file> …"
return head.slice(head.lastIndexOf("/") + 1); // basename of a --plans path (no-op when there is no `/`)
}
/** The plan CONTENT identity, trimmed to a legible 8-hex prefix for the list cell (kestrel-etyu). This
* is the field that distinguishes two runs of ONE `--plans` path edited in place. `—` for anything
* {@link isPlanDigest} calls UNKNOWN (absent, empty, malformed) — the SAME predicate `samePlan` reads,
* so the list cell and the compare verdict can never disagree about what is known. */
function shortDigest(digest: string | undefined): string {
return isPlanDigest(digest) ? digest.slice(0, 8) : "—";
}
/**
* A DISPLAY-only column: header + alignment + a human cell string. Used by the human/text branches
* ({@link glyphTable}/{@link tsv}), which need nothing else. `json` is optional HERE and serves one
* purpose — a `money` column's colorizer reads it for the numeric value.
*
* This is the weaker of the two column types on purpose. {@link Col} (below) is what a json-rendered
* table must use, and it REQUIRES the json pair — so `tsc`, not a docstring, keeps every column of a
* machine schema in that schema.
*/
interface DisplayCol<R> {
readonly header: string;
readonly right: boolean;
readonly cell: (r: R) => string; // shared by human + text
/** When true, positive→green / negative→red in the human branch (money columns). Needs {@link json}. */
readonly money?: boolean;
/** The numeric/raw value — read ONLY by the money colorizer on a display column. */
readonly json?: (r: R) => unknown;
}
/**
* A JSON-PROJECTED column: a {@link DisplayCol} that additionally names its wire (key,value) pair.
* `jsonKey`/`json` are REQUIRED — a table rendered through {@link jsonRowObjs} takes `Col<R>[]`, so a
* column added without a json projection is a COMPILE error, never a field that silently vanishes from
* `kestrel.runs/v1` / `kestrel.lineage/v1` / `kestrel.leaderboard/v1`.
*/
interface Col<R> extends DisplayCol<R> {
readonly jsonKey: string;
readonly json: (r: R) => unknown;
}
/** Render a header + rows as a fixed-width glyph table with optional color (human branch). */
function glyphTable<R>(cols: readonly DisplayCol<R>[], rows: readonly R[], ctx: OutputCtx): string {
const headers = cols.map((c) => c.header);
const body = rows.map((r) => cols.map((c) => c.cell(r)));
const widths = headers.map((h, c) => Math.max(h.length, ...body.map((row) => (row[c] ?? "").length)));
const colorCell = (raw: string, c: number, r: R): string => {
if (!ctx.color) return raw;
const col = cols[c]!;
if (col.money && col.json !== undefined) {
const v = col.json(r);
if (typeof v === "number") return v >= 0 ? `${GREEN}${raw}${RESET}` : `${RED}${raw}${RESET}`;
}
return raw;
};
const fmtHeader = headers.map((h, c) => (cols[c]!.right ? padL(h, widths[c]!) : pad(h, widths[c]!)));
const headerLine = ctx.color ? fmtHeader.map((h) => `${BOLD}${h}${RESET}`).join(" ") : fmtHeader.join(" ");
const sepRaw = widths.map((w) => "─".repeat(w)).join(" ");
const sep = ctx.color ? `${DIM}${sepRaw}${RESET}` : sepRaw;
const bodyLines = rows.map((r, ri) =>
cols
.map((col, c) => {
const raw = body[ri]![c]!;
const padded = col.right ? padL(raw, widths[c]!) : pad(raw, widths[c]!);
return colorCell(padded, c, r);
})
.join(" "),
);
return [headerLine, sep, ...bodyLines].join("\n");
}
/** Text branch: TSV of the shared cell() strings, no header, no color. */
function tsv<R>(cols: readonly DisplayCol<R>[], rows: readonly R[]): string {
return rows.map((r) => cols.map((c) => c.cell(r)).join("\t")).join("\n");
}
/** The per-row `{key:value}` object for the json branch (keys sorted for determinism). */
function jsonRowObjs<R>(cols: readonly Col<R>[], rows: readonly R[]): Record<string, unknown>[] {
return rows.map((r) => {
const o: Record<string, unknown> = {};
for (const c of cols) o[c.jsonKey] = c.json(r);
return sortKeys(o);
});
}
/** Json branch: `{schema, rows:[{key:value}]}`, keys sorted for determinism. */
function jsonRows<R>(schema: string, cols: readonly Col<R>[], rows: readonly R[]): string {
return JSON.stringify({ schema, rows: jsonRowObjs(cols, rows) });
}
/** Deterministic key ordering for machine JSON. */
function sortKeys<T extends Record<string, unknown>>(o: T): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const k of Object.keys(o).sort()) out[k] = o[k];
return out;
}
// ─────────────────────────────────────────────────────────────────────────────
// runs list
// ─────────────────────────────────────────────────────────────────────────────
const RUN_COLS: readonly Col<RunRow>[] = [
// `source` labels the run's lineage (`local` = a `run`/`day` session recorded here; `hosted` = a
// `sim` receipt, rendered from HOSTED_COLS below) so a merged `runs list` is legible (kestrel-pvlc).
{ header: "source", right: false, cell: () => "local", jsonKey: "source", json: () => "local" },
{ header: "run_id", right: false, cell: (r) => shortId(r.run_id), jsonKey: "run_id", json: (r) => r.run_id },
{ header: "date", right: false, cell: (r) => r.session_date, jsonKey: "session_date", json: (r) => r.session_date },
{ header: "mode", right: false, cell: (r) => r.mode, jsonKey: "mode", json: (r) => r.mode },
{ header: "fill_model", right: false, cell: (r) => r.fill_model, jsonKey: "fill_model", json: (r) => r.fill_model },
{ header: "cal", right: false, cell: (r) => (r.calibrated ? "✓" : ""), jsonKey: "calibrated", json: (r) => Boolean(r.calibrated) },
{ header: "realized", right: true, money: true, cell: (r) => usd(r.realized_floor_usd), jsonKey: "realized_floor_usd", json: (r) => r.realized_floor_usd },
{ header: "expected", right: true, money: true, cell: (r) => usd(r.expected_usd), jsonKey: "expected_usd", json: (r) => r.expected_usd },
{ header: "premium", right: true, cell: (r) => r.premium_spent.toFixed(2), jsonKey: "premium_spent", json: (r) => r.premium_spent },
{ header: "events", right: true, cell: (r) => String(r.bus_events), jsonKey: "bus_events", json: (r) => r.bus_events },
];
/** The hosted-lineage columns (`sim` receipts, kestrel-pvlc). Same three skins as the local runs;
* the shared `source` column ties the two lineages into one `runs list`. The human/text branches show
* a readable subset; the json branch emits the FULL {@link HostedRunRow} (every artifact id + the
* signed timestamp) so an agent can re-resolve the whole receipt, not just what fits a glyph table.
*
* Hence {@link DisplayCol}, not {@link Col}: these columns are the human/text SUBSET and never project
* the wire schema (the spread in {@link renderRuns} does), so they carry no `jsonKey`/`json` pair — and
* the type makes passing them to {@link jsonRowObjs} a compile error rather than a silent half-schema. */
const HOSTED_COLS: readonly DisplayCol<HostedRunRow>[] = [
{ header: "source", right: false, cell: (h) => h.source },
{ header: "scenario", right: false, cell: (h) => h.slug },
// The plan that produced the run (kestrel-etyu) — the human LABEL for readability + the CONTENT
// digest that actually distinguishes two runs of ONE path edited in place ("did my tweak help?").
{ header: "strategy", right: false, cell: (h) => compactStrategy(h.strategy) },
{ header: "digest", right: false, cell: (h) => shortDigest(h.strategy_digest) },
{ header: "operation", right: false, cell: (h) => h.operation_id },
{ header: "orders", right: true, cell: (h) => String(h.order_count) },
{ header: "fills", right: true, cell: (h) => String(h.fill_count) },
// `json` here is NOT a wire projection — it is the money colorizer's numeric accessor (see DisplayCol).
{ header: "realized", right: true, money: true, cell: (h) => usd(h.realized_pnl), json: (h) => h.realized_pnl },
{ header: "proof", right: false, cell: (h) => h.proof_url },
];
/**
* Render the local runs and the hosted-lineage receipts as ONE `runs list` (kestrel-pvlc). The two
* lineages carry different columns, so human/text keep them as two labeled sections; json merges them
* into a single self-describing `rows` array (each row's `source` names its lineage). An all-empty
* result stays the prior `(no runs)` / empty-payload sentinel byte-for-byte.
*/
export function renderRuns(rows: readonly RunRow[], ctx: OutputCtx, hosted: readonly HostedRunRow[] = []): string {
if (ctx.mode === "json") {
const local = jsonRowObjs(RUN_COLS, rows);
// The full receipt, not the glyph subset — sorted keys for determinism, like every json row.
const hostedObjs = hosted.map((h) => sortKeys({ ...h }));
return JSON.stringify({ schema: "kestrel.runs/v1", rows: [...local, ...hostedObjs] }) + "\n";
}
if (ctx.mode === "text") {
const parts: string[] = [];
if (rows.length > 0) parts.push(tsv(RUN_COLS, rows));
if (hosted.length > 0) parts.push(tsv(HOSTED_COLS, hosted));
return parts.length === 0 ? "" : parts.join("\n") + "\n";
}
if (rows.length === 0 && hosted.length === 0) return "(no runs)\n";
const parts: string[] = [];
if (rows.length > 0) parts.push(glyphTable(RUN_COLS, rows, ctx) + `\n\n${rows.length} run(s)`);
if (hosted.length > 0) parts.push(glyphTable(HOSTED_COLS, hosted, ctx) + `\n\n${hosted.length} hosted run(s)`);
return parts.join("\n\n") + "\n";
}
// ─────────────────────────────────────────────────────────────────────────────
// runs show (a run header block + its plans table)
// ─────────────────────────────────────────────────────────────────────────────
const PLAN_COLS: readonly Col<PlanInstanceRow>[] = [
{ header: "plan", right: false, cell: (p) => p.name, jsonKey: "name", json: (p) => p.name },
{ header: "final_state", right: false, cell: (p) => p.final_state, jsonKey: "final_state", json: (p) => p.final_state },
{ header: "outcome", right: false, cell: (p) => p.outcome ?? "—", jsonKey: "outcome", json: (p) => p.outcome },
{ header: "fired", right: true, cell: (p) => (p.fired ? "yes" : "no"), jsonKey: "fired", json: (p) => Boolean(p.fired) },
{ header: "orders", right: true, cell: (p) => String(p.orders), jsonKey: "orders", json: (p) => p.orders },
{ header: "fills", right: true, cell: (p) => String(p.fills), jsonKey: "fills", json: (p) => p.fills },
{ header: "realized", right: true, money: true, cell: (p) => usd(p.realized_usd), jsonKey: "realized_usd", json: (p) => p.realized_usd },
{ header: "expected", right: true, money: true, cell: (p) => usd(p.expected_usd), jsonKey: "expected_usd", json: (p) => p.expected_usd },
];
export function renderRunShow(rec: RunWithPlans, ctx: OutputCtx): string {
const r = rec.run;
if (ctx.mode === "json") {
const run: Record<string, unknown> = {
run_id: r.run_id,
session_date: r.session_date,
mode: r.mode,
instruments: r.instruments,
fill_model: r.fill_model,
calibrated: Boolean(r.calibrated),
recorded_at: r.recorded_at,
r_usd: r.r_usd,
realized_floor_usd: r.realized_floor_usd,
expected_usd: r.expected_usd,
premium_spent: r.premium_spent,
bus_events: r.bus_events,
bus_sha256: r.bus_sha256,
plans_sha256: r.plans_sha256,
determinism_hash: r.determinism_hash,
report_path: r.report_path,
};
const plans = rec.plans.map((p) =>
sortKeys({
name: p.name,
final_state: p.final_state,
outcome: p.outcome,
fired: Boolean(p.fired),
orders: p.orders,
fills: p.fills,
realized_usd: p.realized_usd,
expected_usd: p.expected_usd,
}),
);
return JSON.stringify({ schema: "kestrel.run/v1", run: sortKeys(run), plans }) + "\n";
}
if (ctx.mode === "text") {
const lines = [
`run_id\t${r.run_id}`,
`session_date\t${r.session_date}`,
`mode\t${r.mode}`,
`instruments\t${r.instruments}`,
`fill_model\t${r.fill_model}`,
`calibrated\t${Boolean(r.calibrated)}`,
`recorded_at\t${r.recorded_at}`,
`r_usd\t${r.r_usd}`,
`realized_floor_usd\t${r.realized_floor_usd}`,
`expected_usd\t${r.expected_usd}`,
`premium_spent\t${r.premium_spent}`,
`bus_events\t${r.bus_events}`,
`bus_sha256\t${r.bus_sha256}`,
`determinism_hash\t${r.determinism_hash}`,
`report_path\t${r.report_path ?? ""}`,
];
const plansTsv = rec.plans.length === 0 ? "" : "\n" + tsv(PLAN_COLS, rec.plans);
return lines.join("\n") + plansTsv + "\n";
}
// human
const head = [
`run_id ${r.run_id}`,
`session ${r.session_date} ${r.mode} [${r.instruments}]`,
`fill_model ${r.fill_model}${r.calibrated ? " (calibrated)" : ""}`,
`recorded_at ${r.recorded_at} (settle ts, injected)`,
`r_usd ${r.r_usd.toFixed(2)}`,
`totals realized ${usd(r.realized_floor_usd)} expected ${usd(r.expected_usd)} premium ${r.premium_spent.toFixed(2)}`,
`bus ${r.bus_events} events sha ${shortId(r.bus_sha256)} det ${shortId(r.determinism_hash)}`,
r.report_path !== null ? `report_path ${r.report_path}` : "report_path (none)",
"",
].join("\n");
const plansTable = rec.plans.length === 0 ? "(no plan instances)\n" : glyphTable(PLAN_COLS, rec.plans, ctx) + "\n";
return head + "\n" + plansTable;
}
/**
* Re-print a HOSTED `sim` receipt (kestrel-pvlc) — the full print-once story the sim showed exactly once
* on stdout, resolved back from the local `hosted-runs.jsonl` lineage: the scenario, the grade digest,
* every durable evidence artifact id, and the shareable proof URL (the last human line, mirroring the sim
* story). A distinct `kestrel.hosted-run/v1` json schema so a hosted receipt is never mistaken for a local
* run's shape. Pure text — this render path is node-light, like the store it reads.
*/
export function renderHostedRunShow(h: HostedRunRow, ctx: OutputCtx): string {
if (ctx.mode === "json") {
return JSON.stringify({ schema: "kestrel.hosted-run/v1", hosted_run: sortKeys({ ...h }) }) + "\n";
}
if (ctx.mode === "text") {
return (
[
`source\t${h.source}`,
`operation_id\t${h.operation_id}`,
`slug\t${h.slug}`,
`requested_slug\t${h.requested_slug}`,
`strategy\t${h.strategy ?? ""}`,
`strategy_digest\t${isPlanDigest(h.strategy_digest) ? h.strategy_digest : ""}`,
`order_count\t${h.order_count}`,
`fill_count\t${h.fill_count}`,
`realized_pnl\t${h.realized_pnl}`,
// The grader's never-fired reason (kestrel-kglw) — present ONLY on a re-shown 0-order run that
// carried one, so a returning user re-reads WHY nothing fired instead of a bare order_count=0.
...(h.never_fired_reason !== undefined ? [`never_fired_reason\t${h.never_fired_reason}`] : []),
`grade_artifact_id\t${h.grade_artifact_id}`,
`bus_artifact_id\t${h.bus_artifact_id}`,
`manifest_artifact_id\t${h.manifest_artifact_id}`,
`issued_at\t${h.issued_at}`,
...(h.copy_token !== undefined ? [`continues_copy_token\t${h.copy_token}`] : []),
`proof_url\t${h.proof_url}`,
].join("\n") + "\n"
);
}
// human — the proof URL is the LAST line, mirroring the sim story's ordering.
const aliasLine = h.requested_slug !== h.slug ? [`alias ${h.requested_slug} → ${h.slug}`] : [];
// The recorded continuation lineage (kestrel-gu9h): the opaque copy-token this run continued from.
const lineageLine = h.copy_token !== undefined ? [`continues copy-token ${h.copy_token}`] : [];
return (
[
`operation ${h.operation_id} (source: hosted)`,
`scenario ${h.slug}`,
...aliasLine,
`strategy ${h.strategy ?? "(unknown — receipt predates the plan field)"}`,
`plan digest ${isPlanDigest(h.strategy_digest) ? h.strategy_digest : "(unknown — predates the plan digest, or unusable)"}`,
`grade order_count=${h.order_count} fill_count=${h.fill_count} realized_pnl=${usd(h.realized_pnl)}`,
// The never-fired reason (kestrel-kglw): a re-shown 0-order run states WHY nothing fired, exactly
// as the sim story did once — so `runs show` is never the bare `order_count=0` the receipt used to
// re-print (the silent-gate class, one surface downstream). Only when the receipt carried a reason.
...(h.never_fired_reason !== undefined ? [`why no orders fired — ${h.never_fired_reason}`] : []),
`evidence manifest=${h.manifest_artifact_id} bus=${h.bus_artifact_id} grade=${h.grade_artifact_id}`,
...lineageLine,
`issued_at ${h.issued_at === "" ? "(unstamped)" : h.issued_at}`,
`proof ${h.proof_url}`,
"",
].join("\n")
);
}
// ─────────────────────────────────────────────────────────────────────────────
// runs compare (kestrel-etyu) — diff two hosted `sim` receipts, "did my tweak help?"
// ─────────────────────────────────────────────────────────────────────────────
/** Signed integer delta glyph (`+3`, `-1`, `0`) — deterministic, no locale. */
function signedInt(n: number): string {
return (n > 0 ? "+" : "") + String(n);
}
/** Signed 2dp money delta (`+12.00`, `-12.00`) reusing {@link usd}. */
function signedUsd(n: number): string {
return usd(n);
}
/**
* The plan-identity relationship between two receipts (kestrel-etyu): do they carry the SAME plan
* CONTENT? `true`/`false` only when BOTH digests are KNOWN per {@link isPlanDigest}; `null` whenever
* either is unknown (absent, empty, or malformed) — never a guessed verdict in EITHER direction. This
* is the signal that makes "did my tweak help?" answerable even when both runs used the SAME `--plans`
* path (an in-place edit moves the digest).
*
* It reads the shared predicate rather than an `=== undefined` check on purpose: a bare undefined-check
* let an empty-string digest count as known, so two UNKNOWN digests compared EQUAL and this returned
* `true` — "A and B ran the SAME plan content" — for runs the list was rendering `—`. Unknown ≠ match.
*/
function samePlan(a: HostedRunRow, b: HostedRunRow): boolean | null {
if (!isPlanDigest(a.strategy_digest) || !isPlanDigest(b.strategy_digest)) return null;
return a.strategy_digest === b.strategy_digest;
}
/**
* Diff two HOSTED `sim` receipts (kestrel-etyu) — the returning user's "did my tweak help?" job. `a`
* is the baseline, `b` the tweak; every delta is `b − a` (how B moved from A). It shows WHICH plan
* each run used — its LABEL *and* its CONTENT digest, plus whether the two are the same plan content
* — and diffs the graded activity metrics (orders, fills, realized P&L). The digest is what makes the
* in-place-edit workflow work: two runs of ONE `--plans` path share a label but differ by digest.
*
* Pure + deterministic: integer/`toFixed` arithmetic + a hex compare over the two rows only — no wall
* clock, no RNG, so the same pair renders byte-identically. A missing `strategy`/`strategy_digest` (a
* pre-field receipt) reads `(unknown)` and yields `same_plan: null`; comparing two DIFFERENT scenarios
* is not blocked but is flagged, since cross-scenario P&L is not directly comparable.
*/
export function renderRunsCompare(a: HostedRunRow, b: HostedRunRow, ctx: OutputCtx): string {
const dOrders = b.order_count - a.order_count;
const dFills = b.fill_count - a.fill_count;
const dPnl = b.realized_pnl - a.realized_pnl;
const stratA = a.strategy ?? "(unknown)";
const stratB = b.strategy ?? "(unknown)";
const sameScenario = a.slug === b.slug;
const same = samePlan(a, b);
if (ctx.mode === "json") {
return (
JSON.stringify({
schema: "kestrel.runs-compare/v1",
same_scenario: sameScenario,
// Whether A and B ran the SAME plan CONTENT (by digest); null when either digest is unknown.
same_plan: same,
a: sortKeys({ ...a }),
b: sortKeys({ ...b }),
// Deltas are B − A (the tweak relative to the baseline) — the activity family the receipt carries.
delta: sortKeys({ order_count: dOrders, fill_count: dFills, realized_pnl: dPnl }),
}) + "\n"
);
}
if (ctx.mode === "text") {
const lines = [
`a_operation_id\t${a.operation_id}`,
`b_operation_id\t${b.operation_id}`,
`a_scenario\t${a.slug}`,
`b_scenario\t${b.slug}`,
`same_scenario\t${sameScenario}`,
`a_strategy\t${a.strategy ?? ""}`,
`b_strategy\t${b.strategy ?? ""}`,
`a_strategy_digest\t${isPlanDigest(a.strategy_digest) ? a.strategy_digest : ""}`,
`b_strategy_digest\t${isPlanDigest(b.strategy_digest) ? b.strategy_digest : ""}`,
`same_plan\t${same === null ? "unknown" : same}`,
`metric\ta\tb\tdelta`,
`order_count\t${a.order_count}\t${b.order_count}\t${dOrders}`,
`fill_count\t${a.fill_count}\t${b.fill_count}\t${dFills}`,
`realized_pnl\t${a.realized_pnl}\t${b.realized_pnl}\t${dPnl}`,
];
return lines.join("\n") + "\n";
}
// human — a small metric table (A | B | Δ) under a header naming each run's scenario + plan (label
// AND content digest). The plan line states whether A and B ran the same plan content.
const metrics: readonly { label: string; a: string; b: string; d: string }[] = [
{ label: "orders", a: String(a.order_count), b: String(b.order_count), d: signedInt(dOrders) },
{ label: "fills", a: String(a.fill_count), b: String(b.fill_count), d: signedInt(dFills) },
{ label: "realized", a: usd(a.realized_pnl), b: usd(b.realized_pnl), d: signedUsd(dPnl) },
];
const wLabel = Math.max(6, ...metrics.map((m) => m.label.length));
const wA = Math.max(1, "A".length, ...metrics.map((m) => m.a.length));
const wB = Math.max(1, "B".length, ...metrics.map((m) => m.b.length));
const wD = Math.max(1, "Δ".length, ...metrics.map((m) => m.d.length));
const planNote =
same === null
? "plan content identity UNKNOWN (a receipt predates the plan digest, or carries an unusable one)"
: same
? "plan A and B ran the SAME plan content (digests match)"
: "plan A and B ran DIFFERENT plan content (digests differ) — a real tweak";
const head = [
`compare · ${sameScenario ? a.slug : `${a.slug} ↔ ${b.slug}`}`,
`A ${shortId(a.operation_id)} ${a.slug}`,
` plan ${stratA} #${shortDigest(a.strategy_digest)}`,
`B ${shortId(b.operation_id)} ${b.slug}`,
` plan ${stratB} #${shortDigest(b.strategy_digest)}`,
planNote,
...(sameScenario ? [] : ["note different scenarios — P&L is not directly comparable"]),
"",
`${pad("metric", wLabel)} ${padL("A", wA)} ${padL("B", wB)} ${padL("Δ", wD)}`,
`${"─".repeat(wLabel)} ${"─".repeat(wA)} ${"─".repeat(wB)} ${"─".repeat(wD)}`,
];
const rows = metrics.map((m) => `${pad(m.label, wLabel)} ${padL(m.a, wA)} ${padL(m.b, wB)} ${padL(m.d, wD)}`);
return [...head, ...rows, ""].join("\n");
}
// ─────────────────────────────────────────────────────────────────────────────
// lineage
// ─────────────────────────────────────────────────────────────────────────────
const LINEAGE_COLS: readonly Col<LineageInstance>[] = [
{ header: "date", right: false, cell: (i) => i.session_date, jsonKey: "session_date", json: (i) => i.session_date },
{ header: "mode", right: false, cell: (i) => i.mode, jsonKey: "mode", json: (i) => i.mode },
{ header: "fill_model", right: false, cell: (i) => i.fill_model, jsonKey: "fill_model", json: (i) => i.fill_model },
{ header: "final_state", right: false, cell: (i) => i.final_state, jsonKey: "final_state", json: (i) => i.final_state },
{ header: "outcome", right: false, cell: (i) => i.outcome ?? "—", jsonKey: "outcome", json: (i) => i.outcome },
{ header: "fired", right: true, cell: (i) => (i.fired ? "yes" : "no"), jsonKey: "fired", json: (i) => Boolean(i.fired) },
{ header: "fills", right: true, cell: (i) => String(i.fills), jsonKey: "fills", json: (i) => i.fills },
{ header: "realized", right: true, money: true, cell: (i) => usd(i.realized_usd), jsonKey: "realized_usd", json: (i) => i.realized_usd },
{ header: "expected", right: true, money: true, cell: (i) => usd(i.expected_usd), jsonKey: "expected_usd", json: (i) => i.expected_usd },
];
/**
* Render a plan lineage. `coverage`, when supplied (the command passes it on an EMPTY result,
* kestrel-qozv), names what lineage DOES track so an empty result is never SILENT: json carries it
* as a structured `coverage` field (machine-legible, not advisory noise), human appends it under the
* `(no instances)` sentinel. text stays a pure TSV payload — the command mirrors the notice to stderr.
*/
export function renderLineage(
name: string,
rows: readonly LineageInstance[],
ctx: OutputCtx,
coverage?: string,
): string {
if (ctx.mode === "json") {
const body = JSON.parse(jsonRows("kestrel.lineage/v1", LINEAGE_COLS, rows)) as { schema: string; rows: unknown[] };
return (
JSON.stringify({
schema: body.schema,
name,
rows: body.rows,
...(coverage !== undefined ? { coverage } : {}),
}) + "\n"
);
}
if (ctx.mode === "text") return rows.length === 0 ? "" : tsv(LINEAGE_COLS, rows) + "\n";
if (rows.length === 0)
return `lineage: ${name}\n\n(no instances)\n${coverage !== undefined ? `\n${coverage}\n` : ""}`;
const realizedSum = rows.reduce((s, i) => s + i.realized_usd, 0);
const expectedSum = rows.reduce((s, i) => s + i.expected_usd, 0);
return (
`lineage: ${name}\n\n` +
glyphTable(LINEAGE_COLS, rows, ctx) +
`\n\n${rows.length} instance(s) realized ${usd(realizedSum)} expected ${usd(expectedSum)}\n`
);
}
// ─────────────────────────────────────────────────────────────────────────────
// leaderboard
// ─────────────────────────────────────────────────────────────────────────────
const LEADER_COLS: readonly Col<LeaderboardRow>[] = [
{ header: "lineage", right: false, cell: (r) => r.name, jsonKey: "name", json: (r) => r.name },
{ header: "runs", right: true, cell: (r) => String(r.runs), jsonKey: "runs", json: (r) => r.runs },
{ header: "fired", right: true, cell: (r) => String(r.fired), jsonKey: "fired", json: (r) => r.fired },
{ header: "fills", right: true, cell: (r) => String(r.fills), jsonKey: "fills", json: (r) => r.fills },
{ header: "realized_sum", right: true, money: true, cell: (r) => usd(r.realized_sum), jsonKey: "realized_sum", json: (r) => r.realized_sum },
{ header: "expected_sum", right: true, money: true, cell: (r) => usd(r.expected_sum), jsonKey: "expected_sum", json: (r) => r.expected_sum },
{ header: "realized_avg", right: true, money: true, cell: (r) => usd(r.realized_avg), jsonKey: "realized_avg", json: (r) => r.realized_avg },
{ header: "expected_avg", right: true, money: true, cell: (r) => usd(r.expected_avg), jsonKey: "expected_avg", json: (r) => r.expected_avg },
];
export function renderLeaderboard(rows: readonly LeaderboardRow[], ctx: OutputCtx): string {
if (ctx.mode === "json") return jsonRows("kestrel.leaderboard/v1", LEADER_COLS, rows) + "\n";
if (ctx.mode === "text") return rows.length === 0 ? "" : tsv(LEADER_COLS, rows) + "\n";
if (rows.length === 0) return "(no lineages)\n";
return glyphTable(LEADER_COLS, rows, ctx) + `\n\n${rows.length} lineage(s)\n`;
}