first commit

This commit is contained in:
strepsiades
2026-03-09 16:31:42 +08:00
commit 4a9c444b10
486 changed files with 52479 additions and 0 deletions
@@ -0,0 +1,498 @@
# 状态机形式化规范
本文档用 **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_prepareE12/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()`
---
#### 🔗 阶段 1Bind
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()`
---
#### ⚡ 阶段 2Create / 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) |