Files
yuanshuai c91a64fddb
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run
chore(repo): initialize team collaboration repository
2026-07-27 20:40:12 +08:00

100 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构:五层 → 代码目录的逐层映射
> **实现状态(2026-07-27)**:本文描述目标架构。当前已落地控制平面的模型、
> 安全门禁、事件记录、Run 转移表、恢复决策、最小证据包、内置模拟执行器,以及
> 独立 Host Agent 的注册、token、心跳和 Host 状态老化链路;Step 租约、Adapter
> 执行、消息总线替换和生产对象存储仍未落地。
方案 §5.1 定义了五层架构。这里给出每一层**落到哪个目录、边界在哪、谁不能调用谁**。
```
┌─────────────────────────────────────────────────────────────┐
│ 交互与集成层 apps/console (Next.js) · CLI · Open API │
├─────────────────────────────────────────────────────────────┤
│ 控制平面 services/control-plane/flashops_control/ │
│ services/ 编排 safety/ 门禁 api/ 接口 │
├─────────────────────────────────────────────────────────────┤
│ 执行平面 services/host-agent/flashops_agent/ │
│ adapters/ 工具适配器 │
├─────────────────────────────────────────────────────────────┤
│ 带外平面 services/host-agent/flashops_agent/oob/ │
│ (独立进程/独立网络,模拟实现在 simulator/) │
├─────────────────────────────────────────────────────────────┤
│ 证据与智能层 control-plane/.../evidence/ · signatures/ │
│ events/ 事件总线 │
└─────────────────────────────────────────────────────────────┘
```
## 依赖方向(单向,不可逆)
```
console ──HTTP/SSE──▶ control-plane ◀──HTTP──── host-agent
│ │
│ ├─▶ adapters ─▶ 客户工具
▼ │
数据库 └─▶ oob ─▶ 带外控制器
```
**硬规则:**
- `host-agent` **不得**导入 `control-plane` 的任何模块。两者只通过 `docs/agent-protocol.md`
定义的 HTTP 契约通信。理由:Agent 要能单文件分发到客户 Windows 机器,
控制平面代码不进客户测试主机。
- `control-plane` **不得**主动连接 Agent(Agent 是拉模式)。理由:客户测试主机在
内网/NAT 后面,反向连接在真实实验室里活不下来。
- `engine/` **不得**直接读写数据库。它只操作 `Snapshot` 值对象,返回 `Effect` 列表。
持久化由 `runtime.py` 施加。理由:状态机要能不起数据库单测。
- `api/` **不得**包含业务判断。路由只做校验 + 调用 `engine`/`services`
- 任何危险动作(刷写、Format、Sanitize、断电)**必须**先过 `safety/`
`safety/` 的拒绝发生在下发给 Agent 之前。
## 控制平面内部分层
```
flashops_control/
├─ config.py 配置(产品名、DSN、路径、超时策略)
├─ db.py 引擎/会话/建表
├─ models/ SQLAlchemy 表定义(贫血模型,只管持久化)
├─ schemas.py Pydantic 出入参(当前最小 API 契约)
├─ events/
│ ├─ bus.py 进程内事件总线(生产换 NATS/Redis,接口不变)
│ └─ recorder.py 事件溯源写入:唯一允许改 Run 状态的入口
├─ engine/
│ ├─ states.py Run/Step 状态枚举 + 转移表(纯数据)
│ ├─ recovery.py 五级恢复阶梯策略(纯函数)
│ └─ (目标)planner/runtime:独立 Agent 接入后补齐
├─ safety/
│ ├─ gates.py DUT 绑定 / 系统盘保护 / 破坏性白名单
│ └─ templates.py 签名命令模板注册表 + 参数 schema 校验
├─ signatures/ (目标)失败签名(8 要素哈希)与聚类
├─ evidence/ 最小 Evidence Bundlemanifest + 事件流)
├─ services/runs.py 当前内置模拟编排(后续拆成 Agent + runtime
├─ seed.py 可重复执行的模拟资产与工作流
└─ api/ FastAPI 路由
```
**`engine/` 的纯度是刻意的。** 当前 `states.py` / `recovery.py`
不导入 SQLAlchemy、不导入 FastAPI、不做 I/O、不看时钟
(时间从 `Snapshot.now` 传入)。这让状态机的全部分支都能用普通 pytest 覆盖,
不需要起库、不需要 sleep。后面几波会不断往状态机里加分支(新故障类型、
新恢复策略、多工位调度),这条边界是唯一能防止它烂掉的东西。
## 开发形态 vs 生产形态
| 组件 | 开发(`make dev` | 生产(`docker-compose` | 换的时候动哪 |
|---|---|---|---|
| 数据库 | SQLite (aiosqlite) | PostgreSQL 16 | 只改 `DATABASE_URL` |
| 队列/总线 | 进程内 asyncio | Redis / NATS | `events/bus.py` 换实现 |
| 对象存储 | `var/objects/` 本地目录 | MinIO | `storage.py` 换实现 |
| Agent | 内置模拟执行器 + 独立 Agent 心跳 | 客户主机上的 systemd/Windows 服务 | 补齐 Step lease/runtime |
| 带外 | 模拟控制器 | 树莓派 / JetKVM / 智能 PDU | `oob/` 换实现 |
三处"换实现"都在接口后面,且开发态实现本身就是测试替身——不存在"生产才发现跑不通"。
## 为什么不用 Temporal / Airflow
见 [ADR-0003](decisions/0003-own-state-machine-over-temporal.md)。一句话:
恢复语义(带外断电、冻结现场、误盘防护)是本产品的差异化本身,
不能交给通用工作流引擎的重试模型。等到多工位跨节点调度成为瓶颈时再评估 Temporal,
`runtime.py` 是唯一需要替换的文件。