# 同步流程（pipeline -> strategy -> datasource）

## 1. 全流程主线
1. pipeline 装配组件并加载持久化。
2. pipeline 为 datasource 绑定 collection，但不在 bootstrap 阶段全量拉取业务数据。
3. pipeline 在每个 `node_type` 开始执行前，单独加载该 `node_type` 的最新数据。
4. pipeline 调用 `strategy.bind()`。
5. pipeline 调用 `strategy.create()` / `strategy.update()` 生成动作节点。
6. pipeline 调用 `datasource.sync_all()` 执行动作并回写执行状态。
7. pipeline 输出统计并持久化。

## 2. reset 时序

### 2.1 加载时 reset（phase1 语义）
- 在 `common/collection.py::load_from_persistence()` 执行。
- 语义：仅恢复持久化状态（不在此阶段做状态归并）。

### 2.2 加载后 cleanup（phase2 语义）
- pipeline 调用 `BaseSyncStrategy.run_reset()`。
- 实际实现：`strategy_ops/reset_ops.py::run_phase2_cleanup()`。
- 核心事件：`E01`（`S16 -> S00/S15`）。
- `S16` 表示“持久化加载输入态”：本轮从持久化恢复出的节点集合（可包含上轮遗留节点，也可包含上轮已稳定节点）。
- 判定：
	- `is_create_zombie=true` -> `S15`，节点删除（并尝试解绑）；
	- `is_create_zombie=false` -> `S00`，节点归并为 `UNCHECKED/NONE/PENDING` 并清空 `error`。

### 2.2.1 `enable_persistence` 与 bootstrap 关系
- `enable_persistence=true`：先 `load_from_persistence()`，再对加载出的节点执行 `E01`（`S16` 域内处理）。
- `enable_persistence=false`：本轮没有持久化输入，`S16/E01` 不参与；随后 Stage1 新加载节点直接进入本轮 bind/create/update/sync 主流程。

### 2.3 加载异常隔离（S17）
- 持久化中若出现非法枚举值（`action/status/binding_status`），节点标记为 `bootstrap_blocked`（`S17` 语义）。
- 这类节点保留 `error`，用于人工排查。
- 这类节点不会进入 `E01`，也不会参与 bind/create/update/sync 自动流程。

## 3. bind 流程（strategy_ops）
- 入口：`strategy_ops/bind_ops.py::run_bind()`
- 入口状态：`S00`

### 核心绑定判定
- `phase_core_state()` 内触发 `E10`。
- 有绑定记录：根据本端/对端有效性进入 `S01/S03/S04`。
- 无绑定记录：进入 `S02`。

### 依赖检查
- `phase_dependency_check()` -> `check_dependencies_for_nodes()` 内触发 `E15`。
- `data_present=false` -> `S03`。
- `dep_satisfied=false` -> `S05`。
- `depend_fields` 为空 -> `S04`（无法自动处理，阻断后续自动创建）。

### 自动绑定
- `phase_auto_binding()` 内触发 `E16`。
- `ONE_TO_ONE` -> `S01`；`AMBIGUOUS/KEY_MISSING/DATA_MISSING` -> `S04`；`KEY_ID_RESOLVE_FAIL` -> `S05`。
- `ZERO + create_enabled=true` -> `S13`（`PEER_CREATING/NONE/PENDING`，待对侧创建中间态）；`ZERO + create_enabled=false` -> `S04`。
- `auto_bind=false` 时，`E16` 直接落 `S04`（自动绑定未启用），不会进入自动创建路径。

## 4. create/update 准备

### create
- 入口：`strategy_ops/create_ops.py::run_create()`
- 运行时事件：`E20`（`E21/E22` 为配置保留分支，当前 strategy 未接入自动执行）
- 运行时入口状态：`S13`
- 说明：`E16` 负责识别“需自动创建”并将源节点置为 `S13`；业务代码执行对侧 spawn+bind 后，再通过 `E20` 将 `S13` 提交到 `S01`（成功）；若 spawn 失败，`E20` 不迁移并保留在 `S13` 等待人工/重试。
- `data_id` 约束：
	- `S13/S01` 要求源节点 `data_id` 存在（`data_id_exist=true`）。
	- 新创建目标节点在 `S06/S10C` 阶段允许 `data_id` 为空；`CREATE SUCCESS`（`S11C`）必须携带非空 `result_data_id`，否则事件直接报错。

### update
- 入口：`strategy_ops/update_ops.py::run_update()`
- 事件：`E30`
- 入口状态（source 节点）：`S01`
- `target_has_data_id=false` 或 `update_id_resolve_ok=false` -> `S08`
- `has_diff=true` -> `S07`

### post-create 归并
- 入口：`pipeline/full_sync_pipeline.py::normalize_create_success_to_update_entry()`
- 事件：`E41`
- 语义：`CREATE` 执行成功后，节点先落 `S11C`（SUCCESS），再归并 `S11C -> S01`，作为同轮 update 起点。

## 5. 执行回写（datasource）
- 入口：`datasource/datasource.py`
- 事件：`E40C_START/E40C`、`E40U_START/E40U`、`E40D_START/E40D`
- 当前约束：delete stage 尚未接入 strategy 主流程；若运行时检测到 `S09/DELETE` 节点，DataSource 会显式抛错阻断（避免散落删除执行）。
- 执行准备入口状态：
	- `CREATE`: `S06`
	- `UPDATE`: `S07`
	- `DELETE`: `S09`
- `S10` 为执行中状态（由本轮执行产生），执行流转固定为“入口 -> S10 -> 终态”，不再出现入口直接到成功/失败。
- 结果：
	- `SUCCESS -> S11C | S11U | S11D`（按动作）
	- `FAILED -> S12C | S12U | S12D`（按动作）
	- `IN_PROGRESS -> S10C | S10U | S10D`（按动作，支持异步 polling 闭环）
	- `SKIPPED + action=UPDATE -> S14`

## 6. 各阶段入口状态总表

| 阶段 | 入口状态 | 说明 |
|---|---|---|
| Reset(E01) | `S16` | create_zombie -> `S15`；其余 -> `S00` |
| Bind(E10/E15/E16) | `S00` | bind 三段统一从 `S00` 进入 |
| Create Prepare(E20/E21/E22) | `S02` / `S13` | 配置入口含 `S02/S13`；当前 strategy 运行时只处理 `S13` 的 `E20` 提交，`S02` 分支保留未接入 |
| Post-create Ready(E41) | `S11C` | `CREATE` 成功节点归并到 `S01`，进入同轮 update |
| Update Prepare(E30) | `S01` | 仅从 NORMAL 继续判定（create 成功先经 E41 归并） |
| Sync Execute(E40C/U/D) | `S06` / `S07` / `S09` / `S10C` / `S10U` / `S10D` | 执行与回写：按动作分事件；入口先进入 `S10*`，再由 `S10*` 进入 `S11*`/`S12*`；轮询返回 `IN_PROGRESS` 时只记录任务进度，不新增状态迁移；仅 UPDATE 可走 `S14` |
| 隔离（人工） | `S17` | 不参与自动阶段，仅人工处理 |

## 7. 不变量保障
- 每次事件落地后，`engine/events.py::_apply_decision()` 会执行 `runtime.check_invariants()`。
- 违反 invariant 直接抛错，终止当前路径。
