## ADDED Requirements

### Requirement: DataSource 接口替代 SceneStrategy 游标逻辑

系统 SHALL 提供 `DataSource` 接口作为场景数据获取的标准契约，接口仅包含 `Count`、`Headers`、`Fetch` 三个方法，框架自动将 `Count` 结果按 `ExportDefaultShardSize` 切分为 `(offset, limit)` 对，开发者不感知游标分页。

#### Scenario: 框架根据 Count 自动切分分片

- **WHEN** dispatch 阶段调用 `DataSource.Count` 返回 5000 条，`ExportDefaultShardSize` 为 2000
- **THEN** 框架生成 3 个分片：`(0,2000)` / `(2000,2000)` / `(4000,1000)`，不要求 DataSource 实现任何游标逻辑

#### Scenario: Fetch 按 offset/limit 返回数据行

- **WHEN** shard 阶段以 `offset=2000, limit=2000` 调用 `DataSource.Fetch`
- **THEN** DataSource 实现返回第 2001-4000 条记录，每条记录为 `[]string`，列数与 `Headers` 返回值一致

#### Scenario: DataSource 实现支持任意 SQL JOIN

- **WHEN** 场景需要 `tb_iot_card LEFT JOIN tb_package_usage` 联表
- **THEN** DataSource 实现可在 `Fetch` 中自由编写含 JOIN 的 GORM Raw SQL，框架不干涉查询内容

---

### Requirement: 动态列支持

`DataSource.Headers` SHALL 接收 `ExportParams` 参数并可在运行时返回动态列头；dispatch 阶段调用一次 `Headers` 后将结果存入任务元数据，所有分片均使用同一份固定列头。

#### Scenario: 设备绑多卡时动态扩展列

- **WHEN** 某批次设备中最多绑了 3 张卡，dispatch 阶段调用 `Headers` 时返回 `["设备号","IMEI","ICCID_1","ICCID_2","ICCID_3"]`
- **THEN** 所有分片的 CSV 文件均使用该 5 列结构，finalize 合并后列头一致

#### Scenario: 同一任务内各分片列数一致

- **WHEN** dispatch 阶段已固定 `resolved_headers` 为 5 列
- **THEN** 所有 shard 任务的 `Fetch` 返回每行均为 5 个元素，列数不匹配时记录警告日志并补空字符串对齐

---

### Requirement: 旧 SceneStrategy 接口作为逃生通道保留

系统 SHALL 同时支持 `DataSource` 和 `SceneStrategy` 两种注册方式；Registry 在执行时 MUST 优先识别 `DataSource`，降级识别 `SceneStrategy`；两种接口可混合注册，互不影响。

#### Scenario: 同时注册两种策略类型

- **WHEN** Registry 中注册了实现 `DataSource` 的 `IotCardDataSource` 和实现 `SceneStrategy` 的 `LegacyDeviceStrategy`
- **THEN** 框架对 `iot_card` 走 offset/limit 路径，对 `device`（若未迁移）走原 Keyset 路径，两者并存正常工作

#### Scenario: 未注册场景被创建任务时拒绝

- **WHEN** 用户创建任务时传入 `scene=unknown_scene`
- **THEN** 系统返回参数错误，不创建任务，Registry 不需要知道该 scene 的任何信息
