Files
ecm_sync_system/docs/archive/from_root_docs/状态机形式化规范.md
T
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

29 KiB
Raw Blame History

状态机形式化规范

本文档用 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 的当前行为

    • 自动绑定分支 E08fS02 -> 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()


📡 阶段 3Sync(物理同步)

状态转换图:

    [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
节点生命周期(叙事) 节点生命周期状态转移