# 状态机形式化规范

本文档用 **Guard-Event-Action (GEA)** 形式严格定义节点状态机。  
所有转换以代码实现为准，模糊之处在文档中显式标注。

> **代码基线**
> - 枚举: `sync_system_new/common/types.py`
> - 决策引擎: `sync_system_new/sync_system/decision.py`
> - 重置策略: `sync_system_new/common/state_reset.py`
> - 同步策略: `sync_system_new/sync_system/strategy.py`
> - 物理同步: `sync_system_new/datasource/datasource.py`

> **配置基线（状态机真源）**
> - `sync_system_new/state_machine/node_state_machine.yaml`

### 2026-02 对齐说明（重构前必读）

以下语义以 `node_state_machine.yaml` 为准，用于修正文档中易混淆点：

1. **E12a 为什么从 S01 出发？**
    - `S01` 只约束**源节点**是 NORMAL/NONE/PENDING 且有 `data_id`。
    - `E12a` 的 guard 是 `target_has_data_id=false`，检查对象是**目标节点**。
    - 所以“源节点在 S01”与“E12a 触发前置失败”并不冲突。

2. **S01 会不会触发创建？**
    - 当前配置中，create 的入口状态是 `S02(MISSING)`，不是 `S01`。
    - `S01` 进入的是 update_prepare（E12/E13/E14）。

3. **N:0 + create_enabled=true 的当前行为**
    - 自动绑定分支 `E08f`：`S02 -> S01`，并 `emit spawn_target_node_state: S06`。
    - create_prepare 分支 `E09b` 也可在检查成功后 `S02 -> S01 + spawn S06`。
    - 即：源节点不会停在“等待创建结果”的专用状态；创建执行结果体现在目标创建节点链路 `S06 -> S10/S11/S12/S13`。

4. **旧章节中的事件编号偏差**
    - 本文后续仍有部分“E11a/E11b/…”旧编号描述，已与当前 YAML 不一致。
    - 进行代码重构时，优先依据 YAML 的 `E08/E09/E10/E12/E13/E14/E15..E20`。

---

## §1 状态空间定义

### §1.1 状态维度

节点携带 **三个独立的状态字段** 和 **两个辅助字段**：

| 字段 | 枚举 | 值域 | 语义 |
|------|------|------|------|
| **B** – `binding_status` | `BindingStatus` | `UNCHECKED` · `NORMAL` · `MISSING` · `ABNORMAL` · `WARNING` · `DEPENDENCY_ERROR` | 绑定健康度 |
| **A** – `action` | `SyncAction` | `NONE` · `CREATE` · `UPDATE` · `DELETE` | 待执行动作 |
| **S** – `status` | `SyncStatus` | `PENDING` · `IN_PROGRESS` · `SUCCESS` · `FAILED` · `SKIPPED` · `PRECONDITION_FAILED` | 执行结果 |

辅助字段（影响 Guard 但不是状态维度）：

| 字段 | 类型 | 语义 |
|------|------|------|
| `data_id` | `str` | 业务主键（空串 `""` = 尚未从远程获取） |
| `error` | `str \| None` | 最近一次错误信息 |

理论组合 = 6 × 4 × 6 = **144**，实际可达 ≈ **23** 种。

### §1.2 可达状态枚举

以下列出所有在正常流程中可以到达的 `(binding_status, action, status)` 三元组。

| # | binding_status | action | status | 语义 | 产生阶段 |
|---|---|---|---|------|---------|
| R1 | UNCHECKED | NONE | PENDING | 首次加载 | 数据加载 |
| R2 | UNCHECKED | NONE | SUCCESS | 从持久化恢复(上轮成功)后重置 | 阶段1/2重置 |
| R3 | UNCHECKED | NONE | FAILED | 从持久化恢复(上轮失败)后重置 | 阶段1/2重置 |
| R4 | UNCHECKED | NONE | SKIPPED | 从持久化恢复(上轮跳过)后重置 | 阶段1/2重置 |
| R5 | UNCHECKED | NONE | PRECONDITION_FAILED | 从持久化恢复(上轮前置失败)后重置 | 阶段1/2重置 |
| R6 | NORMAL | NONE | PENDING | 绑定正常，无需操作(首次) | bind.p1 |
| R7 | NORMAL | NONE | SUCCESS | 绑定正常，无需操作(历史成功) | bind.p1 |
| R8 | NORMAL | NONE | FAILED | 绑定正常但上次失败，被 FAILED 保护 | bind.p1 |
| R9 | MISSING | NONE | PENDING | 孤儿，等待自动绑定或创建(首次) | bind.p1 |
| R10 | MISSING | NONE | SUCCESS | 孤儿(历史成功但绑定丢失?) | bind.p1 |
| R11 | ABNORMAL | NONE | * | 数据损坏，需人工 | bind.p1 |
| R12 | WARNING | NONE | * | 多候选/对方损坏，需人工 | bind.p1/p3 |
| R13 | DEPENDENCY_ERROR | NONE | PENDING | 依赖不满足 | bind.p2/p3/create |
| R14 | NORMAL | CREATE | PENDING | 准备创建(目标节点) | create |
| R15 | NORMAL | CREATE | IN_PROGRESS | 异步创建进行中 | sync |
| R16 | NORMAL | CREATE | SUCCESS | 创建成功 | sync |
| R17 | NORMAL | CREATE | FAILED | 创建失败 | sync |
| R18 | NORMAL | UPDATE | PENDING | 准备更新(目标节点) | update |
| R19 | NORMAL | UPDATE | IN_PROGRESS | 异步更新进行中 | sync |
| R20 | NORMAL | UPDATE | SUCCESS | 更新成功 | sync |
| R21 | NORMAL | UPDATE | FAILED | 更新失败 | sync |
| R22 | NORMAL | UPDATE | PRECONDITION_FAILED | 更新前置条件失败 | update |
| R23 | NORMAL | CREATE | SKIPPED | 创建被 Handler 跳过 | sync |

> **不可达组合（示例）**：
> - `(MISSING, UPDATE, *)` — MISSING 节点不可能被安排 UPDATE
> - `(ABNORMAL, CREATE, *)` — ABNORMAL 节点不可能被安排 CREATE
> - `(*, DELETE, *)` — DELETE 尚未实现
> - `(UNCHECKED, CREATE, *)` — UNCHECKED 是瞬态，在 bind 之前不会有 CREATE
> - `(DEPENDENCY_ERROR, UPDATE, *)` — DEPENDENCY_ERROR 节点不可能进入 update 流程

### §1.3 辅助字段约束

| 对应状态 | binding_status | action | status | data_id 约束 | error 约束 |
|---------|---|---|---|-------------|-----------|
| R14 | NORMAL | CREATE | PENDING | `""` (待远程返回) | None |
| R16 | NORMAL | CREATE | SUCCESS | 非空 (远程回填) | None |
| R17 | NORMAL | CREATE | FAILED | `""` (未回填) | 非空 |
| R22 | NORMAL | UPDATE | PRECONDITION_FAILED | * | 非空 |
| R13 | DEPENDENCY_ERROR | NONE | PENDING | * | 非空 |

---

## §2 不变量 (Invariants)

以下约束在**任何事件执行后**都必须成立。违反即为 Bug。

```
INV-1  B = UNCHECKED  ⟹  A = NONE
       (UNCHECKED 是瞬态，在 bind 之前不允许有动作)

INV-2  B ∈ {ABNORMAL, WARNING, DEPENDENCY_ERROR}  ⟹  A = NONE
       (阻断态节点不允许执行任何同步动作)

INV-3  B = MISSING  ⟹  A ∈ {NONE, CREATE}
       (MISSING 节点只能等待创建或保持不动)

INV-4  A = CREATE  ⟹  B = NORMAL
       (CREATE 动作只出现在目标节点上，其 binding_status 已被设为 NORMAL)

INV-5  A = UPDATE  ⟹  B = NORMAL
       (UPDATE 动作只对 NORMAL 节点执行)

INV-6  S = IN_PROGRESS  ⟹  A ∈ {CREATE, UPDATE, DELETE}
       (只有提交过 API 的节点才可能处于 IN_PROGRESS)

INV-7  S = PRECONDITION_FAILED  ⟹  A = UPDATE
       (PRECONDITION_FAILED 只在 update 阶段产生)

INV-8  A = CREATE ∧ S = SUCCESS  ⟹  data_id ≠ ""
       (创建成功必须回填 data_id)

INV-9  S = FAILED  ⟹  error ≠ None
       (失败必须有错误信息)

INV-10 B = DEPENDENCY_ERROR  ⟹  error ≠ None
       (依赖阻断必须有错误信息)
```

---

## §3 事件与转换

### §3.1 事件总览图

```
                    ┌─────────────────────────────────────────────────────┐
                    │            节点生命周期事件流                         │
                    └─────────────────────────────────────────────────────┘

  持久化恢复                 策略层 (Strategy)                      执行层 (DataSource)
 ═══════════         ══════════════════════════════════        ══════════════════════════
                     ┌──────────────────────────────┐
  ┌──────┐           │         bind()               │         ┌──────────────────────┐
  │E01   │──────────▶│  E04 ─┐                      │         │   sync_all()         │
  │RESET1│           │  E05  ├─ phase1: core_state   │         │                      │
  └──┬───┘           │  E06 ─┘                      │         │  E12  SYNC_OK        │
     │               │  E07 ── phase2: dep_check     │    ┌───▶│  E13  SYNC_FAIL      │
  ┌──▼───┐           │  E08 ─┐                      │    │    │  E14  SYNC_ASYNC     │
  │E02   │           │  E09  ├─ phase3: auto_bind    │    │    │  E15  SYNC_TIMEOUT   │
  │ZOMBIE│           │  E10 ─┘                      │    │    │  E16  HANDLER_SKIP   │
  └──┬───┘           └──────────────────────────────┘    │    │  E17  HANDLER_DOWN   │
     │               ┌──────────────────────────────┐    │    └──────────────────────┘
  ┌──▼───┐           │       create() / update()    │    │
  │E03   │──────────▶│  E11a CREATE_OK              │────┘
  │RESET2│           │  E11b CREATE_DEP_FAIL        │
  └──────┘           │  E11c UPDATE_DIFF            │
                     │  E11d UPDATE_PRECOND_FAIL    │
                     │  E11e UPDATE_SKIP_FAILED     │
                     └──────────────────────────────┘
```


### §3.2 状态转换详解

本节按 Pipeline 执行阶段组织。每个阶段包含：
1. **状态转换图**：状态（R 编号）为节点，事件（E 编号）为有向边
2. **转换表**：每个事件的精确规格——起始状态、判断条件、结束状态、副作用

---

#### 🔄 阶段 0：重置

**状态转换图：**

```
    ╔═══════════════════════════════════════╗
    ║  任意持久化终态 (R6 ~ R23)             ║
    ╚═══════════════════╤═══════════════════╝
                        │ E01
                        ▼
    ┌────────────────────────────────────────┐     E02
    │  瞬态: UNCHECKED                       │────────────▶ [节点删除 + 解除绑定]
    │  (action 保留, status 保留, error=None) │
    └────────────────────┬──────────────────┘
                         │ E03 (存活节点)
                         ▼
    ╔══════════════════════════════════════════════════════════════╗
    ║ [R1]  UNCHECKED,  NONE,  PENDING              ← 首次加载    ║
    ║ [R2]  UNCHECKED,  NONE,  SUCCESS              ← 上轮成功    ║
    ║ [R3]  UNCHECKED,  NONE,  FAILED               ← 上轮失败    ║
    ║ [R4]  UNCHECKED,  NONE,  SKIPPED              ← 上轮跳过    ║
    ║ [R5]  UNCHECKED,  NONE,  PRECONDITION_FAILED  ← 上轮前置    ║
    ╚══════════════════════════════════════════════════════════════╝
```

**转换表：**

| 事件 | 起始状态 | 判断条件 (Guard) | 结束状态 | 副作用 |
|------|---------|-----------------|---------|--------|
| **E01**: PHASE1_RESET | R6~R23 (任意持久化终态) | 节点从持久化加载 | 瞬态 (UNCHECKED, action 保留, status 保留) | error → None; data/origin_data → None |
| **E02**: ZOMBIE_KILL | 瞬态 | action = CREATE ∧ (status = FAILED ∨ data_id = "") | **节点删除** | 解除绑定关系; 对 local/remote 两侧都扫描 |
| **E03**: ACTION_RESET | 瞬态 (存活节点) | E02 完成后所有存活节点 | R1 / R2 / R3 / R4 / R5 (按原 status 分) | action → NONE |

> **代码**: `NodeStateReset.get_phase1_reset_defaults()` · `cleanup_create_failed_zombies()` · `reset_action_for_nodes()`

---

#### 🔗 阶段 1：Bind

Bind 由三个子阶段串行执行。后一阶段只处理前一阶段的遗留 MISSING 节点。

**状态转换图：**

```
    [R1~R5]  UNCHECKED, NONE, *
        │
        │  ══════════ Phase 1: 核心状态判定 ══════════
        │
        ├── E04 (有绑定, 双方数据 VALID) ───────────▶ [R6/R7/R8] NORMAL ──────▶ 出口 ✅
        ├── E04 (有绑定, 自身数据缺失/损坏) ────────▶ [R11] ABNORMAL ─────────▶ 出口 🔒
        ├── E04 (有绑定, 对方数据缺失/损坏) ────────▶ [R12] WARNING ──────────▶ 出口 🔒
        │
        └── E05 (无绑定记录) ───────────────────────▶ [R9] MISSING
                                                          │
            ══════════ Phase 2: 依赖检查 ══════════      │
                                                          │
            ├── E06 (data = None) ──────────────────────▶ [R11] ABNORMAL ──────▶ 出口 🔒
            ├── E07 (依赖节点不满足) ───────────────────▶ [R13] DEP_ERROR ─────▶ 出口 🔒
            │   (无依赖配置 或 依赖全部满足 → 继续 ▼)
            │
            ══════════ Phase 3: 自动绑定 ══════════
            │
            ├── E08 (auto_bind 关闭) ──────────────────▶ [R12] WARNING ────────▶ 出口 🔒
            ├── E09 (去噪后 1:1 匹配成功) ─────────────▶ [R6] NORMAL ─────────▶ 出口 ✅
            ├── E10a (去噪后非 1:1, 多候选) ───────────▶ [R12] WARNING ────────▶ 出口 🔒
            ├── E10b (业务键字段缺失) ─────────────────▶ [R12] WARNING ────────▶ 出口 🔒
            ├── E10c (业务键中 ID 字段解析失败) ───────▶ [R13] DEP_ERROR ─────▶ 出口 🔒
            │
                └── (1:0 无远程匹配)
                    ├── create_enabled = True  → [R9]  MISSING ──────────────▶ 进入 create
                    └── create_enabled = False → [R12] WARNING ─────────────▶ 出口 🔒
```

**转换表：**

| 事件 | 起始状态 | 判断条件 (Guard) | 结束状态 | 副作用 |
|------|---------|-----------------|---------|--------|
| **E04**: BIND_PAIRED | R1~R5 | 有绑定记录 ∧ 双方数据 VALID | R6 (PENDING) / R7 (SUCCESS) / R8 (FAILED) — status 保持 | 远程节点同步设置 binding_status |
| **E04**: BIND_PAIRED (自身异常) | R1~R5 | 有绑定记录 ∧ 本方数据缺失或损坏 | R11 (ABNORMAL) | 对方节点设为 WARNING |
| **E04**: BIND_PAIRED (对方异常) | R1~R5 | 有绑定记录 ∧ 对方数据缺失或损坏 | R12 (WARNING) | 对方节点设为 ABNORMAL |
| **E05**: BIND_ORPHAN | R1~R5 | 无绑定记录 | R9 (MISSING, NONE, status 保持) | — |
| **E06**: DATA_NULL | R9 | binding_status = MISSING ∧ data = None | R11 (ABNORMAL) | error = "数据损坏: 节点数据为空或不存在" |
| **E07**: DEP_CHECK_FAIL | R9 | depend_fields 配置非空 ∧ ∃依赖节点: 不存在 ∨ status ≠ NORMAL ∨ data_id 为空 | R13 (DEPENDENCY_ERROR) | error = "依赖项不满足: ..."; depend_ids 填充 |
| **E08**: AUTOBIND_DISABLED | R9 | auto_bind = False 或 auto_bind_fields 为空 | R12 (WARNING) | — |
| **E09**: AUTOBIND_OK | R9 | 去噪后 local:remote = 1:1 ∧ 双方都是 MISSING | R6 (NORMAL) | 建立绑定记录; 双方都设为 NORMAL |
| **E10a**: AUTOBIND_AMBIGUOUS | R9 | 去噪后非 1:1 (多候选) | R12 (WARNING) | error += "存在多个候选节点" |
| **E10b**: AUTOBIND_KEY_MISSING | R9 | auto_bind_fields 中有字段不存在于 data | R12 (WARNING) | error += "缺少业务键字段: ..." |
| **E10c**: AUTOBIND_ID_FAIL | R9 | 业务键包含 ID 字段 ∧ ID 解析失败 | R13 (DEPENDENCY_ERROR) | error = "业务键中的ID字段解析失败: ..." |
| **E10d**: ORPHAN_CREATE_DISABLED | R9 | 去噪后 1:0 ∧ create_enabled = False | R12 (WARNING) | error += "孤儿且不允许自动创建" |

> **E04 核心状态矩阵** (完整查表):
>
> | | 远程: VALID | 远程: ABSENT | 远程: INVALID |
> |---|---|---|---|
> | **本地: VALID** | 本:NORMAL 远:NORMAL | 本:WARNING 远:ABNORMAL | 本:WARNING 远:ABNORMAL |
> | **本地: ABSENT** | 本:ABNORMAL 远:WARNING | 本:ABNORMAL 远:ABNORMAL | — |
> | **本地: INVALID** | 本:ABNORMAL 远:WARNING | — | 本:ABNORMAL 远:ABNORMAL |
>
> 不在矩阵中的组合 → 默认 (ABNORMAL, ABNORMAL)

> **代码**: `SyncDecisionEngine.determine_core_status()` · `strategy_ops/bind_ops.py::phase_core_state()` · `strategy_ops/bind_ops.py::check_dependencies_for_nodes()` · `strategy_ops/bind_ops.py::phase_auto_binding()`

---

#### ⚡ 阶段 2：Create / Update

**状态转换图：**

```
    ═══════════════ Create ═══════════════

    [R9]  MISSING, NONE, PENDING  (源节点, data ≠ None)
        │
        ├── E11a (ID 解析成功) ────▶  源节点: [R9] → [R6] NORMAL
        │                            新建目标节点: → [R14] NORMAL, CREATE, PENDING
        │                            + 建立绑定记录, 目标 data_id = ""
        │
        └── E11b (ID 解析失败) ────▶  源节点: [R9] → [R13] DEPENDENCY_ERROR


    ═══════════════ Update ═══════════════

    [R6/R7]  NORMAL, NONE, PENDING/SUCCESS  (源节点, status ≠ FAILED)
        │
        ├── E11c (差异检测通过) ────▶  目标节点 → [R18] NORMAL, UPDATE, PENDING
        ├── E11d.1 (目标 data_id="") ▶  目标节点 → [R22] status = PRECONDITION_FAILED
        ├── E11d.2 (ID 解析失败) ───▶  目标节点 → [R22] action = UPDATE, status = PRECOND_FAIL
        └── (无差异) ──────────────▶  保持不变


    ═══════════════ FAILED 保护 ═══════════════

    [R8]  NORMAL, NONE, FAILED  (源节点)
        │
        └── E11e ──────────────────▶  跳过，保持 [R8] 不变
```

**转换表：**

| 事件 | 起始状态 (源节点) | 判断条件 (Guard) | 结束状态 | 副作用 |
|------|---------|-----------------|---------|--------|
| **E11a**: CREATE_OK | R9 (MISSING) | data ≠ None ∧ IDResolver.resolve_and_validate() = OK | 源 → R6 (NORMAL); **新建**目标 → R14 | 建立绑定; 新节点 data = 已替换 ID 的副本, data_id = "" |
| **E11b**: CREATE_DEP_FAIL | R9 (MISSING) | data ≠ None ∧ IDResolver.resolve_and_validate() = FAIL | R13 (DEPENDENCY_ERROR) | error = "创建时依赖 ID 未解析: ..." |
| **E11c**: UPDATE_DIFF | R6/R7 (NORMAL, status≠FAILED) | 目标存在 ∧ 目标 data_id ≠ "" ∧ ID 解析 OK ∧ _needs_update() = True | 目标 → R18 (UPDATE, PENDING) | 目标: data 替换, error = None |
| **E11d**: UPDATE_PRECOND_FAIL | R6/R7 (NORMAL, status≠FAILED) | 目标 data_id = "" (d1) 或 ID 解析失败 (d2) | 目标 → R22 (UPDATE, PRECONDITION_FAILED) | error = "目标缺少 data_id" 或 "依赖 ID 未解析" |
| **E11e**: UPDATE_SKIP_FAILED | R8 (NORMAL, FAILED) | status = FAILED | R8 不变 | 保护上次的 error 和 FAILED 状态，不做任何修改 |

> **代码**: `strategy_ops/create_ops.py::add_create_action()` · `strategy_ops/update_ops.py::add_update_action()`

---

#### 📡 阶段 3：Sync（物理同步）

**状态转换图：**

```
    [R14] NORMAL, CREATE, PENDING       [R18] NORMAL, UPDATE, PENDING
        │                                   │
        └───────────────┬───────────────────┘
                        │
                        ├── E12 (API 成功) ──────▶ [R16] CREATE,SUCCESS / [R20] UPDATE,SUCCESS
                        ├── E13 (API 失败) ──────▶ [R17] CREATE,FAILED  / [R21] UPDATE,FAILED
                        ├── E16 (Handler 跳过) ──▶ [R23] CREATE, SKIPPED
                        ├── E17 (Handler 降级) ──▶ [R6]  NORMAL, NONE, PENDING  (仅 UPDATE)
                        │
                        └── E14 (异步提交) ──────▶ [R15] CREATE,IN_PROGRESS / [R19] UPDATE,INP
                                                       │
                                                       ├── (轮询成功) ──▶ [R16] / [R20] SUCCESS
                                                       └── E15 (超时)  ──▶ [R17] / [R21] FAILED
```

**转换表：**

| 事件 | 起始状态 | 判断条件 (Guard) | 结束状态 | 副作用 |
|------|---------|-----------------|---------|--------|
| **E12**: SYNC_OK | R14 或 R18 | Handler API 返回 SUCCESS | R16 (CREATE) 或 R20 (UPDATE) | error → None; data_id 回填 (CREATE); origin_data 更新 (UPDATE) |
| **E13**: SYNC_FAIL | R14 或 R18 | Handler API 返回 FAILED | R17 (CREATE) 或 R21 (UPDATE) | error = Handler 返回的错误信息 |
| **E14**: SYNC_ASYNC | R14 或 R18 | Handler API 返回 IN_PROGRESS | R15 (CREATE) 或 R19 (UPDATE) | 记录 task_id, 进入轮询队列 |
| **E15**: SYNC_TIMEOUT | R15 或 R19 | 轮询次数 > max_retries | R17 (CREATE) 或 R21 (UPDATE) | error = "Async task timeout after N seconds" |
| **E16**: HANDLER_SKIP | R14 或 R18 | Handler API 返回 SKIPPED | R23 (CREATE,SKIPPED) | error → None |
| **E17**: HANDLER_DOWNGRADE | R18 (仅 UPDATE) | Handler 运行时发现无实际变更字段 | R6 (NORMAL, NONE, PENDING) | action: UPDATE → NONE; 不提交 API; 统计列从 update 移入 none |

> **代码**: `BaseDataSource._handle_task_result()` · `_poll_async_tasks()` · `domain/*/api_handler.py`

---

## §4 终态分类与死胡同

### §4.1 终态分类

| 分类 | 状态 | (binding_status, action, status) | 语义 | 下一轮行为 |
|------|------|------|------|---------|
| ✅ 成功 | R16 | (NORMAL, CREATE, SUCCESS) | 创建成功 | →R2→bind→R7→update 检查 |
| ✅ 成功 | R20 | (NORMAL, UPDATE, SUCCESS) | 更新成功 | →R2→bind→R7→update 检查 |
| ❌ 失败 | R17 | (NORMAL, CREATE, FAILED) | 创建失败 | E02 zombie 判定→删除节点→对方回到 R9 重建 |
| ❌ 失败 | R21 | (NORMAL, UPDATE, FAILED) | 更新失败 | →R3→bind→R8→FAILED 保护(E11e)→保持 |
| 🔒 阻断 | R11 | (ABNORMAL, NONE, \*) | 数据损坏 | 每轮重新 bind 检查; 需人工修复数据 |
| 🔒 阻断 | R12 | (WARNING, NONE, \*) | 多候选/对方异常 | 每轮重新 bind 检查; 需人工绑定或修复 |
| 🔒 阻断 | R13 | (DEPENDENCY_ERROR, NONE, PENDING) | 依赖不满足 | 每轮重新检查依赖; 依赖修复后自动恢复 |
| ⏭ 跳过 | R23 | (NORMAL, CREATE, SKIPPED) | Handler 跳过创建 | →R4→bind→R6→update 检查（无绑定则再 create） |
| 🔄 异步 | R15 | (NORMAL, CREATE, IN\_PROGRESS) | 创建异步中 | 轮询直到 SUCCESS 或超时→FAILED |
| 🔄 异步 | R19 | (NORMAL, UPDATE, IN\_PROGRESS) | 更新异步中 | 轮询直到 SUCCESS 或超时→FAILED |
| 🔄 前置失败 | R22 | (NORMAL, UPDATE, PRECONDITION\_FAILED) | 前置条件不满足 | →R5→bind→R6/R7→update 重试 |

### §4.2 恢复路径

```
R17 (CREATE, FAILED):
    持久化 → 下轮 E01 → 瞬态(UNCHECKED, CREATE, FAILED)
    → E02: action=CREATE ∧ (status=FAILED ∨ data_id="") → 满足 zombie 条件
    → 删除节点 + 解除绑定
    → 对方节点回到 R9 (MISSING) → 重新进入 create 流程

R21 (UPDATE, FAILED):
    持久化 → 下轮 E01 → 瞬态(UNCHECKED, UPDATE, FAILED)
    → E02: action=UPDATE ≠ CREATE → 非 zombie，存活
    → E03: action → NONE → R3 (UNCHECKED, NONE, FAILED)
    → bind → R8 (NORMAL, NONE, FAILED)
    → E11e: FAILED 保护 → 跳过 update → 保持 R8
    ⚠ 需人工清除 FAILED 才能恢复

R22 (UPDATE, PRECONDITION_FAILED):
    持久化 → 下轮 E01 → 瞬态(UNCHECKED, UPDATE, PRECONDITION_FAILED)
    → E02: action=UPDATE ≠ CREATE → 非 zombie，存活
    → E03: action → NONE → R5 (UNCHECKED, NONE, PRECONDITION_FAILED)
    → bind → R6/R7 (NORMAL) → update 流程重试

R11 (ABNORMAL):
    每轮 E01 重置 → bind 重新检查
    → 若数据被人工修复 → E04 → R6/R7/R8 (NORMAL)
    → 若数据仍损坏 → 再次 R11

R12 (WARNING):
    每轮 E01 重置 → bind 重新检查
    → 若人工建立了绑定 → E04 → R6/R7 (NORMAL)
    → 若对方数据被修复 → E04 → R6/R7 (NORMAL)

R13 (DEPENDENCY_ERROR):
    每轮 E01 重置 → bind 重新检查依赖
    → 依赖节点达到 NORMAL 且 data_id 非空后
    → E07 通过 → 继续 auto_bind 或 create 流程
```

### §4.3 FAILED 保护机制详解

FAILED 保护确保失败节点不会被后续流程静默覆盖，保留 error 信息供人工调查。

```
进入条件:
    节点经过 bind 后为 R8 (NORMAL, NONE, FAILED)
    即: 绑定关系正常，但上一轮执行失败

保护行为:
    `strategy_ops/update_ops.py::add_update_action()` 中:
        if source.status == SyncStatus.FAILED:
            skip  (→ 事件 E11e)
    结果: R8 原样保持 → 持久化 → 下一轮依然 R8

解除条件 (需人工介入):
    1. 清除 error 字段 (→ None)
    2. 将 status 改为 PENDING 或 SUCCESS
    3. 下轮 bind 后 → R6 或 R7 → 正常进入 update 流程
```

---

## §5 完备性分析

### §5.1 状态 × 事件矩阵

行 = 当前状态，列 = 可能触发的事件。`→Rx` = 转换到目标状态，`—` = 不可能触发。

| 状态 | E01 | E02 | E03 | E04/E05 | E06~E10 | E11a/b | E11c/d/e | E12~E17 |
|------|-----|-----|-----|---------|---------|--------|----------|---------|
| R1~R5 | — | — | — | →R6~R13 | — | — | — | — |
| R6/R7 | →瞬态 | — | — | — | — | — | →R18/R22 | — |
| R8 | →瞬态 | — | — | — | — | — | →R8(skip) | — |
| R9 | →瞬态 | — | — | — | →R6/R11~R13 | →R6+R14/R13 | — | — |
| R11~R13 | →瞬态 | — | — | — | — | — | — | — |
| R14 | →瞬态 | — | — | — | — | — | — | →R15~R17/R23 |
| R15 | →瞬态 | — | — | — | — | — | — | →R16/R17 |
| R18 | →瞬态 | — | — | — | — | — | — | →R6/R19~R21/R23 |
| R19 | →瞬态 | — | — | — | — | — | — | →R20/R21 |
| R16/R17 | →瞬态 | — | — | — | — | — | — | — |
| R20~R23 | →瞬态 | — | — | — | — | — | — | — |

> **读表方式**: 找到当前状态行和事件列组的交叉点，即可得到目标状态。`—` 表示该状态不可能触发该事件。

### §5.2 已知 GAP

| GAP | 描述 | 风险 | 建议 |
|-----|------|------|------|
| GAP-1 | `set_binding_status()` / `set_action()` / `set_status()` 是纯 setter，不验证转换合法性 | 中 | 加入前置断言：检查 (当前状态, 新状态) 是否匹配某条已知转换 |
| GAP-2 | INV-1 ~ INV-10 未在代码中 assert | 中 | 每次 setter 调用后或阶段结束时断言所有不变量 |
| GAP-3 | R22 (PRECONDITION\_FAILED) 节点下一轮行为路径需验证 | 低 | 已补充恢复路径: R22→E01→E03→R5→bind→R6/R7→update 重试 |
| GAP-4 | Handler 降级 (E17) 后 status 残留为 PENDING 但 action 已清零 | 低 | 可考虑降级时同时重置 status 为 SUCCESS（当前保持 PENDING） |
| GAP-5 | 物理同步后缺少整体一致性检查 (post-sync invariant check) | 低 | 在 `sync_all()` 结束后遍历所有节点验证 INV-1 ~ INV-10 |

---

## §6 引用

| 文档 | 路径 |
|------|------|
| 枚举定义 | `sync_system_new/common/types.py` |
| 决策引擎 | `sync_system_new/sync_system/decision.py` |
| 重置策略 | `sync_system_new/common/state_reset.py` |
| 同步策略 | `sync_system_new/sync_system/strategy.py` |
| 物理同步 | `sync_system_new/datasource/datasource.py` |
| ID 解析 | `sync_system_new/sync_system/resolve_ids.py` |
| 节点生命周期(叙事) | [节点生命周期状态转移](./节点生命周期状态转移.md) |
