44 KiB
STORAGE LABOS / FlashOps — AI 协作与系统操作指南
文档版本:2.0
核对日期:2026-07-27
适用对象:Codex、Claude Code、Cursor、GitHub Copilot Agent,以及参与项目的人类开发者
目标:只把本文件交给 AI,也能让它安全地理解、运行、修改、验证和交付本系统
本文件是可以单独分发给 AI 的完整系统说明书,不依赖仓库中的任何其他文档。它已经内嵌产品边界、 架构、数据模型、Run/Step 状态机、恢复阶梯、API、Agent 协议、开发流程、生产运维、Git 和安全规则。 即使 AI 只能看到这一份文件,也应能够正确查询线上系统、运行本地模拟、判断允许执行的动作,并在 获得仓库或服务器权限后安全开发和运维。
如果 AI 同时能看到源码,以实际代码和测试结果为准;如果生产状态与本文不一致,先只读核验并报告 差异,不要凭本文直接覆盖服务器。
0. 直接交给 AI 的任务开场词
把下面内容和具体任务一起发给 AI:
你正在维护 STORAGE LABOS / FlashOps。
你收到的这份文档已经包含操作本系统所需的产品、架构、状态机、API、Agent、生产运维、
安全和 Git 规则。请完整阅读并只以本文作为任务上下文,不要要求用户再提供其他项目文档。
如果任务同时提供仓库,先检查状态并保护现有未提交改动;如果只有本文,先使用公开 GET 接口进行
只读核验。默认只操作本地开发环境;除非任务明确要求,不得修改生产服务器、生产数据、DNS、证书、
Gitea 设置或调用生产环境写接口。任何真实硬件、固件刷写、Format、Sanitize、Reset、断电或 OOB
动作都必须停下并取得明确授权。
任务:<在这里写具体目标>
完成后请报告:改动文件、关键决策、测试结果、未完成项、风险、提交号和远程分支。
1. AI 必须先记住的系统事实
| 项目 | 当前值 |
|---|---|
| 产品 | STORAGE LABOS,后端代号 FlashOps |
| 用途 | 存储固件回归测试的无人值守控制平面 |
| Git 网页 | https://git.imagebrewing.com/yuanshuai/storage-labos |
| 克隆地址 | https://git.imagebrewing.com/yuanshuai/storage-labos.git |
| 默认分支 | main |
| 公网预览 | https://flashops.imagebrewing.com/ |
| 公网 API 文档 | https://flashops.imagebrewing.com/docs |
| 本地控制台 | http://localhost:8000/console/Dashboard.dc.html |
| 本地 API 文档 | http://localhost:8000/docs |
| 后端 | Python 3.9+、FastAPI、SQLAlchemy |
| 当前数据库 | SQLite,必须保持单 worker |
| 仓库代码能力 | 模拟 Run 与 Agent dry-run;不代表真实硬件动作已交付 |
| 当前线上能力 | API 版本 0.1.0,仅模拟 Run;Agent 路由尚未发布 |
| 当前测试基线 | 25 项 |
| 生产认证边界 | 预览站匿名开放,尚无应用级 RBAC |
| Gitea | 公开仓库匿名可读;注册账号需管理员批准 |
| CI | 生产服务器没有 Runner,测试结果必须由提交者本地提供 |
重要:公网预览虽然没有登录限制,但这不等于 AI 获得了生产写权限。当前部分 POST 接口在技术上 也能匿名调用,这是尚未补齐 RBAC 的原型边界,不应被当作授权。
2. 权限与行为边界
AI 应按以下顺序判断是否可以行动:
| 行为 | 默认是否允许 | 条件 |
|---|---|---|
| 阅读仓库、文档、Git 历史 | 是 | 保持只读 |
| 查看公网页面、调用健康检查和 GET 查询 | 是 | 不产生业务状态变化 |
| 在本地新分支修改代码和文档 | 用户要求开发时允许 | 保护已有改动 |
| 本地运行测试和模拟演示 | 是 | 不连接真实设备 |
| 创建本地模拟 Run | 是 | 仅使用本地数据库 |
| 提交 Git | 用户要求开发或交付时允许 | 检查范围、测试和秘密后提交 |
| 推送功能分支 | 用户要求共享或交付时允许 | 不强推 main |
| 合并 Pull Request | 否 | 需要仓库负责人明确授权或正常评审 |
| 调用生产 POST、PATCH、PUT、DELETE | 否 | 必须得到针对该动作的明确授权 |
| 修改 DNS、Nginx、systemd、证书、Gitea 设置 | 否 | 必须明确要求部署或运维 |
| 操作真实 DUT、NVMe、固件或 OOB | 否 | 必须明确目标、批准范围和回滚方案 |
| Format、Sanitize、刷写、Reset、断电 | 绝不默认执行 | 需要明确授权、可牺牲 DUT、审批和证据链 |
遇到权限不清时,继续完成安全的本地分析、测试或方案设计,把真正需要授权的动作单独列出。
3. 决策依据与证据优先级
AI 不应只根据文件名或旧说明猜测当前能力,按下面顺序取证:
- 当前用户的明确任务、授权与限制;
- 本文件写明的安全边界和不可执行动作;
- 线上健康检查、API 返回、服务状态和日志等实际证据;
- 如果 AI 获得仓库:当前工作树、Git diff、源码和自动化测试;
- 如果 AI 获得服务器:生效配置、运行进程、数据路径和备份;
- 历史提交或历史部署记录;
- 推测。
如果本文、代码、测试和生产状态互相矛盾,必须在交付说明中指出。不得把“规划”“草案”“静态 页面”描述成已经可用于真实硬件的能力,也不得因为拥有技术访问能力就推断拥有写入授权。
4. 第一次接手:安全启动流程
4.1 全新克隆
git clone https://git.imagebrewing.com/yuanshuai/storage-labos.git
cd storage-labos
git status -sb
cd flashops
make setup
make test
make demo
make setup 会初始化本地虚拟环境,并重建 flashops/var 下的本地 SQLite 数据。它只适合全新克隆 或确认可以重置的本地开发环境;如果工作目录里已有需要保留的本地演示数据,不要直接运行。
4.2 已存在的工作目录
git status -sb
git diff
git diff --cached
git log -5 --oneline
先识别用户已有的未提交改动。不得使用 reset --hard、checkout 丢弃文件、clean -fd 或其他会 抹掉用户工作的命令。不得把不相关改动混进自己的提交。
4.3 只有本文时
只有本文、没有仓库时,AI 仍可以:
- 使用第 6 节列出的公开 GET 接口查询健康、指标、Run、事件和 Host;
- 根据第 5 节解释状态、恢复、检查点和系统边界;
- 根据第 11 节指导获授权的人完成服务器检查、发布、备份和故障排查;
- 根据第 2 节拒绝未授权生产写入与危险硬件动作;
- 输出诊断结论、变更方案、验证清单和交接记录。
只有本文时,AI 不得声称已经修改代码、提交 Git、连接服务器或执行生产动作。只有在真正获得相应 工具和授权后,才能执行这些操作。
5. 系统结构与当前边界
STORAGE LABOS/
├── CONTRIBUTING.md 本文:完整 AI 系统说明书
├── .gitea/ PR 模板与未来 CI 工作流
├── flashops/
│ ├── services/control-plane/ FastAPI 控制平面
│ ├── services/host-agent/ 拉模式 Host Agent
│ ├── deploy/ FlashOps 与 Gitea 部署模板
│ ├── var/ 本地数据库和证据,禁止提交
│ └── Makefile 开发和验证入口
└── 前端UI八页面完成/ FastAPI 同源挂载的静态控制台
当前已经具备:
- FastAPI 控制平面、14 张持久化表和 SQLite 数据库;
- Run 事件溯源、状态投影、状态机和安全门禁;
- 模拟回归、故障注入、L1–L3 恢复演示和 Evidence Bundle;
- 仓库代码已具备 Agent 注册、token、心跳、步骤租约、ACK、续租、事件、完成、超时回收和幂等链路, 但该增量尚未发布到当前公网环境;
- 公开预览控制台及部分真实 API 接入页面。
当前不应声称已经具备:
- 真实 nvme-cli 固件刷写、Format 或 Sanitize;
- 真实 JetKVM、PDU 或 AC 断电控制;
- 完整 SSO、RBAC、审批与审计授权;
- 多工位生产调度、PostgreSQL 高可用;
- 已验收的失败聚类自动决策。
5.1 五层架构与依赖方向
浏览器控制台 ──HTTP──▶ FastAPI 控制平面 ◀──HTTP 长轮询── Host Agent
│ │
▼ ├──▶ 工具适配器
SQLite / 证据 └──▶ 独立 OOB 控制器
五个逻辑层:
- 产品交互层:控制台页面和 HTTP API;
- 控制平面:工作流物化、状态机、安全门禁、资源锁、事件和恢复决策;
- 数据面:客户测试主机上的拉模式 Agent,只执行控制平面批准的步骤;
- 带外平面:独立于测试主机的 OOB 观测与电源控制,主机死亡后仍应可用;
- 证据与分析层:事件时间线、Evidence Bundle、失败签名和审计。
依赖必须单向:
- Agent 不依赖控制平面的 Python 模块,只通过 HTTP 契约通信;
- 控制平面不主动反连 Agent,Agent 必须主动注册、心跳和领取步骤;
- 状态机与恢复决策保持纯函数,不做数据库、网络或时钟 I/O;
- API 路由只校验请求并调用 service,不承载核心业务判断;
- 所有危险动作在下发 Agent 前先经过安全门禁;
- 带外通道不能依赖被测主机的 OS、网络栈或 Agent 进程。
5.2 核心数据模型
当前 SQLite 中的核心对象如下,AI 讨论数据时应使用这些名称:
| 对象 | 作用 |
|---|---|
| TestHost | 测试主机、Agent 身份、心跳和工具链能力 |
| OobController | 独立带外控制器、电源和观测能力 |
| Dut | 被测盘及序列号、系统盘保护、状态 |
| FirmwareArtifact | 固件版本、型号适配和校验信息 |
| Workflow | 版本化 SOP 与步骤定义 |
| Run | 一次工作流执行及当前状态投影 |
| RunStep | Run 物化后的顺序步骤、attempt、租约和结果 |
| RunEvent | append-only 统一事件时间线 |
| RecoveryAction | 恢复级别、触发原因、结果和证据 |
| ResourceLock | DUT、Host 等独占资源锁 |
| FailureSignature | 归一化失败签名 |
| RunFailure | Run 与失败签名的关联 |
| EvidenceBundle | 证据包 URI、完整率、大小和哈希 |
| AuditLog | 人工或系统管理动作的审计记录 |
RunEvent 是状态变化的权威事实;Run 和 RunStep 上的 state 是事件施加后的当前投影。修改投影时必须 与事件处于同一数据库事务,不能先改状态后补事件。
5.3 Run 状态机
QUEUED ──资源锁成功──▶ PREFLIGHT ──六项门禁通过──▶ RUNNING
│ │ │
└──紧急停止──▶ ABORTED ├──门禁拒绝──▶ REJECTED ├──完成──▶ COMPLETED
└──紧急停止──▶ ABORTED ├──人工暂停──▶ PAUSED
│ │
│ └──继续──▶ RUNNING
├──故障──▶ RECOVERING
│ ├──恢复成功──▶ RUNNING
│ └──耗尽/不安全──▶ FROZEN
├──冻结──▶ FROZEN
└──紧急停止──▶ ABORTED
状态含义:
| 状态 | 含义 |
|---|---|
| QUEUED | 等待 DUT、Host 等独占资源 |
| PREFLIGHT | 执行安全门禁并冻结环境指纹 |
| RUNNING | 正在执行步骤或循环 |
| RECOVERING | 正在执行恢复阶梯 |
| PAUSED | 在安全检查点人工暂停 |
| COMPLETED | 正常结束,verdict 为 PASS 或 FAIL |
| FROZEN | 冻结现场,禁止覆盖性动作 |
| ABORTED | 紧急停止 |
| REJECTED | 安全门禁拒绝启动 |
终态为 COMPLETED、FROZEN、ABORTED、REJECTED,终态不可继续转移。任何非法转移都应被视为代码 缺陷,不能吞掉异常继续运行。任意非终态都可以因紧急停止进入 ABORTED;终态不能再次停止、暂停或继续。
5.4 Step 状态机与租约
PENDING ──租给 Agent──▶ DISPATCHED ──ACK──▶ RUNNING
▲ ├──▶ SUCCEEDED
│ ├──▶ FAILED
└──仅幂等且可重试时 attempt+1 ◀─────────────├──▶ TIMED_OUT
├──▶ CANCELLED
└──▶ SKIPPED
- PENDING:等待调度;
- DISPATCHED:已分配 Agent 并创建 60 秒租约,等待 ACK;
- RUNNING:Agent 已确认接手,执行期间必须续租;
- SUCCEEDED、FAILED、TIMED_OUT、CANCELLED、SKIPPED:步骤终态;
- 每次派发增加 attempt,旧 attempt 的 ACK、续租、事件和完成上报必须返回 409;
- DISPATCHED 未 ACK 而租约到期时退回 PENDING;
- RUNNING 租约到期时,只有幂等且仍有重试额度的步骤能退回 PENDING;
- 非幂等步骤或重试耗尽时进入 TIMED_OUT,Run verdict 变为 INCONCLUSIVE 并冻结;
- retry 默认是 0;刷写、Format、Sanitize 等危险步骤默认非幂等且不得自动重试。
5.5 五级恢复阶梯
恢复动作根据可用通道逐级升级,不允许跳过安全判断:
| 级别 | 动作 | 使用前提 | 典型触发 |
|---|---|---|---|
| L1_AGENT_SOFT | Agent 优雅停止或软重启进程 | Agent 仍在线 | 脚本卡死、进程僵死 |
| L2_OS_REBOOT | 通过 OS 通道重启主机 | 主机网络仍在线 | Agent 崩溃、驱动异常 |
| L3_OOB_RESET | OOB 触发主板 Reset | OOB 在线 | 心跳丢失、蓝屏 |
| L4_OOB_ATX_POWER | OOB 模拟 ATX 长按关机再开机 | OOB 在线 | Reset 无效 |
| L5_OOB_AC_CYCLE | AC 断电、安全等待、重新上电 | OOB 在线 | ATX 无效、DUT 掉盘最后手段 |
| FREEZE | 冻结现场并停止自动恢复 | 无 | 数据完整性失败或阶梯耗尽 |
不可更改的恢复规则:
- 数据完整性不一致直接 FREEZE,不能先重启或重跑;
- OOB 离线时 L3–L5 不可用,应标记 INFRA_FAILURE 并冻结,不能误判 DUT_DEFECT;
- 每次恢复必须记录 trigger、level、outcome、时间和 evidence_uri;
- 恢复成功只表示系统重新可执行,不表示原步骤成功;
- 每循环最多恢复 3 次,每 Run 最多恢复 20 次;
- L5 断电后默认安全等待 10 秒再上电;
- 真实 L3–L5 当前没有交付,现有演示只证明状态和证据链路。
失败分类必须区分:
- DUT_DEFECT:证据支持被测盘缺陷;
- INFRA_FAILURE:供电、网络、Host、OOB 等基础设施问题;
- SCRIPT_FAILURE:测试脚本或工具问题;
- DATA_INTEGRITY:数据校验失败,立即冻结;
- UNKNOWN:证据不足,不能强行归类。
5.6 检查点与断点续跑
检查点只在安全的步骤边界创建,不在无法证明幂等的步骤中间创建。
| 中断位置 | 恢复后的行为 |
|---|---|
| 步骤已 SUCCEEDED 且检查点已保存 | 从下一步继续 |
| RUNNING 步骤幂等 | 可重跑当前步骤 |
| RUNNING 步骤非幂等 | 不自动重跑,INCONCLUSIVE 并等待人工确认 |
| 循环体中的步骤 | 从当前 loop_index 的循环起点重跑整轮 |
检查点至少要能确定 loop_index、下一步骤、当前固件/环境指纹和最后事件序号。保存检查点必须先于 推进下一步骤;恢复时必须验证数据库投影、Agent attempt 和 DUT 当前状态一致。
5.7 心跳与双通道观测
Agent 正常心跳:每 5 秒
15 秒未收到:Host → DEGRADED,只记事件,不立刻做危险动作
30 秒未收到:Host → OFFLINE,并结合 OOB 判断
├── OOB 在线且主机有电:可进入 L3
├── OOB 在线但主机无电:INFRA_FAILURE
└── OOB 也离线:INFRA_FAILURE + FREEZE
心跳恢复:核对 Run、Step、attempt、租约和 DUT 枚举后再决定续跑
单一心跳、网络 ping 或 UI 状态都不能独立证明 DUT 故障。涉及恢复和缺陷归因时必须组合 Agent、 Host 网络、OOB 电源/画面和 DUT 枚举至少两个通道的证据。
5.8 六项安全门禁
创建 Run 前至少检查:
- discovered_serial 与登记 DUT 序列号完全一致;
- 目标不是系统盘,且没有系统分区或根文件系统;
- 破坏性动作已有明确授权且授权尚未过期;
- 固件型号、目标设备和校验值匹配;
- Host 在线、未被其他 Run 占用且环境指纹满足要求;
- 需要 OOB 的工作流必须确认 OOB 在线且能力匹配。
任一门禁失败都进入 REJECTED,不得通过 UI、API 参数或直接数据库写入绕过。真实危险命令还必须 增加签名命令模板、参数 schema、allowlist、审批人、审批时间、可牺牲 DUT 和短期授权。
5.9 Evidence Bundle
最小证据包包含 manifest.json 与按 seq 排序的 events.ndjson,并记录:
- Run、Workflow、DUT、Firmware、Host 和环境指纹;
- 完整事件时间线和人工介入;
- 步骤结果、退出码、日志 URI;
- 恢复动作与恢复前后证据;
- 检查点、verdict、完整率、大小和哈希。
大日志和二进制产物存对象目录,数据库只保存索引、摘要和 URI。事件按控制平面分配的 seq 排序, 不能依赖不同主机的本地时间戳。任何人工暂停、继续、急停、审批或覆盖默认策略的动作都必须留下事件。
6. AI 如何安全使用正在运行的系统
6.0 控制台页面
| 页面 | URL | 当前能力 |
|---|---|---|
| 总览 | /console/Dashboard.dc.html | 产品看板,部分指标仍为静态展示 |
| 任务中心 | /console/Tasks.dc.html | 已接安全预检和创建 Run |
| 实时运行 | /console/LiveRun.dc.html | 已接 Run、事件、演示、暂停、继续和急停 |
| 证据浏览器 | /console/Evidence.dc.html | 主要为静态样例,真实下载待接 |
| 失败中心 | /console/Failures.dc.html | 静态原型,失败聚类执行待实现 |
| 资产中心 | /console/Assets.dc.html | 静态原型,资产 CRUD 待实现 |
| 版本比较 | /console/Compare.dc.html | 静态原型,真实 A/B 查询待实现 |
| 工作流模板 | /console/Workflows.dc.html | 静态原型,编辑与发布 API 待实现 |
控制台和 API 同源,动态请求使用 /api/v1 相对路径。页面展示不是后端能力证据;AI 必须通过 API 返回或测试确认功能是否真实接入。事件轮询使用 after_seq 增量读取。
6.1 公网只读检查
以下请求只读取公开信息,可用于健康诊断:
curl -fsS https://flashops.imagebrewing.com/api/v1/health
curl -fsS https://flashops.imagebrewing.com/api/v1/dashboard/summary
curl -fsS 'https://flashops.imagebrewing.com/api/v1/runs?limit=10'
不要在诊断任务中为了“试一下”调用生产 POST 接口。健康检查成功不代表业务能力或真实硬件已经验收。 当前公网 OpenAPI 版本为 0.1.0,Agent 路径尚未发布,访问 /api/v1/agent/hosts 返回 404 是已知部署 边界,不应误判成 Gitea、Nginx 或 Host 故障。
Run 查询的关键字段:
| 字段 | 含义 |
|---|---|
| id、name | Run 标识和名称 |
| state、verdict | 当前状态与 PASS / FAIL / INCONCLUSIVE |
| execution_mode | simulated 或 agent_dry_run |
| workflow_key、workflow_version | 固定的工作流版本 |
| dut_id、test_host_id | 绑定资产 |
| loop_target、loop_done、progress | 循环目标与完成进度 |
| checkpoint | 最近安全恢复点 |
| environment | 创建时冻结的环境指纹 |
| recovery_count | 已执行恢复次数 |
| unattended_completion | 是否无人干预完成 |
| human_touches | 人工介入次数 |
| freeze_reason | 冻结原因 |
| evidence | 详情接口返回的证据包索引 |
事件字段为 id、seq、ts、source、kind、severity、step_id、loop_index、message、payload。必须按 seq 消费;after_seq 传客户端最后确认的 seq。source 可能是 control_plane、agent、oob 或 human。
6.2 本地启动
cd flashops
make dev
另开终端检查:
curl -fsS http://127.0.0.1:8000/api/v1/health
curl -fsS http://127.0.0.1:8000/api/v1/dashboard/summary
6.3 本地创建安全的模拟 Run
只在 localhost 使用下面示例:
curl -fsS -X POST http://127.0.0.1:8000/api/v1/runs \
-H 'Content-Type: application/json' \
-d '{
"name": "AI local verification",
"loops": 2,
"inject_failure": true,
"execution_mode": "simulated",
"created_by": "ai-local"
}'
返回 Run ID 后:
curl -fsS http://127.0.0.1:8000/api/v1/runs/RUN_ID
curl -fsS 'http://127.0.0.1:8000/api/v1/runs/RUN_ID/events?after_seq=0'
也可以直接执行 make demo;它会创建独立的本地模拟链路并输出 Evidence Bundle 路径。
6.4 API 快速索引
下表中的非 Agent 路径已在当前公网部署;Agent 路径属于仓库代码基线,当前只适合本地开发和测试。
| 方法与路径 | 含义 | 默认安全级别 |
|---|---|---|
| GET /api/v1/health | 服务健康 | 只读 |
| GET /api/v1/dashboard/summary | 汇总指标 | 只读 |
| GET /api/v1/runs | Run 列表 | 只读 |
| GET /api/v1/runs/{run_id} | Run 详情与证据 | 只读 |
| GET /api/v1/runs/{run_id}/events | 增量事件 | 只读 |
| GET /api/v1/agent/hosts | Agent Host 状态 | 只读 |
| POST /api/v1/preflight | 安全预检 | 计算型;生产调用仍需明确目的 |
| POST /api/v1/runs | 创建 Run | 写操作;生产默认禁止 |
| POST /api/v1/runs/demo | 创建演示 Run | 写操作;生产默认禁止 |
| POST /api/v1/runs/{id}/pause | 暂停 | 写操作 |
| POST /api/v1/runs/{id}/resume | 继续 | 写操作 |
| POST /api/v1/runs/{id}/emergency-stop | 紧急停止 | 高影响写操作 |
| POST /api/v1/agent/* | Agent 注册与执行协议 | 凭据操作;不得随意调用 |
核心请求体已经内嵌如下;运行时 OpenAPI 只用于核验部署版本是否发生变化,不是阅读本文的前置条件。
Preflight 请求:
{
"dut_id": "可选 DUT ID",
"host_id": "可选 Host ID",
"firmware_id": "可选固件 ID",
"discovered_serial": "探针实际发现的序列号"
}
Create Run 请求:
{
"name": "固件 A/B 无人值守回归",
"workflow_id": null,
"dut_id": null,
"host_id": null,
"firmware_a_id": null,
"firmware_b_id": null,
"loops": 8,
"inject_failure": true,
"execution_mode": "simulated",
"created_by": "console-demo"
}
- loops 范围 1–500;
- execution_mode 只能是 simulated 或 agent_dry_run;
- simulated 会由控制平面启动内置模拟执行器;
- agent_dry_run 会物化步骤并等待独立 Agent 领取,但不允许真实主机命令。
暂停、继续和紧急停止使用:
{
"actor": "操作者或 AI 任务标识",
"reason": "可审计的具体原因"
}
常见状态码:
| 状态码 | 含义 |
|---|---|
| 200 | 查询或动作成功 |
| 201 | Run、注册等资源创建成功 |
| 204 | Agent 当前没有可领取步骤 |
| 400 | 业务参数或资产关系无效 |
| 401 | Agent 注册口令或 bearer token 无效 |
| 404 | Run、Host、DUT 等对象不存在 |
| 409 | 非法状态、资源冲突、旧 attempt 或幂等键冲突 |
| 422 | 请求字段或 Idempotency-Key 校验失败 |
| 503 | 生产环境缺少 Agent 注册口令 |
6.5 Agent 使用规则
- 本节描述仓库已经实现但尚未发布到公网的 Agent 协议;不要向当前生产域名调用这些端点;
- Agent 是拉模式,控制平面永远不主动连接测试主机;
- 生产注册需要 X-FlashOps-Enrollment-Token;
- 注册成功返回的原始 bearer token 只出现一次,只能保存到权限 0600 的本机状态文件;
- 后续 Agent 请求使用 Authorization: Bearer;
- ACK、续租、事件和完成接口要求 UUID 格式的 Idempotency-Key;
- 不得把 enrollment token、bearer token 或 agent-state.json 输出到聊天、日志或 Git;
- 未经授权不得代表真实 Host 注册、轮换 token 或领取生产步骤;
- 日常开发优先使用 make dev-agent、make agent-demo 和测试夹具。
Agent 端点和顺序:
| 顺序 | 方法与路径 | 行为 |
|---|---|---|
| 1 | POST /api/v1/agent/register | 注册或轮换 token,上报 Host 指纹 |
| 2 | POST /api/v1/agent/{agent_id}/heartbeat | 上报健康、工具链和设备探针 |
| 3 | POST /api/v1/agent/{agent_id}/lease | 领取步骤,最长等待 20 秒 |
| 4 | POST /api/v1/agent/{agent_id}/steps/{step_id}/ack | 确认当前 attempt |
| 5 | POST /api/v1/agent/{agent_id}/steps/{step_id}/renew | 续租,不超过步骤 timeout |
| 6 | POST /api/v1/agent/{agent_id}/steps/{step_id}/events | 批量上报 1–100 条事件 |
| 7 | POST /api/v1/agent/{agent_id}/steps/{step_id}/complete | 上报 SUCCEEDED 或 FAILED |
注册体至少需要 agent_version,可选 host_id 或 host_name,并可携带 os_family、os_version、kernel、 cpu、motherboard、bios_version、ip_address、toolchain 和 labels。心跳体可携带 healthy、 agent_version、ip_address、toolchain 和 device_probe。
租约请求为:
{"wait_timeout_s": 0}
wait_timeout_s 范围 0–20。ACK 与 renew 请求为 {"attempt": 1}。事件请求示例:
{
"attempt": 1,
"events": [
{
"message": "step started",
"severity": "INFO",
"payload": {}
}
]
}
完成请求示例:
{
"attempt": 1,
"status": "SUCCEEDED",
"exit_code": 0,
"error_class": null,
"result": {},
"log_uri": null
}
ACK、renew、events、complete 都必须带新的 UUID 格式 Idempotency-Key。相同操作重放返回当前投影 并标记 replayed=true;同一个 key 用于其他步骤或其他动作时返回 409。重新注册会轮换 token,使旧 token 立即失效。控制平面只保存 token 的 SHA-256 摘要。
7. 三条不可破坏的架构红线
7.1 控制面是状态唯一真相
Agent 和测试主机只持有执行缓存。Run、Step、锁、检查点和证据索引的权威状态在控制平面。 不得把 Agent 本地状态反向当成数据库真相。
7.2 状态变化必须追加事件
所有 Run 状态变化必须经过 events/recorder.py 的 append_event。不得绕过事件记录直接 UPDATE Run 状态。新增状态或事件时必须同步修改枚举、转移表、投影逻辑、测试和文档。
7.3 安全默认拒绝
序列号、破坏性授权、系统盘、固件型号、Host 和 OOB 任一条件不满足都必须拒绝下发。危险步骤 默认不可自动重试。数据完整性失败要冻结现场,不能为了完成率自动重跑并覆盖证据。
8. AI 修改代码的标准流程
8.1 开始前
- 确认用户目标属于解释、诊断、开发还是部署;
- 检查 Git 状态、当前分支和已有改动;
- 阅读与任务直接相关的源码、测试和文档;
- 找到当前行为的证据,不能只根据 UI 文案猜测;
- 若涉及高影响选择,先说明方案和边界。
8.2 实现时
- 修改现有项目,不随意建立第二套平行架构;
- 优先小而完整的改动,不混入无关格式化;
- 修复缺陷时增加能复现问题的测试;
- API、状态、事件、环境变量或部署变化必须同步文档;
- 保持 Python 3.9 兼容,不在未修改运行基线前使用更高版本独占语法;
- SQLite 环境保持单 worker;
- 不绕过 service、event recorder、state machine 或 safety gate;
- 保留用户未提交的改动,不擅自覆盖或清理。
8.3 完成前
cd flashops
make test
make demo
回到仓库根目录:
git status -sb
git diff --check
git diff --stat
只暂存本次任务相关文件:
git add <明确的文件或目录>
git diff --cached --stat
git diff --cached --check
AI 还必须确认暂存区没有:
- .env、密码、Token、私钥或证书私钥;
- SQLite、日志、Evidence Bundle、Agent state;
- 虚拟环境、缓存、构建输出;
- 未脱敏的客户数据或真实设备标识;
- 无关的用户改动。
9. 测试与验收门禁
| 变更类型 | 最低验证 |
|---|---|
| 纯文档 | 链接和命令核对、git diff --check |
| Python 逻辑 | make test |
| 状态机、恢复、安全 | 对应单测 + make test + make demo |
| API | 正常、错误、幂等或冲突路径测试 |
| Agent | 控制面与 host-agent 测试,凭据权限检查 |
| 前端 | 真实浏览器检查受影响页面,确认控制台与 API 一致 |
| Nginx | nginx -t 后才能 reload |
| systemd | systemd-analyze verify 或目标机安全验证 |
| 生产部署 | 本地测试、备份、健康检查、HTTPS、日志和回滚检查 |
测试失败时不得把失败包装成完成。先判断是本次回归、环境差异还是已有问题,并保留原始证据。
10. Git 与 Gitea 交付规范
10.1 分支
- main:稳定、可测试、可发布,禁止强推;
- feat/:功能;
- fix/:缺陷;
- docs/:文档;
- chore/:工具、依赖和部署。
git switch main
git pull --ff-only
git switch -c docs/ai-operating-guide
10.2 提交
使用 Conventional Commits:
| 类型 | 示例 |
|---|---|
| feat | feat(agent): add signed command registry |
| fix | fix(events): preserve sequence under concurrent writes |
| docs | docs(ai): add system operating guide |
| test | test(safety): reject system disk target |
| refactor | refactor(runs): isolate checkpoint projection |
| perf | perf(api): reduce event timeline query cost |
| chore | chore(deploy): pin gitea image |
一次提交只表达一个完整意图。提交前检查暂存区,不能用一条模糊提交吞掉多项无关工作。
10.3 推送与 Pull Request
git commit -m 'docs(ai): add system operating guide'
git push -u origin docs/ai-operating-guide
在 Gitea 创建 Pull Request,说明:
- 改了什么、为什么;
- 影响的系统边界;
- 实际运行的测试及结果;
- 安全影响;
- 风险、回滚方法和未完成项。
至少一名团队成员评审后合并。优先 Squash merge,PR 标题使用 Conventional Commit。 生产服务器没有 Gitea Actions Runner;仓库中的工作流是未来隔离 Runner 的模板,不能把“工作流存在” 误报成“CI 已运行”。
10.4 凭据
公开仓库允许匿名读取和克隆。写操作需要经过批准的 Gitea 账号。管理员密码和访问令牌只允许存在于 系统钥匙串、密码管理器或被 .gitignore 覆盖的本机文件中;不得写进 remote URL、文档、脚本或提交。
11. 生产部署上下文
| 资源 | 当前值 |
|---|---|
| 云服务器 | 腾讯云 CVM,62.234.39.66 |
| 操作系统 | Ubuntu 24.04 |
| DNS | DNSPod A 记录,TTL 600 |
| FlashOps 域名 | flashops.imagebrewing.com |
| FlashOps 服务 | flashops.service |
| 当前公网 API | 0.1.0,仅模拟 Run,未发布 Agent 路由 |
| 应用用户 | ubuntu |
| 应用目录 | /data/wangzhan/app/storage-labos |
| 内部端口 | 127.0.0.1:18080 |
| 应用数据 | /data/wangzhan/app/storage-labos/flashops/var |
| 私有环境文件 | /etc/flashops/flashops.env |
| FlashOps Nginx | /www/server/panel/vhost/nginx/flashops.imagebrewing.com.conf |
| FlashOps 证书 | /etc/letsencrypt/live/flashops.imagebrewing.com/ |
| Gitea 域名 | git.imagebrewing.com |
| Gitea 容器 | gitea,镜像版本固定为 1.27.0 |
| Gitea 内部端口 | 127.0.0.1:13000 |
| Gitea 实时数据 | /data/gitea/data |
| Gitea Nginx | /www/server/panel/vhost/nginx/git.imagebrewing.com.conf |
| Gitea 证书 | /etc/letsencrypt/live/git.imagebrewing.com/ |
| COS 挂载 | /chucun |
| Gitea 备份 | /chucun/wangzhan-production/backups/gitea/ |
生产操作规则:
- 只有用户明确要求部署或运维时才连接服务器;
- 先核对主机、域名、当前服务、端口和目标文件;
- 部署前运行本地测试并备份当前版本和数据;
- 不直接在服务器编辑项目源码,应由 Git 提交或可核验发布包交付;
- Nginx 修改必须先测试配置再 reload;
- SQLite 继续使用单 worker,扩展并发前先迁移 PostgreSQL;
- Gitea 实时仓库和 SQLite 不得放到 COSFS;
- COS 只作备份目标,备份需要 ZIP 与 SHA-256 sidecar;
- 不在输出中展示 SSH、Gitea、Agent、DNS 或云平台凭据;
- 部署后验证内部 health、外部 HTTPS、最终跳转、日志和回滚点。
FlashOps 当前是匿名预览环境。接入真实硬件或非公开数据前,必须先完成应用级 SSO / RBAC、 审批、审计和命令模板签名;不能只依赖 Nginx Basic Auth。
11.1 FlashOps 服务管理与健康检查
只有获得服务器运维授权后才执行:
sudo systemctl status flashops --no-pager -l
sudo systemctl is-enabled flashops
curl -fsS http://127.0.0.1:18080/api/v1/health
curl -fsS https://flashops.imagebrewing.com/api/v1/health
需要重启时先确认没有不可中断的 Run,再执行:
sudo systemctl restart flashops
sudo systemctl status flashops --no-pager -l
curl -fsS http://127.0.0.1:18080/api/v1/health
服务必须只绑定 127.0.0.1:18080,不能把 Uvicorn 直接暴露公网。当前 SQLite 只允许运行一个 worker;增加 worker 前必须先迁移 PostgreSQL,并处理跨进程事件总线与锁。
关键环境变量:
| 变量 | 作用 |
|---|---|
| FLASHOPS_ENV | production 或 development |
| FLASHOPS_DATABASE_URL | 数据库 DSN |
| FLASHOPS_OBJECT_STORE_URL | 证据对象目录或对象存储 |
| FLASHOPS_BUS_URL | 外部事件总线,当前未启用 |
| FLASHOPS_AGENT_ENROLLMENT_TOKEN | Agent 首次注册口令 |
| FLASHOPS_API_HOST | API 监听地址 |
| FLASHOPS_API_PORT | API 端口 |
| FLASHOPS_CORS_ORIGINS | 允许的前端来源 |
| FLASHOPS_ENGINE_TICK_S | 引擎 tick |
| FLASHOPS_TIME_SCALE | 模拟时间倍率 |
生产秘密只放在 root-only、权限 0600 的 /etc/flashops/flashops.env。不得把值写进 systemd unit、 Nginx 注释、命令历史、聊天或 Git。
11.2 日志
sudo journalctl -u flashops -n 200 --no-pager
sudo journalctl -u flashops --since '30 minutes ago' --no-pager
sudo tail -n 100 /www/wwwlogs/flashops.imagebrewing.com.log
sudo tail -n 100 /www/wwwlogs/flashops.imagebrewing.com.error.log
持续跟踪日志只能用于短时诊断,结束后退出,避免长期占用会话。输出日志前先检查是否包含 token、 客户数据、设备序列号或其他敏感信息。
11.3 Nginx、DNS 与证书
任何配置修改都必须先测试,成功后才能 reload:
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certificates
getent ahostsv4 flashops.imagebrewing.com
getent ahostsv4 git.imagebrewing.com
证书模拟续期:
sudo certbot renew --dry-run --non-interactive --no-random-sleep-on-renew
两个域名应指向 62.234.39.66;HTTP 应 301 到 HTTPS;证书 SAN 必须匹配访问域名。ACME challenge 路径必须保持可达。证书续期后由 deploy hook reload Nginx。
FlashOps 当前配置不应包含 auth_basic、allow 或 deny 访问限制。若匿名访问出现 401/403,先检查 浏览器旧认证缓存、Nginx 生效配置和上游安全产品;Agent API 因 token 无效返回 401 是另一回事。
11.4 标准发布流程
- 确认目标 Git 提交、工作树和本次发布范围;
- 在本地执行 make test 与 make demo;
- 检查暂存和发布内容不含 .venv、var、缓存、凭据和运行产物;
- 使用 SQLite 在线备份 API 备份当前生产数据库;
- 保存当前可运行发布包、SHA-256 和回滚说明;
- 将新版本上传到独立 incoming 目录,不覆盖正在运行的目录;
- 校验上传包 SHA-256,安装或复用服务器虚拟环境;
- 保留生产 flashops/var,绝不能用本地空 var 覆盖;
- 校验 systemd unit 和私有环境文件权限;
- 切换版本并重启 flashops;
- 轮询 127.0.0.1:18080/api/v1/health;
- 执行 nginx -t,必要时 reload;
- 从服务器内部和外部各验证一次 HTTPS、最终跳转、health 和 dashboard JSON;
- 检查最近日志无新 error,并记录提交号、时间、备份和回滚点。
不要在生产目录直接手工改源码。发布物必须能映射到 Git 提交。发布失败时优先切回已经验证的旧发布 目录,并保留失败目录和日志用于排查,不能边修边覆盖证据。
11.5 SQLite 备份与恢复边界
运行中的 SQLite 应使用在线备份 API,不要直接复制可能正在写入的数据库:
sudo /data/wangzhan/app/storage-labos/flashops/.venv/bin/python - <<'PY'
import datetime
import sqlite3
from pathlib import Path
source = Path('/data/wangzhan/app/storage-labos/flashops/var/flashops.db')
stamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
target = Path('/data/wangzhan/backups') / ('flashops-db-' + stamp + '.db')
target.parent.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(source) as src, sqlite3.connect(target) as dst:
src.backup(dst)
print(target)
PY
备份后记录文件大小和 SHA-256。数据库恢复是高影响动作,本文不授权 AI 自动执行。获明确授权后也要:
- 停止 FlashOps;
- 核对恢复目标、备份时间和校验值;
- 再备份当前数据库;
- 替换后修复 ubuntu:ubuntu 所有权和文件权限;
- 启动服务;
- 验证 health、表数量、最新 Run 和事件 seq 连续性;
- 保留恢复前数据库,直到验收完成。
11.6 Gitea 运维与 COS 备份
Gitea 公开仓库允许匿名查看和 HTTPS 克隆;账号注册后需要管理员人工批准。当前不开放独立 Git SSH 端口,也没有生产 Actions Runner。
获授权后的只读检查:
sudo docker ps --filter name=^gitea$
sudo docker logs --tail 100 gitea
curl -fsS http://127.0.0.1:13000/api/healthz
sudo systemctl status flashops-gitea-backup.timer --no-pager
sudo systemctl list-timers flashops-gitea-backup.timer --no-pager
手工触发一次可恢复的 Gitea dump:
sudo systemctl start flashops-gitea-backup.service
sudo systemctl status flashops-gitea-backup.service --no-pager
find /chucun/wangzhan-production/backups/gitea -maxdepth 1 -type f \
-name 'gitea-*.zip' -ls
备份脚本使用 Gitea 自身 dump,生成 ZIP 和 SHA-256 sidecar,成功复制到 COS 后清理本机临时包。 定时器每日约 03:20 运行并带随机延迟。实时仓库、Git 对象和 Gitea SQLite 必须留在 /data/gitea/data 本地文件系统;COSFS 不提供数据库依赖的完整锁与原子语义,只能用于备份。
Gitea 恢复或升级是高影响操作:必须先验证备份、阅读目标版本迁移说明、安排维护窗口,并保留旧容器 镜像和 /data/gitea/data 快照。镜像必须固定版本,不能使用 latest。
11.7 故障定位
公网 502:
sudo systemctl status flashops --no-pager -l
sudo journalctl -u flashops -n 200 --no-pager
curl -v http://127.0.0.1:18080/api/v1/health
sudo nginx -t
sudo tail -n 100 /www/wwwlogs/flashops.imagebrewing.com.error.log
常见原因是服务未启动、Python 依赖错误、18080 端口不一致、var 不可写或 systemd 安全策略阻止路径。
SQLite locked:
- 确认只有一个 Uvicorn worker;
- 检查是否有脚本绕过 session 直接写数据库;
- 所有状态写入应走同一事务和事件入口;
- 需要多进程时迁移 PostgreSQL,不能继续增加 SQLite 锁重试掩盖问题。
Gitea 页面不可访问:
sudo docker ps --filter name=^gitea$
sudo docker logs --tail 100 gitea
curl -fsS http://127.0.0.1:13000/api/healthz
getent ahostsv4 git.imagebrewing.com
sudo nginx -t
HTTPS 证书异常时核对 DNS、证书路径、证书域名、有效期和 Nginx 是否已 reload。不要在原因未确认时 重复签发证书或删除现有证书目录。
11.8 生产验收清单
- flashops.service 为 active;
- Gitea 容器为 running,loopback health 为 pass;
- Nginx active 且 nginx -t 成功;
- 两个域名解析到 62.234.39.66;
- HTTP 301 到 HTTPS;
- 两张证书域名正确、未过期、自动续期可用;
- 未登录访问 FlashOps 控制台最终返回 200;
- FlashOps health 返回 status=ok;
- dashboard summary 返回合法 JSON;
- 未登录可以查看并克隆 public Git 仓库;
- 生产 var 数据可写,但应用源码目录不被随意写入;
- 最新日志没有新增 error;
- FlashOps 数据库备份有 SHA-256;
- Gitea COS dump 有 ZIP 和有效 SHA-256 sidecar;
- 发布提交号、时间和回滚点已经记录。
12. 常见任务剧本
12.1 “看看项目并继续完善”
- 以本文的能力边界、状态机和安全规则建立基线;
- 若获得仓库,检查工作树、源码和测试;只有本文时先做线上只读核验;
- 运行可用的测试与模拟演示;
- 区分已实现、部分实现和待实现;
- 优先选择一个能独立验收的纵向切片;
- 实现、补测试、同步本文;
- 报告实际证据,不把规划写成完成。
12.2 “排查网站打不开”
- 从公网检查 DNS、TLS、重定向和最终状态码;
- 检查 Nginx 生效配置和日志;
- 检查本机内部端口和服务状态;
- 区分 401、403、404、502 和浏览器缓存;
- 诊断任务只报告原因,除非用户同时要求修复。
12.3 “修改 API”
- 只有获得仓库后才能实施修改;先核对路由、schema、service 和测试;
- 保持业务逻辑不堆在 route;
- 状态写入走事件记录;
- 为正常与错误路径增加测试;
- 检查 OpenAPI;
- 更新本文中的接口、状态码、权限和部署说明。
12.4 “增加真实硬件能力”
先停止直接实现危险命令。必须先确认:
- 使用可牺牲 DUT,不是系统盘;
- 有明确序列号和固件型号匹配;
- 已实现签名命令模板与 allowlist;
- 有审批、短期授权、审计和 Evidence Bundle;
- Agent 与控制面身份可信;
- 有恢复、急停和人工接管方案;
- 用户明确批准真实动作。
条件不完整时,只允许实现接口、模拟器、只读探针和测试。
12.5 “发布一个版本”
- 确认工作树和目标提交;
- 运行完整测试与演示;
- 检查秘密和发布范围;
- 备份服务器应用数据;
- 部署可追溯提交;
- 重启并验证内部与公网健康;
- 检查日志、证书和备份;
- 记录提交号、时间、验证结果和回滚点。
13. AI 交付时的固定报告格式
AI 最终回复至少包含:
结果:
- 本次目标是否完成
改动:
- 文件与关键行为
验证:
- 实际运行的命令
- 通过/失败数量
- 浏览器或生产验收结果
Git:
- 分支
- 提交号
- Pull Request 或远程地址
风险与边界:
- 尚未实现或未验证的内容
- 是否涉及生产、真实设备或凭据
下一步:
- 仅列真正需要用户决定或后续开发的事项
不得只说“应该可以”“理论上完成”。必须区分已执行验证、静态检查和推断。
14. 完成定义
一项 AI 开发任务只有同时满足以下条件才算完成:
- 用户要求的行为已经实现,而不是只写方案;
- 原有未提交工作得到保护;
- 相关测试通过,或失败原因被明确记录;
- 安全门禁、事件写路径和状态机未被绕过;
- 文档与实际代码、API 和部署保持一致;
- 没有提交凭据、生产数据或运行产物;
- Git 提交范围清晰并可回滚;
- 如果要求部署,公网、内部健康、日志和备份均已验证;
- 最终报告包含证据、提交号、风险和边界。
当本文件中的域名、端口、路径、API、安全策略、测试数量或部署结构发生变化时,修改代码的 AI 必须在同一个 Pull Request 中同步更新本文件。