吸收数字员工管理端治理策略版本预览

This commit is contained in:
baiyanyun
2026-06-15 14:01:10 +08:00
parent ff7576f33d
commit fd3b149172
7 changed files with 371 additions and 4 deletions

View File

@@ -64,6 +64,12 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private Boolean autoDecisionDisabled;
@JsonProperty("policy_reason")
private String policyReason;
@JsonProperty("governance_policy_id")
private String governancePolicyId;
@JsonProperty("policy_guard_status")
private String policyGuardStatus;
@JsonProperty("hard_interrupt_disabled")
private Boolean hardInterruptDisabled;
@JsonProperty("artifact_id")
private String artifactId;
@JsonProperty("artifact_group_id")
@@ -250,6 +256,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String jobId;
@JsonProperty("revision_id")
private String revisionId;
@JsonProperty("revision_fact_id")
private String revisionFactId;
@JsonProperty("revision_status")
private String revisionStatus;
@JsonProperty("revision_materialized")
private Boolean revisionMaterialized;
@JsonProperty("base_revision_id")
private String baseRevisionId;
@JsonProperty("changed_fields")
private List<String> changedFields;
@JsonProperty("occurred_at")
private String occurredAt;
@JsonProperty("synced_at")

View File

@@ -76,6 +76,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"memory_policy:pull_policy", "memory_policy:mark_reviewed", "memory_policy:dismiss",
"skill:mark_reviewed", "skill:dismiss",
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
"governance_policy:pull_policy", "governance_policy:mark_reviewed", "governance_policy:dismiss",
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
"plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check",
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
@@ -1105,6 +1106,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.batchAcceptRemoteDisabled(nullableBooleanValue(firstPresent(businessView.get("batch_accept_remote_disabled"), businessView.get("batchAcceptRemoteDisabled"), payload.get("batch_accept_remote_disabled"), payload.get("batchAcceptRemoteDisabled"))))
.autoDecisionDisabled(nullableBooleanValue(firstPresent(businessView.get("auto_decision_disabled"), businessView.get("autoDecisionDisabled"), payload.get("auto_decision_disabled"), payload.get("autoDecisionDisabled"))))
.policyReason(firstString(businessView.get("policy_reason"), businessView.get("policyReason"), payload.get("policy_reason"), payload.get("policyReason")))
.governancePolicyId(firstString(businessView.get("governance_policy_id"), businessView.get("governancePolicyId"), payload.get("governance_policy_id"), payload.get("governancePolicyId")))
.policyGuardStatus(firstString(businessView.get("policy_guard_status"), businessView.get("policyGuardStatus"), payload.get("policy_guard_status"), payload.get("policyGuardStatus")))
.hardInterruptDisabled(nullableBooleanValue(firstPresent(businessView.get("hard_interrupt_disabled"), businessView.get("hardInterruptDisabled"), payload.get("hard_interrupt_disabled"), payload.get("hardInterruptDisabled"))))
.artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null))
.artifactGroupId(firstString(businessView.get("artifact_group_id"), businessView.get("artifactGroupId"), payload.get("artifact_group_id"), payload.get("artifactGroupId")))
.artifactCount(numberValue(firstPresent(businessView.get("artifact_count"), businessView.get("artifactCount"), payload.get("artifact_count"), payload.get("artifactCount"))))
@@ -1199,6 +1203,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.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")))
.revisionFactId(firstString(businessView.get("revision_fact_id"), businessView.get("revisionFactId"), payload.get("revision_fact_id"), payload.get("revisionFactId")))
.revisionStatus(firstString(businessView.get("revision_status"), businessView.get("revisionStatus"), payload.get("revision_status"), payload.get("revisionStatus")))
.revisionMaterialized(nullableBooleanValue(firstPresent(businessView.get("revision_materialized"), businessView.get("revisionMaterialized"), payload.get("revision_materialized"), payload.get("revisionMaterialized"))))
.baseRevisionId(firstString(businessView.get("base_revision_id"), businessView.get("baseRevisionId"), payload.get("base_revision_id"), payload.get("baseRevisionId")))
.changedFields(stringListValue(firstPresent(businessView.get("changed_fields"), businessView.get("changedFields"), payload.get("changed_fields"), payload.get("changedFields"))))
.occurredAt(businessRow.getOccurredAt())
.syncedAt(record.getSyncedAt())
.clientKeyMasked(maskClientKey(record.getClientKey()))
@@ -1234,6 +1243,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
case "plan_step_dispatch" -> isPlanStepDispatchRow(row);
case "plan_governance" -> isPlanGovernanceRow(row);
case "plan_revisions" -> isPlanRevisionRow(row);
case "governance_policies" -> isGovernancePolicyRow(row);
case "plan_revision_materialization" -> isPlanRevisionMaterializationRow(row);
default -> true;
};
}
@@ -1499,6 +1510,18 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| contains(kind, "route_governance");
}
private boolean isGovernancePolicyRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return Set.of("governance_policy", "plan_governance_policy", "manual_intervention_policy", "risk_guard_policy").contains(category)
|| contains(kind, "governance_policy")
|| contains(kind, "manual_intervention_policy")
|| contains(kind, "risk_guard_policy")
|| StringUtils.isNotBlank(row.getGovernancePolicyId())
|| StringUtils.isNotBlank(row.getPolicyGuardStatus())
|| row.getHardInterruptDisabled() != null;
}
private boolean isPlanRevisionRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
@@ -1508,6 +1531,16 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| StringUtils.isNotBlank(row.getRevisionId());
}
private boolean isPlanRevisionMaterializationRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return Set.of("plan_revision_fact", "plan_revision_materialization", "plan_revision_review").contains(category)
|| contains(kind, "plan_revision_materialized")
|| contains(kind, "plan_revision_review")
|| StringUtils.isNotBlank(row.getRevisionFactId())
|| row.getRevisionMaterialized() != null;
}
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"));
@@ -1541,8 +1574,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
if (startsWithAny(kind, "memory_")) return "memory_governance";
if (startsWithAny(kind, "skill_call_")) return "skill_call_audit";
if (startsWithAny(kind, "doctor_", "diagnostic_", "repair_") || contains(kind, "policy_pull_error")) return "diagnostic_repair";
if (contains(kind, "governance_policy") || contains(kind, "manual_intervention_policy") || contains(kind, "risk_guard_policy")) return "governance_policy";
if (contains(kind, "policy")) return "policy_snapshot";
if (contains(kind, "scheduler") || contains(kind, "cron") || contains(kind, "lease")) return "scheduler_operation";
if (contains(kind, "plan_revision_materialized") || contains(kind, "plan_revision_review")) return "plan_revision_fact";
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";

View File

@@ -1019,6 +1019,112 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(1).getRevisionId()).isEqualTo("rev-2");
}
@Test
void queryAdminWorkbenchGovernancePoliciesReturnsPolicyRowsOnly() {
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-governance-policy", "event", "event-governance-policy-1", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "governance_policy",
"kind", "governance_policy_snapshot",
"status", "active",
"title", "治理策略快照",
"plan_id", "plan-1",
"governance_policy_id", "governance-policy-1",
"policy_guard_status", "guarded",
"policy_reason", "manual_review_required",
"hard_interrupt_disabled", true
),
Map.of("id", "event-governance-policy-1", "kind", "governance_policy_snapshot")),
record("outbox-manual-policy", "event", "event-governance-policy-2", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"kind", "manual_intervention_policy_updated",
"status", "review_required",
"title", "人工介入策略",
"governance_policy_id", "manual-policy-1",
"policy_guard_status", "review_required"
),
Map.of("id", "event-governance-policy-2", "kind", "manual_intervention_policy_updated")),
record("outbox-plan-governance", "event", "event-plan-governance", 101L, new Date(12_000),
Map.of("entity_type", "event", "category", "plan_governance", "kind", "plan_action_recorded", "title", "计划治理动作", "status", "recorded"),
Map.of("id", "event-plan-governance", "kind", "plan_action_recorded")),
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"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"governance_policies", 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-governance-policy-1", "event-governance-policy-2");
assertThat(result.getRecords().get(0).getView()).isEqualTo("governance_policies");
assertThat(result.getRecords().get(0).getGovernancePolicyId()).isEqualTo("governance-policy-1");
assertThat(result.getRecords().get(0).getPolicyGuardStatus()).isEqualTo("guarded");
assertThat(result.getRecords().get(0).getPolicyReason()).isEqualTo("manual_review_required");
assertThat(result.getRecords().get(0).getHardInterruptDisabled()).isTrue();
assertThat(result.getRecords().get(1).getGovernancePolicyId()).isEqualTo("manual-policy-1");
}
@Test
void queryAdminWorkbenchPlanRevisionMaterializationReturnsRevisionRowsOnly() {
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-revision-fact", "event", "event-revision-fact-1", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "plan_revision_fact",
"kind", "plan_revision_materialized",
"status", "materialized",
"title", "计划版本已物化",
"plan_id", "plan-1",
"revision_id", "rev-1",
"revision_fact_id", "revision-fact-1",
"revision_materialized", true,
"base_revision_id", "rev-0",
"changed_fields", List.of("steps", "sla")
),
Map.of("id", "event-revision-fact-1", "kind", "plan_revision_materialized")),
record("outbox-revision-review", "event", "event-revision-review-1", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"kind", "plan_revision_review_requested",
"status", "pending",
"title", "版本物化复核",
"plan_id", "plan-1",
"revision_id", "rev-2",
"revision_fact_id", "revision-fact-2",
"revision_materialized", false
),
Map.of("id", "event-revision-review-1", "kind", "plan_revision_review_requested")),
record("outbox-plan", "plan", "plan-ordinary", 101L, new Date(12_000),
Map.of("entity_type", "plan", "title", "普通计划", "status", "running"),
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_revision_materialization", 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-fact-1", "event-revision-review-1");
assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_revision_materialization");
assertThat(result.getRecords().get(0).getRevisionId()).isEqualTo("rev-1");
assertThat(result.getRecords().get(0).getRevisionFactId()).isEqualTo("revision-fact-1");
assertThat(result.getRecords().get(0).getRevisionMaterialized()).isTrue();
assertThat(result.getRecords().get(0).getBaseRevisionId()).isEqualTo("rev-0");
assertThat(result.getRecords().get(0).getChangedFields()).containsExactly("steps", "sla");
assertThat(result.getRecords().get(1).getRevisionFactId()).isEqualTo("revision-fact-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)))
@@ -1977,6 +2083,51 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action");
}
@Test
void createAdminActionAllowsGovernancePolicyRevisionLowRiskActions() {
DigitalEmployeeAdminActionResultDto pullGovernancePolicy = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "pull_policy")
);
DigitalEmployeeAdminActionResultDto markPolicyReviewed = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "mark_reviewed")
);
DigitalEmployeeAdminActionResultDto dismissPolicy = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "dismiss")
);
DigitalEmployeeAdminActionResultDto requestRevisionReview = service.createAdminAction(
adminActionRequest("revision", "rev-1", "request_revision_review")
);
assertThat(pullGovernancePolicy.getOk()).isTrue();
assertThat(markPolicyReviewed.getOk()).isTrue();
assertThat(dismissPolicy.getOk()).isTrue();
assertThat(requestRevisionReview.getOk()).isTrue();
assertThat(requestRevisionReview.getStatus()).isEqualTo("pending");
assertThat(requestRevisionReview.getReasonCodes()).contains("action_recorded");
}
@Test
void createAdminActionRejectsHighRiskGovernanceExecutionActions() {
DigitalEmployeeAdminActionResultDto hardInterrupt = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "hard_interrupt")
);
DigitalEmployeeAdminActionResultDto activatePlan = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "activate_plan")
);
DigitalEmployeeAdminActionResultDto cancelRun = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "cancel_run")
);
DigitalEmployeeAdminActionResultDto cancelTask = service.createAdminAction(
adminActionRequest("governance_policy", "governance-policy-1", "cancel_task")
);
assertThat(hardInterrupt.getOk()).isFalse();
assertThat(hardInterrupt.getReasonCodes()).contains("hard_interrupt_disabled");
assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled");
assertThat(cancelRun.getReasonCodes()).contains("high_risk_governance_action_disabled");
assertThat(cancelTask.getReasonCodes()).contains("high_risk_governance_action_disabled");
}
@Test
void createAdminActionAllowsLowRiskDiagnosticRepairIntent() {
DigitalEmployeeAdminActionResultDto markReviewed = service.createAdminAction(