Files
storage-labos/flashops/docs/architecture.md
T
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

6.4 KiB
Raw Blame History

架构:五层 → 代码目录的逐层映射

实现状态(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。一句话: 恢复语义(带外断电、冻结现场、误盘防护)是本产品的差异化本身, 不能交给通用工作流引擎的重试模型。等到多工位跨节点调度成为瓶颈时再评估 Temporal, runtime.py 是唯一需要替换的文件。