Failure Modes
This page is the authoritative inventory of local operational failure behavior in TradingChassis Ops Lab.
Scope of this page:
- Local, deterministic operational failure states only.
- Not production incident response.
- Not live or testnet exchange monitoring.
- Not chaos engineering.
- Not trading performance analysis.
- Not PnL, alpha, or strategy evaluation.
Failure modes here are triggered by local command invocation against artifact files.
They are observable through structured JSON artifacts, journal.jsonl events, Prometheus metrics,
and Grafana dashboard signals. Recovery steps are local and do not require exchange connectivity,
account access, or production systems.
For the full artifact contract, see Run model. For operational boundaries, see Scope and Limitations. Step-by-step recovery procedures live in the Runbooks section:
- Artifact health — missing or malformed run artifacts
- Evidence compare —
tc evidence comparediagnostics - Safety gate — paper blocked by kill switch
- Observability no data — Grafana/Prometheus no-data states
- Stale market data — stale market data drill
- Reconciliation mismatch — reconciliation mismatch drill
- Restart recovery — restart recovery drill
- Connectivity probe failed — probe failure cases
How to read this contract
Hard failures (exit 1)
A hard failure means the command could not safely proceed:
- Corrupt or unparseable input file (malformed JSON/JSONL/YAML)
- Missing required precondition (run directory not initialized, required file absent)
- Unsafe operation (path traversal in run ID, invalid loopback target)
- I/O error when writing artifacts
Hard failures exit non-zero and print a descriptive message to stderr. No partial artifact is guaranteed.
Soft failures (exit 0 with structured artifact)
A soft failure means the operation completed and recorded an expected operational gap:
- Missing credentials →
state=missing_credentialsinconnectivity_readiness.json - Probe unreachable →
state=probe_unreachableinconnectivity_probe.json - Paper blocked by active kill switch →
status=safety_blockedin run artifacts - Evidence missing some artifacts →
comparison_status=missing_artifactsin evidence JSON
Soft failures exit 0. Full artifacts are written. Metrics and dashboard panels reflect the recorded state. These outcomes are not errors — they are valid local operational observations.
expected_exit_code
The table below includes an Expected exit column. Non-zero exit is part of the operator contract
for certain drills (for example tc drill reconciliation-mismatch intentionally exits 1 to confirm
that a mismatch is detected). Do not treat exit-1 drills as command failures — they are the correct
outcome.
Dashboard values
Grafana panels are artifact-backed. They summarize completed local run artifacts. They do not stream live trading state, process health, or exchange telemetry. Panel values should be read as local operational signals derived from file-based artifacts.
Failure mode inventory
Artifact / run artifact health
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
Missing metrics.json |
tc metrics export for a run without metrics.json |
1 | stderr RunArtifactsFileMissingError |
— | tc metrics serve emits Prometheus # comment; dashboard shows no data for that run |
Re-run the lifecycle command that writes metrics.json (backtest or paper) |
existing, documented (run-model caveat) |
Missing metadata.json |
tc metrics export, tc connectivity readiness/probe, tc evidence compare |
1 (export, connectivity) / 0 (evidence) | stderr or evidence missing_artifacts JSON |
— | Evidence comparison_status=missing_artifacts; serve skips run |
Re-initialize run with tc run init and repeat lifecycle |
existing, partially documented |
Malformed metadata.json |
Any command that parses metadata.json (export, kill snapshot update, evidence compare) |
1 | stderr RunArtifactsParseError or KillSwitchReadError |
— | Export fails; serve skips with # comment |
Inspect with cat artifacts/runs/<run_id>/metadata.json \| python -m json.tool; re-run lifecycle to regenerate; see Artifact health runbook |
existing, tested; runbook implemented (Unit 4) |
Malformed metrics.json |
tc metrics export, tc evidence compare |
1 | stderr RunArtifactsParseError or EvidenceArtifactsParseError |
— | Export fails; serve skips run with # comment |
Inspect JSON; re-run backtest or paper lifecycle; see Artifact health runbook | existing, tested; runbook implemented (Unit 4) |
Malformed journal.jsonl |
tc metrics export (line parse error), tc evidence compare |
1 | stderr with line number; evidence: stderr then no evidence artifact written | — | Export fails; evidence errors before writing artifact | Inspect journal.jsonl around reported line number; re-run lifecycle; see Artifact health runbook |
existing, tested; runbook implemented (Unit 4) |
Missing or malformed run_spec.yaml |
tc run init/backtest/paper, tc connectivity readiness/probe, tc spec validate |
1 | stderr RunSpecLoadError |
— | — | Re-run with a valid spec; use tc spec validate --spec <path> to check before init |
existing, tested; restart-recovery drill checks artifact presence |
Evidence
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Missing evidence artifact | tc metrics serve --evidence-root when no evidence pairs exist |
0 | No evidence metrics rendered | — | Evidence panels show no data in Grafana | Run tc evidence compare first; then restart tc metrics serve |
existing, documented |
| Malformed evidence JSON | tc metrics serve --evidence-root over a corrupted evidence file |
0 | Prometheus # skipped evidence artifact due to malformed JSON <path> |
— | Affected evidence pair skipped; other pairs still rendered | Inspect evidence JSON; re-run tc evidence compare for that pair |
existing, tested |
| Missing compared run directories | tc evidence compare when one or both run directories are absent |
0 | backtest_vs_paper_evidence.json with comparison_status=missing_artifacts; evidence MD written |
— | evidence_backtest_vs_paper_status_total{status=missing_artifacts} incremented |
Re-initialize and re-run the missing lifecycle to produce run artifacts; see Evidence compare runbook | existing, tested; runbook implemented (Unit 4) |
| Incompatible backtest/paper modes | tc evidence compare when both runs use the same mode (both backtest or both paper) |
0 | Evidence JSON with comparison_status=incompatible_runs |
— | evidence_backtest_vs_paper_status_total{status=incompatible_runs} incremented |
Ensure one run is mode: backtest and one is mode: paper; re-run compare; see Evidence compare runbook |
existing, tested; runbook implemented (Unit 4) |
| Invalid evidence run IDs / path traversal | tc evidence compare with run IDs containing .., /, or other unsafe patterns |
1 | stderr EvidenceWriteError |
— | No evidence artifact written | Use valid run IDs matching the pattern used at tc run init |
existing, tested |
| Evidence known gaps are high but expected | tc metrics serve with accumulated evidence artifacts |
0 | evidence_known_gaps_total reflects expected design gaps |
— | Evidence Known Gaps panel shows non-zero count |
High count is expected by design (no PnL, no fill quality, candle-only data, synthetic paper); not an error | existing, by design; documented in demo-flow |
Safety
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Kill switch active | tc kill activate --run-id <id> --reason <text> |
0 | runtime/kill_switch/<run_id>.state.json (state=active); runtime/kill_switch/<run_id>.events.jsonl |
kill_switch_activated |
tradingchassis_ops_lab_kill_switch_state{run_id}=2; Grafana Kill Switch State panel |
Inspect state file; clear with tc kill clear --run-id <id> --reason <text>; see Safety gate runbook |
existing, tested; runbook implemented (Unit 4) |
| Kill switch cleared / absent | tc kill clear or no prior activation |
0 | state=cleared / state file absent | kill_switch_cleared / — |
kill_switch_state=1 (cleared) or kill_switch_state=0 (absent) |
Normal state; no action required | existing, tested |
| Paper blocked by safety gate | tc run paper when kill switch is active for that run ID |
0 | Full run artifacts written with status=safety_blocked; paper_safety_blocked event in journal.jsonl; Safety section in report.md |
paper_safety_blocked |
kill_switch_state=2; paper metrics include heartbeat_count=0 |
Clear kill switch (tc kill clear); use a fresh run ID or re-run paper; see Safety gate runbook |
existing, tested; runbook implemented (Unit 4) |
Connectivity readiness
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Missing credentials | tc connectivity readiness when required env vars are absent or empty |
0 | connectivity_readiness.json with state=missing_credentials |
connectivity_readiness_evaluated |
tradingchassis_ops_lab_connectivity_readiness_state; Grafana Connectivity Readiness panel |
Expected local state; set dummy non-empty env vars to reach configured state |
existing, tested, documented |
| Invalid readiness config | tc connectivity readiness when RunSpec connectivity_readiness block has validation errors (duplicate env names, invalid format) |
0 | connectivity_readiness.json with state=invalid_config; errors list in artifact |
connectivity_readiness_evaluated |
tradingchassis_ops_lab_connectivity_readiness_state=3 |
Inspect connectivity_readiness.json errors field; fix RunSpec and re-run |
existing, tested, documented |
Connectivity probe
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Probe unreachable | tc connectivity probe when no local server is listening at target loopback URL |
0 | connectivity_probe.json with state=probe_unreachable |
connectivity_probe_evaluated |
tradingchassis_ops_lab_connectivity_probe_state; Grafana Connectivity Probe State |
Start a local fake endpoint (python -m http.server <port> --bind 127.0.0.1); re-run probe |
existing, tested; see Connectivity probe runbook Case 2 |
| Probe timeout | tc connectivity probe when local endpoint responds slower than --timeout-ms |
0 | connectivity_probe.json with state=probe_timeout |
connectivity_probe_evaluated |
tradingchassis_ops_lab_connectivity_probe_state |
Increase --timeout-ms; verify local endpoint responds |
existing, tested; see runbook Case 3 |
| Probe HTTP error | tc connectivity probe when local endpoint returns non-2xx |
0 | connectivity_probe.json with state=probe_http_error; http_status recorded |
connectivity_probe_evaluated |
tradingchassis_ops_lab_connectivity_probe_http_status |
Adjust local endpoint to return 2xx; re-run probe | existing, tested; see runbook Case 4 |
| Probe unknown / unexpected failure | tc connectivity probe when an unexpected local runtime error occurs |
0 | connectivity_probe.json with state=probe_unknown |
connectivity_probe_evaluated |
tradingchassis_ops_lab_connectivity_probe_state=-1 |
Inspect connectivity_probe.json for details; rerun with a known-good loopback target |
existing; see runbook Case 5 |
| Probe target rejected (hard fail) | tc connectivity probe with non-loopback, non-HTTP, or structurally invalid URL |
1 | stderr ConnectivityProbeInvalidTargetError; no probe artifact written |
— | — | Use http://127.0.0.1:<port>/..., http://localhost:<port>/..., or http://[::1]:<port>/... only |
existing, tested; see runbook Case 1 |
Reconciliation / drills
Reconciliation metrics (tradingchassis_ops_lab_reconciliation_status,
tradingchassis_ops_lab_reconciliation_checks_total) render from reconciliation_result.json.
Unit 2 renders pass/outcome metrics from existing drills/*.json artifacts; Unit 3 exposes these signals through Grafana panels.
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Reconciliation mismatch | tc drill reconciliation-mismatch --run-id <id> (or tc reconcile check with mismatch fixture) |
1 (by design) | drills/reconciliation_mismatch.json with outcome=expected_mismatch; reconciliation_result.json with status=mismatch |
failure_drill_executed (if journal.jsonl exists) |
tradingchassis_ops_lab_reconciliation_status{status=mismatch}, tradingchassis_ops_lab_reconciliation_checks_total (existing); tradingchassis_ops_lab_failure_drill_last_pass, tradingchassis_ops_lab_failure_drill_last_outcome (Unit 2); Reconciliation Status, Failure Drill Last Pass, Failure Drill Outcome panels (Unit 3) |
Exit 1 is expected; confirm pass=true in drill JSON; review checks[] for mismatch details |
existing, tested; drill-artifact metrics implemented (Unit 2); dashboard panels implemented (Unit 3) |
| Stale market data drill | tc drill stale-market-data --run-id <id> |
0 | drills/stale_market_data.json with outcome=expected_warning; reconciliation_result.json with status=warning |
failure_drill_executed (if journal.jsonl exists) |
tradingchassis_ops_lab_reconciliation_status{status=warning}, tradingchassis_ops_lab_reconciliation_checks_total (existing); tradingchassis_ops_lab_failure_drill_last_pass, tradingchassis_ops_lab_failure_drill_last_outcome (Unit 2); Reconciliation Status, Failure Drill Last Pass, Failure Drill Outcome panels (Unit 3) |
Confirm pass=true in drill JSON; review summary.warning count; see runbook |
existing, tested; drill-artifact metrics implemented (Unit 2); dashboard panels implemented (Unit 3) |
| Restart recovery drill | tc drill restart-recovery --run-id <id> |
0 | drills/restart_recovery.json with outcome=simulated_recovery_ok and artifact presence checklist |
failure_drill_executed (if journal.jsonl exists) |
tradingchassis_ops_lab_failure_drill_last_pass, tradingchassis_ops_lab_failure_drill_last_outcome (Unit 2); Failure Drill Last Pass, Failure Drill Outcome panels (Unit 3) |
Confirm pass=true and all required artifacts present in summary; see runbook |
existing, tested; drill-artifact metrics implemented (Unit 2); dashboard panels implemented (Unit 3) |
| Existing drill artifacts | Inspect artifacts/runs/<run_id>/drills/*.json after running any drill |
— | schema_version, drill_name, outcome, pass, checks[], summary, limitations[] in each drill JSON |
— | tradingchassis_ops_lab_failure_drill_executed_total, tradingchassis_ops_lab_failure_drill_last_pass, tradingchassis_ops_lab_failure_drill_last_outcome (Unit 2) |
Use cat artifacts/runs/<run_id>/drills/<drill_name>.json to inspect outcome |
existing (artifact-only, pre-Unit 2 metrics); Prometheus metrics implemented (Unit 2) |
Observability
| Failure mode | Trigger | Expected exit | Artifact / signal | Journal event | Metric / dashboard signal | Recovery / verification | 0.8.0 status |
|---|---|---|---|---|---|---|---|
| Grafana shows no data | tc metrics serve is not running, Prometheus is not scraping, or metrics.json prerequisite is missing |
— | Grafana panels empty for selected $run_id |
— | Empty panels; Prometheus targets page shows down state | Verify tc metrics serve is running; check Prometheus targets at /targets; confirm metrics.json exists for selected run; see Observability no data runbook |
existing behavior; runbook implemented (Unit 4) |
| Prometheus target down | tc metrics serve is stopped or unreachable while Prometheus is running |
— | Prometheus targets page shows target state as down | — | Dashboard stale or empty | Restart tc metrics serve; refresh Prometheus; wait one scrape interval; see Observability no data runbook |
existing behavior; runbook implemented (Unit 4) |
| Metrics serve vs metrics export confusion | Operator uses tc metrics export expecting Grafana to update |
— | Export writes Prometheus text to stdout or file; does not feed Grafana | — | Dashboard unchanged | Use tc metrics serve for Grafana; use tc metrics export only for one-shot inspection |
existing, documented in quickstart / demo-flow |
Artifact model
0.8.0 reuses existing artifact locations. No new artifact roots or file types are introduced.
| Artifact type | Location |
|---|---|
| Per-run core artifacts | artifacts/runs/<run_id>/ |
| Drill artifacts | artifacts/runs/<run_id>/drills/<drill_name>.json |
| Reconciliation artifact | artifacts/runs/<run_id>/reconciliation_result.json |
| Evidence artifacts | artifacts/evidence/<backtest_run_id>__<paper_run_id>/ |
The following are not introduced in 0.8.0:
artifacts/failures/rootfailure_drill.jsonfile typefailure_recovery.jsonfile type- New Markdown failure report format (evidence MD is a separate contract)
0.8.0 scope
0.8.0 — Expanded Failure Modes makes existing local operational failure behavior visible,
documented, and recoverable. It does not add a chaos engineering framework, external connectivity,
or new run modes.
Unit split
| Unit | Goal | Status |
|---|---|---|
| Unit 1 — Failure Mode Contract / Inventory | This page; docs/failure-modes.md; MkDocs nav; cross-links; roadmap alignment |
implemented |
| Unit 2 — Failure Drill & Reconciliation Metrics | Extend observability/metrics.py to render existing drill artifacts as Prometheus metrics |
implemented |
| Unit 3 — Minimal Dashboard Panels | Add Reconciliation Status, Failure Drill Last Pass, and Failure Drill Outcome panels to Grafana dashboard |
implemented |
| Unit 4 — Runbooks / Demo Flow / Changelog | Add runbooks for artifact health, evidence, safety gate, and observability no-data; update demo flow and release docs | implemented |
Deferred from 0.8.0
The following were evaluated and explicitly deferred:
| Deferred item | Reason |
|---|---|
missing-update drill (new command) |
Fixture exists; defer to 0.8.1 to keep Unit 1 docs-only and scope minimal |
disconnect drill as separate command |
Substantially covered by probe_unreachable + existing probe runbook |
| Rate-limit exhaustion drill | No local rate-limit fixture or model; defer until a deterministic fixture contract exists |
| Stale orderbook drill | No orderbook / LOB data in repo; LOB support is explicitly deferred in Limitations |
tc run verify / artifact linter |
Restart-recovery drill is the minimal precursor; full linter deferred |
artifacts/failures/ root |
Run-scoped drill artifacts under drills/*.json are sufficient |
| Alertmanager integration | Out of scope per Future work |
| Kubernetes / GitOps | Out of scope per Roadmap |
| External exchange / testnet probes | Out of scope per Scope |
See also
- Run model — artifact contract and metrics serve vs export
- Scope — in-scope and out-of-scope implementation boundaries
- Limitations — explicit implementation constraints
- Demo flow — end-to-end walkthrough including safety, reconciliation, and drill steps
- Backtest vs paper — evidence workflow and known gaps
- Artifact health runbook — missing or malformed run artifacts
- Evidence compare runbook —
tc evidence comparediagnostics - Safety gate runbook — paper blocked by kill switch
- Observability no data runbook — Grafana/Prometheus no-data states
- Stale market data runbook — stale data drill recovery
- Reconciliation mismatch runbook — mismatch drill recovery
- Restart recovery runbook — restart recovery drill
- Connectivity probe runbook — probe failure cases