# 状态机形式化规范 本文档用 **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) |