吸收数字员工跨设备调度派发策略冲突预览

This commit is contained in:
baiyanyun
2026-06-14 20:59:41 +08:00
parent 39371861f3
commit cc55fd819f
7 changed files with 134 additions and 3 deletions

View File

@@ -51,6 +51,16 @@ public class DigitalEmployeePolicySnapshotRowDto {
private Boolean autoDecisionDisabled;
@JsonProperty("policy_reason")
private String policyReason;
@JsonProperty("dispatch_policy_key")
private String dispatchPolicyKey;
@JsonProperty("max_per_sweep")
private Long maxPerSweep;
@JsonProperty("auto_dispatch_ready_steps")
private Boolean autoDispatchReadySteps;
@JsonProperty("catch_up_on_startup")
private Boolean catchUpOnStartup;
@JsonProperty("max_run_history")
private Long maxRunHistory;
@JsonProperty("sync_record_url")
private String syncRecordUrl;
@JsonProperty("client_key_masked")

View File

@@ -614,6 +614,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.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")))
.dispatchPolicyKey(firstString(latestView.get("dispatch_policy_key"), latestView.get("dispatchPolicyKey")))
.maxPerSweep(numberValue(firstPresent(latestView.get("max_per_sweep"), latestView.get("maxPerSweep"))))
.autoDispatchReadySteps(nullableBooleanValue(firstPresent(latestView.get("auto_dispatch_ready_steps"), latestView.get("autoDispatchReadySteps"))))
.catchUpOnStartup(nullableBooleanValue(firstPresent(latestView.get("catch_up_on_startup"), latestView.get("catchUpOnStartup"))))
.maxRunHistory(numberValue(firstPresent(latestView.get("max_run_history"), latestView.get("maxRunHistory"))))
.syncRecordUrl(syncRecordUrl(latest.getOutboxId()))
.clientKeyMasked(maskClientKey(latest.getClientKey()))
.deviceId(latest.getDeviceId())
@@ -687,7 +692,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"policy_source",
"auto_create_governance_commands",
"max_per_sweep",
"auto_dispatch_ready_steps",
"catch_up_on_startup",
"max_run_history",
"approval_required_risk_levels",
"approval_required_actions",
"auto_reject_actions",

View File

@@ -1882,6 +1882,82 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(row.getRecommendedResolution()).isEqualTo("review_latest_policy_snapshot");
}
@Test
void queryAdminPolicySnapshotsSummarizesDispatchAndSchedulerPolicyConflicts() {
DigitalEmployeeSyncRecord dispatchA = record("outbox-dispatch-policy-a", "event", "event-dispatch-policy-a", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "policy_snapshot",
"kind", "plan_step_dispatch_policy_pulled",
"policy_kind", "dispatch",
"policy_key", "plan_step_dispatch",
"enabled", true,
"max_per_sweep", 3,
"auto_dispatch_ready_steps", true,
"dispatch_policy_key", "plan_step_dispatch"
),
Map.of("kind", "plan_step_dispatch_policy_pulled"));
dispatchA.setDeviceId("device-a");
DigitalEmployeeSyncRecord dispatchB = record("outbox-dispatch-policy-b", "event", "event-dispatch-policy-b", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "policy_snapshot",
"kind", "plan_step_dispatch_policy_pulled",
"policy_kind", "dispatch",
"policy_key", "plan_step_dispatch",
"enabled", true,
"max_per_sweep", 8,
"auto_dispatch_ready_steps", false,
"dispatch_policy_key", "plan_step_dispatch"
),
Map.of("kind", "plan_step_dispatch_policy_pulled"));
dispatchB.setDeviceId("device-b");
DigitalEmployeeSyncRecord schedulerA = record("outbox-scheduler-policy-a", "event", "event-scheduler-policy-a", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"category", "policy_snapshot",
"kind", "scheduler_policy_snapshot",
"policy_kind", "scheduler",
"policy_key", "scheduler",
"enabled", true,
"catch_up_on_startup", true,
"max_run_history", 20
),
Map.of("kind", "scheduler_policy_snapshot"));
schedulerA.setDeviceId("device-a");
DigitalEmployeeSyncRecord schedulerB = record("outbox-scheduler-policy-b", "event", "event-scheduler-policy-b", 101L, new Date(13_000),
Map.of(
"entity_type", "event",
"category", "policy_snapshot",
"kind", "scheduler_policy_snapshot",
"policy_kind", "scheduler",
"policy_key", "scheduler",
"enabled", true,
"catch_up_on_startup", false,
"max_run_history", 50
),
Map.of("kind", "scheduler_policy_snapshot"));
schedulerB.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(dispatchA, dispatchB, schedulerA, schedulerB)));
DigitalEmployeePolicySnapshotPageDto dispatchResult = service.queryAdminPolicySnapshots(null, null, "conflict", "dispatch", null, null, 1L, 20L);
DigitalEmployeePolicySnapshotPageDto schedulerResult = service.queryAdminPolicySnapshots(null, null, "conflict", "scheduler", null, null, 1L, 20L);
DigitalEmployeePolicySnapshotRowDto dispatchRow = dispatchResult.getRecords().get(0);
assertThat(dispatchRow.getPolicyKind()).isEqualTo("dispatch");
assertThat(dispatchRow.getConflictKeys()).contains("max_per_sweep", "auto_dispatch_ready_steps");
assertThat(dispatchRow.getDispatchPolicyKey()).isEqualTo("plan_step_dispatch");
assertThat(dispatchRow.getMaxPerSweep()).isEqualTo(8L);
assertThat(dispatchRow.getAutoDispatchReadySteps()).isFalse();
DigitalEmployeePolicySnapshotRowDto schedulerRow = schedulerResult.getRecords().get(0);
assertThat(schedulerRow.getPolicyKind()).isEqualTo("scheduler");
assertThat(schedulerRow.getConflictKeys()).contains("catch_up_on_startup", "max_run_history");
assertThat(schedulerRow.getCatchUpOnStartup()).isFalse();
assertThat(schedulerRow.getMaxRunHistory()).isEqualTo(50L);
}
@Test
void recordDailyReportSendResultStoresRecordedObservation() {
DigitalEmployeeAdminRecordRequestDto request = adminRecordRequest("daily_report", "report-1");

View File

@@ -726,6 +726,11 @@ const DigitalEmployeeOps: React.FC = () => {
batch_accept_remote_disabled: record.batch_accept_remote_disabled,
auto_decision_disabled: record.auto_decision_disabled,
policy_reason: record.policy_reason,
dispatch_policy_key: record.dispatch_policy_key,
max_per_sweep: record.max_per_sweep,
auto_dispatch_ready_steps: record.auto_dispatch_ready_steps,
catch_up_on_startup: record.catch_up_on_startup,
max_run_history: record.max_run_history,
latest_synced_at: record.latest_synced_at,
synced_at: record.latest_synced_at,
client_key_masked: record.client_key_masked,
@@ -902,10 +907,26 @@ const DigitalEmployeeOps: React.FC = () => {
` · 跨设备路由干预冲突 ${detailRecord.intervention_conflict_status}`}
{(detailRecord.approval_policy_id || detailRecord.conflict_policy_id || detailRecord.decision_guard_status) &&
` · 跨设备审批策略冲突`}
{(detailRecord.policy_kind === 'dispatch' ||
detailRecord.dispatch_policy_key ||
detailRecord.max_per_sweep != null ||
detailRecord.auto_dispatch_ready_steps != null) &&
` · 跨设备派发策略冲突`}
{(detailRecord.policy_kind === 'scheduler' ||
detailRecord.catch_up_on_startup != null ||
detailRecord.max_run_history != null) &&
` · 跨设备调度策略冲突`}
{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.dispatch_policy_key && ` · 派发策略 ${detailRecord.dispatch_policy_key}`}
{detailRecord.max_per_sweep != null && ` · max_per_sweep ${detailRecord.max_per_sweep}`}
{detailRecord.auto_dispatch_ready_steps != null &&
` · auto_dispatch_ready_steps ${detailRecord.auto_dispatch_ready_steps ? '开启' : '关闭'}`}
{detailRecord.catch_up_on_startup != null &&
` · catch_up_on_startup ${detailRecord.catch_up_on_startup ? '开启' : '关闭'}`}
{detailRecord.max_run_history != null && ` · max_run_history ${detailRecord.max_run_history}`}
{detailRecord.batch_accept_remote_disabled && ' · 批量采纳远端禁用'}
{detailRecord.auto_decision_disabled && ' · 自动裁决禁用'}
{detailRecord.route_decision_count != null &&

View File

@@ -600,6 +600,10 @@ export interface DigitalEmployeeAdminWorkbenchRow {
lease_source?: string;
dispatch_status?: string;
dispatch_policy_key?: string;
max_per_sweep?: number;
auto_dispatch_ready_steps?: boolean;
catch_up_on_startup?: boolean;
max_run_history?: number;
orchestration_id?: string;
trigger_source?: string;
approval_id?: string;
@@ -728,6 +732,11 @@ export interface DigitalEmployeePolicySnapshotRow {
batch_accept_remote_disabled?: boolean;
auto_decision_disabled?: boolean;
policy_reason?: string;
dispatch_policy_key?: string;
max_per_sweep?: number;
auto_dispatch_ready_steps?: boolean;
catch_up_on_startup?: boolean;
max_run_history?: number;
sync_record_url?: string;
client_key_masked?: string;
device_id?: string;

View File

@@ -1283,18 +1283,25 @@ function checkDigitalAdminPolicySnapshots() {
[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, "max_per_sweep", "backend policy snapshot max per sweep field"],
[backendDto, "catch_up_on_startup", "backend policy snapshot catch up field"],
[backendDto, "route_decision_count", "backend route decision count field"],
[backendTest, "queryAdminPolicySnapshotsSummarizesRouteInterventionConflicts", "backend route intervention conflict test"],
[backendTest, "queryAdminPolicySnapshotsSummarizesApprovalPolicyConflicts", "backend approval policy conflict test"],
[backendTest, "queryAdminPolicySnapshotsSummarizesDispatchAndSchedulerPolicyConflicts", "backend dispatch scheduler 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, "max_per_sweep", "qiming dispatch policy snapshot type field"],
[qimingTypes, "catch_up_on_startup", "qiming scheduler 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 dispatch policy conflict copy"],
[qimingOps, "跨设备调度策略冲突", "qiming scheduler policy conflict copy"],
[qimingOps, "裁决保护", "qiming decision guard conflict copy"],
[qimingOps, "跨设备路由干预冲突", "qiming route intervention conflict copy"],
[qimingOps, "不恢复执行", "qiming route recovery guard copy"],
@@ -1302,6 +1309,7 @@ function checkDigitalAdminPolicySnapshots() {
[qimingOps, "不远程改策略", "qiming remote policy edit guard copy"],
[docs, "跨设备策略冲突视图", "docs policy conflict absorption"],
[docs, "跨设备审批策略冲突预览", "docs approval policy conflict absorption"],
[docs, "跨设备调度派发策略冲突预览", "docs dispatch scheduler policy conflict absorption"],
[docs, "跨设备路由干预冲突可视化", "docs route intervention conflict absorption"],
];
const missing = requiredSnippets

View File

@@ -551,13 +551,13 @@ 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、依赖状态、租约、派发事件和策略拉取审计只记录标记已看、忽略和重试策略检查意图不远程派发、不抢占租约、不释放租约。
- 后续缺口:跨设备审批策略冲突预览已开始吸收,策略冲突页可展示审批策略、冲突策略、裁决保护和自动裁决保护差异;远程策略编辑安全模型、真实派发/租约裁决和跨设备策略自动合并仍待吸收。
- 后续缺口:跨设备审批策略冲突预览已开始吸收,策略冲突页可展示审批策略、冲突策略、裁决保护和自动裁决保护差异;跨设备调度派发策略冲突预览已开始吸收,策略冲突页可展示 `max_per_sweep``auto_dispatch_ready_steps``catch_up_on_startup``max_run_history` 差异;远程策略编辑安全模型、真实派发/租约裁决和跨设备策略自动合并仍待吸收。
- 建议:下一轮围绕管理端正式路由 UI 或跨端审批策略 UI把本地可治理调度继续推进到跨端治理策略。
2. **调度 / 定时 / 周期任务**
- 已吸收:`digital_schedules``digital_schedule_runs` 已进入 qimingclaw 本地 SQLite 与 outboxentity type 为 `schedule` / `schedule_run`;旧 `/api/cron``/api/scheduler/jobs` 和治理命令会优先读取真实本地调度记录。
- 当前能力:主进程 `DigitalEmployeeScheduler` 可按本机 5 字段 cron 到点触发本地 `/computer/chat`,支持启动 catch-up、立即运行、暂停/恢复/删除、失败重试、运行历史和同步诊断。多调度并发策略 UI 已开始吸收embedded 新增“调度运营”入口,可查看 scheduler jobs、最近运行、`catch_up_on_startup``max_run_history`并触发一次立即检查调度。正式管理端调度运营预览已开始吸收qiming-backend 新增 `scheduler_operations` 运营台视图聚合 scheduler job、cron settings、lease、dispatch sweep 和 recent run 摘要qiming“数字员工运营台”的“调度运营”tab 只开放立即检查调度、标记已看和忽略意图,不开放 start/stop 或高风险立即运行。
- 后续缺口:更细的秒级 cron、复杂日历排除规则、正式外部管理端调度策略下发和跨设备调度冲突合并仍待吸收。
- 后续缺口:跨设备调度派发策略冲突预览已开始吸收,管理端策略冲突页只读展示调度 catch-up、运行历史和派发 sweep 策略差异,不立即调度、不接管租约、不自动合并策略;更细的秒级 cron、复杂日历排除规则、正式外部管理端调度策略下发和跨设备调度冲突合并仍待吸收。
- 建议:下一轮围绕入口路由/任务分流,把 schedule 触发的计划与具体 Skill Policy、Task Agent 状态机更精确地绑定。
3. **MemoryEntry / 长期记忆(已开始)**
@@ -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` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`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 记录策略拉取、立即检查调度、复核和忽略意图。业务事实物化查询已开始吸收:后端新增 `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 物理业务表。