Full project import for the self-hosted code hosting (https://code.aifunny.ltd/gavindiaz/my-NexusTradingForexBot.git). Includes the live-freshness + scaler-hash fixes for the permanent STALE dashboard (tick_pipeline now_utc stamps; infer_probabilities computes model_input_hash on the actual scaled 70D tensor), the data-fetch default-path fix, the g29 web-auth test fixes, and a Chinese user manual (使用手册.md) for the web dashboard. Scratch diagnostic scripts (check*.py, contain a live web-auth token) and the .kilo/ agent-worktree directory are gitignored, not committed. Co-Authored-By: Claude Code <noreply@anthropic.com>
49 KiB
agents/APISkill.md — NEXUS API AGENT SKILL (CANONICAL OPERATIONAL CONTRACT)
Machine-oriented contract for Hermes/Nexus agents working with the Nexus Scalp Engine APIs. Read this file before touching any API: it tells you WHAT exists, WHICH endpoint answers WHICH question, HOW to call it, WHAT it depends on, WHAT can fail, HOW to test it, and WHAT MUST NEVER be done.
- Spec of record (design):
docs/api/API_PLATFORM_V1.md- Developer reference (usage):
docs/api/API_REFERENCE.md- This file: agent-facing operational contract + drift-protected registry
- Drift validator:
scripts/dev/api_skill_drift_check.py(exit 0 = in sync)Maintainer: Hermes-Main (API Platform owner, TASK-API-PLATFORM, CHG-0043). Companion file convention: see
agents/skill.md(architecture map).
0. STATE AT A GLANCE (verify before trusting)
| fact | value | how to re-verify |
|---|---|---|
| canonical tree | src/nexus_scalp/web/api_v1/ |
ls src/nexus_scalp/web/api_v1/ |
| mount point | ONE call: web/api_v1_wiring.register_api_v1(app) at end of web/server.create_app() |
grep -n "register_api_v1" src/nexus_scalp/web/server.py |
| live operation count | 68 (63 GET + 5 POST) | .venv/Scripts/python.exe scripts/dev/api_contract_check.py |
| legacy dashboard routes | 280 decorators, still served, additive-untouched | grep @(app|router).(get|post...) over web/*.py excluding api_v1* |
| superseded tree | src/nexus_scalp/api/v1 — REMOVED (was 45 routes, commit e51a910 removed it) |
must NOT exist; do not resurrect |
| envelope | {data, meta} / {error{...}} |
§4 |
| auth | none (localhost bind; no broker mutations by design) | §7 |
| platform owner registry | TASK-API-PLATFORM → VERIFIED (CHG-0043) | agents/taskboard.md |
1. API LANDSCAPE: FOUR STATUS CLASSES
Every API path in this repository is exactly ONE of these. Never blur them.
| class | meaning | examples |
|---|---|---|
| CURRENT / CANONICAL | /api/v1/* — the platform of record. Build here. |
all 68 operations in §4/§5 |
| LEGACY / HISTORICAL | the ~280 dashboard routes (/api/status, /api/live/state, /api/models/*, /api/research/*, /api/diagnostics/*, /api/db/console/*, …). Still served for the Web UI. Do NOT build new client work on them; consume via /api/v1 equivalents where they exist. |
§2 |
| DEPRECATED / SUPERSEDED | legacy paths whose truth now has a canonical v1 endpoint. Keep working (UI depends on them) but prefer v1. | /api/status → /api/v1/system/status; /api/models/champion → /api/v1/model/champion |
| REMOVED | trees deleted from the codebase. They do not exist. Any doc claiming otherwise is a defect. | src/nexus_scalp/api/v1/* (the first 45-route v1, superseded envelope) — removed in consolidation commit e51a910 |
Hard rule: an agent must never resurrect src/nexus_scalp/api/v1, never
mount a second v1 tree, and never add routes outside web/api_v1/ for new
platform capabilities. Extension workflow: §14.
2. LEGACY / HISTORICAL INVENTORY (verified from source, not memory)
Current decorator scan of web/*.py (excluding api_v1*): 280 routes across
10 route modules + server.py static/JS file routes. Families (count = decorators):
| module | count | family / path pattern | status | canonical v1 replacement |
|---|---|---|---|---|
debug_research_routes.py |
50 | /api/debug/*, /api/research/*, /api/experience/*, /api/account/* |
LEGACY (UI-owned) | /api/v1/research/*, /api/v1/decisions/*, /api/v1/audit/* |
model_governance_routes.py |
45 | /api/models/* (train/worker/shadow/governance/promotion/emergency) |
LEGACY — operator+UI only | /api/v1/model/* (read-only truth); POST governance actions stay legacy |
diagnostics_state_routes.py |
43 | /api/status, /health, /api/live/*, /api/account/*, /api/engine/*, /api/config, /api/settings/*, /api/db/*, /api/rules/* |
LEGACY — UI control plane | /api/v1/system/*, /api/v1/config/*, /api/v1/database/* |
news_liquidity_mslie_routes.py |
23 | /api/news/*, /api/liquidity/*, /api/mslie/* |
LEGACY | none in v1 (news/liquidity domain not yet versioned) |
intelligence_routes.py |
10 | /api/intelligence/* |
LEGACY | none in v1 |
server.py (inline) |
9 | /api/ticks/stream (SSE), /api/chart, /api/mt5, /api/positions, /api/status helpers, /ws |
LEGACY | /api/v1/market/*, /api/v1/positions (polling; SSE remains UI-owned) |
command_center_integration.py |
8 | /api/command-center/* |
LEGACY | none in v1 |
operator_routes.py |
6 | operator console routes | LEGACY | none in v1 |
factory_routes.py |
~18 | /api/factory/*, /loop/*, /generations/*, /llm-config/*, /analyze/* |
LEGACY | none in v1 |
replay_routes.py |
5 | /api/replay/* |
LEGACY | none in v1 (replay sessions are CHG-0043-adjacent, not yet versioned) |
db_console.py |
10 | /api/db/console/* (SQL console, API keys) |
LEGACY — read-only by contract, UI-owned | /api/v1/database/* (metadata only; v1 NEVER executes user SQL) |
dependency_routes.py |
9 | /api/dependency/* |
LEGACY | none in v1 |
news_intelligence_routes.py |
7 | /pro/*, /apikey/*, /vendor/* |
LEGACY | none in v1 |
server.py static files |
~10 | /app.js, /api_client.js, *.css |
static assets (not APIs) | n/a |
Migration rule: v1 duplicates legacy read truths only where a stable
contract was worth publishing; legacy control endpoints (engine toggle,
model training, promotion, SQL console) are deliberately NOT in v1 — they stay
operator-console/CLI owned. Do not search for them under /api/v1.
3. CURRENT CANONICAL API MAP (per-domain operational entries)
Format per operation: Purpose / Returns / Dependencies / Errors / Agent use /
CLI / Tests / Smoke. All GETs unless marked POST. All paths below are verified
against the live OpenAPI (scripts/dev/api_skill_drift_check.py enforces this).
3.1 SYSTEM — module web/api_v1/system.py
GET /api/v1/system/status
- Purpose: one-shot operational snapshot (health verdict + version + mode + freshness + check counts).
- Returns:
data{health_verdict, critical_failures[], version{product,version,commit,channel}, runtime{engine_attached,engine_running,mode,freshness_overall}, checks_count}. - Dependencies: HealthEngine (3s TTL cache + prewarm thread); engine attrs if attached.
- Errors: 503
DEPENDENCY_UNAVAILABLE(health engine itself failed). - Agent use: FIRST call to answer "what is the engine doing right now?" — but read §11 truth rules:
health_verdictis a layered verdict, not a trading guarantee. - CLI:
nexus api status(human rendering). - Tests:
tests/unit/test_api_v1_contracts.py,tests/integration/test_api_v1_platform.py. - Smoke:
python scripts/dev/api_smoke.py --embedded|--live.
GET /api/v1/system/health
- Purpose: full HealthEngine check list (verbatim per-layer entries).
- Returns:
data{verdict, checks[{category,verdict,...}], critical_failures[]}. - Dependencies: HealthEngine (cached 3s).
- Errors: 503
DEPENDENCY_UNAVAILABLE. - Agent use: WHEN a status layer failed and you need the per-layer detail.
- CLI:
nexus api health.
GET /api/v1/system/readiness
- Purpose: can the system accept work? Required layers (SYSTEM/RUNTIME/CONFIGURATION/DATABASE/MODEL/FEATURE_SCHEMA) must have no FAIL.
- Returns:
data{ready: bool, verdict, required_layers[], optional_layers[]}. - Errors: 503
DEPENDENCY_UNAVAILABLE. - Agent use: gate any automation on
data.ready— never on HTTP 200 alone.
GET /api/v1/system/version
- Purpose: build/revision identity.
- Returns: full
get_version_info()dict (product, version, commit, channel, python, platform, mode…). - Dependencies: build metadata (memoized).
- Errors: 503
DEPENDENCY_UNAVAILABLE. - CLI:
nexus api version.
GET /api/v1/system/runtime
- Purpose: runtime environment + mode + warmup + inference flag + freshness contract.
- Returns:
data{engine_attached, engine_running, warmup_state, inference_enabled, freshness, mode, effective_mode}. - Errors: truthful nulls when engine absent (HTTP 200,
engine_attached:false).
GET /api/v1/system/capabilities
- Purpose: platform discovery — domains + endpoint counts + pagination model + read_only flag; built from the REAL mounted route table.
- Returns:
data{api_version, spec, read_only, domains{}, domain_count, endpoint_count, pagination{...}, generated_at}. - Agent use: always the first discovery step on an unfamiliar deployment.
- CLI:
nexus api capabilities.
GET /api/v1/system/workers
- Purpose: worker lifecycle (accounting/incident/hygiene/model-lifecycle workers).
- Returns:
data{workers[{name,state,attached}], engine_attached}; honestNOT_ATTACHEDwhen absent.
POST /api/v1/system/refresh
- Purpose: safe refresh of runtime-observable state (re-read account/symbol snapshots, bump versioner).
- Body: none. Headers: optional
Idempotency-Key(echoed inmeta.idempotency_key). - Returns:
data{refreshed{account{available}}, at}. - Dependencies: ENGINE + adapter → 503
ENGINE_UNAVAILABLEwhen absent. - Side effects: NONE that reach a broker (snapshot reads only).
POST /api/v1/system/diagnostics/run
- Purpose: run the bounded observability selftest; caches result on app.state for
/diagnostics. - Returns: selftest result dict. Idempotency-Key echoed.
GET /api/v1/system/diagnostics
- Purpose: latest diagnostics = adapter
diagnostics_summary()(or null) + last selftest + version. - Returns:
data{mt5, last_selftest, version}; never 503 — truthful nulls when no adapter.
3.2 RUNTIME — module web/api_v1/runtime.py
GET /api/v1/runtime/mode
- Purpose: configured mode + effective
_runtime_mode+ replay flag. - Returns:
data{mode, effective_mode, engine_attached, replaying}; HTTP 200 with nulls when engine absent. - CLI:
nexus api mode.
GET /api/v1/runtime/freshness
- Purpose: the live-freshness contract (market/inference stages + overall) from
engine.compute_live_freshness(). - Returns:
data{freshness, available}. - Errors: 503
ENGINE_UNAVAILABLE.
POST /api/v1/runtime/mode/validate
- Purpose: pure transition validation of a PROPOSED mode — never applies.
- Body:
{"mode": "PAPER"}(ModeProposal). Returnsdata{valid, current_mode, proposed_mode, errors[], warnings[]}; invalid → HTTP 422 with the same body (valid:false). - Agent use: pre-flight any mode reasoning;
LIVEproposals always carry a warning that v1 has no mutation path.
POST /api/v1/runtime/mode/preview
- Purpose: impact matrix of a proposed mode (which subsystems it would touch).
- Body: ModeProposal. Returns
data{validation, impact{touches[],matrix{...}}}; 422 on invalid mode.
3.3 MARKET — module web/api_v1/market.py (ALL → 503 ENGINE_UNAVAILABLE without engine)
GET /api/v1/market/snapshot — data{symbol, account, symbol_spec} from real adapter snapshots (?symbol= override).
GET /api/v1/market/quote — data = broker tick (bid/ask/spread/freshness/stale) from get_broker_tick.
GET /api/v1/market/bars — ?limit≤500 completed M-bars from the engine aggregator (engine-local scope, stated in response).
GET /api/v1/market/regime — data{regime, evidence} from _last_regime_state; None before first inference (truthful, not 404).
GET /api/v1/market/symbols — configured symbol(s) + spec block.
3.4 SIGNALS — module web/api_v1/signals.py (audit_signals DB)
GET /api/v1/signals/latest — most recent audit_signals row (ORDER BY id DESC) + sanitized parsed payload; 404 truthful when table empty.
GET /api/v1/signals/history — paginated; filters symbol, action (upper-cased), hours_back (≤720); 422 pagination errors.
3.5 DECISIONS — module web/api_v1/decisions.py (audit_signals; decision_id = request_id)
GET /api/v1/decisions — paginated history; filters symbol/action/execution_mode/decision_stage/hours_back.
GET /api/v1/decisions/latest — full summary (decision_id, action, confidence, gates-adjacent fields, risk_checks).
GET /api/v1/decisions/stats — ?hours_back window; data{window_hours,total,by_action{},by_stage{}}.
GET /api/v1/decisions/no-trade — NO_TRADE analytics: data{total,reasons[],latest}.
GET /api/v1/decisions/no-trade/reasons — data{total, reasons{reason_code:count}}.
GET /api/v1/decisions/{decision_id} — detail by request_id; 404 truthful.
GET /api/v1/decisions/{decision_id}/evidence — raw payload (sanitized of secret-shaped keys).
GET /api/v1/decisions/{decision_id}/gates — ordered gate trace: decision_stage, blocked_by, reason_code, guardian_status, risk_allowed each with {value,passed:bool}.
GET /api/v1/decisions/{decision_id}/explanation — human-readable sentence composed ONLY from real fields.
3.6 POSITIONS / EXECUTION / MODEL / FEATURES — module web/api_v1/positions.py
GET /api/v1/positions — real adapter.get_all_positions() snapshots (MT5 field contract); 503 ENGINE_UNAVAILABLE without engine.
GET /api/v1/positions/{ticket} — one open position; 404 truthful if ticket not open.
GET /api/v1/positions/history/{ticket} — ledger row + broker deals for a historical ticket (DB-backed; works without engine).
GET /api/v1/execution/status — data{adapter_present, connection_state, connected, engine_running, mode}; 503 without engine.
GET /api/v1/execution/history — paginated audit_executions (order events), filters status,symbol.
GET /api/v1/model/status — data{bundle_loaded, inference_enabled, warmup_state, runtime_mode}; 503 without engine.
GET /api/v1/model/identity — bundle manifest identity + feature_schema_hash; {available:false} truthful when no bundle.
GET /api/v1/model/contracts — expected_schema_ids() + compatible_model_schemas() matrix.
GET /api/v1/model/champion — champion identity from the SERVING bundle (authoritative for "what is actually serving"); not the governance registry.
GET /api/v1/features/status — warmup + last-vector availability + missing features from last proposal.
GET /api/v1/features/contract — canonical 70D SSoT: SCHEMA_ID, feature_count, schema hash, family groups, registry_canonical.
GET /api/v1/features/groups — family → names/counts.
GET /api/v1/features/current — last computed vector (dimension + named dict or honest absence).
3.7 RISK — module web/api_v1/risk.py
GET /api/v1/risk/status — last proposal risk_checks (sanitized) + risk config block; 503 without engine.
GET /api/v1/risk/summary — open exposure by symbol (volume/profit) + account margin metrics from the adapter; exposure.available:false truthful.
3.8 RESEARCH — module web/api_v1/research.py (research store/registry over audit DB)
GET /api/v1/research/status — health + registry summaries; 503 DEPENDENCY_UNAVAILABLE if stores unreadable.
GET /api/v1/research/strategies — paginated registry entries; ?lifecycle=.
GET /api/v1/research/strategies/{strategy_id} — detail + invariant_check; 404 truthful.
GET /api/v1/research/runs — paginated run inventory; ?strategy_id=.
GET /api/v1/research/datasets — distinct dataset_ids + run counts (real rows only).
3.9 SHADOW — module web/api_v1/shadow.py (ShadowStore + Shadow70Store)
GET /api/v1/shadow/status — both stores' summary(); per-block failure isolation (null block ≠ fake zero).
GET /api/v1/shadow/runs — paginated PHASE-11 run inventory.
GET /api/v1/shadow/runs/{run_id} — run + comparison + promotion; 404 truthful.
GET /api/v1/shadow/decisions — paginated decision records; ?run_id=.
GET /api/v1/shadow/70d — 70D observer: summary + disagreement counts (valid-only) + drift alerts + feature health.
3.10 OBSERVABILITY / AUDIT / INCIDENTS / DATABASE / CONFIG — module web/api_v1/incidents.py
GET /api/v1/observability/events — paginated audit_events tail; ?event_type=.
GET /api/v1/observability/metrics — real process counters (threads; POSIX rusage; UNAVAILABLE on Windows — never fabricated) + SSE diag + engine peak equity.
GET /api/v1/audit/events — paginated audit_ledger tail; ?status=.
GET /api/v1/incidents — paginated incident inventory; filters status/severity/category/component.
GET /api/v1/incidents/stats — counts + by-component + recurring fingerprints.
GET /api/v1/incidents/{incident_id} — full Incident.as_dict(); 404 truthful.
GET /api/v1/incidents/{incident_id}/timeline — timeline events; 404 truthful.
GET /api/v1/database/status — O(1) metadata (filename, size, table_count); integrity_endpoint pointer. Never per-call PRAGMA.
GET /api/v1/database/integrity — quick_check + bounded row counts (explicit heavier route).
GET /api/v1/database/tables — table names only (no row dumps ever).
GET /api/v1/config — full engine config sanitized (secret-shaped keys → ***); 503 without engine.
GET /api/v1/config/schema — AppConfig section/field inventory (pydantic-derived).
POST /api/v1/config/validate — validate a partial config dict against real pydantic models; no apply (422 with field errors on invalid; applied:false always).
4. MACHINE-READABLE REGISTRY (drift-protected)
Every current operation. Columns: METHOD | PATH | DOMAIN | MODULE | READ/WRITE | ENGINE | DB | primary errors | tests | CLI.
E = requires engine (503 otherwise) · E-soft = degrades truthfully without engine · S = static/config/schema-backed.
| METHOD | PATH | DOMAIN | MODULE | READ/WRITE | ENGINE | DB | ERRORS | TESTS | CLI |
|---|---|---|---|---|---|---|---|---|---|
| GET | /api/v1/system/status | system | system.py | READ_ONLY | E-soft | S | 503 DEP | contracts+integration | nexus api status |
| GET | /api/v1/system/health | system | system.py | READ_ONLY | E-soft | S | 503 DEP | contracts+integration | nexus api health |
| GET | /api/v1/system/readiness | system | system.py | READ_ONLY | E-soft | S | 503 DEP | contracts | — |
| GET | /api/v1/system/version | system | system.py | READ_ONLY | N | S | 503 DEP | contracts+integration | nexus api version |
| GET | /api/v1/system/runtime | system | system.py | READ_ONLY | E-soft | N | — | contracts | — |
| GET | /api/v1/system/capabilities | system | system.py | READ_ONLY | N | N | — | contracts+integration | nexus api capabilities |
| GET | /api/v1/system/workers | system | system.py | READ_ONLY | E-soft | N | — | contracts | — |
| POST | /api/v1/system/refresh | system | system.py | REFRESH | E (adapter) | N | 503 ENGINE | integration | — |
| POST | /api/v1/system/diagnostics/run | system | system.py | DIAGNOSTIC | N | N | 500 | contracts | nexus api diagnostics --run |
| GET | /api/v1/system/diagnostics | system | system.py | READ_ONLY | N (adapter null) | N | — | integration | nexus api diagnostics |
| GET | /api/v1/runtime/mode | runtime | runtime.py | READ_ONLY | E-soft | N | — | contracts+integration | nexus api mode |
| GET | /api/v1/runtime/freshness | runtime | runtime.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| POST | /api/v1/runtime/mode/validate | runtime | runtime.py | SAFE_VALIDATION | E-soft | N | 422 | contracts | — |
| POST | /api/v1/runtime/mode/preview | runtime | runtime.py | SAFE_VALIDATION | E-soft | N | 422 | contracts | — |
| GET | /api/v1/market/snapshot | market | market.py | READ_ONLY | E | N | 503 ENGINE | integration | — |
| GET | /api/v1/market/quote | market | market.py | READ_ONLY | E | N | 503 ENGINE | contracts+integration | — |
| GET | /api/v1/market/bars | market | market.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/market/regime | market | market.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/market/symbols | market | market.py | READ_ONLY | E | N | 503 ENGINE | integration | — |
| GET | /api/v1/signals/latest | signals | signals.py | READ_ONLY | N | Y | 404 | contracts+integration | nexus api signals |
| GET | /api/v1/signals/history | signals | signals.py | READ_ONLY | N | Y | 422 | contracts+integration | — |
| GET | /api/v1/decisions | decisions | decisions.py | READ_ONLY | N | Y | 422 | contracts+integration | nexus api decisions |
| GET | /api/v1/decisions/latest | decisions | decisions.py | READ_ONLY | N | Y | 404 | contracts+integration | nexus api decisions --latest |
| GET | /api/v1/decisions/stats | decisions | decisions.py | READ_ONLY | N | Y | — | integration | — |
| GET | /api/v1/decisions/no-trade | decisions | decisions.py | READ_ONLY | N | Y | — | integration | — |
| GET | /api/v1/decisions/no-trade/reasons | decisions | decisions.py | READ_ONLY | N | Y | — | integration | — |
| GET | /api/v1/decisions/{decision_id} | decisions | decisions.py | READ_ONLY | N | Y | 404 | contracts+integration | nexus api decisions --id X |
| GET | /api/v1/decisions/{decision_id}/evidence | decisions | decisions.py | READ_ONLY | N | Y | 404 | integration | — |
| GET | /api/v1/decisions/{decision_id}/gates | decisions | decisions.py | READ_ONLY | N | Y | 404 | contracts+integration | nexus api decisions --id X --gates |
| GET | /api/v1/decisions/{decision_id}/explanation | decisions | decisions.py | READ_ONLY | N | Y | 404 | contracts | — |
| GET | /api/v1/positions | positions | positions.py | READ_ONLY | E | N | 503 ENGINE, 503 DEP | contracts+integration | — |
| GET | /api/v1/positions/{ticket} | positions | positions.py | READ_ONLY | E | N | 404, 503 ENGINE | integration | — |
| GET | /api/v1/positions/history/{ticket} | positions | positions.py | READ_ONLY | N | Y | 404 | integration | — |
| GET | /api/v1/execution/status | execution | positions.py | READ_ONLY | E | N | 503 ENGINE | contracts+integration | — |
| GET | /api/v1/execution/history | execution | positions.py | READ_ONLY | N | Y | 422 | contracts+integration | — |
| GET | /api/v1/model/status | model | positions.py | READ_ONLY | E | N | 503 ENGINE | contracts+integration | — |
| GET | /api/v1/model/identity | model | positions.py | READ_ONLY | E | N | 503 ENGINE | contracts+integration | nexus api model |
| GET | /api/v1/model/contracts | model | positions.py | READ_ONLY | E-soft | N | — | contracts | — |
| GET | /api/v1/model/champion | model | positions.py | READ_ONLY | E-soft | N | — | contracts | — |
| GET | /api/v1/features/status | features | positions.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/features/contract | features | positions.py | READ_ONLY | N | S | 503 DEP | contracts+integration | nexus api features |
| GET | /api/v1/features/groups | features | positions.py | READ_ONLY | N | S | 503 DEP | contracts | — |
| GET | /api/v1/features/current | features | positions.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/risk/status | risk | risk.py | READ_ONLY | E | N | 503 ENGINE | contracts+integration | — |
| GET | /api/v1/risk/summary | risk | risk.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/research/status | research | research.py | READ_ONLY | N | Y | 503 DEP | contracts+integration | — |
| GET | /api/v1/research/strategies | research | research.py | READ_ONLY | N | Y | 422, 503 DEP | contracts | — |
| GET | /api/v1/research/strategies/{strategy_id} | research | research.py | READ_ONLY | N | Y | 404, 503 DEP | contracts | — |
| GET | /api/v1/research/runs | research | research.py | READ_ONLY | N | Y | 422, 503 DEP | contracts+integration | — |
| GET | /api/v1/research/datasets | research | research.py | READ_ONLY | N | Y | 503 DEP | contracts | — |
| GET | /api/v1/shadow/status | shadow | shadow.py | READ_ONLY | N | Y | — | contracts+integration | — |
| GET | /api/v1/shadow/runs | shadow | shadow.py | READ_ONLY | N | Y | 422 | contracts+integration | — |
| GET | /api/v1/shadow/runs/{run_id} | shadow | shadow.py | READ_ONLY | N | Y | 404 | contracts | — |
| GET | /api/v1/shadow/decisions | shadow | shadow.py | READ_ONLY | N | Y | 422 | contracts | — |
| GET | /api/v1/shadow/70d | shadow | shadow.py | READ_ONLY | N | Y | — | contracts | — |
| GET | /api/v1/observability/events | observability | incidents.py | READ_ONLY | N | Y | 422 | contracts | — |
| GET | /api/v1/observability/metrics | observability | incidents.py | READ_ONLY | E-soft | N | — | contracts+integration | — |
| GET | /api/v1/audit/events | audit | incidents.py | READ_ONLY | N | Y | 422 | contracts+integration | — |
| GET | /api/v1/incidents | incidents | incidents.py | READ_ONLY | N | Y | 422 | contracts+integration | — |
| GET | /api/v1/incidents/stats | incidents | incidents.py | READ_ONLY | N | Y | — | contracts | — |
| GET | /api/v1/incidents/{incident_id} | incidents | incidents.py | READ_ONLY | N | Y | 404 | contracts+integration | — |
| GET | /api/v1/incidents/{incident_id}/timeline | incidents | incidents.py | READ_ONLY | N | Y | 404 | contracts | — |
| GET | /api/v1/database/status | database | incidents.py | READ_ONLY | N | Y | — | contracts+integration | — |
| GET | /api/v1/database/integrity | database | incidents.py | READ_ONLY | N | Y | 503 DEP | contracts | — |
| GET | /api/v1/database/tables | database | incidents.py | READ_ONLY | N | Y | 503 DEP | contracts | — |
| GET | /api/v1/config | config | incidents.py | READ_ONLY | E | N | 503 ENGINE | contracts | — |
| GET | /api/v1/config/schema | config | incidents.py | READ_ONLY | N | S | — | contracts+integration | — |
| POST | /api/v1/config/validate | config | incidents.py | SAFE_VALIDATION | N | S | 422 | contracts+integration | — |
68 documented operations (63 GET + 5 POST). Drift-protected by
scripts/dev/api_skill_drift_check.py.
5. ENVELOPE CONTRACT (§3 of spec of record)
Success — data present:
{"data": <payload>, "meta": {"request_id": "req_ab12cd34", "generated_at": "2026-09-02T10:00:00+00:00"}}
Error — error present, data absent:
{"error": {"code": "RESOURCE_NOT_FOUND", "message": "...", "details": {}, "request_id": "req_...", "retryable": false}}
Rules:
- Exactly one of
data/errorat top level. Never both, never neither (2xx always hasdata). meta.request_id: echoes the clientX-Request-IDwhen supplied (truncated 64 chars), else server-generatedreq_<10hex>.meta.generated_at: server wall clock at response build, UTC ISO-8601 (+00:00). It is a RESPONSE time, not an event time — never use it to order domain events (use the domain's own timestamps, e.g.generated_aton audit rows).- Serialization: enums →
.valuestrings; datetimes → UTC-aware ISO-8601 (naive datetimes are stamped UTC); Pydantic models →model_dump(). Handled centrally incommon.jsonable()/ok()— routes never hand-roll it. nullsemantics:Nonemeans unknown/unavailable/not-yet-computed (e.g.regime:nullbefore first inference,mt5:nullwithout adapter). It NEVER means zero/empty/healthy.- Unavailable subsystem blocks inside a 200 body (e.g. shadow store blocks) carry their own
null/available:falseand are failure-isolated — anullblock is not a platform error.
6. ERROR CONTRACT (all real codes — none invented)
| code | HTTP | retryable | server condition | client/agent action |
|---|---|---|---|---|
| VALIDATION_ERROR | 422 | no | bad query/pagination/body (incl. FastAPI request validation) | fix the request; error.details.errors[] names fields |
| RESOURCE_NOT_FOUND | 404 | no | id unknown OR dataset legitimately empty (e.g. no incidents yet) | do NOT retry blindly; distinguish empty-vs-missing via list endpoints |
| METHOD_NOT_ALLOWED | 405 | no | wrong verb on v1 path | use the documented method |
| PAYLOAD_TOO_LARGE | 413 | no | oversized body | shrink request |
| CONFLICT | 409 | no | state conflict (reserved) | re-read state first |
| ENGINE_UNAVAILABLE | 503 | yes | API server running without attached engine/adapter | NOT an empty dataset, NOT zero positions, NOT a healthy engine — a missing dependency; retry after engine start or use DB-backed routes |
| DEPENDENCY_UNAVAILABLE | 503 | yes | backing store/registry/health-engine read failed | retry with backoff; check logs via request_id |
| RESOURCE_UNAVAILABLE | 503 | yes | transient resource (reserved, same family) | retry |
| TIMEOUT | 504 | yes | upstream timeout (reserved) | retry bounded |
| INTERNAL_ERROR | 500 | no | unhandled exception (full detail logged under request_id) |
report bug with request_id; never assume data validity |
Handlers: web/api_v1/errors.py (path-guarded — legacy routes keep legacy bodies).
7. ENGINE-DEPENDENT BEHAVIOR MATRIX
Dependency classes (verified per route, §4 registry):
| class | behavior when ENGINE absent (server up, engine=None) | routes |
|---|---|---|
| E (engine/adapter hard) | 503 ENGINE_UNAVAILABLE |
market/* (5), positions (2 GET on adapter), risk/* (2), execution/status, model/status, model/identity, features/status, features/current, runtime/freshness, system/refresh |
| E-soft (engine fields degrade truthfully) | HTTP 200 with engine_attached:false / nulls |
system/status·health·readiness·runtime·workers, runtime/mode, model/contracts, model/champion, observability/metrics |
| DB (audit DB-backed) | fully functional (real SQLite reads); empty tables → truthful 404/empty pages | signals/, decisions/, execution/history, positions/history/{ticket}, research/, shadow/, incidents/, observability/events, audit/events, database/ |
| STATIC (schema/config/build) | fully functional offline | features/contract·groups, config/schema, system/version |
Interpretation rules for agents:
503 ENGINE_UNAVAILABLE≠ empty dataset ≠ zero positions ≠ healthy engine ≠ successful operation. It means: the dependency was absent at request time.- Engine states: READY (all E routes 200), STOPPED (server may still be up; E routes may attach but return stopped-state truthfully), NOT_INITIALIZED/UNAVAILABLE (E routes → 503), DEGRADED (see
/system/healthlayers). - Environment matrix (verified):
| environment | behavior |
|---|---|
EMBEDDED (create_v1_app() + TestClient) |
no engine → E routes 503; DB routes need a repo on app.state.audit_v1_repo or default-path artifacts DB |
LIVE uvicorn (dashboard create_app) |
full surface; engine-dependent routes reflect the attached engine |
| PAPER/REPLAY/SHADOW/LIVE engine modes | no route-class change — modes affect engine payload content, not API availability |
| NO ENGINE at all | v1 standalone app still serves DB/STATIC routes (CLI + tests rely on this) |
8. READ/WRITE & SIDE-EFFECT CLASSIFICATION
| class | count | operations | broker reach |
|---|---|---|---|
| READ_ONLY | 63 | all GETs in §4 | none |
| SAFE_VALIDATION | 3 | runtime/mode/validate, runtime/mode/preview, config/validate — pure functions, zero state change |
none |
| REFRESH | 1 | system/refresh — re-reads adapter snapshots, bumps UI versioner |
none (snapshot reads only) |
| DIAGNOSTIC | 1 | system/diagnostics/run — bounded offline selftest, caches result |
none |
| STATE_AFFECTING | 0 | — | none |
| EXECUTION_CAPABLE | 0 | v1 can never submit/modify/close an order | none |
ABSOLUTE RULE: NO REAL TRADING FROM AGENT API TESTS. The v1 platform has no order path at all; still, tests must use TestClient / embedded apps / PAPER / REPLAY / MOCK fixtures only. Live-mode selection, engine toggling, model training/promotion and SQL consoles remain legacy-console/CLI owned and are out of scope for agent API testing.
9. PAGINATION CONTRACT
- Params:
page(default 1, ≥1),page_size(default 50, 1..200). Violations → 422VALIDATION_ERROR. - Shape:
data{items[], page, page_size, has_more}.has_moreis the ONLY continuation signal; no total counts (full-scan protection). - Ordering: stable (
id DESCor documented store order); repeated identical queries return identicaldata(meta timestamp varies). - Invariants proven by tests: no duplicates across pages, no skipped rows, page k = exact slice
rows[(k-1)*s : k*s]of the unpaginated order. - Filters compose with pagination; indexed columns only (see §10).
Example:
GET /api/v1/decisions?page=2&page_size=5
→ {"data": {"items": [...5...], "page": 2, "page_size": 5, "has_more": true}, "meta": {...}}
10. FILTER CONTRACT (verified bounds)
| filter | routes | type | semantics | bound |
|---|---|---|---|---|
| symbol | signals/history, decisions, execution/history, market/quote·snapshot, positions | str ≤32 | exact match | — |
| action | signals/history, decisions | str | upper-cased exact (BUY/SELL/NO_TRADE) | — |
| status | execution/history, audit/events, incidents | str ≤24 | exact match | — |
| severity/category/component | incidents | str ≤24/32/48 | exact match | — |
| execution_mode, decision_stage | decisions | str | exact match | — |
| event_type | observability/events | str ≤48 | exact match | — |
| lifecycle | research/strategies | str ≤32 | exact match | — |
| strategy_id | research/runs | str ≤64 | exact match | — |
| run_id | shadow/decisions | str ≤64 | exact match | — |
| hours_back | signals/history, decisions | int | generated_at lower bound (UTC) | 1..720 (decisions enforce via Query ge/le; signals caps server-side) |
| limit | market/bars | int | bar count | ≤500 |
All filters are parameterized SQL/store parameters — never string-interpolated. Unbounded queries are impossible (server-side LIMIT always injected).
11. TIME CONTRACT
- Transport: ISO-8601, timezone-aware, UTC (
+00:00). Naive datetimes are stamped UTC bycommon.jsonable()before serialization. meta.generated_at= response build time — NOT event time.- Domain event times (e.g.
generated_aton decisions,timestampon ledger rows) are the ONLY ordering truth for domain events. freshnessendpoints carry engine-model stage ages — the authoritative "is data fresh" signal.- WARNING: never mix local time, UTC, and broker/server time. MT5 epochs are SERVER-LOCAL (GMT+3, BUG-070) — raw epoch fields from broker snapshots are not UTC. Convert only with the broker-aware providers; do not epoch-math in agents.
12. REQUEST ID / CORRELATION / IDEMPOTENCY
X-Request-ID(any HTTP request): accepted on ALL v1 routes; echoed asmeta.request_id+X-Request-IDresponse header + server log correlation. Max 64 chars.Idempotency-Key(POSTs): echoed asmeta.idempotency_key(≤128 chars). v1 POSTs are naturally idempotent (validate/refresh/diagnostics) — no server-side dedup store; the echo is for caller-side correlation.- Retry guidance: retryable codes (503/504) may be retried with backoff using the SAME request id for traceability; non-retryable (4xx/500) must not be retried unchanged.
- All failures are logged internally with the same
request_id— quote it in bug reports.
13. VERSIONING + OPENAPI
- Single mount:
/api/v1fromweb/api_v1_wiring.py. Incompatible changes →/api/v2(never break v1 in place). - OpenAPI:
GET /openapi.jsonon the v1 app (or dashboard app) — 100% operation coverage enforced (68/68). - Regenerate snapshot:
.venv/Scripts/python.exe scripts/dev/api_openapi_snapshot.py→artifacts/api/openapi_snapshot.json(sorted, timestamp-free, deterministic). - Detect drift:
.venv/Scripts/python.exe scripts/dev/api_openapi_diff.py OLD.json NEW.json --fail-on-breaking— BREAKING = removed endpoint/method or request-schema change → exit 2; additions → note, exit 0. - APISkill drift:
.venv/Scripts/python.exe scripts/dev/api_skill_drift_check.py— this file ↔ live OpenAPI (missing/extra ops, count mismatch) → exit 1 on drift. - Breaking changes REQUIRE explicit review (change-control entry + taskboard row) before merge.
14. EXTENSION WORKFLOW (adding an API — the ONLY sanctioned path)
- Define the contract (purpose, request, response, errors) in
docs/api/API_PLATFORM_V1.md§7 inventory. - Implement the route in the matching
web/api_v1/<domain>.py(reusecommon.ok/fail/parse_pagination/build_page/fetch_rows_bounded; no god router, no inline SQL from user input). - Wire nothing new if the domain router already exists (single mount covers it); new domains require a new module + wiring import in
_include_routers. - Add contract tests (
tests/unit/test_api_v1_contracts.py) + integration tests (tests/integration/test_api_v1_platform.py) incl. engineless 503 or DB fixture paths. ruff check --fix && ruff format && mypyon touched files.- Regenerate snapshot; run
api_openapi_diff.py OLD NEW --fail-on-breaking; runapi_contract_check.py(expect N+1 ops documented). - Update
docs/api/API_REFERENCE.md+ §3/§4 of THIS file (drift check must PASS). api_smoke.py --embeddedPASS; benchmark if performance-sensitive.- Commit (
<AGENT>:<task>convention), push, verifyorigin/main == HEAD.
Removal workflow: deprecate first (mark in docs + snapshot note), name the replacement, keep serving through the compatibility window, then remove code + docs + snapshot entries + add a regression test asserting the removal — never leave dead documentation claiming the route exists.
15. TOOLING REFERENCE (scripts/dev/)
| tool | purpose | input | output | exit codes |
|---|---|---|---|---|
api_smoke.py |
per-domain reachability + truthfulness | --embedded (no server) or --live BASE_URL |
per-domain ✓/○/✗ + API_SMOKE = PASS|FAIL |
0/1 |
api_contract_check.py |
OpenAPI quality gate: route↔spec parity, summaries/tags, schema refs, secret-shape scan, min-capability floor | none (embedded app) | API CONTRACT CHECK = PASS (68 operations…) |
0/1 |
api_openapi_snapshot.py |
deterministic artifact for drift detection | --out PATH |
sorted JSON snapshot | 0 |
api_openapi_diff.py |
snapshot-vs-snapshot breaking-change classifier | OLD.json NEW.json [--fail-on-breaking] | BREAKING/note lines + verdict | 0 compatible, 2 breaking, 1 IO |
api_benchmark.py |
bounded local perf (median/p95/p99, ≤300 req/route, read-only) | --requests N |
per-route table + API_BENCHMARK = PASS|PASS_WITH_NOTES (p95 budget 250ms) |
0 |
api_skill_drift_check.py |
THIS file ↔ live OpenAPI sync | none | APISKILL DRIFT = PASS|FAIL |
0/1 |
16. CLI → API MAP (verified from cli/api_commands.py)
| command | endpoint(s) |
|---|---|
nexus api status |
/api/v1/system/status (human rendering) |
nexus api health |
/api/v1/system/health |
nexus api version |
/api/v1/system/version |
nexus api capabilities |
/api/v1/system/capabilities |
nexus api mode |
/api/v1/runtime/mode |
nexus api signals |
/api/v1/signals/latest |
nexus api decisions [--latest | --id X [--gates]] |
/api/v1/decisions/latest, /api/v1/decisions, /api/v1/decisions/{id}, /decisions/{id}/gates |
nexus api model |
/api/v1/model/identity |
nexus api features |
/api/v1/features/contract |
nexus api diagnostics [--run] |
POST /api/v1/system/diagnostics/run, GET /api/v1/system/diagnostics |
nexus api get PATH [--page --page-size] |
any /api/v1/* path |
nexus api smoke |
all domains via the Python client → API_SMOKE = PASS |
All CLI commands consume NexusApiClient over real HTTP (--base-url / NEXUS_API_BASE, default http://127.0.0.1:8080) — the SAME contracts as external clients; zero duplicated logic.
17. NEXUSAPICLIENT CONTRACT
from nexus_scalp.api_client import NexusApiClient, NexusApiError, DEFAULT_BASE_URL
client = NexusApiClient("http://127.0.0.1:8080", timeout=10.0, headers={})
client.get(
path, params=...
) # → envelope dict {"data","meta"} (+ "pagination" keys inside data on lists)
client.post(path, json_body=..., idempotency_key=...)
# typed shortcuts: system_status/health/version, capabilities, runtime_mode,
# market_quote, signals_latest/history, decisions_latest/decisions/
# decision_detail/decision_gates, positions, risk_status, execution_status,
# model_status/identity, features_contract, research_status, shadow_status,
# incidents, database_status, observability_metrics, run_diagnostics
- Path normalization: leading
/api/v1auto-prepended (client.get("incidents")works). - Errors raise
NexusApiError(code, message, request_id, retryable, status, details)— parsed from the v1 error envelope; transport failures raise the same type withcode=DEPENDENCY_UNAVAILABLE, retryable=True. - No automatic retries inside the client (agents decide policy per
retryable). - No pagination helpers beyond passing params — the envelope IS the contract.
18. AGENT CALLING PATTERNS (verified commands)
# discovery
nexus api capabilities
curl -s http://127.0.0.1:8080/api/v1/system/capabilities
# operational truth
nexus api status
curl -s -H "X-Request-ID: req_agent_001" http://127.0.0.1:8080/api/v1/system/status
# decision forensics
nexus api decisions --latest
curl -s http://127.0.0.1:8080/api/v1/decisions/req_ab12cd34/gates
# paginated + filtered
curl -s "http://127.0.0.1:8080/api/v1/decisions?page=2&page_size=50&symbol=XAUUSD&action=NO_TRADE"
# validation POST (no state change)
curl -s -X POST http://127.0.0.1:8080/api/v1/runtime/mode/validate \
-H "Content-Type: application/json" -d '{"mode": "PAPER"}'
# test batteries (NO live trading — embedded/TestClient/PAPER only)
python scripts/dev/api_smoke.py --embedded
python scripts/dev/api_smoke.py --live http://127.0.0.1:8080
python scripts/dev/api_contract_check.py
python scripts/dev/api_skill_drift_check.py
.venv/Scripts/python.exe -m pytest tests/unit/test_api_v1_contracts.py tests/integration/test_api_v1_platform.py -q
19. TEST MAP (real files — do not invent others)
| domain/aspect | tests |
|---|---|
| envelope/pagination/sanitize/errors/capabilities/versioning | tests/unit/test_api_v1_contracts.py |
| full request paths + fixtures + fuzz + security + additive wiring | tests/integration/test_api_v1_platform.py |
| cross-surface state truth (CLI/API/runtime parity) | tests/unit/test_state_truth_parity.py |
| client E2E golden journeys (live HTTP against the API) | tests/e2e_client/ (e2e_harness.py, j1_golden.py, j2_mode_switch.py, j3_signal_decisions.py, j4_resilience.py) |
| perf | scripts/dev/api_benchmark.py (manual, bounded) |
20. SECURITY RULES FOR AGENTS
MUST NOT:
- expose secrets/tokens/passwords/broker credentials (sanitizer masks
token|password|secret|apikey|api_key|credential|login|account_number-shaped keys →***; report any leak as a BUG) - expect or extract stack traces/filesystem paths/SQL from error bodies (they are contractually absent; internal detail lives in logs only)
- attempt SQL/shell/command injection via params or IDs — hostile-input handling is tested (bounded fuzz: 4xx envelope, never 500); do not try to bypass it
- execute arbitrary SQL anywhere (v1 has no SQL endpoint; the legacy db-console SQL route is UI-owned and out of scope for agents)
- call anything that could reach a broker in tests (§8: v1 has zero execution path; keep it that way — TestClient/PAPER/REPLAY only)
- hammer endpoints: bounded requests only (pagination caps exist for a reason; benchmark tool is the sanctioned load path)
21. PERFORMANCE CONTRACT
Protections (verified): health 3s TTL cache + daemon prewarm at mount; version memoization; O(1) /database/status (integrity is explicit); pagination caps; server-side LIMITs; hours_back windows.
- Run
api_benchmark.pyafter adding routes or touching stores. Regression = p95 > 250ms on a previously-green route, or any route newly over budget. - Do not hard-code old numbers in agent logic; measure current.
22. DOMAIN GUIDANCE (avoid asking the wrong endpoint)
| domain | USE WHEN | does NOT answer | dependencies | common misuse |
|---|---|---|---|---|
| decisions | why was X traded/rejected; NO_TRADE analysis; evidence replay | live market state, model weights | audit DB | treating decision_id as broker ticket (that's positions/{ticket}) |
| model | which artifact is SERVING (/model/identity), warmup/inference state |
training/provenance lineage (use /research/runs, legacy /api/models/*) |
engine bundle | conflating champion-registry with serving-bundle truth — /model/champion is bundle-authoritative |
| features | active 70D contract (SSoT), last vector | historical vectors (not stored per-tick for API) | schema contract / engine | expecting per-feature history |
| risk | last proposal risk checks, current exposure | policy edits (none exist in v1) | engine + adapter | reading risk_checks as a guarantee of future acceptance |
| runtime | mode/freshness truth | mode CHANGES (validate-only; apply is console/CLI) | engine | applying a mode via API (impossible by design) |
| shadow | 70D observer health/disagreements/drift | promotion decisions (governance legacy) | audit DB | interpreting observer counters as champion impact |
| research | runs/strategies/datasets lineage | live training control | audit DB | using /research/runs as model registry |
| incidents | incident inventory/timelines/stats | real-time alerting (that's Telegram notifier) | audit DB | polling faster than bounded pages allow |
23. ONE QUESTION → ONE API (routing table)
| question | endpoint |
|---|---|
| What is the engine currently doing? | /api/v1/system/status |
| Can the system accept work? | /api/v1/system/readiness (data.ready) |
| Which layers failed and why? | /api/v1/system/health (checks) |
| What model is actually serving? | /api/v1/model/identity |
| What feature contract is active? | /api/v1/features/contract |
| Why was a decision rejected? | /api/v1/decisions/{id}/gates (+ /evidence) |
| What NO_TRADE reasons occurred? | /api/v1/decisions/no-trade (+ /no-trade/reasons) |
| Is market data fresh? | /api/v1/runtime/freshness |
| What capabilities exist? | /api/v1/system/capabilities |
| What incidents exist? | /api/v1/incidents (+ /stats) |
| Current exposure/margin? | /api/v1/risk/summary |
| What changed in the audit ledger? | /api/v1/audit/events |
| Is the broker connected? | /api/v1/execution/status |
| Database healthy? | /api/v1/database/status (+ /integrity) |
| What config is effective (sanitized)? | /api/v1/config |
| Would a mode change be valid? | POST /api/v1/runtime/mode/validate (never apply) |
24. TRUTH / CONSISTENCY RULES
- Never infer READY/HEALTHY/LIVE/TRADING/MODEL_VALID/DATA_FRESH from a single field unless the contract states it. Compose:
readiness.ready+health.checks+runtime/freshness. - Authoritative endpoints per fact: serving model →
/model/identity(bundle), NOT governance pages; mode →/runtime/mode; data freshness →/runtime/freshness; decision truth → audit-backed/decisions/*. - Compare snapshots via
meta.request_id/generated_at; different endpoints sampled at different times legitimately differ — do not manufacture contradictions from timestamp skew. - 503 ≠ empty. null ≠ zero.
available:false≠ error.
25. DISCOVERY WORKFLOW (recommended for a fresh agent)
nexus api capabilities(or read §4 registry here)nexus api status(operational posture)- consult §23 routing table → call the specific endpoint
- verify assumptions with
api_smoke.py --embedded/--live - extend? → §14 workflow; check drift after docs edits:
api_skill_drift_check.py
26. HISTORICAL RECONCILIATION (why the old tree is gone)
legacy 257→280 dashboard routes (UI-owned, still served)
→ first /api/v1 tree (src/nexus_scalp/api/v1, 45 routes, superseded envelope — committed mid-flight by a parallel session)
→ spec-of-record authored (docs/api/API_PLATFORM_V1.md, 65 capabilities)
→ consolidation decision: the WIP web/api_v1 tree matched the spec's envelope/pagination/error contracts, so it became canonical; the 45-route tree was DELETED (e51a910) and its salvable patterns (store queries, exception handlers) were ported
→ current 68 operations, drift-protected.
Never resurrect src/nexus_scalp/api/v1. Its paths are NOT current. Any
agent finding references to it (old docs, old sessions) must treat them as
historical context only.
27. STALENESS / DRIFT DETECTION (contract)
Sync axes: implementation ↔ OpenAPI ↔ API reference ↔ APISkill ↔ tests. Any mismatch = documentation/contract defect (file it, don't paper over it).
python scripts/dev/api_contract_check.py # implementation ↔ OpenAPI
python scripts/dev/api_openapi_snapshot.py # regenerate artifact
python scripts/dev/api_openapi_diff.py OLD NEW --fail-on-breaking # snapshot ↔ snapshot
python scripts/dev/api_skill_drift_check.py # THIS FILE ↔ OpenAPI
Update order when changing APIs: code → tests → snapshot → docs → THIS file → re-run all four checks.
28. SKILL SELF-AUDIT (Phase 33 — answered)
- discover every current API? ✅ §3 + §4 registry (drift-enforced 68/68)
- current vs legacy? ✅ §1/§2 status classes + §26 reconciliation
- right endpoint for common questions? ✅ §23
- engine-dependency per endpoint? ✅ §4 E/E-soft/N columns + §7 matrix
- error semantics? ✅ §6 (all real codes)
- construct a valid request? ✅ §5/§9/§10/§18
- correct smoke/contract test? ✅ §15/§19
- safe testing (no live trading)? ✅ §8/§20
- identify breaking change? ✅ §13 (diff tool + exit codes)
- extend without a second API tree? ✅ §14 (single-mount, single-tree rule)