吸收数字员工产物访问治理

This commit is contained in:
baiyanyun
2026-06-10 10:00:34 +08:00
parent cc36d8dcaf
commit 362f397e37
18 changed files with 1016 additions and 166 deletions

View File

@@ -212,6 +212,32 @@ export interface DigitalEmployeeSkillCallAuditRecord {
payload: Record<string, unknown>;
}
export type DigitalEmployeeArtifactAccessAction = "preview" | "download" | "open_location";
export type DigitalEmployeeArtifactAccessStatus = "allowed" | "rejected";
export interface DigitalEmployeeArtifactAccessAuditInput {
artifactId?: string | null;
action: DigitalEmployeeArtifactAccessAction | string;
status: DigitalEmployeeArtifactAccessStatus;
reasonCodes?: string[] | null;
uriSummary?: Record<string, unknown> | null;
deliverySnapshot?: Record<string, unknown> | null;
actor?: string | null;
source?: string | null;
planId?: string | null;
taskId?: string | null;
runId?: string | null;
occurredAt?: string | null;
message?: string | null;
}
export interface DigitalEmployeeArtifactAccessAuditRecord {
eventId: string;
kind: "artifact_access_allowed" | "artifact_access_rejected";
status: DigitalEmployeeArtifactAccessStatus;
payload: Record<string, unknown>;
}
export interface DigitalEmployeeRouteDecisionRecord {
id: string;
route_kind: string;
@@ -1017,6 +1043,85 @@ export function recordDigitalEmployeeSkillCallAudit(
return { eventId, kind, decision, payload };
}
export function recordDigitalEmployeeArtifactAccess(
input: DigitalEmployeeArtifactAccessAuditInput,
): DigitalEmployeeArtifactAccessAuditRecord | null {
const db = getDigitalEmployeeDb();
if (!db) return null;
const now = input.occurredAt || new Date().toISOString();
const status: DigitalEmployeeArtifactAccessStatus = input.status === "allowed" ? "allowed" : "rejected";
const kind = `artifact_access_${status}` as DigitalEmployeeArtifactAccessAuditRecord["kind"];
const artifactId = stringValue(input.artifactId) || "unknown-artifact";
const action = normalizeArtifactAccessAction(input.action);
const eventId = `artifact-access:${status}:${safeId(artifactId)}:${safeId(action)}:${safeId(now)}`;
const payload: Record<string, unknown> = {
artifact_id: artifactId,
action,
status,
reason_codes: normalizeSkillIds(input.reasonCodes ?? []),
uri_summary: sanitizeArtifactUriSummary(objectRecord(input.uriSummary) ?? {}),
delivery_snapshot: sanitizeArtifactDeliverySnapshot(objectRecord(input.deliverySnapshot) ?? {}),
actor: stringValue(input.actor) || null,
source: stringValue(input.source) || "qimingclaw-artifact-access",
plan_id: stringValue(input.planId) || null,
task_id: stringValue(input.taskId) || null,
run_id: stringValue(input.runId) || null,
};
insertEvent(
{
event_id: eventId,
kind,
occurred_at: now,
message: input.message || artifactAccessAuditMessage(status, action, artifactId),
plan_id: stringValue(input.planId) || null,
task_id: stringValue(input.taskId) || null,
},
stringValue(input.runId) || null,
payload,
);
return { eventId, kind, status, payload };
}
function normalizeArtifactAccessAction(action: string): DigitalEmployeeArtifactAccessAction {
return ["preview", "download", "open_location"].includes(action)
? action as DigitalEmployeeArtifactAccessAction
: "preview";
}
function sanitizeArtifactUriSummary(summary: Record<string, unknown>): Record<string, unknown> {
const allowedKeys = new Set(["scheme", "basename", "extension", "size", "exists", "is_directory", "mime"]);
return Object.fromEntries(
Object.entries(summary).filter(([key]) => allowedKeys.has(key)),
);
}
function sanitizeArtifactDeliverySnapshot(snapshot: Record<string, unknown>): Record<string, unknown> {
const allowedKeys = new Set([
"delivery_status",
"delivery_stage",
"workspace_id",
"project_id",
"file_id",
"version",
"file_size",
"source_tool",
]);
return Object.fromEntries(
Object.entries(snapshot).filter(([key]) => allowedKeys.has(key)),
);
}
function artifactAccessAuditMessage(
status: DigitalEmployeeArtifactAccessStatus,
action: DigitalEmployeeArtifactAccessAction,
artifactId: string,
): string {
const actionLabel = action === "open_location" ? "打开位置" : action === "download" ? "下载" : "预览";
return status === "allowed"
? `产物访问已允许:${actionLabel} ${artifactId}`
: `产物访问已拒绝:${actionLabel} ${artifactId}`;
}
function sanitizeSkillPolicySnapshot(snapshot: Record<string, unknown>): Record<string, unknown> {
const allowedKeys = new Set([
"skill_enabled",