吸收数字员工管理端审计诊断预览
This commit is contained in:
@@ -604,6 +604,101 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("deleted_at", "2026-06-13T00:00:00+08:00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchAuditsReturnsAuditRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
.thenReturn(page(List.of(
|
||||
record("outbox-sandbox-audit", "event", "event-audit-1", 101L, new Date(10_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "sandbox_audit",
|
||||
"kind", "sandbox_command_observed",
|
||||
"status", "warn",
|
||||
"level", "warn",
|
||||
"title", "Sandbox 命令观察",
|
||||
"operation", "shell_exec",
|
||||
"summary", "命令参数已脱敏"
|
||||
),
|
||||
Map.of("id", "event-audit-1", "kind", "sandbox_command_observed")),
|
||||
record("outbox-token-cost", "event", "event-audit-2", 101L, new Date(11_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "token_cost",
|
||||
"kind", "token_cost_recorded",
|
||||
"status", "recorded",
|
||||
"title", "Token 成本估算",
|
||||
"model", "gpt-4.1",
|
||||
"input_tokens", 120,
|
||||
"output_tokens", 60,
|
||||
"total_tokens", 180,
|
||||
"estimated_cost_usd", 0.012
|
||||
),
|
||||
Map.of("id", "event-audit-2", "kind", "token_cost_recorded")),
|
||||
record("outbox-tool-audit", "event", "event-audit-3", 101L, new Date(12_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "tool_call_audit",
|
||||
"kind", "tool_call_audit",
|
||||
"status", "rejected",
|
||||
"level", "error",
|
||||
"title", "工具调用被拒绝",
|
||||
"tool_name", "shell",
|
||||
"server_id", "local"
|
||||
),
|
||||
Map.of("id", "event-audit-3", "kind", "tool_call_audit")),
|
||||
record("outbox-sync-failure", "event", "event-audit-4", 101L, new Date(13_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "sync_failure",
|
||||
"kind", "digital_sync_failed",
|
||||
"status", "failed",
|
||||
"title", "同步失败",
|
||||
"reason", "remote_timeout"
|
||||
),
|
||||
Map.of("id", "event-audit-4", "kind", "digital_sync_failed")),
|
||||
record("outbox-governance", "event", "event-audit-5", 101L, new Date(14_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "governance",
|
||||
"kind", "governance_skip_task",
|
||||
"status", "recorded",
|
||||
"title", "治理命令审计",
|
||||
"command_id", "command-1"
|
||||
),
|
||||
Map.of("id", "event-audit-5", "kind", "governance_skip_task")),
|
||||
record("outbox-task", "task", "task-1", 101L, new Date(15_000),
|
||||
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
|
||||
Map.of("id", "task-1", "status", "running")),
|
||||
record("outbox-approval", "approval", "approval-1", 101L, new Date(16_000),
|
||||
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
|
||||
Map.of("id", "approval-1", "status", "pending")),
|
||||
record("outbox-notification", "notification", "notification-1", 101L, new Date(17_000),
|
||||
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||
Map.of("id", "notification-1", "status", "unread")),
|
||||
record("outbox-artifact", "artifact", "artifact-1", 101L, new Date(18_000),
|
||||
Map.of("entity_type", "artifact", "title", "普通产物", "status", "available"),
|
||||
Map.of("id", "artifact-1", "status", "available"))
|
||||
)));
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||
"audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||
);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(5);
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||
.containsExactly("event-audit-1", "event-audit-2", "event-audit-3", "event-audit-4", "event-audit-5");
|
||||
assertThat(result.getRecords().get(0).getView()).isEqualTo("audits");
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getCategory)
|
||||
.containsExactly("sandbox_audit", "token_cost", "tool_call_audit", "sync_failure", "governance");
|
||||
assertThat(result.getRecords().get(0).getLevel()).isEqualTo("warn");
|
||||
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("命令参数已脱敏");
|
||||
assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("total_tokens", 180);
|
||||
assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("estimated_cost_usd", 0.012);
|
||||
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("tool_name", "shell");
|
||||
assertThat(result.getRecords().get(3).getSummary()).isEqualTo("remote_timeout");
|
||||
assertThat(result.getRecords().get(4).getBusinessView()).containsEntry("command_id", "command-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
|
||||
@@ -1770,6 +1770,75 @@ function checkDigitalAdminArtifactLifecycleWorkbench() {
|
||||
console.log("[DigitalEmployeeCheck] admin artifact lifecycle workbench hooks OK");
|
||||
}
|
||||
|
||||
function checkDigitalAdminAuditDiagnosticsWorkbench() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify admin audit diagnostics workbench hooks");
|
||||
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
||||
const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming");
|
||||
const servicePath = path.join(
|
||||
backendRoot,
|
||||
"app-platform-modules",
|
||||
"app-platform-agent",
|
||||
"app-platform-agent-core-application",
|
||||
"src",
|
||||
"main",
|
||||
"java",
|
||||
"com",
|
||||
"xspaceagi",
|
||||
"agent",
|
||||
"core",
|
||||
"application",
|
||||
"service",
|
||||
"DigitalEmployeeSyncApplicationServiceImpl.java",
|
||||
);
|
||||
const testPath = path.join(
|
||||
backendRoot,
|
||||
"app-platform-modules",
|
||||
"app-platform-agent",
|
||||
"app-platform-agent-core-application",
|
||||
"src",
|
||||
"test",
|
||||
"java",
|
||||
"com",
|
||||
"xspaceagi",
|
||||
"agent",
|
||||
"core",
|
||||
"application",
|
||||
"service",
|
||||
"DigitalEmployeeSyncApplicationServiceImplTest.java",
|
||||
);
|
||||
const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx");
|
||||
const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts");
|
||||
const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md");
|
||||
const service = fs.readFileSync(servicePath, "utf8");
|
||||
const test = fs.readFileSync(testPath, "utf8");
|
||||
const qimingOps = fs.readFileSync(qimingOpsPath, "utf8");
|
||||
const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8");
|
||||
const docs = fs.readFileSync(docsPath, "utf8");
|
||||
const requiredSnippets = [
|
||||
[service, "audits", "backend audits view"],
|
||||
[service, "isAuditRow", "backend audit matcher"],
|
||||
[service, "sandbox_audit", "backend sandbox audit category"],
|
||||
[service, "token_cost", "backend token cost category"],
|
||||
[service, "tool_call_audit", "backend tool call audit category"],
|
||||
[service, "sync_failure", "backend sync failure audit category"],
|
||||
[test, "queryAdminWorkbenchAuditsReturnsAuditRowsOnly", "backend audit diagnostics test"],
|
||||
[test, "doesNotContainKeys(\"token\", \"authorization\", \"raw_input\", \"command\", \"prompt\")", "backend audit redaction assertion"],
|
||||
[qimingTypes, "audits", "qiming audits view type"],
|
||||
[qimingOps, "审计诊断", "qiming audit diagnostics tab"],
|
||||
[qimingOps, "标记复核", "qiming audit reviewed action"],
|
||||
[qimingOps, "忽略", "qiming audit dismiss action"],
|
||||
[qimingOps, "标记归档", "qiming audit archive action"],
|
||||
[docs, "正式管理端审计诊断预览", "docs audit diagnostics absorption"],
|
||||
];
|
||||
const missing = requiredSnippets
|
||||
.filter(([content, snippet]) => !content.includes(snippet))
|
||||
.map(([, , label]) => label);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing admin audit diagnostics workbench hooks: ${missing.join(", ")}`);
|
||||
}
|
||||
console.log("[DigitalEmployeeCheck] admin audit diagnostics workbench hooks OK");
|
||||
}
|
||||
|
||||
function checkLocalDatabaseSchema() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
|
||||
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
|
||||
@@ -1837,6 +1906,7 @@ function main() {
|
||||
checkDigitalAdminRouteGovernanceWorkbench();
|
||||
checkDigitalAdminNotificationWorkbench();
|
||||
checkDigitalAdminArtifactLifecycleWorkbench();
|
||||
checkDigitalAdminAuditDiagnosticsWorkbench();
|
||||
checkLocalDatabaseSchema();
|
||||
console.log("\n[DigitalEmployeeCheck] All checks passed");
|
||||
} catch (error) {
|
||||
|
||||
@@ -604,7 +604,7 @@ GET /api/digital-employee/approvals
|
||||
- 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。
|
||||
|
||||
11. **诊断 / 成本 / 审计视图**
|
||||
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影;sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit`、`token_cost`、`tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览已开始吸收,embedded “技能审计”会消费 sandbox/tool/cost 风险来源并保留安全 payload preview。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。发送账单审计外壳已开始吸收,后端提供估算账单查询和审计归档记录接口,qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。
|
||||
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影;sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit`、`token_cost`、`tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收,qiming-backend 的 `audits` 运营台视图会聚合 sandbox / token cost / tool call / sync failure / governance 审计业务视图,qiming“数字员工运营台”的“审计诊断”tab 可标记复核、忽略和脱敏归档。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。发送账单审计外壳已开始吸收,后端提供估算账单查询和审计归档记录接口,qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。
|
||||
- 缺口:真实计费账单、独立 sandbox audit 文件归档、自动诊断修复动作和正式外部管理端审计视图尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。
|
||||
- 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user