吸收数字员工管理端治理执行安全壳

This commit is contained in:
baiyanyun
2026-06-13 14:56:54 +08:00
parent f08b0c39d9
commit 95838dba04
7 changed files with 297 additions and 4 deletions

View File

@@ -63,6 +63,8 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String scheduleId;
@JsonProperty("job_id")
private String jobId;
@JsonProperty("revision_id")
private String revisionId;
@JsonProperty("occurred_at")
private String occurredAt;
@JsonProperty("synced_at")

View File

@@ -74,6 +74,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"skill:mark_reviewed", "skill:dismiss",
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
"revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review",
"audit:mark_reviewed", "audit:dismiss",
"event:mark_reviewed", "event:dismiss"
);
@@ -937,6 +939,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
case "edit_policy_remote" -> List.of("edit_policy_remote_disabled");
case "run_schedule_now" -> List.of("run_schedule_now_disabled");
case "start_scheduler" -> List.of("start_scheduler_disabled");
case "hard_interrupt" -> List.of("hard_interrupt_disabled");
case "activate_plan", "cancel_run", "cancel_task", "accept_remote_approval_batch" -> List.of("high_risk_governance_action_disabled");
default -> List.of();
};
}
@@ -1006,6 +1010,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.policyKey(firstString(businessView.get("policy_key"), businessView.get("policyKey"), payload.get("policy_key"), payload.get("policyKey")))
.scheduleId(firstString(businessView.get("schedule_id"), businessView.get("scheduleId"), payload.get("schedule_id"), payload.get("scheduleId")))
.jobId(firstString(businessView.get("job_id"), businessView.get("jobId"), payload.get("job_id"), payload.get("jobId")))
.revisionId(firstString(businessView.get("revision_id"), businessView.get("revisionId"), payload.get("revision_id"), payload.get("revisionId")))
.occurredAt(businessRow.getOccurredAt())
.syncedAt(record.getSyncedAt())
.clientKeyMasked(maskClientKey(record.getClientKey()))
@@ -1035,6 +1040,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
case "skill_audits" -> isSkillAuditRow(row);
case "policy_center" -> isPolicyCenterRow(row);
case "scheduler_operations" -> isSchedulerOperationsRow(row);
case "plan_governance" -> isPlanGovernanceRow(row);
case "plan_revisions" -> isPlanRevisionRow(row);
default -> true;
};
}
@@ -1166,6 +1173,26 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| StringUtils.isNotBlank(row.getJobId());
}
private boolean isPlanGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return StringUtils.equals(category, "plan_governance")
|| StringUtils.equals(category, "governance")
|| StringUtils.equals(category, "route_governance")
|| StringUtils.equals(category, "manual_intervention")
|| startsWithAny(kind, "governance_", "plan_action_", "escalation_", "manual_intervention_")
|| contains(kind, "route_governance");
}
private boolean isPlanRevisionRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return StringUtils.equals(category, "plan_revision")
|| StringUtils.equals(category, "plan_revisions")
|| startsWithAny(kind, "plan_revision", "governance_revision")
|| StringUtils.isNotBlank(row.getRevisionId());
}
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
@@ -1187,6 +1214,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
if (startsWithAny(kind, "skill_call_")) return "skill_call_audit";
if (contains(kind, "policy")) return "policy_snapshot";
if (contains(kind, "scheduler") || contains(kind, "cron") || contains(kind, "lease")) return "scheduler_operation";
if (startsWithAny(kind, "plan_revision", "governance_revision")) return "plan_revision";
if (startsWithAny(kind, "plan_action_", "escalation_", "manual_intervention_")) return "plan_governance";
if (startsWithAny(kind, "governance_")) return "governance";
if (contains(kind, "audit")) return kind;
return row.getEntityType();

View File

@@ -567,6 +567,115 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(1).getKind()).isEqualTo("scheduler_check_completed");
}
@Test
void queryAdminWorkbenchPlanGovernanceReturnsGovernanceEscalationRowsOnly() {
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-plan-action", "event", "event-plan-1", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "plan_governance",
"kind", "plan_action_recorded",
"status", "recorded",
"title", "计划治理动作",
"plan_id", "plan-1",
"reason", "record_escalation"
),
Map.of("id", "event-plan-1", "kind", "plan_action_recorded")),
record("outbox-governance", "event", "event-plan-2", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "governance",
"kind", "governance_pause_plan",
"status", "accepted",
"title", "暂停计划",
"plan_id", "plan-1",
"risk_level", "low"
),
Map.of("id", "event-plan-2", "kind", "governance_pause_plan")),
record("outbox-escalation", "event", "event-plan-3", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"kind", "escalation_requested",
"status", "pending",
"title", "升级人工复核",
"plan_id", "plan-2",
"priority", "high"
),
Map.of("id", "event-plan-3", "kind", "escalation_requested")),
record("outbox-plan", "plan", "plan-ordinary", 101L, new Date(12_500),
Map.of(
"entity_type", "plan",
"title", "普通计划",
"status", "running",
"plan_id", "plan-ordinary"
),
Map.of("id", "plan-ordinary", "status", "running")),
record("outbox-notification", "notification", "notification-1", 101L, new Date(13_000),
Map.of("entity_type", "notification", "title", "通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"plan_governance", 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("event-plan-1", "event-plan-2", "event-plan-3");
assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_governance");
assertThat(result.getRecords().get(0).getPlanId()).isEqualTo("plan-1");
assertThat(result.getRecords().get(1).getCategory()).isEqualTo("governance");
assertThat(result.getRecords().get(2).getPriority()).isEqualTo("high");
}
@Test
void queryAdminWorkbenchPlanRevisionsReturnsRevisionRowsOnly() {
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-plan-revision", "event", "event-revision-1", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "plan_revision",
"kind", "plan_revision_created",
"status", "draft",
"title", "计划版本草稿",
"plan_id", "plan-1",
"revision_id", "rev-1",
"revision_status", "draft",
"base_revision_id", "rev-0",
"changed_fields", List.of("objective", "steps"),
"created_by", "admin"
),
Map.of("id", "event-revision-1", "kind", "plan_revision_created")),
record("outbox-governance-revision", "event", "event-revision-2", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"kind", "governance_revision_review_requested",
"status", "pending",
"title", "版本复核请求",
"plan_id", "plan-1",
"revision_id", "rev-2"
),
Map.of("id", "event-revision-2", "kind", "governance_revision_review_requested")),
record("outbox-task", "task", "task-1", 101L, new Date(12_000),
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
Map.of("id", "task-1", "status", "running"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"plan_revisions", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(2);
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
.containsExactly("event-revision-1", "event-revision-2");
assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_revisions");
assertThat(result.getRecords().get(0).getRevisionId()).isEqualTo("rev-1");
assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("base_revision_id", "rev-0");
assertThat(result.getRecords().get(1).getRevisionId()).isEqualTo("rev-2");
}
@Test
void queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly() {
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
@@ -1137,11 +1246,19 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
DigitalEmployeeAdminActionResultDto startScheduler = service.createAdminAction(
adminActionRequest("scheduler", "job-daily", "start_scheduler")
);
DigitalEmployeeAdminActionResultDto hardInterrupt = service.createAdminAction(
adminActionRequest("plan", "plan-1", "hard_interrupt")
);
DigitalEmployeeAdminActionResultDto activatePlan = service.createAdminAction(
adminActionRequest("plan", "plan-1", "activate_plan")
);
assertThat(editPolicy.getOk()).isFalse();
assertThat(editPolicy.getReasonCodes()).contains("edit_policy_remote_disabled");
assertThat(runSchedule.getReasonCodes()).contains("run_schedule_now_disabled");
assertThat(startScheduler.getReasonCodes()).contains("start_scheduler_disabled");
assertThat(hardInterrupt.getReasonCodes()).contains("hard_interrupt_disabled");
assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled");
}
@Test