# 节点生命周期状态转移 (Node Lifecycle State Transitions)

本文档从一个**单节点视角**出发，完整描述一个 `SyncNode` 从诞生到持久化的全部状态变化。

> **代码映射**：
> - 持久化重置：`sync_system_new/common/state_reset.py` → `NodeStateReset`
> - 绑定判定：`sync_system_new/sync_system/decision.py` → `SyncDecisionEngine`
> - 策略执行：`sync_system_new/sync_system/strategy.py` → `DefaultSyncStrategy`
> - ID 解析：`sync_system_new/sync_system/resolve_ids.py` → `IDResolver`
> - 物理同步：`sync_system_new/datasource/datasource.py` → `BaseDataSource`
> - Handler 降级：`sync_system_new/domain/*/api_handler.py`

---

## 状态字段一览

| 字段 | 类型 | 含义 | 谁负责设置 |
|------|------|------|-----------|
| `binding_status` | BindingStatus | 绑定健康状态 | Strategy.bind() |
| `action` | SyncAction | 待执行动作 | Strategy.create() / update() |
| `status` | SyncStatus | 执行结果状态 | DataSource.sync_all() |
| `data_id` | str | 业务主键ID | 数据加载 / DataSource 回填 |
| `error` | str \| None | 错误信息 | 各阶段 |

---

## 完整生命周期

### 第一次运行（无持久化数据）

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                          节点尚不存在                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                            数据源加载 (Stage 1)
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 节点诞生                                                                     │
│                                                                             │
│ binding_status = UNCHECKED     ← Collection 创建节点时的初始值                │
│ action         = NONE                                                       │
│ status         = PENDING                                                    │
│ data_id        = "abc-123"     ← 从数据源获取的业务主键                       │
│ data           = {...}         ← 从数据源获取的业务数据                       │
│ origin_data    = {...}         ← 同上，原始快照                              │
│ depend_ids     = []            ← 空，待 bind 阶段填充                        │
│ error          = None                                                       │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

### 第二次及后续运行（有持久化数据）

```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 从持久化恢复（原始值）                                                        │
│                                                                             │
│ binding_status = NORMAL (上次值)                                             │
│ action         = UPDATE (上次值)                                             │
│ status         = SUCCESS (上次值)                                            │
│ data_id        = "abc-123"                                                  │
│ error          = None (上次值)                                               │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                          阶段1：加载时重置 (NodeStateReset)
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段1重置后                                                                  │
│                                                                             │
│ binding_status = UNCHECKED     ← 重置（每次重新判定）                         │
│ action         = UPDATE        ← 保留！（用于阶段2识别 CREATE 失败）          │
│ status         = SUCCESS       ← 保留！（不自动重置）                         │
│ data_id        = "abc-123"     ← 不变                                       │
│ data           = None          ← 重置（等待数据源覆盖）                       │
│ origin_data    = None          ← 重置（等待数据源覆盖）                       │
│ error          = None          ← 清空（避免上次错误干扰）                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                          阶段2：CREATE 僵尸清理
                          (只影响 action=CREATE 且 status=FAILED 或 data_id="" 的节点)
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段2清理后（正常节点）                                                       │
│                                                                             │
│ action         = NONE          ← 全部重置为 NONE，准备进入策略阶段            │
│ 其他字段不变                                                                  │
│                                                                             │
│ （若为 CREATE 僵尸节点：直接从 Collection 删除 + 解除绑定）                   │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                           数据源加载覆盖 (Stage 1 后半)
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 数据源加载后                                                                  │
│                                                                             │
│ data           = {...}         ← 数据源最新数据覆盖                           │
│ origin_data    = {...}         ← 数据源最新数据覆盖                           │
│ 其他字段不变                                                                  │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## Bind 阶段状态转移 (Stage 2.1)

Bind 分三个子阶段顺序执行，每个子阶段只修改 `binding_status`。

### Phase 1: 核心状态判定

> **代码**：`strategy_ops/bind_ops.py::phase_core_state()` → `SyncDecisionEngine.determine_core_status()`

```
输入：binding_status = UNCHECKED

                    ┌─ 有绑定记录？
                    │
              ┌─────┤
              │ YES │
              │     └─── 本地数据 + 远程数据 都健康？
              │              │
              │         ┌────┴────┐
              │         │ YES     │ NO
              │         ▼         ▼
              │      NORMAL    WARNING / ABNORMAL
              │
              │ NO
              └──────────────► MISSING
```

| 输入条件 | binding_status 输出 |
|---------|-------------------|
| 有绑定，双方数据健康 | **NORMAL** |
| 有绑定，自身数据损坏 | **ABNORMAL** |
| 有绑定，对方数据损坏 | **WARNING** |
| 无绑定记录 | **MISSING** |

### Phase 2: 依赖检查

> **代码**：`strategy_ops/bind_ops.py::phase_dependency_check()` → `strategy_ops/bind_ops.py::check_dependencies_for_nodes()`

**仅处理 `binding_status = MISSING` 的节点**，其他状态跳过。

```
输入：binding_status = MISSING

                    ┌─ 有配置 depend_fields？
                    │
              ┌─────┤
              │ NO  └──► 保持 MISSING（无依赖，直接通过）
              │
              │ YES ──► 逐一检查依赖节点：
              │              │
              │         ┌────┴────────────────────────┐
              │         │ 全部依赖节点:               │
              │         │  - 存在？ ✅                │
              │         │  - binding_status=NORMAL？ ✅ │
              │         │  - data_id 非空？ ✅         │
              │         └────┬────────────────────────┘
              │              │
              │         ┌────┴────┐
              │         │ 全通过   │ 任一失败
              │         ▼         ▼
              │      MISSING   DEPENDENCY_ERROR
              │                + error = "依赖项不满足: ..."
              │                + depend_ids 已填充
```

**副作用**：无论检查是否通过，都会填充 `node.depend_ids`。

### Phase 3: 自动绑定

> **代码**：`strategy_ops/bind_ops.py::phase_auto_binding()`

**仅处理 `binding_status = MISSING` 且依赖已通过的节点**。

```
输入：binding_status = MISSING

auto_bind = False?
    └──► MISSING → WARNING（"跳过自动绑定，无法确认是否需要创建"）

auto_bind = True:
    │
    ├─ 业务键字段缺失？
    │   └──► MISSING → WARNING（"缺少业务键字段: ..."）
    │
    ├─ 业务键中 ID 字段解析失败？
    │   └──► MISSING → DEPENDENCY_ERROR（"业务键中的ID字段解析失败"）
    │
    ├─ 去噪后 1:1 匹配？
    │   └──► MISSING → NORMAL（执行自动绑定，建立绑定记录）
    │
    ├─ N:0（无远程匹配）？
    │   ├──► create_enabled = True  → MISSING → NORMAL，并 spawn 目标 CREATE 节点（S06）
    │   └──► create_enabled = False → MISSING → WARNING（孤儿且不允许自动创建，需人工处理）
    │
    └─ N:M / 多对多 / 多对一？
        └──► MISSING → WARNING（"存在多个候选节点，无法自动绑定"）
```

### Bind 阶段结束后可能的状态

| binding_status | 后续可执行的操作 |
|---------------|----------------|
| **NORMAL** | UPDATE（如有差异）|
| **MISSING** | CREATE |
| **ABNORMAL** | 无（需人工） |
| **WARNING** | 无（阻止自动操作） |
| **DEPENDENCY_ERROR** | 无（等待依赖修复） |

---

## Create 阶段状态转移 (Stage 2.2)

> **代码**：`create()` → `strategy_ops/create_ops.py::add_create_action()`

> **对齐说明（与状态机配置一致）**：
> - create_prepare 的入口仍是源节点 `binding_status = MISSING`。
> - 但当前配置下，`N:0 + create_enabled=true` 可以在自动绑定阶段直接把源节点转为 `NORMAL`，并 `emit spawn_target_node_state=S06`。
> - 因此“触发创建”的入口有两条：
>   1) auto_bind 分支（E08f）直接 spawn；
>   2) create_prepare 分支（E09b）在 create 检查成功后 spawn。

**仅处理 `binding_status = MISSING` 的节点**。

```
输入：源节点 binding_status = MISSING, action = NONE

        ┌─ 源节点数据为空？
        │   └──► 跳过，记录日志
        │
        ├─ IDResolver.resolve_and_validate() 失败？
        │   └──► binding_status → DEPENDENCY_ERROR
        │        error = "创建时依赖 ID 未解析: ..."
        │
        └─ 成功 ──► 在目标 Collection 创建新节点
                    │
                    ├─ 源节点：
                    │   binding_status → NORMAL（"从本地创建到远程"）
                    │   action 不变（仍为 NONE）
                    │
                    └─ 新建目标节点：
                        binding_status = NORMAL
                        action = CREATE
                        status = PENDING
                        data_id = ""（待远程返回）
                        data = 已替换 ID 的数据副本
                        │
                        └─ 同时建立绑定记录：local_id ↔ remote_id
```

---

## Commit Creates — 物理同步 (Stage 2.3)

> **代码**：`DataSource.sync_all()` → Handler.create()

```
输入：action = CREATE, status = PENDING

        ┌─ Handler 执行 API 调用
        │
        ├─ 同步 API 成功（200/201）
        │   └──► status → SUCCESS
        │        data_id = "remote-xyz"（远程分配的 ID 回填）
        │        data["id"] = "remote-xyz"（主键字段回写）
        │
        ├─ 异步 API 提交成功（202）
        │   └──► status → IN_PROGRESS
        │        context.task_id = "task-xxx"
        │        │
        │        └─ 轮询结果：
        │            ├─ 成功 → status → SUCCESS, data_id 回填
        │            ├─ 失败 → status → FAILED, error = "..."
        │            └─ 超时 → status → FAILED, error = "Timeout"
        │
        └─ API 失败（4xx/5xx）
            └──► status → FAILED
                 error = "远程操作失败: ..."
```

---

## Update 阶段状态转移 (Stage 2.4)

> **代码**：`update()` → `strategy_ops/update_ops.py::add_update_action()`

> **对齐说明**：图里的 `E12a` 从 `S01` 出发并不矛盾。`S01` 约束的是“源节点”有 `data_id`；
> `E12a` 检查的是“目标节点”是否有 `data_id`（guard: `target_has_data_id=false`），两者不是同一个对象。

**过滤条件**：`binding_status = NORMAL` 且 `status != FAILED`

```
输入：源节点 binding_status = NORMAL, action = NONE

        ┌─ status == FAILED？
        │   └──► 跳过（保护上次失败的状态和错误信息）
        │
        ├─ 源节点数据为空？
        │   └──► 跳过
        │
        ├─ 通过绑定找不到目标节点？
        │   └──► 跳过，记录日志
        │
        ├─ 目标节点 data_id 为空？
        │   └──► 目标节点 status → PRECONDITION_FAILED
        │        （"目标节点缺少 data_id"）
        │
        ├─ IDResolver.resolve_and_validate() 失败？
        │   └──► 目标节点 action → UPDATE
        │        目标节点 status → PRECONDITION_FAILED
        │        目标节点 error = "依赖 ID 未解析: ..."
        │
        ├─ _needs_update() 判定无差异？
        │   └──► 跳过（不设置 action）
        │
        └─ 有差异 ──► 更新目标节点：
                      action → UPDATE
                      status → PENDING
                      data = 已替换 ID 的数据副本
                      error = None
```

---

## Commit Updates — 物理同步 (Stage 2.5)

> **代码**：`DataSource.sync_all()` → Handler.update()

```
输入：action = UPDATE, status = PENDING

        ┌─ Handler 构建 API 请求
        │
        ├─ Handler 发现无实际变更字段
        │   └──► action → NONE（Handler 降级，不提交 API）
        │
        ├─ 同步 API 成功（200）
        │   └──► status → SUCCESS
        │
        ├─ 异步 API 提交成功（202）
        │   └──► status → IN_PROGRESS → （轮询）→ SUCCESS / FAILED
        │
        └─ API 失败
            └──► status → FAILED
                 error = "远程操作失败: ..."
```

---

## 持久化阶段 (Stage 5)

```
节点当前状态原样写入存储。

持久化字段：
  ✅ node_id, data_id, data, origin_data
  ✅ binding_status, action, status, error, context
  ❌ depend_ids（不持久化，下次 bind 时从 data 实时计算）
```

---

## 全景时间线表

下表展示**一个节点在典型场景中的状态变化**：

### 场景 A：首次创建（本地 → 远程）

| 阶段 | binding_status | action | status | data_id | error |
|------|---------------|--------|--------|---------|-------|
| 数据加载 | UNCHECKED | NONE | PENDING | "abc-123" | None |
| bind phase1 | **MISSING** | NONE | PENDING | "abc-123" | None |
| bind phase2 | MISSING | NONE | PENDING | "abc-123" | None |
| bind phase3 | MISSING | NONE | PENDING | "abc-123" | None |
| create | **NORMAL** | NONE | PENDING | "abc-123" | None |
| _(新建远程节点)_ | NORMAL | **CREATE** | PENDING | **""** | None |
| commit create | NORMAL | CREATE | **SUCCESS** | **"remote-456"** | None |
| 持久化 | NORMAL | CREATE | SUCCESS | "remote-456" | None |

### 场景 B：日常更新

| 阶段 | binding_status | action | status | data_id | error |
|------|---------------|--------|--------|---------|-------|
| 持久化恢复 | NORMAL→**UNCHECKED** | CREATE→保留 | SUCCESS→保留 | "abc-123" | 清空 |
| 阶段2 action重置 | UNCHECKED | **NONE** | SUCCESS | "abc-123" | None |
| 数据源覆盖 | UNCHECKED | NONE | SUCCESS | "abc-123" | None |
| bind phase1 | **NORMAL** | NONE | SUCCESS | "abc-123" | None |
| update 检测差异 | NORMAL | NONE | SUCCESS | "abc-123" | None |
| _(目标节点)_ | NORMAL | **UPDATE** | **PENDING** | "remote-456" | None |
| commit update | NORMAL | UPDATE | **SUCCESS** | "remote-456" | None |

### 场景 C：依赖阻断

| 阶段 | binding_status | action | status | data_id | error |
|------|---------------|--------|--------|---------|-------|
| 数据加载 | UNCHECKED | NONE | PENDING | "contract-789" | None |
| bind phase1 | **MISSING** | NONE | PENDING | "contract-789" | None |
| bind phase2 | **DEPENDENCY_ERROR** | NONE | PENDING | "contract-789" | "依赖项不满足: project_id: 状态=dep_err" |
| _(后续阶段不处理)_ | | | | | |
| 持久化 | DEPENDENCY_ERROR | NONE | PENDING | "contract-789" | "依赖项不满足..." |

### 场景 D：CREATE 失败 → 下次自动恢复

| 阶段 | binding_status | action | status | data_id | error |
|------|---------------|--------|--------|---------|-------|
| **第 1 次运行** | | | | | |
| create | NORMAL | CREATE | PENDING | "" | None |
| commit create 失败 | NORMAL | CREATE | **FAILED** | "" | "远程操作失败: 500" |
| 持久化 | NORMAL | CREATE | FAILED | "" | "远程操作失败: 500" |
| **第 2 次运行** | | | | | |
| 阶段1恢复 | **UNCHECKED** | CREATE | FAILED | "" | **None** |
| 阶段2僵尸清理 | _(节点被删除，绑定被解除)_ | | | | |
| 数据源重新加载 | UNCHECKED | NONE | PENDING | "" → 新 node_id | None |
| bind → create | 重新走首次创建流程 | | | | |

### 场景 E：UPDATE 失败 → 保留供人工处理

| 阶段 | binding_status | action | status | data_id | error |
|------|---------------|--------|--------|---------|-------|
| **第 1 次运行** | | | | | |
| update | NORMAL | UPDATE | PENDING | "remote-456" | None |
| commit update 失败 | NORMAL | UPDATE | **FAILED** | "remote-456" | "403 Forbidden" |
| 持久化 | NORMAL | UPDATE | FAILED | "remote-456" | "403 Forbidden" |
| **第 2 次运行** | | | | | |
| 阶段1恢复 | UNCHECKED | UPDATE | FAILED | "remote-456" | None |
| 阶段2 action重置 | UNCHECKED | **NONE** | **FAILED** | "remote-456" | None |
| bind | NORMAL | NONE | FAILED | "remote-456" | None |
| update 跳过 | _(status=FAILED，自动跳过)_ | | | | |
| 持久化 | NORMAL | NONE | FAILED | "remote-456" | None |
| _(需人工重置 status 后才能重新更新)_ | | | | | |

---

## 状态转换合法性约束

### binding_status 允许的转换

```
UNCHECKED → NORMAL          (核心判定: 有绑定且健康)
UNCHECKED → MISSING         (核心判定: 无绑定记录)
UNCHECKED → ABNORMAL        (核心判定: 自身数据损坏)
UNCHECKED → WARNING         (核心判定: 对方数据损坏)

MISSING → DEPENDENCY_ERROR  (依赖检查: 父节点不满足)
MISSING → WARNING           (自动绑定: 业务键缺失 / 多候选 / auto_bind=False)
MISSING → DEPENDENCY_ERROR  (自动绑定: ID 字段解析失败)
MISSING → NORMAL            (自动绑定: 1:1 成功)
MISSING → NORMAL            (create: 源节点创建成功后)
MISSING → DEPENDENCY_ERROR  (create: ID 解析失败)
```

> **注意**：当前 `SyncNode.set_binding_status()` 不做转换合法性校验【未实现】。

### action 允许的转换

```
NONE → CREATE               (create: 新建目标节点)
NONE → UPDATE               (update: 检测到差异)
UPDATE → NONE               (Handler 降级: 实际无变更字段)
```

### status 允许的转换

```
PENDING → IN_PROGRESS       (DataSource: 异步提交成功)
PENDING → SUCCESS           (DataSource: 同步执行成功)
PENDING → FAILED            (DataSource: 执行失败)
PENDING → SKIPPED           (DataSource: 被跳过)
                            （常见于 CREATE/UPDATE：Handler 返回 SKIPPED（无可执行操作/业务条件不满足），对应状态机里的 E19→S13 或 E20→S14）
PENDING → PRECONDITION_FAILED (Strategy: 前置条件不满足)
IN_PROGRESS → SUCCESS       (DataSource: 异步轮询成功)
IN_PROGRESS → FAILED        (DataSource: 异步轮询失败/超时)
```

> **注意**：当前 `SyncNode.set_status()` 不做转换合法性校验【未实现】。

---

## 参考文档

- [状态定义规范](./状态定义规范.md) — 状态枚举和判定规则
- [持久化与人工介入规范](./持久化与人工介入规范.md) — 持久化重置策略
- [编排流程规范](./编排流程规范.md) — Pipeline 阶段定义
- [架构分层设计](./架构分层设计.md) — 各层职责
- [node_id_data_id_lifecycle](./node_id_data_id_lifecycle.md) — ID 生命周期
