Every contract below shows the TypeScript declaration and the matching JSON Schema, both read directly from packages/polytraders-contracts/. The verification script in tests/verify-contracts.js ensures every TS file pairs with a schema and every schema declares the canonical header fields.
Bot
interface
src: src/Bot.ts · schema: schemas/Bot.schema.json
The interface every bot implements: init, decide, explain, health. The single most important contract in the system.
TypeScript declaration
/**
* Bot — the interface every bot in the system implements.
*
* Four methods: init, decide, explain, health. Anything else lives in the
* runtime, not in the bot. The single most important contract in the system.
*
* Frozen for v3.0.0.
*/
import type { BotContext } from "./BotContext.js";
import type { BotConfig } from "./BotConfig.js";
export interface BotHealthStatus {
schemaVersion: "1.0.0";
botId: string;
heartbeatAgeMs: number;
errorRatePct: number;
decisionsLastMinute: number;
mode: "stub" | "shadow" | "advisory" | "enforced" | "quarantine";
observedAt: number;
}
export interface BotExplanation {
schemaVersion: "1.0.0";
botId: string;
/** Human-readable explanation of the most recent decision. */
summary: string;
/** Reason code that drove the decision. */
reasonCode: string;
/** Optional structured detail — model inputs, thresholds breached, etc. */
detail?: Record<string, unknown>;
}
/**
* Generic Bot<I, O> — input type I, output type O.
*
* - Risk bots: I = OrderIntent, O = RiskVote
* - Strategy bots: I = MarketSnapshot, O = OrderIntent | null
* - Execution utilities: I = OrderIntent, O = ExecutionPlan
* - Intelligence bots: I = raw payload, O = a typed signal
*/
export interface Bot<I, O> {
/** Stable id, e.g. "risk.kill_switch". */
readonly botId: string;
/** Semantic version of the bot implementation. */
readonly version: string;
/** Called once per process. Validates config, opens any local state. */
init(config: BotConfig, ctx: BotContext): Promise<void>;
/** The single decision point. Must be deterministic given (input, config, ctx). */
decide(input: I, ctx: BotContext): Promise<O>;
/** Returns the explanation for the most recent decision. Used by /explain UI and audit. */
explain(): BotExplanation;
/** Returns current health. Polled by the runtime; never blocking. */
health(): BotHealthStatus;
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/Bot",
"title": "Bot",
"description": "Structural description of the Bot interface. Not directly validatable at runtime — bots are TS classes — but documents the four mandatory methods for the dev-guide and code review.",
"type": "object",
"required": ["botId", "version", "methods"],
"additionalProperties": false,
"properties": {
"botId": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
"methods": {
"type": "object",
"required": ["init", "decide", "explain", "health"],
"additionalProperties": false,
"properties": {
"init": { "const": "(config, ctx) => Promise<void>" },
"decide": { "const": "(input, ctx) => Promise<output>" },
"explain": { "const": "() => BotExplanation" },
"health": { "const": "() => BotHealthStatus" }
}
}
}
}
BotContext
interface
src: src/BotContext.ts · schema: schemas/BotContext.schema.json
The injected runtime context every decide() receives. Bots may not reach outside it — no direct network, filesystem or wall-clock.
TypeScript declaration
/**
* BotContext — injected runtime context.
*
* Every decide() call receives a BotContext. The bot uses it to read
* normalised data, emit envelopes, and query its own config. Bots MUST NOT
* reach outside the context (no direct network, no filesystem, no clock
* other than ctx.now).
*
* Frozen for v3.0.0.
*/
import type { MarketSnapshot } from "./MarketSnapshot.js";
import type { OrderBookSnapshot } from "./OrderBookSnapshot.js";
import type { BotConfig } from "./BotConfig.js";
import type { ReportEnvelope } from "./ReportEnvelope.js";
export interface BotContext {
/** Stable id of this bot instance, e.g. "risk.kill_switch". */
botId: string;
/** Loaded configuration. Frozen for the duration of decide(). */
config: BotConfig;
/** Correlation id for the current decision; carried through every emit. */
correlationId: string;
/** Monotonic clock — bots MUST NOT call Date.now() directly. */
now(): number;
/** Look up the latest MarketSnapshot for a market id. */
getMarketSnapshot(marketId: string): MarketSnapshot | null;
/** Look up the latest OrderBookSnapshot for a market id and side. */
getOrderBookSnapshot(marketId: string, side: "yes" | "no"): OrderBookSnapshot | null;
/** Emit a ReportEnvelope. The envelope is timestamped and signed by the runtime. */
emit(envelope: Omit<ReportEnvelope, "envelopeId" | "emittedAt" | "botId" | "botVersion" | "configVersion" | "correlationId" | "mode">): void;
/** Current run mode — bots adjust behaviour based on this. */
mode: "stub" | "shadow" | "advisory" | "enforced" | "quarantine";
/** Logging surface — structured only. Free-text logging is rejected at lint time. */
log(level: "debug" | "info" | "warn" | "error", reasonCode: string, fields?: Record<string, unknown>): void;
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/BotContext",
"title": "BotContext",
"description": "Structural description of the BotContext shape injected into decide(). Not directly validatable at runtime — methods are functions — but documents the surface bots are allowed to touch.",
"type": "object",
"required": ["botId", "config", "correlationId", "mode", "methods"],
"additionalProperties": false,
"properties": {
"botId": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"config": { "$ref": "polytraders/BotConfig" },
"correlationId": { "type": "string", "minLength": 1 },
"mode": { "enum": ["stub", "shadow", "advisory", "enforced", "quarantine"] },
"methods": {
"type": "object",
"required": ["now", "getMarketSnapshot", "getOrderBookSnapshot", "emit", "log"],
"additionalProperties": false,
"properties": {
"now": { "const": "() => number" },
"getMarketSnapshot": { "const": "(marketId) => MarketSnapshot | null" },
"getOrderBookSnapshot": { "const": "(marketId, side) => OrderBookSnapshot | null" },
"emit": { "const": "(envelope) => void" },
"log": { "const": "(level, reasonCode, fields?) => void" }
}
}
}
}
BotConfig
config
src: src/BotConfig.ts · schema: schemas/BotConfig.schema.json
Versioned configuration shape — defaults, warnings, hard limits, feature flags. Every emitted envelope carries the configVersion that produced it.
TypeScript declaration
/**
* BotConfig — versioned configuration for a single bot.
*
* Parameter defaults, warnings, hard limits, feature flags. Every emitted
* envelope carries the configVersion that produced it; replay is impossible
* without this guarantee.
*
* Frozen for v3.0.0.
*/
export interface BotConfigParameter {
name: string;
type: "number" | "integer" | "boolean" | "string" | "duration_ms";
default: number | boolean | string;
warning?: number | string; // soft threshold — emit warning, do not block
hard?: number | string; // hard threshold — block / fall back
description?: string;
}
export interface BotConfig {
schemaVersion: "1.0.0";
botId: string;
configVersion: string; // semver, bumped on every change
signedBy: string; // approver wallet / id
signedAt: number; // ms epoch
parameters: BotConfigParameter[];
featureFlags: Record<string, boolean>;
modeOverride?: "stub" | "shadow" | "advisory" | "enforced" | "quarantine";
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/BotConfig",
"title": "BotConfig",
"description": "Versioned configuration for a single bot.",
"type": "object",
"required": ["schemaVersion", "botId", "configVersion", "signedBy", "signedAt", "parameters", "featureFlags"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"botId": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"configVersion": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
"signedBy": { "type": "string", "minLength": 1 },
"signedAt": { "type": "number" },
"parameters": {
"type": "array",
"items": {
"type": "object",
"required": ["name", "type", "default"],
"additionalProperties": false,
"properties": {
"name": { "type": "string", "minLength": 1 },
"type": { "enum": ["number", "integer", "boolean", "string", "duration_ms"] },
"default": {},
"warning": {},
"hard": {},
"description": { "type": "string" }
}
}
},
"featureFlags": { "type": "object", "additionalProperties": { "type": "boolean" } },
"modeOverride": { "enum": ["stub", "shadow", "advisory", "enforced", "quarantine"] }
}
}
MarketSnapshot
data
src: src/MarketSnapshot.ts · schema: schemas/MarketSnapshot.schema.json
Normalised Polymarket V2 market state at a point in time. The single object every Strategy and Risk bot reads from.
TypeScript declaration
/**
* MarketSnapshot — normalised Polymarket V2 market state at a point in time.
*
* The single object every Strategy and Risk bot reads from. Bots MUST NOT
* consume raw Polymarket payloads. The data-flow layer produces this object;
* downstream consumers trust it.
*
* Frozen for v3.0.0.
*/
export interface MarketSnapshot {
schemaVersion: "1.0.0";
marketId: string; // Polymarket V2 condition id
conditionTokens: string[]; // outcome token ids
resolution: {
status: "open" | "paused" | "resolving" | "resolved" | "invalid";
expectedAt?: number; // ms epoch
};
prices: {
yes: number; // in pUSD, 0..1
no: number;
};
liquidityPusd: number; // total order-book liquidity in pUSD
source: "clob_v2" | "subgraph" | "snapshot_archive";
observedAt: number; // monotonic ms
freshnessMs: number; // age at observation time
provenance: string; // hash of source response
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/MarketSnapshot",
"title": "MarketSnapshot",
"description": "Normalised Polymarket V2 market state at a point in time.",
"type": "object",
"required": ["schemaVersion", "marketId", "conditionTokens", "resolution", "prices", "liquidityPusd", "source", "observedAt", "freshnessMs", "provenance"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"marketId": { "type": "string", "minLength": 1 },
"conditionTokens": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"resolution": {
"type": "object",
"required": ["status"],
"additionalProperties": false,
"properties": {
"status": { "enum": ["open", "paused", "resolving", "resolved", "invalid"] },
"expectedAt": { "type": "number" }
}
},
"prices": {
"type": "object",
"required": ["yes", "no"],
"additionalProperties": false,
"properties": {
"yes": { "type": "number", "minimum": 0, "maximum": 1 },
"no": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"liquidityPusd": { "type": "number", "minimum": 0 },
"source": { "enum": ["clob_v2", "subgraph", "snapshot_archive"] },
"observedAt": { "type": "number" },
"freshnessMs": { "type": "number", "minimum": 0 },
"provenance": { "type": "string", "minLength": 1 }
}
}
OrderBookSnapshot
data
src: src/OrderBookSnapshot.ts · schema: schemas/OrderBookSnapshot.schema.json
Best bid, best ask, depth ladder and timestamp. Carries a freshness stamp and source hash.
TypeScript declaration
/**
* OrderBookSnapshot — best bid, best ask, depth ladder and timestamp.
* Carries a freshness stamp and source hash. Default budget 500 ms.
*
* Frozen for v3.0.0.
*/
export interface OrderBookLevel {
pricePusd: number;
sizePusd: number;
}
export interface OrderBookSnapshot {
schemaVersion: "1.0.0";
marketId: string;
side: "yes" | "no";
bestBidPusd: number;
bestAskPusd: number;
spreadBps: number;
depth: OrderBookLevel[]; // sorted by price
observedAt: number;
freshnessMs: number;
source: "clob_v2";
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/OrderBookSnapshot",
"title": "OrderBookSnapshot",
"description": "Best bid, best ask, depth ladder and timestamp.",
"type": "object",
"required": ["schemaVersion", "marketId", "side", "bestBidPusd", "bestAskPusd", "spreadBps", "depth", "observedAt", "freshnessMs", "source"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"marketId": { "type": "string", "minLength": 1 },
"side": { "enum": ["yes", "no"] },
"bestBidPusd": { "type": "number", "minimum": 0, "maximum": 1 },
"bestAskPusd": { "type": "number", "minimum": 0, "maximum": 1 },
"spreadBps": { "type": "number", "minimum": 0 },
"depth": {
"type": "array",
"items": {
"type": "object",
"required": ["pricePusd", "sizePusd"],
"additionalProperties": false,
"properties": {
"pricePusd": { "type": "number", "minimum": 0, "maximum": 1 },
"sizePusd": { "type": "number", "minimum": 0 }
}
}
},
"observedAt": { "type": "number" },
"freshnessMs": { "type": "number", "minimum": 0 },
"source": { "const": "clob_v2" }
}
}
OrderIntent
decision
src: src/OrderIntent.ts · schema: schemas/OrderIntent.schema.json
Strategy output, before risk approval. Risk bots evaluate OrderIntents — not raw market observations.
TypeScript declaration
/**
* OrderIntent — Strategy output, before risk approval.
*
* Strategy emits an OrderIntent. Risk evaluates it. Risk MUST NOT evaluate
* anything that is not an OrderIntent. There is no other path from a market
* observation to a signed order.
*
* Frozen for v3.0.0.
*/
export interface OrderIntent {
schemaVersion: "1.0.0";
intentId: string; // ULID
emittedBy: string; // bot_id of the Strategy bot
marketId: string;
side: "buy" | "sell";
outcome: "yes" | "no";
sizePusd: number;
limitPricePusd?: number; // omitted for IOC/market intents
timeInForce: "GTC" | "IOC" | "FOK";
reason: string; // reason_code from registry
modelEdgeBps?: number;
emittedAt: number;
expiresAt: number; // hard expiry; risk re-checks against this
builderCode: string; // V2 attribution
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/OrderIntent",
"title": "OrderIntent",
"description": "Strategy output, before risk approval.",
"type": "object",
"required": ["schemaVersion", "intentId", "emittedBy", "marketId", "side", "outcome", "sizePusd", "timeInForce", "reason", "emittedAt", "expiresAt", "builderCode"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"intentId": { "type": "string", "minLength": 1 },
"emittedBy": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"marketId": { "type": "string", "minLength": 1 },
"side": { "enum": ["buy", "sell"] },
"outcome": { "enum": ["yes", "no"] },
"sizePusd": { "type": "number", "exclusiveMinimum": 0 },
"limitPricePusd": { "type": "number", "minimum": 0, "maximum": 1 },
"timeInForce": { "enum": ["GTC", "IOC", "FOK"] },
"reason": { "type": "string", "pattern": "^(INTEL|DISC|STRAT|RISK|EXEC|SEC|GOV)_[A-Z0-9_]+$" },
"modelEdgeBps": { "type": "number" },
"emittedAt": { "type": "number" },
"expiresAt": { "type": "number" },
"builderCode": { "type": "string", "minLength": 1 }
}
}
RiskVote
decision
src: src/RiskVote.ts · schema: schemas/RiskVote.schema.json
Risk-bot decision on an OrderIntent: APPROVE, RESHAPE, REJECT, QUARANTINE — with reason code.
TypeScript declaration
/**
* RiskVote — Risk-bot decision on an OrderIntent.
*
* Every Risk bot in the configured order emits one RiskVote per OrderIntent.
* Any single REJECT halts the pipeline. RESHAPE is allowed but the proposed
* intent must pass every Risk bot again.
*
* Frozen for v3.0.0.
*/
import type { OrderIntent } from "./OrderIntent.js";
export type RiskVerdict = "APPROVE" | "RESHAPE" | "REJECT" | "QUARANTINE";
export interface RiskVote {
schemaVersion: "1.0.0";
voteId: string;
intentId: string; // ties to the OrderIntent
byBot: string;
verdict: RiskVerdict;
reasonCode: string; // from reason-code registry
reshape?: Partial<OrderIntent>;
evaluatedAt: number;
evaluationMs: number;
killSwitchActive: boolean;
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/RiskVote",
"title": "RiskVote",
"description": "Risk-bot decision on an OrderIntent.",
"type": "object",
"required": ["schemaVersion", "voteId", "intentId", "byBot", "verdict", "reasonCode", "evaluatedAt", "evaluationMs", "killSwitchActive"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"voteId": { "type": "string", "minLength": 1 },
"intentId": { "type": "string", "minLength": 1 },
"byBot": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"verdict": { "enum": ["APPROVE", "RESHAPE", "REJECT", "QUARANTINE"] },
"reasonCode": { "type": "string", "pattern": "^(INTEL|DISC|STRAT|RISK|EXEC|SEC|GOV)_[A-Z0-9_]+$" },
"reshape": { "type": "object", "additionalProperties": true },
"evaluatedAt": { "type": "number" },
"evaluationMs": { "type": "number", "minimum": 0 },
"killSwitchActive": { "type": "boolean" }
}
}
ExecutionPlan
decision
src: src/ExecutionPlan.ts · schema: schemas/ExecutionPlan.schema.json
Output of an Execution Utility — shape, timing and sizing of orders to submit.
TypeScript declaration
/**
* ExecutionPlan — output of an Execution Utility.
*
* Describes the shape, timing and sizing of orders to submit. Each step is
* a discrete on-chain action. The plan expires; re-quote if execution
* cannot complete before expiresAt.
*
* Frozen for v3.0.0.
*/
export interface ExecutionStep {
stepNo: number;
action: "submit" | "amend" | "cancel";
pricePusd: number;
sizePusd: number;
submitAt: number; // ms epoch — for paced execution
}
export interface ExecutionPlan {
schemaVersion: "1.0.0";
planId: string;
intentId: string;
steps: ExecutionStep[];
estimatedFeesPusd: number;
estimatedSlippageBps: number;
expiresAt: number;
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/ExecutionPlan",
"title": "ExecutionPlan",
"description": "Output of an Execution Utility — shape, timing and sizing of orders to submit.",
"type": "object",
"required": ["schemaVersion", "planId", "intentId", "steps", "estimatedFeesPusd", "estimatedSlippageBps", "expiresAt"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"planId": { "type": "string", "minLength": 1 },
"intentId": { "type": "string", "minLength": 1 },
"steps": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["stepNo", "action", "pricePusd", "sizePusd", "submitAt"],
"additionalProperties": false,
"properties": {
"stepNo": { "type": "integer", "minimum": 1 },
"action": { "enum": ["submit", "amend", "cancel"] },
"pricePusd": { "type": "number", "minimum": 0, "maximum": 1 },
"sizePusd": { "type": "number", "minimum": 0 },
"submitAt": { "type": "number" }
}
}
},
"estimatedFeesPusd": { "type": "number", "minimum": 0 },
"estimatedSlippageBps": { "type": "number" },
"expiresAt": { "type": "number" }
}
}
ReportEnvelope
telemetry
src: src/ReportEnvelope.ts · schema: schemas/ReportEnvelope.schema.json
Universal reporting wrapper — every bot, every decision. The single emission shape from which audit, dashboards and replay are built.
TypeScript declaration
/**
* ReportEnvelope — universal reporting wrapper.
*
* Every bot, every decision. The single emission shape from which the audit
* log, dashboards, replay simulator and reconcile feed are built.
*
* Frozen for v3.0.0.
*/
export type EnvelopeKind =
| "decision"
| "signal"
| "event"
| "metric"
| "audit"
| "error";
export interface ReportEnvelope {
schemaVersion: "1.0.0";
envelopeId: string;
kind: EnvelopeKind;
botId: string;
botVersion: string;
configVersion: string;
correlationId: string;
parentDecisionId?: string;
marketId?: string;
intentId?: string;
reasonCode?: string;
mode: "stub" | "shadow" | "advisory" | "enforced" | "quarantine";
emittedAt: number;
payload: Record<string, unknown>;
}
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/ReportEnvelope",
"title": "ReportEnvelope",
"description": "Universal reporting wrapper — every bot, every decision.",
"type": "object",
"required": ["schemaVersion", "envelopeId", "kind", "botId", "botVersion", "configVersion", "correlationId", "mode", "emittedAt", "payload"],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": "1.0.0" },
"envelopeId": { "type": "string", "minLength": 1 },
"kind": { "enum": ["decision", "signal", "event", "metric", "audit", "error"] },
"botId": { "type": "string", "pattern": "^[a-z]+\\.[a-z0-9_]+$" },
"botVersion": { "type": "string", "minLength": 1 },
"configVersion": { "type": "string", "minLength": 1 },
"correlationId": { "type": "string", "minLength": 1 },
"parentDecisionId": { "type": "string" },
"marketId": { "type": "string" },
"intentId": { "type": "string" },
"reasonCode": { "type": "string", "pattern": "^(INTEL|DISC|STRAT|RISK|EXEC|SEC|GOV)_[A-Z0-9_]+$" },
"mode": { "enum": ["stub", "shadow", "advisory", "enforced", "quarantine"] },
"emittedAt": { "type": "number" },
"payload": { "type": "object", "additionalProperties": true }
}
}
ReasonCode
telemetry
src: src/ReasonCode.ts · schema: schemas/ReasonCode.schema.json
Registry-aligned reason code. Free-text errors are forbidden — every emit carries one of these.
TypeScript declaration
/**
* ReasonCode — registry-aligned reason codes.
*
* Every emit, every reject, every halt carries one of these. Free-text
* error messages are forbidden. The full registry is rendered on the
* dev-guide /standards/reason-codes page; this file is the typed view.
*
* Frozen for v3.0.0. New codes are additive only.
*/
export type ReasonCodePrefix =
| "INTEL"
| "DISC"
| "STRAT"
| "RISK"
| "EXEC"
| "SEC"
| "GOV";
export type ReasonCode = `${ReasonCodePrefix}_${string}`;
/**
* Sentinel codes guaranteed to exist in every release. Bots MAY reference
* these by name; the full registry is loaded at runtime.
*/
export const SENTINEL_CODES = {
INTEL_FEED_STALE: "INTEL_FEED_STALE",
STRAT_NO_EDGE: "STRAT_NO_EDGE",
RISK_KILL_SWITCH_ACTIVE: "RISK_KILL_SWITCH_ACTIVE",
RISK_STALE_BOOK: "RISK_STALE_BOOK",
RISK_MARKET_HALTED: "RISK_MARKET_HALTED",
EXEC_BUDGET_BREACH: "EXEC_BUDGET_BREACH",
SEC_WALLET_FUNDING_SHORT: "SEC_WALLET_FUNDING_SHORT",
GOV_RECONCILE_MISMATCH: "GOV_RECONCILE_MISMATCH",
GOV_API_DEGRADED: "GOV_API_DEGRADED",
GOV_CONFIG_DRIFT: "GOV_CONFIG_DRIFT",
} as const;
export type SentinelCode = (typeof SENTINEL_CODES)[keyof typeof SENTINEL_CODES];
JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "polytraders/ReasonCode",
"title": "ReasonCode",
"description": "Registry-aligned reason code. Free-text errors are forbidden.",
"type": "string",
"pattern": "^(INTEL|DISC|STRAT|RISK|EXEC|SEC|GOV)_[A-Z0-9_]+$",
"examples": [
"INTEL_FEED_STALE",
"STRAT_NO_EDGE",
"RISK_KILL_SWITCH_ACTIVE",
"RISK_STALE_BOOK",
"EXEC_BUDGET_BREACH",
"SEC_WALLET_FUNDING_SHORT",
"GOV_RECONCILE_MISMATCH"
]
}