01 · 系统总体方案
文档定位:本文件是全套开发文档的总纲,给出系统的分层架构、组件职责、数据流、安全边界与关键设计取舍。其余文档均从本文件展开。
关联文档:
CONTEXT.md(领域词汇)、docs/02-安全管控插件层设计.md、docs/03-Agent层设计-OpenClaw二次开发.md、docs/04-MCP安全网关设计.md、docs/05-数据库设计.md、docs/06-Web前端设计.md、docs/07-STM32边缘终端设计.md、docs/08-部署与运维.md、docs/09-原型验证方案.md、docs/10-答辩问答手册.md、docs/adr/
1. 课题定位与研究问题
1.1 业务背景
工业设备的非计划停机代价高昂。预测性运维(Predictive Maintenance)通过持续采集设备温度、振动等状态量,结合故障知识库与专家经验,在故障发生前给出预警与检修建议。
大模型驱动的 AI Agent 具备"读手册 + 看数据 + 写结论"的完整能力,天然适合这一场景。但把它放进工业现场,会遇到一个绕不开的问题:
Agent 越权。 一旦 Agent 通过提示注入或幻觉去读取了它不该读的数据(账号、审计日志、工单、其他产线的数据),工业系统面临的是合规与生产安全问题,而不是"回答得不准"。
1.2 本课题的研究问题
不是"能否让 Agent 做故障诊断"——那是已有答案的问题。
而是:
在一个真实的、基于开源框架二次开发的 Agent 系统上,如何设计并实现一层安全管控,使 Agent 的行为边界可约束、可审计、可复现?
本课题的主要实现部分是安全管控插件层,它必须与 Agent 内核、数据存储、边缘终端形成完整的、可运行的、可被验证的原型。
1.3 三条设计主线
| 主线 | 内容 | 对应文档 |
|---|---|---|
| 边界 | Agent 与业务数据之间不存在任何通路(双库分离 + 唯一出口) | 01 / 04 / 05 |
| 管控 | 每一次工具调用都被拦截、判定、裁剪、审计 | 02 / 03 |
| 验证 | 用真实硬件终端 + 反证实验证明边界成立 | 07 / 09 |
2. 系统分层架构
2.1 总体分层图
┌─────────────────────────────────────────────────────────────────────┐
│ 【前端层】Vue3 + TypeScript Web 管理界面(工程师人机界面) │
│ 设备看板 │ 数据曲线 │ Agent 分析报告 │ 审计日志 │ 工单 │ RBAC 管理 │
└───────────────────────────┬─────────────────────────────────────────┘
│ HTTPS(JWT)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【后端服务层】Node.js + TypeScript │
│ │
│ ┌────────────────────────┐ ┌──────────────────────────────┐ │
│ │ 业务接口服务 │ │ ★ MCP 安全网关(独立服务) │ │
│ │ business-api │ │ —— 系统安全边界 —— │ │
│ │ · 账号 / RBAC │ │ · 统一入口(Agent + 人类) │ │
│ │ · 设备 / 工单 CRUD │ │ · JWT / 设备令牌身份解析 │ │
│ │ · 审计日志查询 │ │ · 安全策略校验 │ │
│ │ · 报告展示 │ │ · 字段白名单裁剪 │ │
│ └───────────┬────────────┘ │ · 审计落库(fail-closed) │ │
│ │ └───────┬───────────┬──────────┘ │
└──────────────┼─────────────────────────────┼───────────┼────────────┘
│ │ │
│ 全量 CRUD │ 只读检索 │ 受限读
▼ ▼ ▼
┌──────────────────┐ ┌────────────┐ ┌──────────────────┐
│ business-db │ │ ai-db │ │ MQTT Broker │
│ 账号/RBAC │ │ 向量切片 │ │ (Mosquitto) │
│ 遥测/工单/设备 │ │ 会话记忆 │ └────────┬─────────┘
│ 审计日志/报告 │ └────────────┘ │
└──────────────────┘ │ MQTT
▲ ▼
│ ┌────────────────────────┐
│ │ 【终端层】STM32 │
│ │ + FreeRTOS + MQTT │
│ │ 温度 / 振动 只上报 │
│ └────────────────────────┘
│
│ 网关写入遥测与审计
│
┌─────────────┴───────────────────────────────────────────────────────┐
│ 【Agent 智能体层】OpenClaw 二次开发 │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ★ 安全管控插件层(本课题主要实现部分,OpenClaw 原生插件) │ │
│ │ api.on(...) 类型化钩子 → 策略引擎 → 参数改写 → 脱敏 → 审计上报 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ · Agent 内核:ReAct 推理循环 │
│ · 可插拔 Skill:故障分析 Skill、知识库检索 Skill │
│ · ⚠ 无任何数据库直连账号;无任何 MQTT 凭证 │
│ · 所有数据获取 → 调用 MCP 网关工具接口 │
└─────────────────────────────────────────────────────────────────────┘2.2 组件职责表
| 组件 | 进程 | 职责 | 持有凭证 | 对应包 |
|---|---|---|---|---|
| Web 前端 | 浏览器 | 工程师操作界面、报告与曲线展示、审计查询 | 无(仅 JWT) | apps/web |
| 业务接口服务 | 独立 Node 进程 | 面向人类的业务 CRUD,操作 business-db | BUSINESS_DB_URL、JWT 私钥 | packages/business-api |
| MCP 安全网关 | 独立 Node 进程 | 系统唯一对外数据出口;身份解析、策略校验、字段裁剪、审计落库 | BUSINESS_DB_URL、AI_DB_URL、MQTT 凭证、JWT 公钥 | packages/mcp-gateway |
| 安全管控插件层 | OpenClaw 进程内 | 工具调用拦截、策略判定、参数改写、脱敏、审计上报、配额 | 仅 Agent 令牌 + 网关地址 | packages/security-plugin |
| Agent 内核 | OpenClaw Gateway 进程 | ReAct 推理循环、Skill 调度、会话与记忆 | 同上 | packages/agent-config / OpenClaw 本体 |
| 共享契约 | 编译期 | MCP 工具契约、DTO、策略枚举 | 无 | packages/shared-contracts |
| MQTT Broker | Mosquitto | 终端上行、指令下行 | — | deploy/mosquitto |
| 边缘终端 | STM32 | 采集温度/振动,MQTT 上报,被动接收指令 | Broker 一机一密凭证 | firmware/stm32-sensor-node |
3. 核心安全设计:双库分离 + 唯一出口
3.1 问题:为什么"数据库只读账号"不够
给 Agent 分配一个只读数据库账号,看似解决了越权写的问题,但它只约束了读写动作,没有约束访问范围:
-- 只读账号完全允许执行
SELECT username, password_hash, email FROM user_account;
SELECT * FROM audit_log ORDER BY ts DESC LIMIT 1000;提示注入一旦成功,Agent 就可以把审计日志读出来、把账号列表读出来。日志上看起来只是"一条合法的 SELECT"。
3.2 本系统的做法:让 Agent 拿不到凭证
关键不变量:Agent 进程的环境变量中不存在 BUSINESS_DB_URL、AI_DB_URL、MQTT_URL 中的任何一个。
❌ 错误模式(绝对禁止)
Agent ──SQL──► business-db
Agent ──MQTT──► Broker(订阅 iot/+/telemetry)
✅ 本系统模式
Agent ──MCP 工具调用──► MCP 安全网关 ──► business-db / ai-db / Broker
(鉴权 + 裁剪 + 审计)Agent 拿到的只是网关裁剪过的精简数据:
// packages/mcp-gateway/src/tools/mqtt.read_sensor.ts(示意)
export async function tool_read_sensor_data(params: ReadSensorParams) {
// 1) 策略校验:该 Agent 是否被授权访问该设备
await policy.assertDeviceScope(ctx.agentId, params.deviceId);
// 2) 网关以自身凭证访问业务数据库(Agent 侧没有这个凭证)
// 注意:库里是窄表(metric / value 一行一指标),返回前由网关透视成宽表
const rows = await businessDb.query(
`SELECT metric, value, ts FROM sensor_data
WHERE device_id = ? AND ts BETWEEN ? AND ?
ORDER BY ts ASC LIMIT ?`,
[params.deviceId, params.from, params.to, params.limit]
);
// 3) 透视 + 字段白名单裁剪:只返回故障分析必需的三个字段
const simpleData = pivotByTs(rows).map((r) => ({
temp: r.temperature, // 单位 ℃
vibration: r.vibration, // 单位 mm/s
time: r.ts,
}));
// 4) 审计(fail-closed:落库失败则本次调用失败)
await audit.record({ tool: 'mqtt.read_sensor', params, rowCount: rows.length });
return simpleData;
}Agent 看不到 sensor_data 表的其他列(内部序列号、校准系数、产线编码),也无法执行任意 SQL。
存储形态与返回形态的分离是这里的关键设计:数据库里存什么形状是存储问题(窄表便于扩展指标),Agent 看到什么形状是接口契约与最小权限问题。两者解耦后,Agent 无法通过构造参数去查其他
metric,更无法查其他表。
3.3 双库权限矩阵
| 角色 | business-db | ai-db | MQTT Broker |
|---|---|---|---|
| Web 前端 | 无直连 | 无 | 无 |
| 业务接口服务 | 完整 CRUD | 无 | 无 |
| MCP 安全网关 | 受限读 + 审计写 + 遥测写 | 只读 | 订阅 + 受限发布 |
| Agent / 安全管控插件层 | 无账号,零权限 | 无账号,零权限 | 无凭证 |
| 边缘终端 | 无 | 无 | 仅自身主题 |
3.4 双库分离的两大收益
| 收益 | 说明 | 可验证方式 |
|---|---|---|
| 边界隔离 | Agent 与业务敏感数据之间不存在通路。提示注入即便完全成功,Agent 能做的也只是"多调用几次工具",无法读取账号、审计、工单 | 在 Agent 进程内尝试连接 business-db,观察凭证缺失导致的连接失败 |
| 故障域隔离 | Agent 或 ai-db 崩溃、索引重建、被拖垮时,business-db 与业务接口服务不受影响,业务系统独立可用 | 停掉 Agent 与 ai-db,验证 Web 界面仍可查询设备、曲线、审计 |
4. 关键数据流
4.1 遥测上行链路
STM32 采集(温度/振动) → 特征粗筛 → MQTT publish iot/{id}/telemetry (QoS1)
→ Mosquitto → 网关订阅 → 校验 deviceId + 一机一密 → 批量写入
→ business-db.sensor_data网关是唯一的订阅方。Agent 侧不部署 MQTT 客户端。
4.2 Agent 诊断链路(含安全管控)
① 工程师在 Web 界面发起"分析 3 号设备振动异常"
└─► business-api 落一条任务记录 → 触发 Agent(带工程师身份与任务上下文)
② Agent 进入 ReAct 循环
├─ Thought: 需要该设备近 1 小时的振动数据
├─ Action: mqtt.read_sensor({deviceId, from, to})
│ │
│ └─► 【安全管控插件层】api.on(tool-call) 拦截
│ · 参数校验:deviceId 是否在授权范围
│ · 行为约束:本会话工具调用配额是否超限
│ · 注入检测:参数是否含可疑载荷
│ · 判定:ALLOW / DENY / REWRITE(收窄时间窗口)
│ · 上报审计(含判定结果)
│
└─► 【MCP 安全网关】执行
· 身份解析(Agent 令牌 → agentId + sessionId)
· 策略二次校验(不信任插件层的判定,独立复核)
· 查询 business-db → 字段白名单裁剪 → 落审计 → 返回
③ Agent 继续推理
├─ Action: kb.search({query: "轴承外圈磨损 振动特征"})
│ └─► 同上经插件层 + 网关 → 网关只读检索 ai-db
├─ 综合遥测数据与手册知识,形成诊断结论
└─ Action: report.write({...})
└─► 经插件层脱敏(去除内部字段)→ 网关 → business-db.agent_report
④ 工程师在 Web 界面查看报告(含 ReAct 链路与每一步的审计 traceId)注意:插件层与网关都做策略校验,不是冗余。插件层能看到会话上下文("这次调用与当前任务是否相关"),网关能强制凭证边界("Agent 根本没有别的路可走")。这是纵深防御的两层,见 ADR-0004。
4.3 审计写入链路(fail-closed)
任意一次工具调用(无论放行/拒绝/改写)
→ 生成 traceId
→ 写入 business-db.audit_log
→ 若写入失败 ⇒ 本次调用判定为失败,不返回数据审计表是系统唯一的"数据访问事实来源"。不允许只在插件内打日志而不落库。
5. 技术选型汇总
| 层 | 选型 | 版本/形态 | 理由 | 备选 |
|---|---|---|---|---|
| 前端 | Vue3 + TypeScript + Vite | — | 组合式 API + 强类型,工程化成熟 | React(团队熟悉度更低) |
| UI 组件 | Element Plus / Naive UI | — | 表格、表单、树形权限选择器现成 | Ant Design Vue |
| 图表 | ECharts | — | 时序曲线与仪表盘能力成熟,中文文档好 | Chart.js |
| 后端语言 | Node.js + TypeScript | Node 24.15+ | 与 OpenClaw 同语言,可共享契约类型 | Go(无法共享类型) |
| 网关接口 | MCP 协议 | — | 标准工具调用协议,降低框架锁定 | REST(见 ADR-0002) |
| Agent 框架 | OpenClaw | 原生插件 + 类型化钩子 | 扩展机制与课题需求对齐 | 见 ADR-0003 |
| business-db | SQLite → PostgreSQL | 原型 / 落地 | 零运维演示 → 强制访问控制 | 见 ADR-0006 |
| ai-db | Chroma / Qdrant | 本地持久化 | 向量检索开箱即用 | pgvector |
| 消息 | MQTT (Mosquitto) | QoS 1 | 工业物联网事实标准,遗嘱消息与低带宽友好 | HTTP 上报(见 ADR-0005) |
| 终端 | STM32 + FreeRTOS | 见 07 | 真实硬件,答辩可信度高 | 纯软件模拟(说服力弱) |
| 工具链 | NVM → PNPM → PM2 | — | 见 ADR-0007 | Docker Compose(演示依赖高) |
6. 模块清单与目录映射
| 模块 | 路径 | 文档 |
|---|---|---|
| MCP 安全网关 | packages/mcp-gateway/ | docs/04 |
| 安全管控插件层 | packages/security-plugin/ | docs/02 |
| Agent 配置与 Skill | packages/agent-config/ | docs/03 |
| 业务接口服务 | packages/business-api/ | docs/05、docs/06 |
| 共享契约 | packages/shared-contracts/ | docs/04 |
| Web 管理界面 | apps/web/ | docs/06 |
| 边缘终端固件 | firmware/stm32-sensor-node/ | docs/07 |
| 部署编排 | deploy/ | docs/08 |
7. 系统关键不变量(Invariants)
这七条是本系统设计成立的前提,任何改动若违反其中一条,必须显式标注并重新走 ADR 流程。
| # | 不变量 | 违反后果 |
|---|---|---|
| I1 | Agent 进程环境变量中不存在 BUSINESS_DB_URL | 双库分离失效,边界隔离不成立 |
| I2 | Agent 进程环境变量中不存在 AI_DB_URL | "所有访问经网关"出现例外 |
| I3 | Agent 不持有任何 MQTT 凭证 | 越权订阅其他设备数据,审计出现盲区 |
| I4 | 每次工具调用必落审计,落库失败即调用失败 | 审计链条断裂,事后无法追溯 |
| I5 | 网关返回载荷只含字段白名单内的键 | 最小权限落空 |
| I6 | ai-db 中不存在业务敏感字段 | 双库分离退化为命名约定 |
| I7 | 下行控制指令必须经网关鉴权与审计 | Agent 可直接影响生产设备 |
8. 原型验证的验收目标
完整验证方案见 docs/09-原型验证方案.md,此处给出总体验收口径:
| 维度 | 验收标准 |
|---|---|
| 功能闭环 | 从终端上报 → 网关入库 → Agent 诊断 → 报告展示 → 审计可查,端到端跑通 |
| 边界成立(反证) | 在 Agent 进程内尝试直连 business-db / ai-db / MQTT 均失败,且失败原因可解释为"凭证不存在" |
| 管控生效 | 构造越权调用(访问未授权设备、超配额、参数注入),插件层 DENY 且审计有记录 |
| 裁剪生效 | 对比网关返回载荷与数据库原始行,证明敏感字段被剥离 |
| 故障域隔离 | 停止 Agent 与 ai-db,Web 端设备查询、曲线、审计查询仍正常 |
| 可复现 | 按 docs/08-部署与运维.md 的步骤,在干净机器上可重复搭建成功 |
9. 本文档的边界
本文件只给出总体方案。以下内容不在此展开:
- 插件层的钩子清单与策略引擎实现 →
docs/02 - OpenClaw 二次开发的具体改造点与 Skill 实现 →
docs/03 - MCP 工具的完整契约(入参/出参/错误码) →
docs/04 - 表结构与 DDL →
docs/05 - 部署脚本与 PM2 配置 →
docs/08 - 答辩问答预演 →
docs/10