# 2026-05-13 cypher-executor multi-node chain context propagation 漏失

> **總耗時**：約 20 分鐘 debug（找 P0 #9 過程中順帶找到）
> **根因**：4 個 edge type 沒 spread baseCtx 給下游節點，原始 context 從第 2 節點開始消失
> **修法**：ON_SUCCESS / ON_FAIL / IF / ON_CLICK 套用 PIPE / FOREACH 同模式 `{...baseCtx, ...result}`
> **影響**：任何 chain workflow 從第 2 節點開始 interpolate context key 都失敗

---

## 症狀

mira `acr run wiki_synthesis`（7 節點 workflow）回 `Unauthorized`。trace 顯示：

- `load_schema`（節點 1）✓ 200，input 含 `api_key=ak_xxx`
- `load_skill`（節點 2）✗ 401 Unauthorized，input 含 `api_key="{{api_key}}"` ← **模板原文未替換**

## 推測 → 驗證

**對照組**：2 節點 chain 看 input keys：

| 節點 | input keys |
|---|---|
| n1 | `api_key, b1, b2, block_id` ✓ 全 context 在 |
| n2 | `blocks, count, success, api_key, block_id` ✗ **`b1, b2` 不見**，`api_key` 是原文 `"{{api_key}}"` |

n2 的 ctx **只有 n1 output spread**，原始 context（`b1`, `b2`, ...）全消失。

## 根因

`graph-executor.ts` 在 outEdges 處理時，PIPE 跟 FOREACH 已有 baseCtx merge：

```typescript
// PIPE (line 381-384)
const pipeContext = {
  ...(context as Record<string, unknown>),
  ...baseResult,
};

// FOREACH (line 438-444)
const baseCtx = ...;
const itemContext = {
  ...baseCtx,
  ...(result as Record<string, unknown>),
  [iteratorKey]: item,
};
```

但 ON_SUCCESS / ON_FAIL / IF / ON_CLICK 直接傳 `result`：

```typescript
case 'ON_SUCCESS':
  result = await this.executeNode(nextNode, graph, result, ...);  // ← bug
```

`result` 是上游節點的 output，**沒有原始 context**。下游 interpolate 找不到原始 key 就原文留下。

## 歷史脈絡

2026-05-07 commit `e8fca33`（"FOREACH preserves outer context"）已意識到問題並修了 FOREACH。但**沒同步處理另外 4 個 edge type**。今天才被 mira 7 節點 workflow 踩到。

→ 教訓：架構級修法要全 edge type 一致掃過，不只修當下踩到的。

## 修法

`cypher-executor/src/graph-executor.ts` 4 個 edge case 補：

```typescript
// 改前
result = await this.executeNode(nextNode, graph, result, ...);

// 改後
const baseCtx = (typeof context === 'object' && context !== null) ? context as Record<string, unknown> : {};
const baseResult = (typeof result === 'object' && result !== null) ? result as Record<string, unknown> : {};
const mergedCtx = { ...baseCtx, ...baseResult };
result = await this.executeNode(nextNode, graph, mergedCtx, ...);
```

套用在 line 407 (ON_SUCCESS) / 415 (ON_FAIL) / 423 (IF) / 472 (ON_CLICK)。

## 驗證

`acr run wiki_synthesis` 7 節點 workflow 端對端跑通：

| 節點 | 修前 input | 修後 input |
|---|---|---|
| n2 (load_skill) | `api_key={{api_key}}` ✗ | `api_key=ak_xxx` ✓ |
| n7 (emit_result) | 上游 spread only | baseCtx + 各上游 spread ✓ |

整條 16.2 秒（含 claude_api 真實 Claude 呼叫），結果 `{success: true, data: {...}}`。

## Known Limitation

`interpolateData()` 只展開 `node.data` top-level string values，**不遞迴 nested object**：

```yaml
# emit_result 的 values 內 {{...}} 不會展開
emit_result:
  component: set
  values:
    text: "{{classify.data.text}}"   # ← 原文傳，不展開
```

非阻擋 P1，SDD 待開 `interpolate-nested-config`。當前 workaround：直接看上游節點的 trace output。

## 未來怎麼避免

1. **新 edge type 加進來時必須走 baseCtx merge 模式**——可以抽出 helper `mergeCtxForDownstream(context, result)` 強制所有 caller 用，避免漏
2. **interpolation 邊界要有測試**：寫一個 2 節點 chain 用 `{{api_key}}` 引用原始 context 的 e2e test，CI 跑過
3. **trace output 要顯示 input** —— 沒看 trace 內 `input` 欄位很難看出 interpolation 失敗（目前 acr CLI 不顯示 input，只顯示 result）。CLI 應加 `--verbose` 顯示每節點 input

## Reference

- 對應 SDD：`matrix/arcrun/.agents/specs/arcrun/arcrun.md` P0 #10
- 相關 commit：`e8fca33` 2026-05-07（FOREACH 同類修法）
- 受影響 SDD：`polaris/mira/.agents/specs/mira-app/tasks.md` 7B.3c
- 同日另一個 incident：[2026-05-13-cypher-outbound-522.md](./2026-05-13-cypher-outbound-522.md)
