29 KiB
状态机形式化规范
本文档用 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 为准,用于修正文档中易混淆点:
-
E12a 为什么从 S01 出发?
S01只约束源节点是 NORMAL/NONE/PENDING 且有data_id。E12a的 guard 是target_has_data_id=false,检查对象是目标节点。- 所以“源节点在 S01”与“E12a 触发前置失败”并不冲突。
-
S01 会不会触发创建?
- 当前配置中,create 的入口状态是
S02(MISSING),不是S01。 S01进入的是 update_prepare(E12/E13/E14)。
- 当前配置中,create 的入口状态是
-
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。
- 自动绑定分支
-
旧章节中的事件编号偏差
- 本文后续仍有部分“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 执行阶段组织。每个阶段包含:
- 状态转换图:状态(R 编号)为节点,事件(E 编号)为有向边
- 转换表:每个事件的精确规格——起始状态、判断条件、结束状态、副作用
🔄 阶段 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 |
| 节点生命周期(叙事) | 节点生命周期状态转移 |