{
  "version": "historical-live-data.workflow.v1",
  "fixtures": [
    {
      "name": "startup_requires_ack_backfill_replay_before_live_processing",
      "intent": "Startup does not enable live processing until exchange acknowledgement, historical backfill, buffered replay, and readiness enablement all complete.",
      "dataFamily": "candles",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "kline",
        "interval": "1m"
      },
      "initial": {
        "config": {
          "symbol": "ASSETQUOTE",
          "interval": "1m"
        },
        "readiness": {
          "transportOpen": false,
          "subscriptionAcknowledged": false,
          "backfillComplete": false,
          "bufferReplayComplete": false,
          "liveProcessingEnabled": false
        },
        "store": {
          "records": []
        },
        "bufferedEvents": []
      },
      "steps": [
        { "type": "transport_open" },
        {
          "type": "subscription_request",
          "payload": { "topic": "kline.1m.ASSETQUOTE" }
        },
        {
          "type": "websocket_event",
          "payload": { "key": "ASSETQUOTE:1m:1000", "final": true, "close": "100.1" }
        },
        { "type": "subscription_ack", "payload": { "topic": "kline.1m.ASSETQUOTE" } },
        {
          "type": "rest_backfill",
          "payload": { "records": [{ "key": "ASSETQUOTE:1m:0", "final": true }] }
        },
        { "type": "replay_buffer" }
      ],
      "expect": {
        "readiness": {
          "subscriptionAcknowledged": true,
          "backfillComplete": true,
          "bufferReplayComplete": true,
          "liveProcessingEnabled": true
        },
        "downstreamEffects": []
      },
      "forbid": ["downstream_before_ready"]
    },
    {
      "name": "subscribe_return_or_socket_open_does_not_enable_workflow",
      "intent": "Socket open and local subscribe return are not sufficient readiness evidence.",
      "dataFamily": "trades",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ALTQUOTE",
        "stream": "trades"
      },
      "initial": {
        "readiness": {
          "transportOpen": false,
          "subscriptionAcknowledged": false,
          "liveProcessingEnabled": false
        }
      },
      "steps": [
        { "type": "transport_open" },
        {
          "type": "subscription_request",
          "payload": { "localReturn": true, "topic": "trades.ALTQUOTE" }
        },
        { "type": "downstream_tick" }
      ],
      "expect": {
        "readiness": {
          "transportOpen": true,
          "subscriptionAcknowledged": false,
          "liveProcessingEnabled": false
        },
        "blocks": [{ "reason": "subscription_ack_required" }],
        "downstreamEffects": []
      },
      "forbid": ["live_processing_enabled_without_ack"]
    },
    {
      "name": "buffered_live_event_replays_after_backfill_once",
      "intent": "A live event received during backfill is replayed once after historical state is loaded.",
      "dataFamily": "candles",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "kline",
        "interval": "1m"
      },
      "initial": {
        "bufferedEvents": []
      },
      "steps": [
        { "type": "subscription_ack" },
        {
          "type": "websocket_event",
          "payload": { "key": "ASSETQUOTE:1m:60000", "final": true, "source": "ws" }
        },
        {
          "type": "rest_backfill",
          "payload": { "records": [{ "key": "ASSETQUOTE:1m:0", "source": "rest" }] }
        },
        { "type": "replay_buffer" }
      ],
      "expect": {
        "store": {
          "keys": ["ASSETQUOTE:1m:0", "ASSETQUOTE:1m:60000"]
        },
        "bufferedEvents": [],
        "downstreamEffects": [{ "key": "ASSETQUOTE:1m:60000", "count": 1 }]
      },
      "forbid": ["duplicate_replay"]
    },
    {
      "name": "duplicate_historical_and_live_record_is_deduped",
      "intent": "The same record from REST and WebSocket converges to one store row and one downstream effect.",
      "dataFamily": "candles",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "kline",
        "interval": "1m"
      },
      "initial": {},
      "steps": [
        { "type": "subscription_ack" },
        {
          "type": "websocket_event",
          "payload": { "key": "ASSETQUOTE:1m:60000", "final": true, "close": "100.1" }
        },
        {
          "type": "rest_backfill",
          "payload": {
            "records": [{ "key": "ASSETQUOTE:1m:60000", "final": true, "close": "100.1" }]
          }
        },
        { "type": "replay_buffer" }
      ],
      "expect": {
        "store": {
          "recordCountByKey": { "ASSETQUOTE:1m:60000": 1 }
        },
        "downstreamEffects": [{ "key": "ASSETQUOTE:1m:60000", "count": 1 }]
      },
      "forbid": ["duplicate_store_row", "duplicate_downstream_effect"]
    },
    {
      "name": "out_of_order_live_events_are_replayed_deterministically",
      "intent": "Buffered live events replay in deterministic event-time order, with receive time only as a tie-breaker.",
      "dataFamily": "trades",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ALTQUOTE",
        "stream": "trades"
      },
      "initial": {},
      "steps": [
        { "type": "subscription_ack" },
        {
          "type": "websocket_event",
          "payload": { "id": "late", "eventTime": 3000, "receivedAt": 10 }
        },
        {
          "type": "websocket_event",
          "payload": { "id": "early", "eventTime": 2000, "receivedAt": 11 }
        },
        { "type": "rest_backfill", "payload": { "records": [] } },
        { "type": "replay_buffer" }
      ],
      "expect": {
        "store": {
          "replayOrder": ["early", "late"]
        }
      },
      "forbid": ["receive_order_replay"]
    },
    {
      "name": "finality_required_before_downstream_side_effect",
      "intent": "Open or unconfirmed data updates can update state but cannot trigger alerts, strategies, order intents, or account decisions.",
      "dataFamily": "candles",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "kline",
        "interval": "1m"
      },
      "initial": {
        "readiness": {
          "liveProcessingEnabled": true
        }
      },
      "steps": [
        {
          "type": "websocket_event",
          "payload": { "key": "ASSETQUOTE:1m:120000", "final": false }
        },
        {
          "type": "downstream_tick",
          "payload": { "key": "ASSETQUOTE:1m:120000" }
        }
      ],
      "expect": {
        "store": {
          "keys": ["ASSETQUOTE:1m:120000"]
        },
        "downstreamEffects": [],
        "blocks": [{ "reason": "finality_required" }]
      },
      "forbid": ["open_candle_downstream_effect"]
    },
    {
      "name": "reconnect_pauses_workflow_until_scoped_resync_completes",
      "intent": "Reconnect restores transport only. Correctness-sensitive workflow remains paused until scoped resync and buffered replay complete.",
      "dataFamily": "orderbook",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "orderbook"
      },
      "initial": {
        "readiness": {
          "liveProcessingEnabled": true
        }
      },
      "steps": [
        { "type": "reconnect" },
        {
          "type": "websocket_event",
          "payload": { "sequence": 101, "side": "bid" }
        },
        { "type": "downstream_tick" },
        {
          "type": "resync",
          "payload": { "snapshotSequence": 100, "replayBuffered": true }
        }
      ],
      "expect": {
        "readiness": {
          "liveProcessingEnabled": true
        },
        "resyncState": {
          "required": false,
          "completed": true
        },
        "blocks": [{ "reason": "resync_required" }]
      },
      "forbid": ["downstream_during_reconnect_gap"]
    },
    {
      "name": "sample_symbol_interval_or_category_is_not_a_hidden_runtime_default",
      "intent": "Examples may contain sample identifiers, but runtime config must make product, symbol, interval, category, or stream explicit.",
      "dataFamily": "candles",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "kline",
        "interval": "1m"
      },
      "initial": {
        "config": {
          "sampleSymbol": "ASSETQUOTE",
          "symbol": null,
          "interval": null
        }
      },
      "steps": [{ "type": "configure" }],
      "expect": {
        "blocks": [{ "reason": "explicit_data_identity_required" }],
        "readiness": {
          "liveProcessingEnabled": false
        }
      },
      "forbid": ["sample_config_used_as_default"]
    },
    {
      "name": "orderbook_sequence_gap_blocks_downstream_until_snapshot_resync",
      "intent": "Order book delta sequence gaps block downstream decisions until a fresh snapshot plus replay restores continuity.",
      "dataFamily": "orderbook",
      "scope": {
        "exchange": "generic",
        "product": "spot",
        "symbol": "ASSETQUOTE",
        "stream": "orderbook"
      },
      "initial": {
        "store": {
          "lastSequence": 100
        }
      },
      "steps": [
        {
          "type": "websocket_event",
          "payload": { "firstSequence": 105, "lastSequence": 106 }
        },
        { "type": "downstream_tick" },
        {
          "type": "resync",
          "payload": { "snapshotSequence": 106 }
        }
      ],
      "expect": {
        "blocks": [{ "reason": "sequence_gap" }],
        "resyncState": {
          "required": false,
          "completed": true
        },
        "downstreamEffects": []
      },
      "forbid": ["book_decision_from_gapped_delta"]
    },
    {
      "name": "private_read_plus_stream_requires_read_only_credentials_and_hydration_gate",
      "intent": "Private data pipelines remain read-only and still use hydration plus live-event readiness gates.",
      "dataFamily": "private-account-read",
      "scope": {
        "exchange": "generic",
        "product": "margin",
        "symbol": "ACCOUNT",
        "stream": "private-account"
      },
      "initial": {
        "config": {
          "credentialScope": "read-only",
          "allowOrderPlacement": false
        },
        "readiness": {
          "liveProcessingEnabled": false
        }
      },
      "steps": [
        { "type": "subscription_ack" },
        {
          "type": "rest_backfill",
          "payload": { "records": [{ "account": "primary", "balance": "100" }] }
        },
        { "type": "replay_buffer" }
      ],
      "expect": {
        "readiness": {
          "liveProcessingEnabled": true
        },
        "blocks": []
      },
      "forbid": ["private_write_client_created", "order_endpoint_called"]
    }
  ]
}
