{
  "format": "siebly-runtime-kit/v1",
  "id": "exchange-state-management-runtime",
  "title": "Exchange State Management Runtime Kit",
  "lastReviewed": "2026-05-12",
  "purpose": "Compact module contract for exchange order, account, and position state-management runtimes.",
  "canonicalArtifacts": {
    "manifest": "https://siebly.io/.well-known/agent-manifests/exchange-state-management/latest.json",
    "runtimeKit": "https://siebly.io/.well-known/runtime-kits/exchange-state-management/v1/index.json",
    "runtimeTypes": "https://siebly.io/.well-known/runtime-kits/exchange-state-management/v1/types.ts",
    "integrationKit": "https://siebly.io/.well-known/integration-kits/exchange-state-management/latest.json",
    "conformancePack": "https://siebly.io/.well-known/conformance/exchange-state-management/latest.json",
    "fixtures": "https://siebly.io/.well-known/conformance/exchange-state-management/v1/fixtures.json",
    "runner": "https://siebly.io/.well-known/conformance/exchange-state-management/v1/runner.ts"
  },
  "moduleOrder": [
    "OrderContextStore",
    "AccountState",
    "PrivateEventRouter",
    "AffectedScopeQueue",
    "EventBurstReconciler",
    "ConfirmationTracker",
    "RecoveryHydrator",
    "WorkflowOwner",
    "Planner",
    "SubmissionExecutor",
    "ExchangeErrorClassifier",
    "StructuredLogger"
  ],
  "modules": [
    {
      "name": "OrderContextStore",
      "owns": "customOrderId -> order context",
      "contract": "Register context before submit, persist when configured, prune against trusted active orders, and never recover slot meaning by parsing custom IDs."
    },
    {
      "name": "AccountState",
      "owns": "subject trust for position state, active orders, fills, balances, filters, contexts, readiness, and sync/recovery flags",
      "contract": "Apply startup/recovery REST snapshots and private events into one account view. REST submit acceptance is coordination evidence only. Track order trust separately from position trust, filter trust, context trust, and readiness trust."
    },
    {
      "name": "PrivateEventRouter",
      "owns": "event classification, accountstate ingestion, affected-scope recording, and pending-confirmation matches",
      "contract": "Ingest private rows immediately, route known custom IDs through OrderContextStore, use bounded fallback scopes for ambiguous rows, and keep wallet-only events out of broad DCA/TP/SL planning unless configured."
    },
    {
      "name": "AffectedScopeQueue",
      "owns": "deduped product/symbol/side work items",
      "contract": "Canonicalize scope keys, dedupe repeats, preserve bounded follow-up reasons, and drain through one workflow owner."
    },
    {
      "name": "EventBurstReconciler",
      "owns": "short-delay reconciliation for private event bursts",
      "contract": "Apply events to AccountState immediately, record the exact product/symbol/side that changed, wait briefly so related events in the same burst can arrive, then run one reconciliation pass from the settled account state with a bounded max delay."
    },
    {
      "name": "ConfirmationTracker",
      "owns": "pending place/amend/cancel confirmations and in-flight custom IDs",
      "contract": "REST acceptance creates pending coordination state. Trusted private state or scoped recovery clears it. Timeout enters scoped recovery."
    },
    {
      "name": "RecoveryHydrator",
      "owns": "scoped REST hydration for untrusted subjects",
      "contract": "When recoveryRequired or sync-required state is set, hydrate positions, open orders, fills/executions, balances/readiness, replay buffered events, clear recovery on success, and requeue affected scopes."
    },
    {
      "name": "WorkflowOwner",
      "owns": "single active reconciliation/planning/submission pass",
      "contract": "Drain one affected scope at a time. If already active, record follow-up work and let the owner schedule one deferred pass."
    },
    {
      "name": "Planner",
      "owns": "desired managed slots and one selected action family",
      "contract": "Plan from trusted AccountState subjects. Products with exchange-provided position rows wait for matching position or account-state trust after order/fill events; products without exchange-provided position rows require trusted local position state. Each reconciliation pass chooses one [action family](https://siebly.io/reference/glossary#action-family) only: recover uncertain state, clean up stale app-owned orders, repair/place protection, place/repair DCA, do nothing, or wait because required evidence or configuration is missing."
    },
    {
      "name": "SubmissionExecutor",
      "owns": "preflight, context registration, exchange call dispatch, and provisional mutation records",
      "contract": "Pre-register contexts, preflight filters, risk geometry, and duplicate IDs, dispatch fresh SL/TP concurrently by default, and never unlock dependent action families from REST acceptance alone."
    },
    {
      "name": "ExchangeErrorClassifier",
      "owns": "deterministic block, benign race, retry/backoff, and scoped recovery decisions",
      "contract": "Classify by action, role, exchange code, current trusted state, and proof. Unknown mutation status enters scoped recovery."
    },
    {
      "name": "StructuredLogger",
      "owns": "redacted lifecycle, planner, submission, recovery, and shutdown logs",
      "contract": "Emit semantic counters and reasons without raw private payloads, secrets, signatures, listen keys, or raw [order intents](https://siebly.io/reference/glossary#order-intent)."
    }
  ],
  "subjectTrust": {
    "subjects": [
      "order trust",
      "position trust",
      "filter trust",
      "context trust",
      "readiness trust"
    ],
    "rules": [
      "Split trust by subject: order trust, position trust, filter trust, context trust, and readiness trust.",
      "A TP/SL/DCA order update proves only order trust; it does not prove position trust, position size, entry/cost basis, exposure, or replacement eligibility.",
      "For products with exchange-provided position rows, order/fill events are triggers; replacement planning waits for matching position trust from private position or account updates or scoped recovery.",
      "For products without exchange-provided position rows, derive local position state from fills/executions, fees, account events, balance updates, and documented matching rules before managed exits, exposure changes, or re-hedging."
    ]
  },
  "symbolScope": [
    "If configuredSymbols is blank, absent, or empty, auto-detect eligible open positions.",
    "When symbols are configured, treat them as an allowlist: manage matching detected positions only and ignore or log out-of-scope private events without mutation.",
    "Sample symbols from docs, tests, prompts, or quickstarts are examples only; never use them as runtime defaults."
  ],
  "riskGeometry": [
    "For broad TP/SL/DCA position managers, use percentage-based strategy inputs by default: derive TP, DCA, and SL prices plus DCA size from trusted entry and position size before order planning.",
    "Absolute TP/DCA/SL prices or DCA quantities are advanced per-symbol operator overrides, not the primary config model. If allowed, they must be explicit, scoped, compatible with percentage defaults, and validated through the same filters and risk geometry.",
    "Startup validation rejects missing, invalid, contradictory, or non-finite strategy inputs before private clients, WebSocket subscriptions, hydration, or exchange mutation.",
    "Validate risk geometry before exchange mutation: for long positions, TP is above entry, DCA is below entry, and SL is below DCA; for short positions, TP is below entry, DCA is above entry, and SL is above DCA.",
    "Reject missing or invalid TP/DCA/SL strategy outputs before order placement. After derivation or override, quantize prices and quantities with hydrated exchange filters before final validation.",
    "After quantization, revalidate risk geometry, min/max, and notional rules; reject only if the quantized TP/DCA/SL values are equal, crossed, or otherwise violate geometry or filters before [EXECUTION_MODE](https://siebly.io/reference/glossary#execution-mode)=DRY_RUN_PRIVATE, DEMO, TESTNET, or LIVE order placement.",
    "Risk geometry validation uses the trusted entry price or explicit operator override, hydrated tick/lot filters, and the managed side before any place/amend/cancel request."
  ],
  "identityChainReview": {
    "cleanPassesRequired": 3,
    "chain": [
      "config scope",
      "product/environment/account",
      "symbol",
      "position mode",
      "managed side",
      "exchange position identity",
      "accountstate position state (native or locally derived)",
      "planner scope",
      "slot scope",
      "custom order ID",
      "durable context",
      "exchange request",
      "REST acceptance",
      "private order/execution/position event",
      "trusted active order",
      "subject trust state",
      "confirmation tracker",
      "recovery scope",
      "cleanup/protective/DCA replanning"
    ],
    "requiredRule": "Do not mark the implementation complete until three consecutive full identity-chain review passes produce no code, tests, fixtures, or documentation changes.",
    "passMustTrace": "Trace mismatched, stale, missing, contradictory, duplicated, late, recovered, and flat identities across every runtime boundary."
  },
  "liveRunFailureFixtures": [
    {
      "name": "context_write_failure_blocks_submission",
      "purpose": "A failed durable order-context write must block exchange submission.",
      "expected": "The planner records the context write failure, submits no order, creates no pending confirmation, and leaves the scope blocked or recovery-ready."
    },
    {
      "name": "private_confirmation_before_rest_accept_is_consumed",
      "purpose": "A private confirmation can arrive before the REST submit promise resolves.",
      "expected": "The early private confirmation is retained, matched to the later accepted submit response, and does not leave stale pending state or duplicate submission."
    },
    {
      "name": "side_flip_cleans_old_side_before_new_side_work",
      "purpose": "A one-way side flip cleans old-side app-owned orders before new-side protection or DCA.",
      "expected": "Cleanup for the old managed side settles first; new-side protective and DCA work stays blocked until the cleanup confirmation is trusted."
    },
    {
      "name": "pending_place_blocks_duplicate_place",
      "purpose": "A pending place confirmation blocks another place for the same slot.",
      "expected": "The planner emits no duplicate place intent while the matching place confirmation is still pending."
    },
    {
      "name": "pending_cancel_blocks_duplicate_cancel",
      "purpose": "A pending cancel confirmation blocks another cancel for the same target.",
      "expected": "The planner emits no duplicate cancel intent while the matching cancel confirmation is still pending."
    },
    {
      "name": "decimal_and_default_equivalent_active_order_does_not_replace",
      "purpose": "Equivalent active orders do not churn because of display precision or hydrated defaults.",
      "expected": "Desired-vs-active comparison normalizes decimals and non-actionable exchange defaults by order kind, then suppresses cancel/recreate when actionable slot fields match."
    }
  ],
  "finalAcceptanceGate": [
    "Trace the full identity chain: config scope -> product/environment/account -> symbol -> position mode -> managed side -> exchange position identity -> accountstate position state (native or locally derived) -> planner scope -> slot scope -> custom order ID -> durable context -> exchange request -> REST acceptance -> private order/execution/position event -> trusted active order -> subject trust state -> confirmation tracker -> recovery scope -> cleanup/protective/DCA replanning.",
    "Do not mark the implementation complete until three consecutive full identity-chain review passes produce no code, tests, fixtures, or documentation changes.",
    "Trace mismatched, stale, missing, contradictory, duplicated, late, recovered, and flat identities across every runtime boundary.",
    "Every accepted lifecycle claim needs an executable fixture; behaviors without fixtures must be listed as unsupported or unverified.",
    "Core [behavior-contract fixtures](https://siebly.io/reference/glossary#behavior-contract-fixture) cover context write failure before submit, early private confirmation before REST accept, one-way side flips, pending duplicate place/cancel guards, and decimal/default-equivalent active-order convergence.",
    "The [EXECUTION_MODE](https://siebly.io/reference/glossary#execution-mode)=DEMO, TESTNET, or LIVE submission paths are implemented for order-capable projects where supported, but they remain disabled until the required identity-chain, subject-trust, symbol-scope, and risk-geometry fixtures exist and pass."
  ],
  "workflow": {
    "eventFlow": [
      "private_event_received",
      "accountstate_applied",
      "confirmation_match_checked",
      "affected_scope_recorded",
      "event_burst_reconcile_scheduled",
      "workflow_owner_drains_scope",
      "planner_selects_one_action_family",
      "submission_records_pending_confirmation",
      "private_or_recovery_state_confirms"
    ],
    "actionFamilyOrder": [
      "recovery",
      "cleanup",
      "protective",
      "dca",
      "noop",
      "blocked"
    ],
    "recoveryRule": "recoveryRequired or sync-required state blocks cleanup, protective, and DCA planning until scoped hydration succeeds and the affected scope is requeued.",
    "dependencyRule": "Dependent action families require trusted private state or scoped recovery evidence, not REST acceptance. For products with exchange-provided position rows, order/fill private rows trigger reconciliation but replacement planning waits for matching position or account-state trust; products without exchange-provided position rows must derive local position state explicitly."
  },
  "exchangeAdapterBoundary": {
    "owns": [
      "SDK clients and method names",
      "custom ID field names and prefix rules",
      "request field builders",
      "private topic names and payload field mapping",
      "exchange rejection taxonomy",
      "filter parsing and decimal formatting"
    ],
    "mustNotOwn": [
      "second account-state reducer",
      "custom-ID state encoding",
      "parallel workflow owner",
      "broad REST hydration on every healthy private event"
    ]
  },
  "localFilesToCreate": [
    "src/state/accountState.ts",
    "src/state/orderContextStore.ts",
    "src/stream/privateEventRouter.ts",
    "src/workflow/affectedScopeQueue.ts",
    "src/workflow/reconciler.ts",
    "src/workflow/confirmationTracker.ts",
    "src/workflow/recoveryHydrator.ts",
    "src/workflow/planner.ts",
    "src/submission/submissionExecutor.ts",
    "src/exchange/errorClassifier.ts",
    "src/logging/logger.ts"
  ],
  "testHooks": [
    "fake clock for event-burst delay and confirmation timeout",
    "fake exchange REST client for hydrate/place/amend/cancel outcomes",
    "private event injector",
    "structured log collector",
    "accountstate snapshot reader",
    "affected-scope queue inspector",
    "context write failure injector"
  ],
  "requiredChecks": [
    "startup_hydrated_position_queues_work_before_service_ready",
    "private_event_records_scope_and_schedules_reconcile",
    "private_event_burst_waits_for_related_events",
    "custom_order_id_registry_routes_exchange_echoes",
    "pending_confirmation_does_not_unlock_dependent_action_family",
    "context_write_failure_blocks_submission",
    "private_confirmation_before_rest_accept_is_consumed",
    "side_flip_cleans_old_side_before_new_side_work",
    "pending_place_blocks_duplicate_place",
    "pending_cancel_blocks_duplicate_cancel",
    "decimal_and_default_equivalent_active_order_does_not_replace",
    "terminal_event_during_cancel_or_amend_is_benign_race",
    "reconnect_queues_known_scopes_for_recovery_even_when_quiet",
    "slot_filter_failures_are_blocked_not_silent_or_retried",
    "flat_position_cleans_app_owned_orders",
    "rest_acceptance_does_not_downgrade_trusted_private_order",
    "terminal_order_status_is_not_open_order_confirmation",
    "execution_fill_is_not_open_order_confirmation",
    "default_dca_active_order_does_not_advance_next_step",
    "startup_adopts_active_contexts_and_prunes_absent_contexts",
    "ambiguous_event_routes_bounded_fallback_then_exact_scope",
    "pending_confirmation_timeout_enters_scoped_recovery",
    "recovery_hydration_blocks_normal_actions_until_trust_restored",
    "blank_symbols_auto_detects_open_positions",
    "absent_symbols_auto_detects_open_positions",
    "blank_symbols_no_positions_noops_without_mutation",
    "symbol_allowlist_manages_matching_detected_positions_only",
    "sample_symbol_not_used_as_runtime_default",
    "configured_symbol_allowlist_blocks_out_of_scope_events",
    "order_confirmation_does_not_imply_position_trust",
    "native_position_dca_fill_waits_for_position_update",
    "native_position_tp_sl_fill_waits_for_position_update",
    "spot_local_position_state_substitutes_for_native_position_rows",
    "long_dca_equal_sl_rejected",
    "short_dca_equal_sl_rejected",
    "dry_run_status_doctor_cannot_submit_live_orders"
  ]
}
