决策「L2 同时暴露 MCP+控制台」。此前 system.audit.l2 只存在于 被控端实现里:MCP 无工具(模型看不到)、控制台无入口无类型 (用户点不到)、审计事件写成 agent.start(与进程生命周期混类筛不出)。 MCP(41 → 42 个工具): - defs.ts 在 na_audit 后新增 na_audit_l2(action: status/enable/ collect/verify + since/limit,additionalProperties: false) - index.ts TOOL_TO_CAPABILITY 映射 CapabilityNames.AuditL2, 分发走 default 直通(参数名与被控端一致,无需改写) - mcp-tools 守卫同步:41→42、BASELINE_SEQ_SHA 重算、 新增「na_audit_l2 必须紧随 na_audit」顺序断言 控制台: - conn.ts WINDOWS_ONLY_CAPS += system.audit.l2(非 Windows 被控端 上按钮禁用并明示原因,不搞「点了才报错」) - types/index.ts 新增 AuditL2Result(四 action 属性并集,对齐 manifest returns_schema)+ AuditType += agent.l2; AuditEntry 同步补 args_bytes/args_truncated(A-P0-4 新字段) - AuditView.vue 新增 L2 卡片:查状态/拉取采集/校验 L2 链/启用策略 (NPopconfirm 二次确认 + 管理员权限提示);信任边界如实说明 (采集器与 agent 同权限,防篡改依赖转发出机,不夸大本地副本) agent.l2 审计类型四端同源: - protocol audit-types.ts AUDIT_TYPES(真源)+= agent.l2 - agent audit.ts AuditType 联合同步(AssertExact 编译期守卫兜底) - audit-l2.ts 两处写入点 agent.start → agent.l2(L2 策略操作与 进程生命周期无关,混类会污染两边统计与筛选) 守卫加强(console-contract 平台门控): - audit-l2.ts 平台门控由裸 throw 改用统一 assertWindows (错误信息格式统一 + 可被守卫扫到) - 守卫扫描范围 +audit-l2.ts、模式 +assertWindows —— 覆盖面扩大; 否则把 system.audit.l2 加进 WINDOWS_ONLY_CAPS 反而报红 - mcp-tools 正则盲区修复:原 na_[a-z_]+ 匹配不了带数字的工具名, na_audit_l2 会整个漏扫(新增了工具两个守卫却无觉)→ 放至 [a-z0-9_]+ 新增 tests/unit/console-l2.test.mjs 5 例:前端字段 ⊆ returns_schema / 契约关键字段 ⊇ 前端渲染 / MCP 工具存在且映射 / agent.l2 四端同源 / 界面纪律(信任边界文案 + enable 二次确认 + 平台门控) 故障注入实测:审计类型退回 agent.start、WINDOWS_ONLY_CAPS 删项 → 3 处红;恢复 → 绿 单测 451/451、e2e 33/33、typecheck 全绿
69 lines
3.2 KiB
JavaScript
69 lines
3.2 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { readFileSync } from 'node:fs';
|
||
import { createHash } from 'node:crypto';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
/**
|
||
* MCP 工具清单的回归守卫(2026-10 拆分时建立)。
|
||
*
|
||
* 背景:TOOLS 从 index.ts 抽到 tools/defs.ts(1139 → 530 行)。抽出的风险是
|
||
* **顺序漂移**或**漏搬**,而这两者都不会让构建失败 —— 只会静默改变客户端看到的工具表。
|
||
* 故用「工具名序列 sha256」做证据:序列一旦变化,本测试立刻红。
|
||
*/
|
||
|
||
const DEFS = fileURLToPath(new URL('../../apps/mcp/src/tools/defs.ts', import.meta.url));
|
||
const src = readFileSync(DEFS, 'utf8');
|
||
|
||
/** 拆分时记录的基线(顺序敏感)。改工具清单时**必须**同步更新此值。 */
|
||
const BASELINE_SEQ_SHA = '68c7715eece68c6ba077b9c21c83a02e013c19b6aec9e57079b81e34ad9dad4d';
|
||
|
||
/**
|
||
* 工具名序列。
|
||
*
|
||
* ⚠️ 2026-10-25(v2.1.0):正则原为 `na_[a-z_]+`,**匹配不了带数字的工具名**——
|
||
* `na_audit_l2` 里的 `2` 直接漏扫(新增了工具但两个数量/序列守卫毫无知觉,
|
||
* 等于没守)。已放宽松到 `[a-z0-9_]+`。宁可放宽字符集,不可再漏工具。
|
||
*/
|
||
function toolNames() {
|
||
return [...src.matchAll(/^\s*name: '(na_[a-z0-9_]+)',/gm)].map((m) => m[1]);
|
||
}
|
||
|
||
test('tools/defs.ts:工具数 42 且名称唯一', () => {
|
||
const names = toolNames();
|
||
assert.equal(names.length, 42, '工具数变了 —— 若有意增删,请同步更新 BASELINE_SEQ_SHA');
|
||
assert.equal(new Set(names).size, names.length, '存在重复工具名(MCP 客户端会不认)');
|
||
// v2.1.0 新增 na_audit_l2(L2 系统级审计)必须紧随 na_audit —— 语义相邻才便于模型选用
|
||
assert.equal(names[names.indexOf('na_audit') + 1], 'na_audit_l2', 'na_audit_l2 应紧跟 na_audit');
|
||
});
|
||
|
||
test('tools/defs.ts:工具名序列与顺序未漂移(顺序即对外呈现顺序)', () => {
|
||
const sha = createHash('sha256').update(toolNames().join('|')).digest('hex');
|
||
assert.equal(
|
||
sha,
|
||
BASELINE_SEQ_SHA,
|
||
'工具顺序变了 —— 顺序即客户端看到的列表次序。若为有意调整,请更新 BASELINE_SEQ_SHA 并说明原因',
|
||
);
|
||
});
|
||
|
||
test('tools/defs.ts:每个工具都有 name/description/inputSchema 三件套', () => {
|
||
const objects = src.split(/\n \{\n/).slice(1);
|
||
for (const obj of objects) {
|
||
assert.match(obj, /name: 'na_/, '缺少 name');
|
||
assert.match(obj, /description:/, '缺少 description(MCP 靠它让模型知道何时用)');
|
||
assert.match(obj, /inputSchema: \{/, '缺少 inputSchema');
|
||
}
|
||
});
|
||
|
||
test('tools/defs.ts:inputSchema 均声明 type=object 且禁止未声明属性', () => {
|
||
const schemas = [...src.matchAll(/inputSchema: \{([^}]*type: 'object'[^}]*)\}/g)];
|
||
assert.ok(schemas.length >= 1, '未找到 inputSchema');
|
||
// 逐工具检查 additionalProperties(允许三种写法:内联 false / 单独一行)
|
||
const inline = (src.match(/additionalProperties: false/g) ?? []).length;
|
||
const tools = toolNames().length;
|
||
assert.ok(
|
||
inline >= tools - 2,
|
||
`additionalProperties: false 只出现 ${inline} 次,少于工具数 ${tools}(可能漏了约束)`,
|
||
);
|
||
});
|