吸收数字员工治理审计补齐

This commit is contained in:
baiyanyun
2026-06-12 10:33:46 +08:00
parent e473b5c147
commit 3a159e2f5a
11 changed files with 230 additions and 57 deletions

View File

@@ -1798,7 +1798,14 @@ describe("digital employee state service", () => {
stepId: "step-governance",
accepted: true,
message: "跳过当前任务",
payload: { reason: "用户选择跳过" },
payload: {
reason: "用户选择跳过",
source: "qimingclaw-ingress-route",
route_decision_id: "route-decision-skip",
route_kind: "PlanControl",
intent_kind: "control",
risk_level: "medium",
},
result: { task_id: "task-governance", run_id: "run-governance", step_id: "step-governance" },
});
@@ -1834,6 +1841,12 @@ describe("digital employee state service", () => {
message: "跳过当前任务",
task_id: "task-governance",
});
expect(JSON.parse(mockState.db?.events.get("run-governance:skip_task:command-skip-task")?.payload ?? "{}")).toMatchObject({
routeDecisionId: "route-decision-skip",
routeKind: "PlanControl",
intentKind: "control",
riskLevel: "medium",
});
expect(mockState.db?.outbox.get("task:upsert:task-governance")).toMatchObject({ entity_type: "task" });
expect(mockState.db?.outbox.get("plan_step:upsert:step-governance")).toMatchObject({ entity_type: "plan_step" });
expect(mockState.db?.outbox.get("run:upsert:run-governance")).toMatchObject({ entity_type: "run" });

View File

@@ -4174,6 +4174,10 @@ export function recordDigitalEmployeeGovernanceCommand(
commandId,
correlationId: command.correlationId,
planId,
routeDecisionId: stringValue(payload.route_decision_id ?? payload.routeDecisionId) || null,
routeKind: stringValue(payload.route_kind ?? payload.routeKind) || null,
intentKind: stringValue(payload.intent_kind ?? payload.intentKind) || null,
riskLevel: stringValue(payload.risk_level ?? payload.riskLevel) || null,
scheduleId: command.scheduleId || stringValue(result.schedule_id) || null,
accepted: command.accepted !== false,
message: command.message,
@@ -4333,6 +4337,10 @@ export function applyDigitalEmployeeTaskGovernanceCommand(
taskId,
stepId: stepId || null,
runId,
routeDecisionId: stringValue(payload.route_decision_id ?? payload.routeDecisionId) || null,
routeKind: stringValue(payload.route_kind ?? payload.routeKind) || null,
intentKind: stringValue(payload.intent_kind ?? payload.intentKind) || null,
riskLevel: stringValue(payload.risk_level ?? payload.riskLevel) || null,
sourceRunId: stringValue(targetRun?.id) || null,
accepted: command.accepted !== false,
message: command.message,

View File

@@ -1348,6 +1348,50 @@ describe("digital employee sync service", () => {
source: "qimingclaw-task-agent",
},
});
insertEventEntity("event-governance-pause", "governance_pause_plan", {
commandKind: "pause_plan",
commandId: "command-pause-plan",
correlationId: "route-1",
planId: "plan-1",
accepted: true,
message: "暂停计划",
riskLevel: "normal",
payload: {
source: "qimingclaw-ingress-route",
route_decision_id: "route-1",
route_kind: "PlanControl",
intent_kind: "control",
plan_id: "plan-1",
},
result: {
plan_id: "plan-1",
status: "accepted",
source: "qimingclaw-adapter",
},
});
insertEventEntity("event-governance-skip", "governance_skip_task", {
command_kind: "skip_task",
command_id: "command-skip-task",
correlation_id: "route-2",
plan_id: "plan-1",
task_id: "task-1",
run_id: "run-1",
step_id: "step-1",
accepted: false,
error: "policy_blocked",
risk_level: "medium",
payload: {
source: "qimingclaw-ingress-route",
route_decision_id: "route-2",
route_kind: "PlanControl",
intent_kind: "control",
task_id: "task-1",
},
result: {
status: "rejected",
error: "policy_blocked",
},
});
insertEventEntity("event-notification-read", "digital_workday_notification_read", {
metadata: {
notification_id: "task:task-1",
@@ -1498,6 +1542,8 @@ describe("digital employee sync service", () => {
{ entity_type: "event", entity_id: "event-lease" },
{ entity_type: "event", entity_id: "event-plan-step-ready" },
{ entity_type: "event", entity_id: "event-governance-input" },
{ entity_type: "event", entity_id: "event-governance-pause" },
{ entity_type: "event", entity_id: "event-governance-skip" },
{ entity_type: "event", entity_id: "event-notification-read" },
{ entity_type: "event", entity_id: "event-notification-dismiss" },
{ entity_type: "event", entity_id: "event-notification-reply" },
@@ -1639,6 +1685,36 @@ describe("digital employee sync service", () => {
input_length: 10,
});
expect(businessView("event-governance-input")).not.toHaveProperty("input");
expect(businessView("event-governance-pause")).toMatchObject({
category: "governance",
command_kind: "pause_plan",
accepted: true,
command_id: "command-pause-plan",
correlation_id: "route-1",
plan_id: "plan-1",
route_decision_id: "route-1",
route_kind: "PlanControl",
intent_kind: "control",
source: "qimingclaw-ingress-route",
status: "accepted",
risk_level: "normal",
});
expect(businessView("event-governance-skip")).toMatchObject({
category: "governance",
command_kind: "skip_task",
accepted: false,
error: "policy_blocked",
command_id: "command-skip-task",
correlation_id: "route-2",
plan_id: "plan-1",
task_id: "task-1",
run_id: "run-1",
step_id: "step-1",
route_decision_id: "route-2",
route_kind: "PlanControl",
intent_kind: "control",
risk_level: "medium",
});
expect(businessView("event-notification-read")).toMatchObject({
category: "notification",
notification_id: "task:task-1",

View File

@@ -1736,7 +1736,7 @@ function eventSpecificBusinessView(
if (kind.startsWith("plan_step_dispatch_")) return planStepDispatchBusinessView(payload);
if (isPlanStepLeaseEvent(kind)) return planStepLeaseBusinessView(payload);
if (isPlanStepDispatchPolicyEvent(kind)) return planStepDispatchPolicyBusinessView(payload);
if (kind === "governance_provide_task_input") return governanceProvideTaskInputBusinessView(payload);
if (kind.startsWith("governance_")) return governanceCommandBusinessView(kind, payload);
if (isNotificationEvent(kind)) return notificationBusinessView(payload);
return {};
}
@@ -1989,18 +1989,35 @@ function planStepDispatchPolicyBusinessView(payload: Record<string, unknown>): R
};
}
function governanceProvideTaskInputBusinessView(payload: Record<string, unknown>): Record<string, unknown> {
function governanceCommandBusinessView(kind: string, payload: Record<string, unknown>): Record<string, unknown> {
const commandPayload = objectRecord(payload.payload) ?? {};
const result = objectRecord(payload.result) ?? {};
const commandKind = stringField(payload.commandKind ?? payload.command_kind) || kind.replace(/^governance_/, "");
const input = stringField(commandPayload.input ?? payload.input);
return {
command_kind: stringField(payload.commandKind ?? payload.command_kind) || "provide_task_input",
const view: Record<string, unknown> = {
command_kind: commandKind,
accepted: typeof payload.accepted === "boolean" ? payload.accepted : null,
error: stringField(payload.error ?? result.error) || null,
command_id: stringField(payload.commandId ?? payload.command_id) || null,
correlation_id: stringField(payload.correlationId ?? payload.correlation_id) || null,
plan_id: stringField(payload.planId ?? payload.plan_id ?? commandPayload.plan_id ?? result.plan_id) || null,
task_id: stringField(payload.taskId ?? payload.task_id ?? commandPayload.task_id ?? result.task_id) || null,
run_id: stringField(payload.runId ?? payload.run_id ?? commandPayload.run_id ?? result.run_id) || null,
step_id: stringField(payload.stepId ?? payload.step_id ?? commandPayload.step_id ?? result.step_id) || null,
schedule_id: stringField(payload.scheduleId ?? payload.schedule_id ?? commandPayload.schedule_id ?? result.schedule_id) || null,
route_decision_id: stringField(payload.routeDecisionId ?? payload.route_decision_id ?? commandPayload.route_decision_id ?? result.route_decision_id) || null,
route_kind: stringField(payload.routeKind ?? payload.route_kind ?? commandPayload.route_kind ?? result.route_kind) || null,
intent_kind: stringField(payload.intentKind ?? payload.intent_kind ?? commandPayload.intent_kind ?? result.intent_kind) || null,
source: stringField(commandPayload.source ?? result.source ?? payload.source) || null,
notification_id: stringField(commandPayload.notification_id ?? payload.notification_id) || null,
input_length: input ? input.length : null,
status: stringField(payload.status ?? result.status) || null,
next_action: stringField(payload.nextAction ?? payload.next_action ?? result.next_action) || null,
risk_level: stringField(payload.riskLevel ?? payload.risk_level ?? commandPayload.risk_level ?? result.risk_level) || null,
};
if (commandKind === "provide_task_input") {
view.notification_id = stringField(commandPayload.notification_id ?? payload.notification_id) || null;
view.input_length = input ? input.length : null;
}
return view;
}
function notificationBusinessView(payload: Record<string, unknown>): Record<string, unknown> {