吸收数字员工产物生命周期治理
This commit is contained in:
@@ -238,6 +238,31 @@ export interface DigitalEmployeeArtifactAccessAuditRecord {
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type DigitalEmployeeArtifactRetentionAction = "mark_keep" | "mark_expire" | "mark_review" | "clear_retention";
|
||||
|
||||
export interface DigitalEmployeeArtifactLifecycleInput {
|
||||
artifactId?: string | null;
|
||||
action: DigitalEmployeeArtifactRetentionAction | "observe_version" | string;
|
||||
reason?: string | null;
|
||||
retentionUntil?: string | null;
|
||||
versionSummary?: Record<string, unknown> | null;
|
||||
retentionSummary?: Record<string, unknown> | null;
|
||||
deliverySummary?: 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 DigitalEmployeeArtifactLifecycleRecord {
|
||||
eventId: string;
|
||||
kind: "artifact_retention_marked" | "artifact_retention_rejected" | "artifact_version_observed";
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeRouteDecisionRecord {
|
||||
id: string;
|
||||
route_kind: string;
|
||||
@@ -1082,6 +1107,97 @@ export function recordDigitalEmployeeArtifactAccess(
|
||||
return { eventId, kind, status, payload };
|
||||
}
|
||||
|
||||
export function recordDigitalEmployeeArtifactLifecycle(
|
||||
input: DigitalEmployeeArtifactLifecycleInput,
|
||||
): DigitalEmployeeArtifactLifecycleRecord | null {
|
||||
const db = getDigitalEmployeeDb();
|
||||
if (!db) return null;
|
||||
const now = input.occurredAt || new Date().toISOString();
|
||||
const artifactId = stringValue(input.artifactId) || "unknown-artifact";
|
||||
const action = stringValue(input.action) || "unknown_action";
|
||||
const validRetentionAction = isDigitalEmployeeArtifactRetentionAction(action);
|
||||
const isVersionObserve = action === "observe_version";
|
||||
const kind: DigitalEmployeeArtifactLifecycleRecord["kind"] = isVersionObserve
|
||||
? "artifact_version_observed"
|
||||
: validRetentionAction
|
||||
? "artifact_retention_marked"
|
||||
: "artifact_retention_rejected";
|
||||
const retentionStatus = retentionStatusForAction(action);
|
||||
const eventId = `artifact-lifecycle:${safeId(artifactId)}:${safeId(action)}:${safeId(now)}`;
|
||||
const payload: Record<string, unknown> = {
|
||||
artifact_id: artifactId,
|
||||
action,
|
||||
status: kind === "artifact_retention_rejected" ? "rejected" : "recorded",
|
||||
reason: stringValue(input.reason) || null,
|
||||
retention_status: retentionStatus,
|
||||
retention_until: stringValue(input.retentionUntil) || null,
|
||||
version_summary: sanitizeArtifactVersionSummary(objectRecord(input.versionSummary) ?? {}),
|
||||
retention_summary: sanitizeArtifactRetentionSummary(objectRecord(input.retentionSummary) ?? {}),
|
||||
delivery_summary: sanitizeArtifactDeliverySnapshot(objectRecord(input.deliverySummary) ?? {}),
|
||||
actor: stringValue(input.actor) || null,
|
||||
source: stringValue(input.source) || "qimingclaw-artifact-lifecycle",
|
||||
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 || artifactLifecycleMessage(kind, action, artifactId),
|
||||
plan_id: stringValue(input.planId) || null,
|
||||
task_id: stringValue(input.taskId) || null,
|
||||
},
|
||||
stringValue(input.runId) || null,
|
||||
payload,
|
||||
);
|
||||
return { eventId, kind, payload };
|
||||
}
|
||||
|
||||
function isDigitalEmployeeArtifactRetentionAction(action: string): action is DigitalEmployeeArtifactRetentionAction {
|
||||
return ["mark_keep", "mark_expire", "mark_review", "clear_retention"].includes(action);
|
||||
}
|
||||
|
||||
function retentionStatusForAction(action: string): string | null {
|
||||
if (action === "mark_keep") return "keep";
|
||||
if (action === "mark_expire") return "expire_candidate";
|
||||
if (action === "mark_review") return "review_required";
|
||||
if (action === "clear_retention") return "unmanaged";
|
||||
return null;
|
||||
}
|
||||
|
||||
function sanitizeArtifactVersionSummary(summary: Record<string, unknown>): Record<string, unknown> {
|
||||
const allowedKeys = new Set(["version", "version_key", "version_rank", "file_id", "workspace_id", "project_id", "created_at", "name"]);
|
||||
return Object.fromEntries(
|
||||
Object.entries(summary).filter(([key]) => allowedKeys.has(key)),
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeArtifactRetentionSummary(summary: Record<string, unknown>): Record<string, unknown> {
|
||||
const allowedKeys = new Set(["retention_status", "retention_until", "previous_status", "reason"]);
|
||||
return Object.fromEntries(
|
||||
Object.entries(summary).filter(([key]) => allowedKeys.has(key)),
|
||||
);
|
||||
}
|
||||
|
||||
function artifactLifecycleMessage(
|
||||
kind: DigitalEmployeeArtifactLifecycleRecord["kind"],
|
||||
action: string,
|
||||
artifactId: string,
|
||||
): string {
|
||||
if (kind === "artifact_version_observed") return `产物版本已观测:${artifactId}`;
|
||||
if (kind === "artifact_retention_rejected") return `产物保留策略已拒绝:${artifactId}`;
|
||||
const actionLabel = action === "mark_keep"
|
||||
? "保留"
|
||||
: action === "mark_expire"
|
||||
? "到期候选"
|
||||
: action === "mark_review"
|
||||
? "待复核"
|
||||
: "清除保留标记";
|
||||
return `产物保留策略已标记:${actionLabel} ${artifactId}`;
|
||||
}
|
||||
|
||||
function normalizeArtifactAccessAction(action: string): DigitalEmployeeArtifactAccessAction {
|
||||
return ["preview", "download", "open_location"].includes(action)
|
||||
? action as DigitalEmployeeArtifactAccessAction
|
||||
|
||||
Reference in New Issue
Block a user