吸收数字员工跨设备审批策略冲突预览
This commit is contained in:
@@ -39,6 +39,18 @@ public class DigitalEmployeePolicySnapshotRowDto {
|
||||
private Integer notMappedCount;
|
||||
@JsonProperty("intervention_conflict_status")
|
||||
private String interventionConflictStatus;
|
||||
@JsonProperty("approval_policy_id")
|
||||
private String approvalPolicyId;
|
||||
@JsonProperty("conflict_policy_id")
|
||||
private String conflictPolicyId;
|
||||
@JsonProperty("decision_guard_status")
|
||||
private String decisionGuardStatus;
|
||||
@JsonProperty("batch_accept_remote_disabled")
|
||||
private Boolean batchAcceptRemoteDisabled;
|
||||
@JsonProperty("auto_decision_disabled")
|
||||
private Boolean autoDecisionDisabled;
|
||||
@JsonProperty("policy_reason")
|
||||
private String policyReason;
|
||||
@JsonProperty("sync_record_url")
|
||||
private String syncRecordUrl;
|
||||
@JsonProperty("client_key_masked")
|
||||
|
||||
@@ -608,6 +608,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
.missingContextCount(routeInterventionCount(routeInterventionViews, "missing_context"))
|
||||
.notMappedCount(routeInterventionCount(routeInterventionViews, "not_mapped"))
|
||||
.interventionConflictStatus(interventionConflictStatus)
|
||||
.approvalPolicyId(firstString(latestView.get("approval_policy_id"), latestView.get("approvalPolicyId")))
|
||||
.conflictPolicyId(firstString(latestView.get("conflict_policy_id"), latestView.get("conflictPolicyId")))
|
||||
.decisionGuardStatus(firstString(latestView.get("decision_guard_status"), latestView.get("decisionGuardStatus")))
|
||||
.batchAcceptRemoteDisabled(nullableBooleanValue(firstPresent(latestView.get("batch_accept_remote_disabled"), latestView.get("batchAcceptRemoteDisabled"))))
|
||||
.autoDecisionDisabled(nullableBooleanValue(firstPresent(latestView.get("auto_decision_disabled"), latestView.get("autoDecisionDisabled"))))
|
||||
.policyReason(firstString(latestView.get("policy_reason"), latestView.get("policyReason")))
|
||||
.syncRecordUrl(syncRecordUrl(latest.getOutboxId()))
|
||||
.clientKeyMasked(maskClientKey(latest.getClientKey()))
|
||||
.deviceId(latest.getDeviceId())
|
||||
@@ -684,7 +690,13 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
"catch_up_on_startup",
|
||||
"approval_required_risk_levels",
|
||||
"approval_required_actions",
|
||||
"auto_reject_actions"
|
||||
"auto_reject_actions",
|
||||
"approval_policy_id",
|
||||
"conflict_policy_id",
|
||||
"decision_guard_status",
|
||||
"batch_accept_remote_disabled",
|
||||
"auto_decision_disabled",
|
||||
"policy_reason"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1816,6 +1816,72 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(row.getConflictKeys()).contains("route_intervention_state");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminPolicySnapshotsSummarizesApprovalPolicyConflicts() {
|
||||
DigitalEmployeeSyncRecord localApprovalPolicy = record("outbox-approval-policy-a", "event", "event-approval-policy-a", 101L, new Date(10_000),
|
||||
Map.ofEntries(
|
||||
Map.entry("entity_type", "event"),
|
||||
Map.entry("category", "policy_snapshot"),
|
||||
Map.entry("kind", "approval_decision_policy_snapshot"),
|
||||
Map.entry("policy_kind", "approval_decision"),
|
||||
Map.entry("policy_key", "approval_decision"),
|
||||
Map.entry("title", "审批裁决策略"),
|
||||
Map.entry("approval_policy_id", "approval-policy-local"),
|
||||
Map.entry("conflict_policy_id", "conflict-policy-local"),
|
||||
Map.entry("decision_guard_status", "protected"),
|
||||
Map.entry("batch_accept_remote_disabled", true),
|
||||
Map.entry("auto_decision_disabled", true),
|
||||
Map.entry("policy_reason", "manual_review_required")
|
||||
),
|
||||
Map.of("kind", "approval_decision_policy_snapshot"));
|
||||
localApprovalPolicy.setClientKey("client-key-a");
|
||||
localApprovalPolicy.setDeviceId("device-a");
|
||||
DigitalEmployeeSyncRecord remoteApprovalPolicy = record("outbox-approval-policy-b", "event", "event-approval-policy-b", 101L, new Date(11_000),
|
||||
Map.ofEntries(
|
||||
Map.entry("entity_type", "event"),
|
||||
Map.entry("category", "policy_snapshot"),
|
||||
Map.entry("kind", "approval_decision_policy_snapshot"),
|
||||
Map.entry("policy_kind", "approval_decision"),
|
||||
Map.entry("policy_key", "approval_decision"),
|
||||
Map.entry("title", "审批裁决策略"),
|
||||
Map.entry("approval_policy_id", "approval-policy-remote"),
|
||||
Map.entry("conflict_policy_id", "conflict-policy-remote"),
|
||||
Map.entry("decision_guard_status", "manual_only"),
|
||||
Map.entry("batch_accept_remote_disabled", true),
|
||||
Map.entry("auto_decision_disabled", false),
|
||||
Map.entry("policy_reason", "remote_policy_changed")
|
||||
),
|
||||
Map.of("kind", "approval_decision_policy_snapshot"));
|
||||
remoteApprovalPolicy.setClientKey("client-key-b");
|
||||
remoteApprovalPolicy.setDeviceId("device-b");
|
||||
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq("event"), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(100L)))
|
||||
.thenReturn(page(List.of(
|
||||
localApprovalPolicy,
|
||||
remoteApprovalPolicy,
|
||||
record("outbox-notification", "notification", "notification-1", 101L, new Date(12_000),
|
||||
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||
Map.of("id", "notification-1", "status", "unread"))
|
||||
)));
|
||||
|
||||
DigitalEmployeePolicySnapshotPageDto result = service.queryAdminPolicySnapshots(null, null, "conflict", "approval_decision", null, null, 1L, 20L);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(1);
|
||||
DigitalEmployeePolicySnapshotRowDto row = result.getRecords().get(0);
|
||||
assertThat(row.getPolicyKind()).isEqualTo("approval_decision");
|
||||
assertThat(row.getStatus()).isEqualTo("conflict");
|
||||
assertThat(row.getClientCount()).isEqualTo(2);
|
||||
assertThat(row.getDeviceCount()).isEqualTo(2);
|
||||
assertThat(row.getConflictKeys()).contains("approval_policy_id", "conflict_policy_id", "decision_guard_status", "auto_decision_disabled", "policy_reason");
|
||||
assertThat(row.getApprovalPolicyId()).isEqualTo("approval-policy-remote");
|
||||
assertThat(row.getConflictPolicyId()).isEqualTo("conflict-policy-remote");
|
||||
assertThat(row.getDecisionGuardStatus()).isEqualTo("manual_only");
|
||||
assertThat(row.getBatchAcceptRemoteDisabled()).isTrue();
|
||||
assertThat(row.getAutoDecisionDisabled()).isFalse();
|
||||
assertThat(row.getPolicyReason()).isEqualTo("remote_policy_changed");
|
||||
assertThat(row.getRecommendedResolution()).isEqualTo("review_latest_policy_snapshot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordDailyReportSendResultStoresRecordedObservation() {
|
||||
DigitalEmployeeAdminRecordRequestDto request = adminRecordRequest("daily_report", "report-1");
|
||||
|
||||
@@ -720,6 +720,12 @@ const DigitalEmployeeOps: React.FC = () => {
|
||||
missing_context_count: record.missing_context_count,
|
||||
not_mapped_count: record.not_mapped_count,
|
||||
intervention_conflict_status: record.intervention_conflict_status,
|
||||
approval_policy_id: record.approval_policy_id,
|
||||
conflict_policy_id: record.conflict_policy_id,
|
||||
decision_guard_status: record.decision_guard_status,
|
||||
batch_accept_remote_disabled: record.batch_accept_remote_disabled,
|
||||
auto_decision_disabled: record.auto_decision_disabled,
|
||||
policy_reason: record.policy_reason,
|
||||
latest_synced_at: record.latest_synced_at,
|
||||
synced_at: record.latest_synced_at,
|
||||
client_key_masked: record.client_key_masked,
|
||||
@@ -894,6 +900,14 @@ const DigitalEmployeeOps: React.FC = () => {
|
||||
: '暂无冲突字段'}
|
||||
{detailRecord.intervention_conflict_status &&
|
||||
` · 跨设备路由干预冲突 ${detailRecord.intervention_conflict_status}`}
|
||||
{(detailRecord.approval_policy_id || detailRecord.conflict_policy_id || detailRecord.decision_guard_status) &&
|
||||
` · 跨设备审批策略冲突`}
|
||||
{detailRecord.approval_policy_id && ` · 审批策略 ${detailRecord.approval_policy_id}`}
|
||||
{detailRecord.conflict_policy_id && ` · 冲突策略 ${detailRecord.conflict_policy_id}`}
|
||||
{detailRecord.decision_guard_status && ` · 裁决保护 ${detailRecord.decision_guard_status}`}
|
||||
{detailRecord.policy_reason && ` · 策略原因 ${detailRecord.policy_reason}`}
|
||||
{detailRecord.batch_accept_remote_disabled && ' · 批量采纳远端禁用'}
|
||||
{detailRecord.auto_decision_disabled && ' · 自动裁决禁用'}
|
||||
{detailRecord.route_decision_count != null &&
|
||||
` · 路由 ${detailRecord.route_decision_count} 条`}
|
||||
{detailRecord.blocked_count != null &&
|
||||
|
||||
@@ -722,6 +722,12 @@ export interface DigitalEmployeePolicySnapshotRow {
|
||||
missing_context_count?: number;
|
||||
not_mapped_count?: number;
|
||||
intervention_conflict_status?: string;
|
||||
approval_policy_id?: string;
|
||||
conflict_policy_id?: string;
|
||||
decision_guard_status?: string;
|
||||
batch_accept_remote_disabled?: boolean;
|
||||
auto_decision_disabled?: boolean;
|
||||
policy_reason?: string;
|
||||
sync_record_url?: string;
|
||||
client_key_masked?: string;
|
||||
device_id?: string;
|
||||
|
||||
@@ -1278,21 +1278,30 @@ function checkDigitalAdminPolicySnapshots() {
|
||||
[backendController, "/snapshots", "backend policy snapshot endpoint"],
|
||||
[backendService, "routeInterventionConflictStatus", "backend route intervention conflict aggregation"],
|
||||
[backendService, "route_intervention_state", "backend route intervention conflict key"],
|
||||
[backendService, "approval_policy_id", "backend approval policy conflict field"],
|
||||
[backendService, "decision_guard_status", "backend approval decision guard conflict field"],
|
||||
[backendDto, "intervention_conflict_status", "backend route intervention conflict status field"],
|
||||
[backendDto, "approval_policy_id", "backend policy snapshot approval policy field"],
|
||||
[backendDto, "auto_decision_disabled", "backend policy snapshot auto decision field"],
|
||||
[backendDto, "route_decision_count", "backend route decision count field"],
|
||||
[backendTest, "queryAdminPolicySnapshotsSummarizesRouteInterventionConflicts", "backend route intervention conflict test"],
|
||||
[backendTest, "queryAdminPolicySnapshotsSummarizesApprovalPolicyConflicts", "backend approval policy conflict test"],
|
||||
[qimingService, "apiDigitalEmployeeAdminPolicySnapshots", "qiming policy snapshot service"],
|
||||
[qimingService, "/api/digital-employee/admin/policies/snapshots", "qiming policy snapshot endpoint"],
|
||||
[qimingTypes, "DigitalEmployeePolicySnapshotRow", "qiming policy snapshot row type"],
|
||||
[qimingTypes, "policy_conflicts", "qiming policy conflicts view type"],
|
||||
[qimingTypes, "approval_policy_id", "qiming approval policy snapshot type field"],
|
||||
[qimingTypes, "intervention_conflict_status", "qiming route intervention conflict type field"],
|
||||
[qimingOps, "策略冲突", "qiming policy conflict tab text"],
|
||||
[qimingOps, "conflict_keys", "qiming conflict key display"],
|
||||
[qimingOps, "跨设备审批策略冲突", "qiming approval policy conflict copy"],
|
||||
[qimingOps, "裁决保护", "qiming decision guard conflict copy"],
|
||||
[qimingOps, "跨设备路由干预冲突", "qiming route intervention conflict copy"],
|
||||
[qimingOps, "不恢复执行", "qiming route recovery guard copy"],
|
||||
[qimingOps, "不绕过审批", "qiming approval guard copy"],
|
||||
[qimingOps, "不远程改策略", "qiming remote policy edit guard copy"],
|
||||
[docs, "跨设备策略冲突视图", "docs policy conflict absorption"],
|
||||
[docs, "跨设备审批策略冲突预览", "docs approval policy conflict absorption"],
|
||||
[docs, "跨设备路由干预冲突可视化", "docs route intervention conflict absorption"],
|
||||
];
|
||||
const missing = requiredSnippets
|
||||
|
||||
@@ -551,7 +551,7 @@ GET /api/digital-employee/approvals
|
||||
1. **PlanStep 与依赖图(依赖图调度已开始)**
|
||||
- 已吸收:Plan / Task / Run 记录、计划模板、`create_plan` 中的 steps/tasks 可物化为任务;PlanStep 已开始作为正式本地实体落入 `digital_plan_steps`。
|
||||
- 当前能力:PlanStep 会进入 `digital_sync_outbox`,entity type 为 `plan_step`;任务中心 flow 和 debug store 优先读取真实 PlanStep 生成步骤图;Task Agent 的 retry / skip / cancel / pause / resume / input / run 控制已可更新真实 Task、PlanStep、Run、Event 和 outbox;前置步骤跳过、取消或重试后会重新评估后继步骤,推进为 ready / blocked / pending 并写入依赖图事件;scheduler sweep 会识别可派发的 ready PlanStep,派发前先通过管理端 `POST /api/digital-employee/leases/plan-step-dispatch/acquire` 做跨设备强租约仲裁,管理端拒绝会以 `remote_lease_rejected` 跳过,管理端异常会降级为 5 分钟本地软租约 `payload.dispatchLease.source = local_fallback` 并记录 `remote_error`;调用本地 `/computer/chat` 后会写入 `plan_step_dispatch_*` 与 `governance_start_run` 事件,派发完成或失败会先尝试 `release` 管理端租约再释放本地 lease 并写入 `plan_step_lease_*` 事件;已有未过期租约的步骤仍会以 `lease_active` 跳过;auto sweep 会按节流从管理端 `GET /api/digital-employee/policies/plan-step-dispatch` 拉取远端派发策略,拉取失败时保留本地策略并记录 `last_pull_error`;embedded 已提供 `/api/digital-employee/plan-steps`、`/api/digital-employee/plan-step-graph`、`/api/digital-employee/plans/:planId/dispatch-ready-steps`、`/api/plan-step/dispatch-policy` 和 `/api/plan-step/dispatch-policy/pull`,ready PlanStep 安全派发、计划级跨任务推进、管理端依赖图视图、本地/远端派发策略、可观察租约状态和首页阻塞依赖处理入口已开始闭环。派发策略管理 UI 已开始吸收,embedded 新增“策略中心”集中展示和编辑 `enabled/max_per_sweep/auto_dispatch_ready_steps`。正式管理端策略中心预览已开始吸收,qiming-backend 新增 `policy_center` 运营台视图聚合派发、风险审批、入口路由和技能策略快照,qiming“数字员工运营台”的“策略中心”tab 可记录批量拉取策略、标记已看和忽略意图,不开放远程策略编辑。正式管理端步骤调度预览已开始吸收,qiming“数字员工运营台”的“步骤调度”tab 会聚合 PlanStep fact、依赖状态、租约、派发事件和策略拉取审计,只记录标记已看、忽略和重试策略检查意图,不远程派发、不抢占租约、不释放租约。
|
||||
- 后续缺口:跨设备审批策略 UI、跨设备策略冲突合并、远程策略编辑安全模型、真实派发/租约裁决和跨设备策略合并仍待吸收。
|
||||
- 后续缺口:跨设备审批策略冲突预览已开始吸收,策略冲突页可展示审批策略、冲突策略、裁决保护和自动裁决保护差异;远程策略编辑安全模型、真实派发/租约裁决和跨设备策略自动合并仍待吸收。
|
||||
- 建议:下一轮围绕管理端正式路由 UI 或跨端审批策略 UI,把本地可治理调度继续推进到跨端治理策略。
|
||||
|
||||
2. **调度 / 定时 / 周期任务**
|
||||
@@ -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 深链和脱敏客户端 Key;Plan 视图会聚合 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 深链。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`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 深链和脱敏客户端 Key;Plan 视图会聚合 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` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`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 派生业务查询模型和 embedded 业务视图预览承接联调,再按容量和查询压力决定是否拆 Plan / Task / Run / Event 物理业务表。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user