Files
storage-labos/CONTRIBUTING.md
T
yuanshuai 7c1ff5d82e
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run
docs(ai): make operating guide fully standalone
2026-07-27 21:07:48 +08:00

44 KiB
Raw Blame History

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,仅模拟 RunAgent 路由尚未发布
当前测试基线 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 不应只根据文件名或旧说明猜测当前能力,按下面顺序取证:

  1. 当前用户的明确任务、授权与限制;
  2. 本文件写明的安全边界和不可执行动作;
  3. 线上健康检查、API 返回、服务状态和日志等实际证据;
  4. 如果 AI 获得仓库:当前工作树、Git diff、源码和自动化测试;
  5. 如果 AI 获得服务器:生效配置、运行进程、数据路径和备份;
  6. 历史提交或历史部署记录;
  7. 推测。

如果本文、代码、测试和生产状态互相矛盾,必须在交付说明中指出。不得把“规划”“草案”“静态 页面”描述成已经可用于真实硬件的能力,也不得因为拥有技术访问能力就推断拥有写入授权。


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 控制器

五个逻辑层:

  1. 产品交互层:控制台页面和 HTTP API;
  2. 控制平面:工作流物化、状态机、安全门禁、资源锁、事件和恢复决策;
  3. 数据面:客户测试主机上的拉模式 Agent,只执行控制平面批准的步骤;
  4. 带外平面:独立于测试主机的 OOB 观测与电源控制,主机死亡后仍应可用;
  5. 证据与分析层:事件时间线、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_OUTRun 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 冻结现场并停止自动恢复 数据完整性失败或阶梯耗尽

不可更改的恢复规则:

  1. 数据完整性不一致直接 FREEZE,不能先重启或重跑;
  2. OOB 离线时 L3L5 不可用,应标记 INFRA_FAILURE 并冻结,不能误判 DUT_DEFECT
  3. 每次恢复必须记录 trigger、level、outcome、时间和 evidence_uri
  4. 恢复成功只表示系统重新可执行,不表示原步骤成功;
  5. 每循环最多恢复 3 次,每 Run 最多恢复 20 次;
  6. L5 断电后默认安全等待 10 秒再上电;
  7. 真实 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 前至少检查:

  1. discovered_serial 与登记 DUT 序列号完全一致;
  2. 目标不是系统盘,且没有系统分区或根文件系统;
  3. 破坏性动作已有明确授权且授权尚未过期;
  4. 固件型号、目标设备和校验值匹配;
  5. Host 在线、未被其他 Run 占用且环境指纹满足要求;
  6. 需要 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.0Agent 路径尚未发布,访问 /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 范围 1500
  • 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 批量上报 1100 条事件
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 范围 020。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 开始前

  1. 确认用户目标属于解释、诊断、开发还是部署;
  2. 检查 Git 状态、当前分支和已有改动;
  3. 阅读与任务直接相关的源码、测试和文档;
  4. 找到当前行为的证据,不能只根据 UI 文案猜测;
  5. 若涉及高影响选择,先说明方案和边界。

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 mergePR 标题使用 Conventional Commit。 生产服务器没有 Gitea Actions Runner;仓库中的工作流是未来隔离 Runner 的模板,不能把“工作流存在” 误报成“CI 已运行”。

10.4 凭据

公开仓库允许匿名读取和克隆。写操作需要经过批准的 Gitea 账号。管理员密码和访问令牌只允许存在于 系统钥匙串、密码管理器或被 .gitignore 覆盖的本机文件中;不得写进 remote URL、文档、脚本或提交。


11. 生产部署上下文

资源 当前值
云服务器 腾讯云 CVM62.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/

生产操作规则:

  1. 只有用户明确要求部署或运维时才连接服务器;
  2. 先核对主机、域名、当前服务、端口和目标文件;
  3. 部署前运行本地测试并备份当前版本和数据;
  4. 不直接在服务器编辑项目源码,应由 Git 提交或可核验发布包交付;
  5. Nginx 修改必须先测试配置再 reload;
  6. SQLite 继续使用单 worker,扩展并发前先迁移 PostgreSQL;
  7. Gitea 实时仓库和 SQLite 不得放到 COSFS
  8. COS 只作备份目标,备份需要 ZIP 与 SHA-256 sidecar
  9. 不在输出中展示 SSH、Gitea、Agent、DNS 或云平台凭据;
  10. 部署后验证内部 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.66HTTP 应 301 到 HTTPS;证书 SAN 必须匹配访问域名。ACME challenge 路径必须保持可达。证书续期后由 deploy hook reload Nginx。

FlashOps 当前配置不应包含 auth_basic、allow 或 deny 访问限制。若匿名访问出现 401/403,先检查 浏览器旧认证缓存、Nginx 生效配置和上游安全产品;Agent API 因 token 无效返回 401 是另一回事。

11.4 标准发布流程

  1. 确认目标 Git 提交、工作树和本次发布范围;
  2. 在本地执行 make test 与 make demo
  3. 检查暂存和发布内容不含 .venv、var、缓存、凭据和运行产物;
  4. 使用 SQLite 在线备份 API 备份当前生产数据库;
  5. 保存当前可运行发布包、SHA-256 和回滚说明;
  6. 将新版本上传到独立 incoming 目录,不覆盖正在运行的目录;
  7. 校验上传包 SHA-256,安装或复用服务器虚拟环境;
  8. 保留生产 flashops/var,绝不能用本地空 var 覆盖;
  9. 校验 systemd unit 和私有环境文件权限;
  10. 切换版本并重启 flashops
  11. 轮询 127.0.0.1:18080/api/v1/health
  12. 执行 nginx -t,必要时 reload
  13. 从服务器内部和外部各验证一次 HTTPS、最终跳转、health 和 dashboard JSON
  14. 检查最近日志无新 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 自动执行。获明确授权后也要:

  1. 停止 FlashOps
  2. 核对恢复目标、备份时间和校验值;
  3. 再备份当前数据库;
  4. 替换后修复 ubuntu:ubuntu 所有权和文件权限;
  5. 启动服务;
  6. 验证 health、表数量、最新 Run 和事件 seq 连续性;
  7. 保留恢复前数据库,直到验收完成。

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 容器为 runningloopback 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 “看看项目并继续完善”

  1. 以本文的能力边界、状态机和安全规则建立基线;
  2. 若获得仓库,检查工作树、源码和测试;只有本文时先做线上只读核验;
  3. 运行可用的测试与模拟演示;
  4. 区分已实现、部分实现和待实现;
  5. 优先选择一个能独立验收的纵向切片;
  6. 实现、补测试、同步本文;
  7. 报告实际证据,不把规划写成完成。

12.2 “排查网站打不开”

  1. 从公网检查 DNS、TLS、重定向和最终状态码;
  2. 检查 Nginx 生效配置和日志;
  3. 检查本机内部端口和服务状态;
  4. 区分 401、403、404、502 和浏览器缓存;
  5. 诊断任务只报告原因,除非用户同时要求修复。

12.3 “修改 API”

  1. 只有获得仓库后才能实施修改;先核对路由、schema、service 和测试;
  2. 保持业务逻辑不堆在 route
  3. 状态写入走事件记录;
  4. 为正常与错误路径增加测试;
  5. 检查 OpenAPI
  6. 更新本文中的接口、状态码、权限和部署说明。

12.4 “增加真实硬件能力”

先停止直接实现危险命令。必须先确认:

  • 使用可牺牲 DUT,不是系统盘;
  • 有明确序列号和固件型号匹配;
  • 已实现签名命令模板与 allowlist;
  • 有审批、短期授权、审计和 Evidence Bundle
  • Agent 与控制面身份可信;
  • 有恢复、急停和人工接管方案;
  • 用户明确批准真实动作。

条件不完整时,只允许实现接口、模拟器、只读探针和测试。

12.5 “发布一个版本”

  1. 确认工作树和目标提交;
  2. 运行完整测试与演示;
  3. 检查秘密和发布范围;
  4. 备份服务器应用数据;
  5. 部署可追溯提交;
  6. 重启并验证内部与公网健康;
  7. 检查日志、证书和备份;
  8. 记录提交号、时间、验证结果和回滚点。

13. AI 交付时的固定报告格式

AI 最终回复至少包含:

结果:
- 本次目标是否完成

改动:
- 文件与关键行为

验证:
- 实际运行的命令
- 通过/失败数量
- 浏览器或生产验收结果

Git:
- 分支
- 提交号
- Pull Request 或远程地址

风险与边界:
- 尚未实现或未验证的内容
- 是否涉及生产、真实设备或凭据

下一步:
- 仅列真正需要用户决定或后续开发的事项

不得只说“应该可以”“理论上完成”。必须区分已执行验证、静态检查和推断。


14. 完成定义

一项 AI 开发任务只有同时满足以下条件才算完成:

  • 用户要求的行为已经实现,而不是只写方案;
  • 原有未提交工作得到保护;
  • 相关测试通过,或失败原因被明确记录;
  • 安全门禁、事件写路径和状态机未被绕过;
  • 文档与实际代码、API 和部署保持一致;
  • 没有提交凭据、生产数据或运行产物;
  • Git 提交范围清晰并可回滚;
  • 如果要求部署,公网、内部健康、日志和备份均已验证;
  • 最终报告包含证据、提交号、风险和边界。

当本文件中的域名、端口、路径、API、安全策略、测试数量或部署结构发生变化时,修改代码的 AI 必须在同一个 Pull Request 中同步更新本文件。