# Kestra V54 阶段验收记录

- 代码分支：`codex/kestra-v50`
- 基线提交：`0a99291`
- 验收日期：2026-07-19
- Python：3.11.15
- 结论：V54 受控 AI 调度规划、只读 Canary 与确定性恢复闭环完成；允许进入
  V55 的双轨迁移开发，但尚不允许切换正式引擎或移除 n8n。

## 完成范围

### 结构化调度规划

- 新增 `app/core/orchestration/agent/`，包括规划器、Prompt、严格输出 Schema、
  离线评估和恢复策略。
- 规划输入绑定业务目标、DataFlow UID、业务域、环境、可用时间窗、授权数据源
  UID、Canary 输入和可选基线执行 ID。
- 模型只能输出严格的 WorkflowSpec、SchedulePlan 和决策摘要，不接受任意 YAML、
  生命周期动作、明文秘密、写节点、无界重试或模型指定的 Backfill 分区。
- 规划顺序由控制器固定为：观察上下文、生成候选、两类 Schema 校验、容量评估、
  调度模拟、创建候选、禁用发布、只读 Canary、可信验证和推广建议。
- V54 推广建议固定为 `shadow_only`；规划器不调用 `promote_candidate`，也不改变
  正式引擎角色。

### 模型与决策审计

- 新增 OpenAI-compatible 规划模型适配器，温度固定为 0，要求 JSON 输出，并将
  完整输出 Schema 提供给模型；最终输出仍必须通过 DataOps 本地验证。
- `workflow_plan_audits` 现在保存模型提供方、模型名、Prompt 版本、Schema 版本、
  上下文哈希、候选哈希、结构化候选、校验结果和经过裁剪的动作结果。
- 审计详情限制为 1 MiB，哈希必须是 SHA-256 十六进制摘要；模型或上游工具的
  原始异常文本不写入审计。
- 已保存的合法候选可在没有模型和外部工具的情况下执行确定性离线重放评估。

### 确定性降级与恢复

- 模型超时/不可用：保留已发布固定调度，不调用 Scheduling MCP。
- 模型无效输出、目标冲突、越界数据源、写入建议、秘密请求和无界策略：安全拒绝，
  不创建候选。
- Context/Scheduling 工具失败：停止后续动作；已创建候选保持未推广状态。
- 数据源池退化：确定性生成更低并发的合法 SchedulePlan。
- Runner 或单数据源不可用：只允许暂停候选调度。
- 瞬时执行失败：最多执行策略限定次数的 Gateway 重试；次数耗尽后回到上一稳定
  版本。
- 模型或 Kestra 不可用：不尝试由模型重规划，继续已发布固定调度。
- 恢复输入采用关闭对象，模型不能传入 `delete_execution`、任意状态修改或其他
  自选动作。

### 首批只读 Canary

- L3 使用一个低频、单节点 `sql.query` 流程，经过 DataOps Context MCP 和
  Scheduling MCP 的注册工具边界，调用真实本地 Kestra、Runner、数据源凭据
  引用和数据库连接池。
- Canary 成功执行一次，Runner 账本记录成功；Kestra Flow 在执行窗口结束后仍为
  disabled，没有推广或切换正式引擎。
- Canary 监控会轮询可信验证器直至 `passed`/`failed` 或有界超时，不再把刚启动的
  `RUNNING` 状态误判为终态。
- 本地对比使用受控的 n8n 基线比较夹具验证 `baseline_execution_id`、等价结论和
  安全指标传递。它证明了比较契约，不代表已经取得生产 n8n 同流程的真实对账
  证据；该证据必须在 V55 逐流程双轨阶段补齐。

## 增量验证

### 基线

```bash
PYTHONPATH=. .venv/bin/python -m pytest -q \
  tests/mcp/test_scheduling_gateway.py \
  tests/security/test_scheduling_tool_boundaries.py \
  tests/core/orchestration
```

结果：`33 passed`。

### L1：Agent 场景

```bash
PYTHONPATH=. .venv/bin/python -m pytest -q tests/agent
```

结果：`17 passed`。覆盖合法只读计划、模型超时、无效/写入输出、目标冲突、
工具失败、离线重放、Canary 轮询、无限重试、秘密请求、模型 Backfill 请求，
以及池退化、有限重试、暂停、回滚和各组件不可用恢复。

### L2/L3：受影响边界与真实场景

```bash
DATAOPS_MCP_PERSISTENCE_INTEGRATION=1 \
DATAOPS_MCP_TEST_DATABASE_URL=postgresql://dataops:***@127.0.0.1:15432/dataops \
RUN_V54_AGENT_L3=1 \
PYTHONPATH=. .venv/bin/python -m pytest -q \
  tests/agent \
  tests/mcp/test_scheduling_gateway.py \
  tests/mcp/test_server_contracts.py \
  tests/mcp/test_dataops_context_mcp.py \
  tests/security/test_scheduling_tool_boundaries.py \
  tests/integration/test_mcp_gateway_persistence.py \
  tests/integration/test_agent_kestra_canary.py \
  tests/integration/test_agent_model_offline.py
```

结果：`40 passed`。

其中：

- PostgreSQL 验证模型、Prompt、Schema、上下文/候选哈希和结构化详情确实落库。
- 真实 Kestra/Runner 只读 Canary：`1 passed`。
- 模型离线且审计落库：`1 passed`。

### 静态与格式检查

```bash
.venv/bin/ruff check \
  app/core/orchestration/agent app/core/mcp/persistence.py \
  tests/agent tests/integration/test_agent_kestra_canary.py \
  tests/integration/test_agent_model_offline.py \
  tests/integration/test_mcp_gateway_persistence.py

.venv/bin/pyright --pythonpath .venv/bin/python \
  app/core/orchestration/agent

git diff --check
```

结果：

- Ruff：通过。
- 新增 Agent 生产模块 Pyright：`0 errors, 0 warnings`。
- Git whitespace：通过。
- 新增生产模块未发现 API Key、云访问密钥或私钥模式。

未将全文件范围 Pyright 作为本阶段门禁：现有 `app/core/mcp/persistence.py` 的旧
Optional 类型问题和测试第三方依赖解析会在该范围产生既有错误；本次修改的持久化
行为已经由真实 PostgreSQL 集成测试覆盖。

## 未执行的测试及原因

- 未执行全仓 Python 测试、前端构建和完整容器栈 L4；V54 没有修改公共前端、
  全局依赖、应用启动入口或正式引擎切换协议，按计划只执行 L1–L3。
- 未调用真实外部 DeepSeek API；模型适配器使用确定性客户端合约测试，规划闭环
  使用固定离线模型，避免外部模型波动影响验收。真实 API 联调不是正式调度持续
  运行的依赖。
- 未进行生产 n8n 与 Kestra 真实同流程对账；本地没有可代表生产的同源 n8n 流程，
  不能制造生产证据。该项属于 V55 双轨迁移门槛。

## 安全边界、已知限制与回滚

- n8n 服务、Workflow 和正式执行路径均未删除、停止或改写。
- V54 只允许只读 Canary；生产写节点、正式切换和自动推广继续失败关闭。
- `reduce_concurrency` 生成新的确定性 SchedulePlan，不直接在线修改现有 Kestra
  定义；后续仍需经过候选、校验、禁用发布和 Canary。
- V54 只验证一个本地只读流程；V55 必须为每个实际迁移流程建立真实双轨基线、
  对账、观察窗口和回切证据。
- 回滚方式：停止调度 Agent，撤销其 Context/Scheduling MCP 身份，禁用并删除
  V54 候选 Kestra Flow；保留审计记录，n8n 继续作为正式引擎。

## 进入 V55 的条件

本记录允许开始 V55 的双轨框架、逐流程迁移和对账实现，不代表批准生产切换。
只有实际流程完成影子运行、真实 n8n/Kestra 对账、故障恢复和回切演练后，才允许
按单流程改变引擎角色；在 V55 统一退出门槛全部满足前不得移除 n8n。
