# ADR-017: Action/Effect Boundary

状态: P0 完成，P1.5-B ExecutionAuthorizationReceipt 升格 normative
日期: 2026-04-28 (P0)，2026-05-06 (P1.5-B receipt normative)

## 背景

在 ActionProposal 之前，Host LLM 直接驱动 runtime 路由和副作用。局部语境请求被误读为全局推进是通用问题。需要在 materialization 之前建立授权边界。

## 决策

**在 State Resolution 完成后、Router 产生副作用之前，插入 ActionProposal 解析与 Effect 授权。**

核心管线：

```
用户自然语言
  → Host LLM 映射为 ActionProposal（action_type + side_effect + confidence + evidence）
  → Validator 基于 ActionProposal + ValidationContext 输出 ValidationDecision
  → Deterministic action 执行
  → Handoff / Receipt 暴露机器事实
```

### 不变量

1. **Host LLM 只是 proposal source，不是 authorizer** — LLM 提交工单，不批准工单
2. **Validator 是唯一授权者** — 判断当前 context 下 action/side effect 是否允许
3. **Validator 不是 executor** — 不做 plan materialization、文件迁移、自动修复、状态推进
4. **执行层不理解人话** — 只按结构化字段和文件事实做事
5. **`fallback_router` 是临时兼容出口** — 应单调收缩，不承接新的长期能力

### ExecutionAuthorizationReceipt — *normative*

> **升格状态**：本节从"方向"升格为 **normative**（P1.5-B 升格）。字段语义使用 RFC 2119 表述。

execution_confirm checkpoint 重分类为机器授权事实：

**不变量（MUST）：**
- Receipt MUST 绑定 plan identity + plan revision + execution gate result + action proposal identity + authorization source
- Receipt MUST 使用 canonical JSON + sha256 生成 fingerprint
- Plan 内容变更（plan_revision_digest 不匹配）后，已有 receipt 自动失效——Validator MUST 返回 DECISION_REJECT
- Fail-closed：任一字段不匹配时 Validator MUST 拒绝执行，MUST NOT 降级为 consult 或自动 re-authorize
- Receipt MUST 持久化到 authoritative runtime state，Validator 从该 state 读取已有 receipt 做 stale 检测（具体位点由参考实现决定，不由 protocol 绑定）
- `action_proposal_id` MUST 由 engine 确定性生成，MUST NOT 由 host 指定（host 传入则 reject）

**Receipt 字段定义：**

| 字段 | 语义 |
|------|------|
| `plan_id` | 目标 plan 的唯一标识（plan 目录名） |
| `plan_path` | 目标 plan 的 workspace-relative 目录路径 |
| `plan_revision_digest` | plan.md 内容的 SHA-256 hex digest（`revision_digest` 在 plan subject 场景的特化命名） |
| `gate_status` | 生成时 ExecutionGate.gate_status 的值 |
| `action_proposal_id` | 触发授权的 ActionProposal 唯一标识（`sha256(canonical_json({action_type, side_effect, subject_ref, revision_digest, request_hash}))[:16]`） |
| `authorization_source` | `{ kind: "request_hash", request_sha1: string }` |
| `fingerprint` | `sha256(canonical_json({plan_id, plan_path, plan_revision_digest, gate_status, action_proposal_id}))` |
| `authorized_at` | ISO 8601 UTC 时间戳 |

Implementation plan 可补充字段，但 MUST NOT 删除或弱化上述字段的 fail-closed 语义。

### Checkpoint 重分类

| 旧类型 | 新定位 | 理由 |
|--------|--------|------|
| `clarification` | **保留为 canonical checkpoint** | 补事实是真协作分叉 |
| `decision` | **保留为 canonical checkpoint** | 拍板选路是真协作分叉 |
| `plan_proposal` | propose_plan pending artifact | ~~等 side-effect proof 稳定后降级~~ **Wave 3a 已 hard-cut 删除** |
| `execution_confirm` | ExecutionAuthorizationReceipt | 授权不是协作分叉 |
| `develop_checkpoint` | develop callback source | 触发 clarification/decision，不是独立类型 |

## P0 Thin Slice（已完成）

- `ActionProposal` schema + `ValidationContext` + `Validator` 已实现
- Gate 接收 `--action-proposal-json`
- Engine pre-route interceptor 已接入
- `consult_readonly` 首个受控 side-effect 已验证
- `archive_plan` 已切入 ActionProposal 管线

## 后续扩展方向

- ✅ `propose_plan` + `write_plan_package` side-effect proof（P1.5-C 完成：authorized_only 策略）
- ✅ `execute_existing_plan` 通过 ExecutionAuthorizationReceipt 授权（P1.5-B 升格 normative）
- `fallback_router` 职责单调收缩

## 后果

- 所有 side-effecting action 必须先映射为 ActionProposal
- `~go finalize` 等 command alias 必须映射为对应 proposal，不绕过 Validator
- Checkpoint 目标压缩为 2 种 canonical type
- required_host_action 目标压缩为 5 种 canonical action
