吸收数字员工管理端产物生命周期预览

This commit is contained in:
baiyanyun
2026-06-13 13:42:50 +08:00
parent 1daf58a94c
commit b0f0b95c2a
3 changed files with 158 additions and 1 deletions

View File

@@ -532,6 +532,78 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference");
}
@Test
void queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly() {
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-artifact", "artifact", "artifact-1", 101L, new Date(10_000),
Map.of(
"entity_type", "artifact",
"title", "日报 PDF",
"status", "available",
"artifact_id", "artifact-1",
"retention_status", "expired",
"cleanup_status", "pending",
"summary", "等待清理"
),
Map.of("id", "artifact-1", "status", "available")),
record("outbox-artifact-retention", "event", "event-artifact-1", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "artifact_lifecycle",
"kind", "artifact_retention_marked",
"status", "recorded",
"title", "产物标记到期",
"artifact_id", "artifact-2",
"retention_status", "expired"
),
Map.of("id", "event-artifact-1", "kind", "artifact_retention_marked")),
record("outbox-artifact-cleanup", "event", "event-artifact-2", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"kind", "artifact_cleanup_executed",
"status", "deleted",
"title", "产物文件已清理",
"artifact_id", "artifact-3",
"cleanup_status", "deleted",
"deleted_at", "2026-06-13T00:00:00+08:00"
),
Map.of("id", "event-artifact-2", "kind", "artifact_cleanup_executed")),
record("outbox-task", "task", "task-1", 101L, new Date(13_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(14_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(15_000),
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread")),
record("outbox-route", "event", "event-route-1", 101L, new Date(16_000),
Map.of("entity_type", "event", "category", "route_governance", "title", "路由", "status", "blocked"),
Map.of("id", "event-route-1", "status", "blocked")),
record("outbox-audit", "event", "event-audit-1", 101L, new Date(17_000),
Map.of("entity_type", "event", "category", "tool_call_audit", "title", "审计", "status", "warn"),
Map.of("id", "event-audit-1", "status", "warn"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"artifact_lifecycle", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(3);
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
.containsExactly("artifact-1", "event-artifact-1", "event-artifact-2");
assertThat(result.getRecords().get(0).getView()).isEqualTo("artifact_lifecycle");
assertThat(result.getRecords().get(0).getArtifactId()).isEqualTo("artifact-1");
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("artifact_lifecycle");
assertThat(result.getRecords().get(0).getStatus()).isEqualTo("available");
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("等待清理");
assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("retention_status", "expired");
assertThat(result.getRecords().get(1).getArtifactId()).isEqualTo("artifact-2");
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("cleanup_status", "deleted");
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("deleted_at", "2026-06-13T00:00:00+08:00");
}
@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)))

View File

@@ -1686,6 +1686,90 @@ function checkDigitalAdminNotificationWorkbench() {
console.log("[DigitalEmployeeCheck] admin notification workbench hooks OK");
}
function checkDigitalAdminArtifactLifecycleWorkbench() {
console.log("\n[DigitalEmployeeCheck] Verify admin artifact lifecycle 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 dtoPath = path.join(
backendRoot,
"app-platform-modules",
"app-platform-agent",
"app-platform-agent-core-adapter",
"src",
"main",
"java",
"com",
"xspaceagi",
"agent",
"core",
"adapter",
"dto",
"digital",
"DigitalEmployeeAdminWorkbenchRowDto.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 dto = fs.readFileSync(dtoPath, "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, "artifact_lifecycle", "backend artifact lifecycle view"],
[service, "isArtifactLifecycleRow", "backend artifact lifecycle matcher"],
[service, "artifact_", "backend artifact event classifier"],
[dto, "artifact_id", "backend artifact id row field"],
[test, "queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly", "backend artifact lifecycle test"],
[qimingTypes, "artifact_lifecycle", "qiming artifact lifecycle view type"],
[qimingOps, "产物治理", "qiming artifact lifecycle tab"],
[qimingOps, "标记保留", "qiming artifact keep action"],
[qimingOps, "标记到期", "qiming artifact expire action"],
[qimingOps, "标记复核", "qiming artifact reviewed action"],
[docs, "正式管理端产物生命周期预览", "docs artifact lifecycle absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing admin artifact lifecycle workbench hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] admin artifact lifecycle workbench hooks OK");
}
function checkLocalDatabaseSchema() {
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
@@ -1752,6 +1836,7 @@ function main() {
checkDigitalAdminServiceOperationsWorkbench();
checkDigitalAdminRouteGovernanceWorkbench();
checkDigitalAdminNotificationWorkbench();
checkDigitalAdminArtifactLifecycleWorkbench();
checkLocalDatabaseSchema();
console.log("\n[DigitalEmployeeCheck] All checks passed");
} catch (error) {

View File

@@ -584,7 +584,7 @@ GET /api/digital-employee/approvals
7. **产物交付闭环**
- 已吸收:运行 payload 中的 artifacts / outputs / files / uri 可提取为正式 `digital_artifacts` 并同步File Server workspace / upload / download / export / file update 生命周期状态已开始写入 Artifact payload 的 `delivery` 对象embedded `/api/artifact` 和日报产物来源可读取交付阶段、状态、文件与 workspace 信息Artifact 预览/下载权限已开始闭环,嵌入页通过受控 bridge 访问本地产物,并写入 `artifact_access_allowed` / `artifact_access_rejected` 审计事件Artifact 版本/保留策略已开始闭环,业务产物视图可读取 version / retention 摘要,保留标记会写入 `artifact_retention_marked` / `artifact_retention_rejected` / `artifact_version_observed` 事件。真实文件清理执行已开始吸收,过期本地普通文件可通过独立 `cleanup` bridge 删除,清理结果会写入 `artifact_cleanup_executed` / `artifact_cleanup_rejected` / `artifact_cleanup_failed`artifact payload 与业务视图会投影 `cleanup_status``deleted_at` 和原因字段。保留策略批量治理和跨设备聚合视图已开始吸收embedded 业务视图新增 `artifact-groups` 产物聚合入口,可按版本/来源聚合产物、统计设备/版本/保留/清理状态,并通过受控批量接口写入保留、到期或复核标记;日报产物来源也提供批量标记到期入口。
- 缺口:仍缺少批量真实文件清理、正式外部管理端生命周期工作台、复杂跨设备图谱和物化业务表。
- 缺口:正式管理端产物生命周期预览已开始吸收qiming“数字员工运营台”的“产物治理”tab 可只读聚合产物 lifecycle / access / retention / cleanup / version 业务视图,并通过低风险 admin action 记录 `mark_keep` / `mark_expire` / `mark_reviewed` 意图,等待客户端拉取执行;仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。
- 建议:下一轮围绕正式管理端生命周期工作台和跨设备图谱,把 embedded 预览聚合推进到跨端运营治理。
8. **审批 / 人工介入增强**