{
  "format": "siebly-agent-recipe/v1",
  "id": "bybit-position-manager",
  "title": "Position Management with Bybit APIs & WebSockets",
  "lastReviewed": "2026-05-12",
  "package": {
    "ecosystem": "npm",
    "name": "bybit-api",
    "install": "npm install bybit-api",
    "versionPolicy": "Use the latest available bybit-api package in the generated project. Record the installed version in the README dependency notes; do not pin implementation guidance to a version observed in one prior test run.",
    "docs": "https://siebly.io/sdk/bybit/javascript",
    "repository": "https://github.com/tiagosiebler/bybit-api"
  },
  "scope": {
    "runtime": "Node.js LTS",
    "language": "TypeScript",
    "products": [
      "Bybit Spot",
      "Bybit linear stablecoin-margined perpetuals"
    ],
    "defaultMode": "EXECUTION_MODE=DRY_RUN_PRIVATE for order-capable local runs",
    "executionMode": {
      "env": "EXECUTION_MODE",
      "values": [
        "PUBLIC",
        "READ_ONLY_PRIVATE",
        "DRY_RUN_PRIVATE",
        "DEMO",
        "TESTNET",
        "LIVE"
      ],
      "exchangeWritesOnlyWhen": "EXECUTION_MODE=DEMO, TESTNET, or LIVE plus venue routing, conformance, [risk gates](https://siebly.io/reference/glossary#risk-gate), credential checks, and manual acknowledgement gates"
    },
    "executionEnvironmentDefault": "EXECUTION_MODE=DEMO maps to demoTrading=true and testnet=false; EXECUTION_MODE=TESTNET maps to Bybit testnet routing",
    "liveExecutionDefault": false,
    "credentials": "scoped API keys from environment variables only for READ_ONLY_PRIVATE, DRY_RUN_PRIVATE, DEMO, TESTNET, LIVE; exchange writes require EXECUTION_MODE=DEMO, TESTNET, or LIVE"
  },
  "requiredSources": [
    "https://siebly.io/ai/exchange-state",
    "https://siebly.io/.well-known/prompts/bybit-position-manager/latest.md",
    "https://siebly.io/.well-known/recipes/bybit-position-manager.json",
    "https://siebly.io/ai/exchange-state/bybit",
    "https://siebly.io/.well-known/integration-kits/bybit-position-manager/latest.json",
    "https://siebly.io/.well-known/agent-manifests/bybit-position-manager/latest.json",
    "https://siebly.io/.well-known/agent-manifests/exchange-state-management/latest.json",
    "https://siebly.io/.well-known/runtime-kits/exchange-state-management/v1/index.json",
    "https://siebly.io/.well-known/runtime-kits/exchange-state-management/v1/types.ts",
    "https://siebly.io/.well-known/integration-kits/exchange-state-management/latest.json",
    "https://siebly.io/.well-known/conformance/exchange-state-management/latest.json",
    "https://siebly.io/.well-known/conformance/exchange-state-management/v1/schema.json",
    "https://siebly.io/.well-known/conformance/exchange-state-management/v1/fixtures.json",
    "https://siebly.io/.well-known/conformance/exchange-state-management/v1/runner.ts",
    "https://siebly.io/.well-known/conformance/bybit-position-manager/latest.json",
    "https://siebly.io/ai/exchange-state",
    "https://siebly.io/.well-known/recipes/exchange-state-management.json"
  ],
  "fallbackSources": [
    "https://siebly.io/sdk/bybit/javascript",
    "https://siebly.io/sdk/bybit/javascript/tutorial",
    "https://siebly.io/llms-tasks.txt",
    "https://siebly.io/.well-known/siebly-sdk-catalog.json",
    "https://github.com/tiagosiebler/bybit-api",
    "https://github.com/sieblyio/crypto-api-examples/tree/master/examples/Bybit"
  ],
  "primarySpec": {
    "id": "exchange-state-management-spec",
    "title": "Exchange State Management",
    "href": "https://siebly.io/ai/exchange-state",
    "purpose": "Exchange-neutral runtime spec for private account, order, and position state workflows, including DCA/TP/SL managers.",
    "architecture": {
      "reusable": "order_account_position_state_machine",
      "lane": "ws_symbol_side_action_gated"
    },
    "role": "Primary implementation spec. Other artifacts support, specialize, or test this workflow; they do not replace it."
  },
  "onePromptPath": [
    "Read primarySpec first",
    "Implement accountstate as the trusted WS cache",
    "Trigger debounced reconciliation from accountstate updates",
    "Gate exchange mutations by slot confirmation",
    "Use REST hydration only for startup, reconnect, recovery, or explicit sync-required states",
    "After implementation, generate/run applicable [behavior-contract fixtures](https://siebly.io/reference/glossary#behavior-contract-fixture) before DEMO, TESTNET, or LIVE"
  ],
  "antiTrace": {
    "normalPrivateWsBurstMustNot": [
      "private event -> REST hydrate",
      "REST acceptance -> trusted active order downgrade",
      "confirmed context -> recovery loop",
      "pending confirmation -> duplicate place",
      "execution fill -> open order confirmation"
    ]
  },
  "reusablePrimitives": {
    "architecture": "order_account_position_state_machine",
    "imports": [
      "custom-order-id-registry",
      "trusted-accountstate-boundary",
      "subject-level-trust-boundary",
      "position-state-update-trigger",
      "symbol-scope-auto-detect",
      "risk-geometry-validation",
      "ws-first-order-state",
      "recovery-hydrate-replan",
      "affected-scope-event-routing",
      "protective-sl-tp-dispatch",
      "mutation-confirmation-gating",
      "stale-mutation-noop-classification",
      "slot-convergence",
      "latch-lifecycle",
      "conformance-fixture-schema",
      "identity-chain-acceptance-gate"
    ],
    "rules": [
      "custom-order-id-registry: [Custom Order IDs](https://siebly.io/reference/glossary#custom-order-id) are lookup keys into one order-context registry/store. Generate a unique random ID, store context before submit, send the order with that ID, and resolve exchange echoes through the store. Follow exchange-specific prefix, length, and character rules only when required. Keep product, symbol, role, side, step, order kind, lifecycle, generation, recovery state, and other strategy state out of the ID text.",
      "trusted-accountstate-boundary: REST submission acceptance is provisional coordination evidence. Trusted order/position state comes from private WS/accountstate ingestion or scoped REST recovery hydration.",
      "subject-level-trust-boundary: 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.",
      "position-state-update-trigger: For products with exchange-provided position rows, private order and fill events trigger reconciliation but replacement, DCA, cleanup, and re-protection planning waits for matching trusted position or account-state evidence or scoped recovery. Products without exchange-provided position rows must derive local position state explicitly from fills/executions, fees, account events, and balance updates.",
      "symbol-scope-auto-detect: 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.",
      "risk-geometry-validation: 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.",
      "ws-first-order-state: Private account/order events update accountstate immediately and may arrive before REST promises resolve. Register custom ID context before sending any REST order request.",
      "recovery-hydrate-replan: Unknown, rejected, conflicting, reconnect, or sync-required state enters scoped recovery. Recovery hydrates affected positions, open orders, fills/executions, and account readiness, clears the latch on success, requeues the affected scope, and replans from trusted accountstate.",
      "affected-scope-event-routing: Ingest relevant private events immediately, but schedule planning only for precise affected symbol-side scopes. Broad unknowns become maintenance or scoped recovery, not product-wide churn by default.",
      "protective-sl-tp-dispatch: For fresh protection, pre-register SL and TP order contexts and dispatch both separate orders concurrently by default. Classify partial outcomes by failure reason and keep exposure-increasing DCA gated until required protection is trusted.",
      "mutation-confirmation-gating: Normal exposure-increasing, replacement, and DCA work advances only after private confirmation or scoped recovery proves the prior outcome.",
      "stale-mutation-noop-classification: Treat stale cancel/amend/no-change responses as idempotent only when current trusted accountstate proves target absence or slot equivalence. Otherwise run scoped recovery.",
      "slot-convergence: Compare desired and active orders by actionable slot fields for the order kind, not by raw request object equality, generated IDs, or parsed custom-ID state. When an accountstate or exchange-overlay comparison helper is available, compare NormalizedOrder-to-NormalizedOrder: convert desired slot/request objects into the accountstate NormalizedOrder shape first, mapping local managedSide/managedStrategySide to strategySide, then compare them with active normalized orders. Do not pass strategy-local DesiredManagedOrder/request objects directly or hand-roll raw exchange-object equality.",
      "latch-lifecycle: Every startup cleanup, recovery, sync-required, pending-confirmation, and readiness latch needs entry conditions, blocked behavior, no-work exit, success exit, failure behavior, and next action.",
      "conformance-fixture-schema: Executable fixtures should include initial accountstate, event or REST result, expected accountstate mutation, affected scopes, selected action family, submission behavior, logs, and latch state.",
      "identity-chain-acceptance-gate: 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."
    ]
  },
  "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."
  },
  "identityMismatchPolicy": {
    "scope": "Trace category, environment, account, symbol, position mode, managed side, positionIdx, durable context, orderLinkId, active order, private order/execution/position event, confirmation tracker, recovery scope, and replanning together.",
    "accountAndPositionMode": [
      "Prefer auto-detection from observed exchange/accountstate identity when the evidence is clear.",
      "Do not block only because configured mode text differs from observed mode; record the mismatch and follow observed identity.",
      "Refuse mutation only while mode evidence is unresolved, stale, or contradictory."
    ],
    "durableContext": [
      "Saved durable context without matching trusted active-order evidence remains unresolved until scoped active-order recovery confirms absence or terminal state.",
      "Active app-owned order without saved context refuses mutation unless an explicit adoption path is configured and fixture-proven.",
      "Terminal order rows are not active protective confirmations.",
      "Filled DCA steps are completed step identity, not active orders, and completed DCA state survives restart."
    ],
    "positionRowsAndRisk": [
      "Flat hedge private position rows are state rows, not close lifecycle events unless previous state was open for that same scoped side.",
      "A flat row on one hedge side must not emit a close notice or cleanup for the opposite open side.",
      "One-way flat rows require previous managed-side identity before close/noop decisions.",
      "Hedge-risk controls block exposure-increasing DCA, not protective TP/SL repair."
    ],
    "recoveryScopes": [
      "Filter-only recovery restores filters and must not prune durable order contexts.",
      "Open-order recovery may confirm or prune contexts only with explicit active-order evidence.",
      "Startup mutation waits until private stream confirmation can be observed."
    ],
    "fixtureNames": [
      "bybit_stale_context_wrong_position_idx_enters_recovery",
      "bybit_active_app_order_without_context_refuses_mutation_or_adopts_explicitly",
      "bybit_context_without_active_order_is_inconclusive_until_recovery",
      "bybit_flat_hedge_row_with_opposite_open_does_not_emit_closed_notice",
      "bybit_one_way_flat_row_uses_previous_side_identity",
      "bybit_completed_dca_state_survives_restart_without_active_order",
      "bybit_filter_only_recovery_does_not_prune_contexts",
      "bybit_open_order_recovery_prunes_absent_contexts",
      "bybit_late_private_event_routes_to_current_or_terminal_scope",
      "bybit_duplicate_slots_same_role_step_resolve_without_extra_place",
      "bybit_pending_confirmation_blocks_duplicate_dca_place",
      "bybit_terminal_dca_fill_then_planner_tick_reprotects_only_after_position_update",
      "bybit_position_mode_auto_detects_observed_exchange_identity",
      "bybit_hedge_risk_blocks_dca_but_allows_protective_repair"
    ]
  },
  "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."
  ],
  "sdkSurfacesToVerify": {
    "restClient": "RestClientV5",
    "websocketClient": "WebsocketClient",
    "websocketSubscription": "subscribeV5",
    "privateTopics": [
      "order",
      "execution",
      "position",
      "wallet"
    ],
    "websocketEvents": [
      "open",
      "response",
      "update",
      "reconnect",
      "reconnected",
      "exception"
    ],
    "shutdown": "closeAll(true) or the current documented closeAll variant",
    "restHydration": [
      "getInstrumentsInfo",
      "getPositionInfo",
      "getActiveOrders",
      "getHistoricOrders",
      "getExecutionList",
      "getWalletBalance",
      "getAccountInfo",
      "getRiskLimit"
    ],
    "orderMethods": [
      "submitOrder",
      "cancelOrder",
      "amendOrder",
      "preCheckOrder"
    ],
    "customClientIdField": "orderLinkId",
    "positionIdentityField": "positionIdx"
  },
  "terminologyWarnings": [
    "Bybit uses orderLinkId, not Binance newClientOrderId or clientAlgoId.",
    "Bybit uses positionIdx for one-way and hedge-side identity, not Binance positionSide.",
    "Bybit conditional close behavior uses closeOnTrigger and reduceOnly, not Binance closePosition.",
    "Bybit routes product family through category values such as spot and linear. Avoid Binance-specific USD-M naming in UI, config, and README copy.",
    "For order workflows, prefer RestClientV5 with throwExceptions=true so non-zero Bybit retCode responses throw through one structured SDK/API error path. If throwExceptions is false, a resolved promise can contain retCode !== 0 and is not exchange acceptance."
  ],
  "workflow": [
    "Start from the agent manifest and integration kit, then generate a local implementation checklist that includes [Behavior-Contract Fixture](https://siebly.io/reference/glossary#behavior-contract-fixture) coverage before editing exchange code.",
    "Install the latest available bybit-api package and record the installed version as SDK surface metadata only.",
    "Create RestClientV5 with explicit Bybit endpoint settings and prefer throwExceptions=true for order workflows that can submit DEMO, TESTNET, or LIVE orders. Use EXECUTION_MODE=DRY_RUN_PRIVATE for order-capable local runs. Map EXECUTION_MODE=DEMO to demoTrading=true/testnet=false and EXECUTION_MODE=TESTNET to Bybit testnet routing.",
    "Do not use Bybit WebSocket API order commands for demo endpoint trading. Use REST submit/amend/cancel/pre-check commands and private WebSocket reconciliation for EXECUTION_MODE=DEMO.",
    "Hydrate instruments first with getInstrumentsInfo(...), including priceFilter.tickSize, lotSizeFilter.qtyStep, minimum/maximum quantity, minimum notional, and product category. Store filters by category/symbol and pass them into the planner.",
    "For Bybit linear startup/restart hydration, call getPositionInfo with settleCoin for each configured settlement coin, defaulting to USDT and USDC. Never call getPositionInfo with only category=linear; use symbol only for deliberately narrow scoped recovery, and filter configuredSymbols locally after settleCoin hydration.",
    "For Bybit linear active-order startup/restart hydration, either capture and preserve a redacted raw getActiveOrders({ category: \"linear\", settleCoin, openOnly: 0 }) response proving regular and conditional StopOrder rows are both returned for the account mode, or defensively fetch orderFilter=\"Order\" and orderFilter=\"StopOrder\" separately for each settleCoin and merge by orderId/orderLinkId.",
    "Hydrate account mode, wallet, positions, active orders, historic orders, executions, and risk limits at startup, reconnect, exception, sync-required, or submission-recovery boundaries before planning any DCA, TP, or SL action. configuredSymbols is a local mutation allowlist, not a reason to force per-symbol startup hydration.",
    "Treat startup and reconnect active-order hydration as a replacement view of currently open exchange state for the category/symbol/positionIdx scope. Use recent history and executions as metadata and terminal evidence, not as the current open-order set.",
    "Connect private account-level WebSockets for order, execution, position, and wallet events after credentials and product scope are explicit. Treat socket open, subscribe response, REST hydration, buffered replay, semantic accountstate ingestion, and manager readiness as different states.",
    "Do not pause order management only because private account event traffic is quiet while the SDK transport is healthy. Use reconnect, reconnected, exception, and failed subscribe responses as stream-health signals.",
    "The manager is a long-running service, not a one-shot order-management script.",
    "Track eligible positions by category, symbol, positionIdx, managed side, size, average entry, account mode, and risk state. This includes manually opened positions and automated positions the app is configured to manage.",
    "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.",
    "Split trust by subject: order trust, position trust, filter trust, context trust, and readiness trust. Bybit order/execution updates can prove order/fill evidence only; linear replacement planning waits for matching private position trust or scoped recovery.",
    "For Bybit products without exchange-provided position rows, including Spot, derive local position state from fills/executions, fees, account events, balance updates, and documented matching rules before enabling any TP/SL/DCA-like behavior.",
    "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.",
    "Map positionIdx explicitly: 0 is one-way, 1 is hedge-mode buy/long side, and 2 is hedge-mode sell/short side. Keep one-way flips and same-symbol hedge sides as separate managed scopes.",
    "Prefer account and position-mode auto-detection from observed exchange/accountstate identity. If configured mode text disagrees with clear observed positionIdx evidence, log the mismatch and follow observed identity; refuse mutation only while evidence is unresolved or contradictory.",
    "Classify app-owned orders from the order-context registry/store. Durable order-context persistence is enabled by default for order-capable projects at .data/bybit-order-contexts.json, loaded before startup hydration, saved on create/update/delete, and pruned against trusted active orders. If persistence is disabled for EXECUTION_MODE=DRY_RUN_PRIVATE, DEMO, TESTNET, or LIVE, require an explicit acknowledgement such as BYBIT_ALLOW_EPHEMERAL_ORDER_CONTEXTS=true. If the runtime store is lost, do not parse orderLinkId for slot meaning.",
    "Treat saved context without a matching trusted active order as unresolved until scoped recovery confirms absence or terminal state. Active app-owned orders without saved context must refuse mutation unless an explicit adoption path is configured and tested.",
    "Treat terminal order rows as terminal evidence, not active protective confirmations. Filled DCA steps are completed step identity, not active orders; persist completed DCA state through restart so terminal context pruning cannot repeat a consumed step.",
    "If the Node.js project uses environment variables or creates .env.example, make .env loading automatic for every normal local entrypoint before config parsing. Prefer Node.js built-in --env-file/--env-file-if-exists in package scripts when supported by the project runtime; otherwise use process.loadEnvFile, dotenv/config, or the repo-local env loader. Document that real process environment variables override .env. Ensure all variables in the .env.example are commented clearly with their purpose and accepted values, and that the README references the .env.example and documents .env loading and precedence.",
    "Order-capable execution mode contract: implement one environment variable, EXECUTION_MODE, with exactly these values: PUBLIC, READ_ONLY_PRIVATE, DRY_RUN_PRIVATE, DEMO, TESTNET, LIVE. Set the default for order-capable local runs and .env.example to EXECUTION_MODE=DRY_RUN_PRIVATE. PUBLIC uses no API keys and cannot create private clients, account readers, [order intents](https://siebly.io/reference/glossary#order-intent), or exchange write requests. READ_ONLY_PRIVATE may use read-only credentials for balances, orders, fills, native positions, or local position-derivation inputs, but cannot place, amend, cancel, borrow, transfer, lever, or otherwise mutate exchange state. DRY_RUN_PRIVATE may use private state and must build the exact place/amend/cancel request objects that DEMO, TESTNET, or LIVE would send, but the submitter records them without calling exchange mutation endpoints. DEMO and TESTNET are write-capable only when the selected exchange supports those non-production venues; if unsupported, they must fail closed with a clear configuration error and must never silently route to production. DEMO may call place/amend/cancel endpoints only against the selected exchange's demo or sandbox environment, and TESTNET may call them only against the selected exchange's testnet environment. LIVE is the only mode that may call production exchange place/amend/cancel endpoints. The finished order-capable project must include the LIVE execution path for the selected exchange and supported DEMO/TESTNET paths where those venues exist; do not leave LIVE submission as a TODO.",
    "Implement execution for when [EXECUTION_MODE](https://siebly.io/reference/glossary#execution-mode)=DEMO, TESTNET, or LIVE is enabled and all gates pass, but keep that path disabled by default. [EXECUTION_MODE](https://siebly.io/reference/glossary#execution-mode)=DRY_RUN_PRIVATE is the default execution mode, where the manager builds and records DCA, take-profit, stop-loss, and trailing order candidates without submitting to the exchange. Always handle incoming account state events to keep internal account state in sync with exchange state, so the system is ready to execute live if requested by the user.",
    "Default DCA to one pending next step at a time. Full pre-placed DCA ladders require explicit opt-in.",
    "For a new managed position with no app-owned orders, emit stale app-owned cleanup first when needed, then pre-register protective SL/TP contexts and dispatch separate SL and TP orders concurrently by default in the protective action family. Exposure-increasing DCA only runs in a later DCA action family after private stream confirmation or scoped recovery hydration.",
    "For Bybit linear managed SL, default to a close-all conditional market StopOrder: qty=\"0\", reduceOnly=true, closeOnTrigger=true, triggerBy=MarkPrice unless explicitly configured, triggerDirection=2 for long SL below market, triggerDirection=1 for short SL above market, positionIdx matching the position side, and no price field. If fixed-quantity SL is chosen, add fixtures proving same-side position adds amend SL quantity before DCA or any exposure-increasing action.",
    "For active limit DCA or TP orders, converge by category, symbol, positionIdx, managed side, role, step, side, quantity, price, and active-order kind; hydrated defaults such as closeOnTrigger=false, reduceOnly=false, empty trigger fields, empty triggerBy, and empty stop-order fields are diagnostic after those fields match.",
    "For conditional SL comparison, require identity and trigger fields to match first: category, symbol, positionIdx, managed side, role, side, orderType, triggerPrice, triggerDirection, triggerBy, orderFilter, reduceOnly/closeOnTrigger semantics, and stop kind. Only after those match may empty/false-like defaults be normalized.",
    "Pre-register context for every orderLinkId before sending. Preflight each DEMO, TESTNET, or LIVE intent before its exchange call and block duplicate orderLinkId values, pending confirmations, and in-flight IDs.",
    "With throwExceptions=true, classify thrown Bybit business/API errors and preserve retCode, retMsg, result, time, request context, and product scope where available. If throwExceptions is false, every submitOrder, cancelOrder, amendOrder, and preCheckOrder response must be classified by retCode === 0. Deterministic request/config failures block and surface the failed slot; unknown, timeout, thrown, or conflicting outcomes enter bounded scoped recoveryRequired with operatorPaused=false.",
    "After Bybit accepts an order, record a pending confirmation, emit a sanitized submission_accepted_pending_confirmation log with request diff/context, and clear the accepted orderLinkId from the in-flight set before waiting for the private stream event.",
    "If later private stream confirmation or scoped active-order recovery omits a pending app-owned order after a short configured grace window, mark it stale so it cannot block missing DCA/TP/SL recreation forever.",
    "Private order, execution, position, and wallet events must feed a serialized ProductWorkflow.reconcile(...) path. Event handlers apply typed events to accountstate, emit semantic WS/accountstate logs, record the affected symbol-side scope, wait briefly for related close events in the same burst, and schedule reconciliation; they do not directly plan or submit.",
    "After readiness, normal private events should replay into trusted accountstate and plan without full REST hydration unless syncRequired, recoveryRequired, reconnect, exception, rejection, unknown submission state, or conflicting state demands REST recovery.",
    "When an event, subscribe ack, reconnect, or timer arrives while the workflow is active, non-owner calls record bounded follow-up reasons only. Only the owner workflow that started and completed may schedule one deferred reconciliation pass. Do not concatenate recursive reason strings or emit unbounded workflow_replan_deferred logs.",
    "Format final request price and quantity strings from hydrated tickSize and qtyStep decimal precision. Do not round with binary floats and then call String(number).",
    "On full close or position flip, clean up only app-owned DCA/TP/SL orders for the old scope. Flat hedge rows are state rows, not close lifecycle events unless the previous state was open for that same scoped side; opposite hedge sides remain untouched. One-way flat rows use previous managed-side identity before close/noop decisions. Clear managed-slot state only after hydration confirms no matching position and no active app-owned orders for that scope.",
    "Scope recovery to the missing evidence. Filter-only recovery restores filters and must not prune durable contexts. Open-order recovery may confirm or prune contexts only from explicit active-order evidence. Startup mutation waits until private stream confirmation can be observed.",
    "Hedge-risk controls block exposure-increasing DCA, not protective SL/TP repair.",
    "On reconnect, SDK exception, thrown Bybit business/API error, manual non-zero retCode classification, unknown submission state, restart, or conflicting state, enter recoveryRequired or syncRequired for the affected product, run scoped REST hydration before normal planning, replay buffered events, reconcile app-owned orders and executions, then resume new exposure only after state is coherent. A workflow with recoveryRequired finishes as blocked or recovery and must not log workflow_finished from a stale pre-recovery snapshot.",
    "Run each EXECUTION_MODE=DRY_RUN_PRIVATE, DEMO, TESTNET, or LIVE reconciliation pass through one action family: scoped recovery hydration first if required, cleanup cancel, concurrent protective SL/TP place or amend after contexts are registered, pending confirmation, then exposure-increasing DCA only in a later pass if the position still exists and still needs DCA. EXECUTION_MODE=DRY_RUN_PRIVATE records candidates only. DEMO, TESTNET, or LIVE may submit exchange mutations after all gates pass.",
    "Doctor, inspect, status, readback, and similar commands must force EXECUTION_MODE=PUBLIC or READ_ONLY_PRIVATE and cannot inherit EXECUTION_MODE=DEMO, TESTNET, or LIVE order placement, cancel, or amend settings from .env.",
    "On shutdown, close WebSockets, stop new workflow passes, flush compact state, and cancel app-owned transient orders only when the user explicitly opted into that cleanup policy."
  ],
  "requestRules": {
    "retCodeClassifier": "const assertBybitOk = (response, context) => {\n  if (response?.retCode === 0) return response.result;\n\n  throw new BybitBusinessError({\n    context,\n    retCode: response?.retCode,\n    retMsg: response?.retMsg,\n    result: response?.result,\n    time: response?.time,\n  });\n};",
    "conditionalStopExample": "await rest.submitOrder({\n  category: 'linear',\n  symbol,\n  side: 'Sell',\n  orderType: 'Market',\n  qty: '0',\n  triggerPrice: stopTriggerPrice,\n  triggerDirection: 2,\n  triggerBy: 'MarkPrice',\n  orderFilter: 'StopOrder',\n  reduceOnly: true,\n  closeOnTrigger: true,\n  positionIdx,\n  orderLinkId,\n});",
    "orderLinkId": [
      "Every app-owned [orderLinkId](https://siebly.io/reference/glossary#custom-order-id) follows the [Custom Order ID](https://siebly.io/reference/glossary#custom-order-id) rule.",
      "Store product, symbol, positionIdx, role, step, order kind, request, and pending action in the registry/store keyed by orderLinkId before submit.",
      "Replacement place IDs must differ from cancel target IDs, but client ID drift alone is not a desired-vs-active replacement reason.",
      "Pending confirmations and in-flight IDs block duplicate exchange writes before Bybit can reject them."
    ],
    "categoryMapping": [
      "spot: Spot order and balance scope.",
      "linear: stablecoin-margined perpetual/futures product scope; discover symbols from hydrated positions/private events or explicit configured symbol scope.",
      "inverse: coin-margined contracts only if explicitly selected and verified."
    ]
  },
  "stateMachine": [
    {
      "state": "config_loaded",
      "allowedAction": "Validate environment, product scope, EXECUTION_MODE gates, and credentials."
    },
    {
      "state": "transport_open",
      "allowedAction": "Observe WebSocket transport health only; no planning or submission."
    },
    {
      "state": "subscribed_private_topics",
      "allowedAction": "Buffer order/execution/position/wallet events; no readiness shortcut."
    },
    {
      "state": "hydrating",
      "allowedAction": "Replace authoritative REST snapshots for filters, positions, wallet, active orders, history, executions, and risk limits."
    },
    {
      "state": "replaying_buffered_events",
      "allowedAction": "Replay private events once against the hydrated snapshot."
    },
    {
      "state": "ready",
      "allowedAction": "Run EXECUTION_MODE=DRY_RUN_PRIVATE planning or explicitly gated DEMO, TESTNET, or LIVE planning."
    },
    {
      "state": "workflow_active",
      "allowedAction": "Finish reconciliation, replay, planning, and submission under one owner workflow."
    },
    {
      "state": "syncRequired",
      "allowedAction": "Block planning/submission and satisfy requested account-state subjects with scoped REST hydration."
    },
    {
      "state": "recoveryRequired",
      "allowedAction": "Block normal cleanup, protection, and DCA actions; hydrate scoped REST state, replay buffered events, clear recovery on success, then requeue/replan."
    },
    {
      "state": "operatorPaused",
      "allowedAction": "Persistent explicit user stop; no DEMO, TESTNET, or LIVE submission until the operator clears it."
    }
  ],
  "recoveryStateModel": {
    "operatorPaused": "Explicit persistent user/manual stop. It blocks cleanup, amendments, placements, and new exposure until the operator clears it.",
    "recoveryRequired": "Temporary exchange or state uncertainty. It blocks normal planner actions until scoped hydration plus replay restores trusted accountstate.",
    "syncRequired": "Named account-state subjects are untrusted or missing and must be satisfied with scoped REST hydration before planning or submission."
  },
  "submissionActionFamilies": [
    "recovery",
    "cleanup",
    "protective",
    "dca",
    "noop",
    "blocked"
  ],
  "goldenRuntimeTrace": {
    "startup": [
      "subscribe private order/execution/position/wallet topics",
      "REST hydrate instruments, account mode, wallet, positions, active orders, executions, and risk limits",
      "wait for private topic ack or topic updates",
      "replay buffered private events",
      "emit private_ws_event_received/accountstate_event_applied/affected_scope_recorded",
      "plan one affected symbol-side from accountstate",
      "emit cleanup candidate or submit cleanup action if write mode permits",
      "pre-register protective SL/TP contexts and record or dispatch separate SL/TP orders concurrently in protective action family",
      "wait for private stream confirmation or scoped recovery hydration",
      "record or submit DCA in a later DCA action family only if the position still exists and still needs DCA",
      "planner_finished intentCount=0"
    ],
    "normalPrivateBurst": [
      "buffer order/execution/position/wallet events for about 25ms",
      "replay burst into accountstate without REST hydration while trusted",
      "emit semantic WS/accountstate logs and record the affected symbol-side scope",
      "plan one affected symbol-side from accountstate",
      "amend same-slot TP/SL/DCA where possible",
      "planner_finished intentCount=0"
    ],
    "reconnectOrException": [
      "mark stream not ready and set recoveryRequired or syncRequired",
      "keep buffering private events where possible",
      "REST hydrate affected category/symbol/positionIdx scope",
      "wait for topic ack or private topic update",
      "replay buffered events",
      "resume only when accountstate is coherent"
    ]
  },
  "redFlagLogs": [
    "repeated nonzero intentCount with no accountstate change",
    "repeated cancel/place for the same logical slot when amend would work",
    "repeated REST hydration on normal private account-event bursts",
    "positionIdx:0 request for normalized hedge LONG or SHORT",
    "product_paused after a submission rejection without explicit operator stop",
    "duplicate orderLinkId after cancel-first replacement",
    "role, step, side, or strategy state encoded into orderLinkId",
    "DCA submitted from a plan made before an intervening close or fill",
    "app-owned cleanup blocked by duplicate or recent place guards",
    "accepted submit response immediately unlocks DCA before private stream confirmation",
    "workflow_finished logged from a stale pre-recovery snapshot"
  ],
  "fixtureCases": [
    {
      "name": "bybit_position_open_reacts_without_timer",
      "expected": "A private order/execution/position event updates accountstate, records the affected symbol-side scope, and the next plan emits protective SL/TP intents without waiting for a periodic timer."
    },
    {
      "name": "bybit_position_open_places_protective_then_dca_after_ws_confirmation",
      "expected": "A newly detected managed position first emits cleanup if needed, then pre-registers and dispatches separate SL/TP protective orders concurrently; DCA is absent until private stream confirmation or scoped recovery hydration proves protective exits and the position still exist."
    },
    {
      "name": "bybit_order_link_id_context_lookup_routes_private_event",
      "expected": "A generated random orderLinkId is stored in the order-context registry before submit; a private order event with that orderLinkId resolves the exact SlotKey without parsing state from the ID."
    },
    {
      "name": "bybit_lost_order_link_id_registry_rebuilds_from_trusted_state",
      "expected": "When the in-memory registry is lost, the manager hydrates trusted state, follows configured cleanup policy, and rebuilds expected orders instead of parsing orderLinkId strings."
    },
    {
      "name": "bybit_partial_protective_reject_blocks_failed_slot_and_surfaces_error",
      "expected": "If one protective order confirms and the other fails deterministically, the confirmed order is adopted when equivalent, the failed slot is marked blocked/requires operator action, DCA remains blocked if required SL is missing, and structured retCode/retMsg context is logged."
    },
    {
      "name": "bybit_pending_confirmation_does_not_unlock_dependent_action_family",
      "expected": "Accepted SL/TP responses create pending confirmations and duplicate-submit protection, but no DCA intent is emitted until accountstate confirmation arrives."
    },
    {
      "name": "bybit_semantic_ws_logs_emitted_after_ingest",
      "expected": "Private updates log private_ws_event_received, accountstate_event_applied, affected_scope_recorded, symbol_side_reconcile_started, action_family_selected, submission_accepted_pending_confirmation, private_ws_confirmation_applied, workflow_reconcile_complete without raw private payloads."
    },
    {
      "name": "bybit_retcode_nonzero_blocks_failed_slot_or_enters_recovery",
      "expected": "With throwExceptions=true, submitOrder throws on a non-zero retCode; with throwExceptions=false, submitOrder resolves with retCode !== 0. Deterministic request failures block/surface the failed slot, unknown outcomes enter recoveryRequired=true with operatorPaused=false, and retCode/retMsg/result are logged structurally."
    },
    {
      "name": "bybit_conditional_sl_requires_trigger_direction",
      "expected": "A linear stop-loss request without triggerDirection is rejected locally; long SL uses triggerDirection=2 and short SL uses triggerDirection=1 after current type verification."
    },
    {
      "name": "bybit_hydrated_defaults_do_not_churn",
      "expected": "Hydrated active orders with closeOnTrigger=false, reduceOnly=false, and empty trigger fields compare equal to matching desired limit TP/DCA orders."
    },
    {
      "name": "bybit_deferred_replan_owner_only",
      "expected": "Non-owner workflow scheduling records bounded follow-up reasons and only the owner schedules one deferred reconciliation after completion."
    },
    {
      "name": "bybit_identity_mismatch_recovery_cases",
      "expected": "Stale, missing, duplicated, contradictory, late, and recovered identities are fixture-tested across durable context, active orders, positionIdx, private events, recovery scopes, completed DCA state, and hedge risk."
    }
  ],
  "criticalFixtureNames": [
    "bybit_position_open_reacts_without_timer",
    "bybit_private_stream_exception_prevents_service_ready",
    "bybit_private_topic_updates_can_complete_readiness_without_args_ack",
    "bybit_subscribe_response_req_id_topics_can_mark_ready",
    "bybit_private_stream_reconnect_clears_exception_before_ack_readiness",
    "bybit_private_position_replay_drives_planning_without_rest_rehydrate",
    "bybit_normal_state_check_uses_accountstate_not_rest",
    "bybit_reconnect_uses_rest_hydration_before_resume",
    "bybit_private_event_burst_groups_to_single_reconcile",
    "bybit_order_execution_position_wallet_out_of_order_converges",
    "bybit_accepted_submissions_settle_to_zero_intents",
    "bybit_golden_live_convergence_trace_settles_after_deferred_dca",
    "bybit_position_open_places_protective_then_dca_after_ws_confirmation",
    "bybit_pending_confirmation_does_not_unlock_dependent_action_family",
    "bybit_semantic_ws_logs_emitted_after_ingest",
    "bybit_symbol_side_queue_groups_private_event_burst",
    "bybit_order_active_confirmation_before_rest_acceptance_no_stale_pending",
    "bybit_rest_acceptance_does_not_downgrade_trusted_private_order",
    "bybit_terminal_order_status_is_not_open_order_confirmation",
    "bybit_execution_fill_is_not_open_order_confirmation",
    "bybit_position_close_between_exit_action_and_dca_skips_dca",
    "bybit_dca_never_submitted_from_stale_pre_close_plan",
    "bybit_conditional_sl_requires_trigger_direction",
    "bybit_hedge_normalized_position_idx_maps_to_bybit_request_idx",
    "bybit_one_way_short_uses_position_idx_zero",
    "bybit_tp_is_reduce_only_limit_with_expected_time_in_force",
    "bybit_position_update_timestamp_does_not_rotate_slots",
    "bybit_full_close_cleanup_cancels_only_app_owned_orders",
    "bybit_one_way_short_then_long_fresh_scope_cleans_old_side",
    "bybit_hedge_same_symbol_sides_do_not_cross_cancel",
    "bybit_startup_wipe_rebuild_cancels_app_orders_before_rebuild",
    "bybit_startup_adopts_active_contexts_and_prunes_absent_contexts",
    "bybit_default_dca_active_order_does_not_advance_next_step",
    "bybit_ambiguous_event_routes_bounded_fallback_then_exact_scope",
    "bybit_linear_close_all_sl_request_is_valid",
    "bybit_same_side_position_add_amends_same_slots_without_cancel_first",
    "bybit_same_slot_update_submits_amends_without_cancel_gap",
    "bybit_cancel_first_replacement_converges_before_place",
    "bybit_amend_rejection_enters_recovery_without_blind_cancel_place",
    "bybit_retcode_nonzero_blocks_failed_slot_or_enters_recovery",
    "bybit_submission_rejection_requires_resync_without_permanent_pause",
    "bybit_order_link_id_context_lookup_routes_private_event",
    "bybit_lost_order_link_id_registry_rebuilds_from_trusted_state",
    "bybit_protective_sl_tp_pre_registered_and_dispatched_concurrently_by_default",
    "bybit_partial_protective_reject_blocks_failed_slot_and_surfaces_error",
    "bybit_unknown_submit_outcome_enters_recovery_after_configured_timeout",
    "bybit_recovery_required_blocks_normal_actions_until_hydration_succeeds",
    "bybit_hydrated_defaults_do_not_churn",
    "bybit_deferred_replan_owner_only",
    "bybit_sub_unit_tick_formats_trigger_price_correctly",
    "bybit_single_digit_price_tick_formats_correctly",
    "bybit_large_price_tick_formats_correctly",
    "bybit_integer_quantity_lot_formats_correctly",
    "bybit_fractional_quantity_lot_formats_correctly",
    "bybit_rejected_order_link_id_is_not_retried_without_state_change",
    "bybit_rejected_order_link_id_can_retry_after_request_change",
    "bybit_duplicate_order_link_id_preflights_place_intent_without_blocking_cleanup",
    "bybit_stale_context_wrong_position_idx_enters_recovery",
    "bybit_active_app_order_without_context_refuses_mutation_or_adopts_explicitly",
    "bybit_context_without_active_order_is_inconclusive_until_recovery",
    "bybit_flat_hedge_row_with_opposite_open_does_not_emit_closed_notice",
    "bybit_one_way_flat_row_uses_previous_side_identity",
    "bybit_completed_dca_state_survives_restart_without_active_order",
    "bybit_filter_only_recovery_does_not_prune_contexts",
    "bybit_open_order_recovery_prunes_absent_contexts",
    "bybit_late_private_event_routes_to_current_or_terminal_scope",
    "bybit_duplicate_slots_same_role_step_resolve_without_extra_place",
    "bybit_pending_confirmation_blocks_duplicate_dca_place",
    "bybit_terminal_dca_fill_then_planner_tick_reprotects_only_after_position_update",
    "bybit_position_mode_auto_detects_observed_exchange_identity",
    "bybit_hedge_risk_blocks_dca_but_allows_protective_repair"
  ],
  "fixtureGroups": {
    "startupAndReadiness": [
      "bybit_position_open_reacts_without_timer",
      "bybit_private_stream_exception_prevents_service_ready",
      "bybit_private_topic_updates_can_complete_readiness_without_args_ack",
      "bybit_subscribe_response_req_id_topics_can_mark_ready",
      "bybit_private_stream_reconnect_clears_exception_before_ack_readiness"
    ],
    "restAccountstateBoundary": [
      "bybit_private_position_replay_drives_planning_without_rest_rehydrate",
      "bybit_normal_state_check_uses_accountstate_not_rest",
      "bybit_reconnect_uses_rest_hydration_before_resume"
    ],
    "privateBursts": [
      "bybit_private_event_burst_groups_to_single_reconcile",
      "bybit_order_execution_position_wallet_out_of_order_converges"
    ],
    "submissionPhasing": [
      "bybit_accepted_submissions_settle_to_zero_intents",
      "bybit_golden_live_convergence_trace_settles_after_deferred_dca",
      "bybit_position_open_places_protective_then_dca_after_ws_confirmation",
      "bybit_pending_confirmation_does_not_unlock_dependent_action_family",
      "bybit_position_close_between_exit_action_and_dca_skips_dca",
      "bybit_dca_never_submitted_from_stale_pre_close_plan",
      "bybit_protective_sl_tp_pre_registered_and_dispatched_concurrently_by_default"
    ],
    "privateEventRouting": [
      "bybit_semantic_ws_logs_emitted_after_ingest",
      "bybit_symbol_side_queue_groups_private_event_burst",
      "bybit_order_active_confirmation_before_rest_acceptance_no_stale_pending",
      "bybit_rest_acceptance_does_not_downgrade_trusted_private_order",
      "bybit_terminal_order_status_is_not_open_order_confirmation",
      "bybit_execution_fill_is_not_open_order_confirmation",
      "bybit_order_link_id_context_lookup_routes_private_event",
      "bybit_ambiguous_event_routes_bounded_fallback_then_exact_scope"
    ],
    "requestShape": [
      "bybit_conditional_sl_requires_trigger_direction",
      "bybit_hedge_normalized_position_idx_maps_to_bybit_request_idx",
      "bybit_one_way_short_uses_position_idx_zero",
      "bybit_tp_is_reduce_only_limit_with_expected_time_in_force",
      "bybit_linear_close_all_sl_request_is_valid"
    ],
    "managedSlotOwnership": [
      "bybit_position_update_timestamp_does_not_rotate_slots",
      "bybit_full_close_cleanup_cancels_only_app_owned_orders",
      "bybit_one_way_short_then_long_fresh_scope_cleans_old_side",
      "bybit_hedge_same_symbol_sides_do_not_cross_cancel",
      "bybit_startup_wipe_rebuild_cancels_app_orders_before_rebuild",
      "bybit_startup_adopts_active_contexts_and_prunes_absent_contexts",
      "bybit_default_dca_active_order_does_not_advance_next_step",
      "bybit_lost_order_link_id_registry_rebuilds_from_trusted_state"
    ],
    "identityMismatchRecovery": [
      "bybit_stale_context_wrong_position_idx_enters_recovery",
      "bybit_active_app_order_without_context_refuses_mutation_or_adopts_explicitly",
      "bybit_context_without_active_order_is_inconclusive_until_recovery",
      "bybit_flat_hedge_row_with_opposite_open_does_not_emit_closed_notice",
      "bybit_one_way_flat_row_uses_previous_side_identity",
      "bybit_completed_dca_state_survives_restart_without_active_order",
      "bybit_filter_only_recovery_does_not_prune_contexts",
      "bybit_open_order_recovery_prunes_absent_contexts",
      "bybit_late_private_event_routes_to_current_or_terminal_scope",
      "bybit_duplicate_slots_same_role_step_resolve_without_extra_place",
      "bybit_pending_confirmation_blocks_duplicate_dca_place",
      "bybit_terminal_dca_fill_then_planner_tick_reprotects_only_after_position_update",
      "bybit_position_mode_auto_detects_observed_exchange_identity",
      "bybit_hedge_risk_blocks_dca_but_allows_protective_repair"
    ],
    "replacementAndAmend": [
      "bybit_same_side_position_add_amends_same_slots_without_cancel_first",
      "bybit_same_slot_update_submits_amends_without_cancel_gap",
      "bybit_cancel_first_replacement_converges_before_place",
      "bybit_amend_rejection_enters_recovery_without_blind_cancel_place",
      "bybit_hydrated_defaults_do_not_churn",
      "bybit_deferred_replan_owner_only"
    ],
    "submissionErrors": [
      "bybit_retcode_nonzero_blocks_failed_slot_or_enters_recovery",
      "bybit_submission_rejection_requires_resync_without_permanent_pause",
      "bybit_rejected_order_link_id_is_not_retried_without_state_change",
      "bybit_rejected_order_link_id_can_retry_after_request_change",
      "bybit_duplicate_order_link_id_preflights_place_intent_without_blocking_cleanup",
      "bybit_partial_protective_reject_blocks_failed_slot_and_surfaces_error",
      "bybit_unknown_submit_outcome_enters_recovery_after_configured_timeout",
      "bybit_recovery_required_blocks_normal_actions_until_hydration_succeeds"
    ],
    "decimalFormatting": [
      "bybit_sub_unit_tick_formats_trigger_price_correctly",
      "bybit_single_digit_price_tick_formats_correctly",
      "bybit_large_price_tick_formats_correctly",
      "bybit_integer_quantity_lot_formats_correctly",
      "bybit_fractional_quantity_lot_formats_correctly"
    ]
  },
  "safety": [
    "Use EXECUTION_MODE=DRY_RUN_PRIVATE for order-capable local runs.",
    "Exchange mutation endpoints require EXECUTION_MODE=DEMO, TESTNET, or LIVE plus explicit endpoint settings, scoped credentials, reviewed code, conformance, [risk gates](https://siebly.io/reference/glossary#risk-gate), and manual acknowledgement.",
    "A rejected DEMO, TESTNET, or LIVE request enters recoveryRequired for the affected product scope and forces hydration/replay/replan before new exposure.",
    "Current Bybit API docs and installed package declarations remain required for exact API details."
  ],
  "promptFrameworkCompletionWorkflow": {
    "instruction": "Recursive completion workflow:\n1. Before implementation, save this exact prompt in docs/AI_PROMPT.md (or docs/SPEC.md when that is the project standard) and write docs/PLAN.md with phases, invariants, tests or fixtures, docs to update, and acceptance gates.\n2. Review docs/PLAN.md for missing workflows, unsafe assumptions, product/exchange-specific leakage, unclear state ownership, confirmation or recovery gaps, missing tests, and incomplete docs. Update docs/PLAN.md and repeat until one full review pass finds no actionable changes.\n3. Implement one plan phase at a time. After each phase, review changed code, tests, fixtures, docs, generated artifacts, and runtime workflows against docs/PLAN.md and this prompt. Fix gaps and repeat until that phase has no actionable changes before starting the next phase.\n4. After all phases, run a full-depth project review across every workflow, lifecycle, state transition, error path, and artifact. This is not a shallow summary pass. Fix every actionable gap and repeat until a full pass finds no further changes, then record the final review outcome in docs/PLAN.md.",
    "acceptanceRequirement": "docs/PLAN.md records the initial plan, plan-review iterations, phase review outcomes, final full-project review, validation commands, and any documented non-claims. No plan phase or project completion is accepted until the recursive review loop finds no actionable gaps, flaws, or incomplete workflows left to correct."
  }
}
