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

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(

View File

@@ -57,6 +57,8 @@ const WORKBENCH_TABS: Array<{
{ key: 'plan_step_dispatch', label: '步骤调度' },
{ key: 'plan_governance', label: '计划治理' },
{ key: 'plan_revisions', label: '计划版本' },
{ key: 'governance_policies', label: '治理策略' },
{ key: 'plan_revision_materialization', label: '版本物化' },
];
const ENTITY_TYPE_OPTIONS = {
@@ -118,6 +120,8 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
record.run_id && `Run ${record.run_id}`,
record.approval_id && `Approval ${record.approval_id}`,
record.approval_policy_id && `ApprovalPolicy ${record.approval_policy_id}`,
record.governance_policy_id && `GovernancePolicy ${record.governance_policy_id}`,
record.revision_fact_id && `RevisionFact ${record.revision_fact_id}`,
record.conflict_policy_id && `ConflictPolicy ${record.conflict_policy_id}`,
record.decision_guard_status && `DecisionGuard ${record.decision_guard_status}`,
record.service_id && `Service ${record.service_id}`,
@@ -273,6 +277,12 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
if (view === 'plan_revisions') {
return { entityType: 'revision', entityId: record.revision_id || record.entity_id };
}
if (view === 'governance_policies') {
return { entityType: 'governance_policy', entityId: record.governance_policy_id || record.policy_key || record.entity_id };
}
if (view === 'plan_revision_materialization') {
return { entityType: 'revision', entityId: record.revision_fact_id || record.revision_id || record.entity_id };
}
return { entityType: record.entity_type, entityId: record.entity_id };
}
@@ -427,6 +437,20 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
{ action: 'request_revision_review', label: '请求版本复核' },
];
}
if (view === 'governance_policies') {
return [
{ action: 'pull_policy', label: '批量拉取策略' },
{ action: 'mark_reviewed', label: '标记已看' },
{ action: 'dismiss', label: '忽略' },
];
}
if (view === 'plan_revision_materialization') {
return [
{ action: 'mark_reviewed', label: '标记已看' },
{ action: 'dismiss', label: '忽略' },
{ action: 'request_revision_review', label: '请求版本复核' },
];
}
return [];
}
@@ -1298,6 +1322,38 @@ const DigitalEmployeeOps: React.FC = () => {
</Typography.Text>
</Typography.Paragraph>
)}
{activeView === 'governance_policies' && (
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{detailRecord.governance_policy_id || detailRecord.policy_key || detailRecord.entity_id || '--'}
{detailRecord.policy_guard_status && ` · 策略保护 ${detailRecord.policy_guard_status}`}
{detailRecord.policy_source && ` · 来源 ${detailRecord.policy_source}`}
{detailRecord.policy_status && ` · 状态 ${detailRecord.policy_status}`}
{detailRecord.policy_reason && ` · 原因 ${detailRecord.policy_reason}`}
{detailRecord.hard_interrupt_disabled && ' · ACP 硬中断禁用'}
<Typography.Text type="secondary">
{' '}
· ACP
</Typography.Text>
</Typography.Paragraph>
)}
{activeView === 'plan_revision_materialization' && (
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{detailRecord.revision_fact_id || detailRecord.revision_id || detailRecord.entity_id || '--'}
{detailRecord.revision_status && ` · 版本状态 ${detailRecord.revision_status}`}
{detailRecord.revision_materialized != null &&
` · 版本事实来源 ${detailRecord.revision_materialized ? 'materialized' : 'fallback'}`}
{detailRecord.base_revision_id && ` · 基线 ${detailRecord.base_revision_id}`}
{detailRecord.changed_fields?.length
? ` · 变更字段 ${detailRecord.changed_fields.join('/')}`
: ''}
<Typography.Text type="secondary">
{' '}
·
</Typography.Text>
</Typography.Paragraph>
)}
{allowedActions(detailRecord, activeView).length > 0 && (
<div style={{ marginBottom: 16 }}>
<Typography.Title level={5}></Typography.Title>

View File

@@ -577,7 +577,9 @@ export type DigitalEmployeeAdminWorkbenchView =
| 'scheduler_operations'
| 'plan_step_dispatch'
| 'plan_governance'
| 'plan_revisions';
| 'plan_revisions'
| 'governance_policies'
| 'plan_revision_materialization';
export interface DigitalEmployeeAdminWorkbenchRow {
view: DigitalEmployeeAdminWorkbenchView | string;
@@ -615,6 +617,9 @@ export interface DigitalEmployeeAdminWorkbenchRow {
batch_accept_remote_disabled?: boolean;
auto_decision_disabled?: boolean;
policy_reason?: string;
governance_policy_id?: string;
policy_guard_status?: string;
hard_interrupt_disabled?: boolean;
artifact_id?: string;
artifact_group_id?: string;
artifact_count?: number;
@@ -709,6 +714,11 @@ export interface DigitalEmployeeAdminWorkbenchRow {
schedule_id?: string;
job_id?: string;
revision_id?: string;
revision_fact_id?: string;
revision_status?: string;
revision_materialized?: boolean;
base_revision_id?: string;
changed_fields?: string[];
conflict_keys?: string[];
recommended_resolution?: string;
route_decision_count?: number;

View File

@@ -1969,6 +1969,104 @@ function checkDigitalAdminPlanGovernanceWorkbenches() {
console.log("[DigitalEmployeeCheck] admin plan governance and revision hooks OK");
}
function checkDigitalAdminGovernancePolicyRevisionMaterialization() {
console.log("\n[DigitalEmployeeCheck] Verify admin governance policy and revision materialization 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, "governance_policies", "backend governance policies view"],
[service, "plan_revision_materialization", "backend plan revision materialization view"],
[service, "isGovernancePolicyRow", "backend governance policy matcher"],
[service, "isPlanRevisionMaterializationRow", "backend revision materialization matcher"],
[service, "governance_policy:pull_policy", "backend governance policy pull action"],
[service, "hard_interrupt_disabled", "backend hard interrupt disabled reason"],
[dto, "governance_policy_id", "backend governance policy id field"],
[dto, "policy_guard_status", "backend policy guard status field"],
[dto, "revision_fact_id", "backend revision fact id field"],
[dto, "revision_materialized", "backend revision materialized field"],
[dto, "changed_fields", "backend changed fields row field"],
[test, "queryAdminWorkbenchGovernancePoliciesReturnsPolicyRowsOnly", "backend governance policies test"],
[test, "queryAdminWorkbenchPlanRevisionMaterializationReturnsRevisionRowsOnly", "backend revision materialization test"],
[test, "createAdminActionAllowsGovernancePolicyRevisionLowRiskActions", "backend low risk action test"],
[qimingTypes, "governance_policies", "qiming governance policies view type"],
[qimingTypes, "plan_revision_materialization", "qiming revision materialization view type"],
[qimingTypes, "governance_policy_id", "qiming governance policy id field"],
[qimingTypes, "revision_fact_id", "qiming revision fact id field"],
[qimingOps, "治理策略", "qiming governance policies tab"],
[qimingOps, "版本物化", "qiming revision materialization tab"],
[qimingOps, "策略保护", "qiming policy guard copy"],
[qimingOps, "版本事实来源", "qiming revision fact source copy"],
[qimingOps, "不开放 ACP 硬中断", "qiming ACP hard interrupt guard copy"],
[docs, "正式管理端治理策略预览", "docs governance policy absorption"],
[docs, "计划版本物化预览", "docs revision materialization absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing admin governance policy and revision materialization hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] admin governance policy and revision materialization hooks OK");
}
function checkDigitalAdminPlanStepDispatchWorkbench() {
console.log("\n[DigitalEmployeeCheck] Verify admin plan step dispatch workbench hooks");
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
@@ -3020,6 +3118,7 @@ function main() {
checkDigitalAdminMemorySkillWorkbench();
checkDigitalAdminPolicySchedulerWorkbenches();
checkDigitalAdminPlanGovernanceWorkbenches();
checkDigitalAdminGovernancePolicyRevisionMaterialization();
checkDigitalAdminPlanStepDispatchWorkbench();
checkDigitalAdminCollaborationOperationsClosure();
checkDigitalAdminServiceOperationsWorkbench();

View File

@@ -568,8 +568,8 @@ GET /api/digital-employee/approvals
4. **治理命令完整生命周期**
- 已吸收:`create_plan``submit_plan``approve_plan``activate_plan` 等基础命令可记录本地运行事实;任务级 `retry_task``skip_task``pause_task``resume_task``provide_task_input``cancel_task``start_run``cancel_run` 已具备本地事实闭环embedded plan / task action endpoint 已开始映射为正式 governance command治理审计链和低风险 action policy 已开始吸收route decision 自动映射 governance command 前会先经过低风险/受保护高风险 action guard未命中的动作写入 `route_action_policy_blocked`,所有 `governance_*` event 的同步 `business_view` 会提炼 command、route、实体目标、风险等级和结果字段`provide_task_input` 只同步 `input_length`
- 当前能力:`/api/plan/:id/submit``/api/debug/plan/:id/:action``/api/task/:id/:action` 会进入 qimingclaw 本地治理记录plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。正式管理端治理命令预览已开始吸收qiming-backend 的 `governance_commands` 运营台视图会聚合治理命令记录、拒绝和低风险输入审计qiming“数字员工运营台”的“治理命令”tab 只开放标记已看和忽略意图,不开放 ACP 硬中断或高风险执行。正式管理端治理执行安全壳预览和正式管理端计划版本预览已开始吸收qiming-backend 新增 `plan_governance` / `plan_revisions` 视图聚合治理动作、升级请求、人工介入和版本复核摘要qiming“数字员工运营台”的“计划治理”“计划版本”tab 只记录标记已看、忽略、请求版本复核和记录升级等低风险意图;`hard_interrupt``activate_plan``cancel_run``cancel_task``accept_remote_approval_batch` 继续返回明确禁用 reason。
- 缺口:正式计划 revision 物理表、治理策略编辑 UI 和对真实 ACP 执行流的硬中断仍不完整。
- 当前能力:`/api/plan/:id/submit``/api/debug/plan/:id/:action``/api/task/:id/:action` 会进入 qimingclaw 本地治理记录plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。正式管理端治理命令预览已开始吸收qiming-backend 的 `governance_commands` 运营台视图会聚合治理命令记录、拒绝和低风险输入审计qiming“数字员工运营台”的“治理命令”tab 只开放标记已看和忽略意图,不开放 ACP 硬中断或高风险执行。正式管理端治理执行安全壳预览和正式管理端计划版本预览已开始吸收qiming-backend 新增 `plan_governance` / `plan_revisions` 视图聚合治理动作、升级请求、人工介入和版本复核摘要qiming“数字员工运营台”的“计划治理”“计划版本”tab 只记录标记已看、忽略、请求版本复核和记录升级等低风险意图;`hard_interrupt``activate_plan``cancel_run``cancel_task``accept_remote_approval_batch` 继续返回明确禁用 reason。正式管理端治理策略预览和计划版本物化预览已开始吸收qiming-backend 新增 `governance_policies` / `plan_revision_materialization` 视图聚合治理策略、策略保护、版本事实来源、基线版本和变更字段qiming“数字员工运营台”的“治理策略”“版本物化”tab 只记录策略拉取、复核、忽略和版本复核意图。
- 缺口:正式计划 revision 物理表、治理策略编辑 UI 和对真实 ACP 执行流的硬中断仍不完整;计划版本物化预览不自动激活计划、不取消运行或任务
- 建议:下一轮围绕正式管理端治理策略和 ACP 硬中断安全设计,把本地治理审计推进到完整跨端裁决与执行控制。
5. **ManagedService 控制面**
@@ -609,7 +609,7 @@ GET /api/digital-employee/approvals
- 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。
12. **管理端业务查询模型**
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version``entity_summary``business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans``/plans/{plan_id}``/tasks``/runs``/events``/artifacts``/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 KeyPlan 视图会聚合 task/run 计数与最近事件Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段本地与管理端业务视图投影已开始闭环。正式外部管理端台面已开始吸收qiming-backend 新增 `/api/digital-employee/admin/workbench/{view}`qiming 管理端新增“数字员工运营台”,统一展示业务视图、介入台、产物治理、通知运营、审计诊断、日报运营和路由治理,只读消费 sync record `business_view` 并提供同步记录深链。管理端低风险动作闭环已开始吸收:后端新增 `/api/digital-employee/admin/actions` 记录动作意图和 `/pending` 拉取队列qimingclaw 只应用保留本地、标记复核、通知已读/忽略、产物保留/到期、日报交付/确认/审批请求等低风险动作并写入 `admin_action_applied` / `admin_action_rejected` / `admin_action_failed`qiming 运营台抽屉只提示“已记录等待客户端拉取”。跨设备策略冲突视图已开始吸收qimingclaw 将派发、风险审批和入口路由策略同步事件投影为 `policy_snapshot` business view后端新增 `/api/digital-employee/admin/policies/snapshots` 聚合 client/device 维度差异并输出 `conflict_keys`qiming 运营台新增“策略冲突” tab 展示 policy drift 与 sync record 深链。跨设备审批策略冲突预览已开始吸收:策略冲突页会把 `approval_policy_id``conflict_policy_id``decision_guard_status``batch_accept_remote_disabled``auto_decision_disabled` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。跨设备调度派发策略冲突预览已开始吸收:策略冲突页会把 `dispatch_policy_key``max_per_sweep``auto_dispatch_ready_steps``catch_up_on_startup``max_run_history` 纳入冲突字段和详情解释,但不立即运行、不接管租约、不远程改策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`admin/workbench` 新增 `policy_center``scheduler_operations` 视图统一消费策略快照、scheduler job、cron settings、lease、dispatch sweep 和 recent run 摘要,并通过低风险 admin action 记录策略拉取、立即检查调度、复核和忽略意图。业务事实物化查询已开始吸收:后端新增 `digital_employee_business_fact``digital_employee_artifact_lifecycle``digital_employee_daily_report_fact` 三张增量物化表,同步入库时 upsert 安全业务摘要,业务查询优先返回 `materialized: true` 行,旧记录继续回退 sync record。
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version``entity_summary``business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans``/plans/{plan_id}``/tasks``/runs``/events``/artifacts``/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 KeyPlan 视图会聚合 task/run 计数与最近事件Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段本地与管理端业务视图投影已开始闭环。正式外部管理端台面已开始吸收qiming-backend 新增 `/api/digital-employee/admin/workbench/{view}`qiming 管理端新增“数字员工运营台”,统一展示业务视图、介入台、产物治理、通知运营、审计诊断、日报运营和路由治理,只读消费 sync record `business_view` 并提供同步记录深链。管理端低风险动作闭环已开始吸收:后端新增 `/api/digital-employee/admin/actions` 记录动作意图和 `/pending` 拉取队列qimingclaw 只应用保留本地、标记复核、通知已读/忽略、产物保留/到期、日报交付/确认/审批请求等低风险动作并写入 `admin_action_applied` / `admin_action_rejected` / `admin_action_failed`qiming 运营台抽屉只提示“已记录等待客户端拉取”。跨设备策略冲突视图已开始吸收qimingclaw 将派发、风险审批和入口路由策略同步事件投影为 `policy_snapshot` business view后端新增 `/api/digital-employee/admin/policies/snapshots` 聚合 client/device 维度差异并输出 `conflict_keys`qiming 运营台新增“策略冲突” tab 展示 policy drift 与 sync record 深链。跨设备审批策略冲突预览已开始吸收:策略冲突页会把 `approval_policy_id``conflict_policy_id``decision_guard_status``batch_accept_remote_disabled``auto_decision_disabled` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。跨设备调度派发策略冲突预览已开始吸收:策略冲突页会把 `dispatch_policy_key``max_per_sweep``auto_dispatch_ready_steps``catch_up_on_startup``max_run_history` 纳入冲突字段和详情解释,但不立即运行、不接管租约、不远程改策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`admin/workbench` 新增 `policy_center``scheduler_operations` 视图统一消费策略快照、scheduler job、cron settings、lease、dispatch sweep 和 recent run 摘要,并通过低风险 admin action 记录策略拉取、立即检查调度、复核和忽略意图。正式管理端治理策略预览和计划版本物化预览已开始吸收:`admin/workbench` 新增 `governance_policies``plan_revision_materialization`,从 sync record / business fact 投影治理策略保护、ACP 硬中断禁用、版本事实来源和变更字段。业务事实物化查询已开始吸收:后端新增 `digital_employee_business_fact``digital_employee_artifact_lifecycle``digital_employee_daily_report_fact` 三张增量物化表,同步入库时 upsert 安全业务摘要,业务查询优先返回 `materialized: true` 行,旧记录继续回退 sync record。
- 缺口:复杂组合检索、跨设备聚合图、远程裁决/策略编辑动作和物化业务表仍待吸收。
- 建议:先使用 sync record 派生业务查询模型和 embedded 业务视图预览承接联调,再按容量和查询压力决定是否拆 Plan / Task / Run / Event 物理业务表。