吸收数字员工产物访问治理
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user