feat(FN-3956): execute provisioning on approval decisions
The merge completes Step 1 of agent provisioning approval followthrough, executing provisioning actions when approval decisions are made, with supporting documentation in AGENTS.md and docs/agents.md. Test coverage spans provisioning policy, approval routes, gating classifications, extension integra Fusion-Task-Id: FN-3956
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { AgentStore, ApprovalRequestStore, type ApprovalRequestActorSnapshot, type ApprovalRequestStatus } from "@fusion/core";
|
||||
import { AgentStore, ApprovalRequestStore, type ApprovalRequest, type ApprovalRequestActorSnapshot, type ApprovalRequestStatus } from "@fusion/core";
|
||||
import { executeApprovedAgentProvisioning } from "@fusion/engine";
|
||||
import { ApiError, badRequest, conflict, notFound } from "../api-error.js";
|
||||
import type { ApiRoutesContext } from "./types.js";
|
||||
import { emitApprovalSseEvent } from "../sse.js";
|
||||
@@ -97,6 +98,34 @@ function toDetailDto(
|
||||
};
|
||||
}
|
||||
|
||||
function emitProvisioningDecisionAudit(params: {
|
||||
scopedStore: import("@fusion/core").TaskStore;
|
||||
request: ApprovalRequest;
|
||||
decision: "approved" | "denied";
|
||||
}): void {
|
||||
const { scopedStore, request, decision } = params;
|
||||
if (request.targetAction.category !== "agent_provisioning") return;
|
||||
|
||||
const action = request.targetAction.action === "delete" ? "delete" : "create";
|
||||
const mutationType = `agent:${action}:${decision}` as const;
|
||||
const event: Parameters<typeof scopedStore.recordRunAuditEvent>[0] = {
|
||||
agentId: request.requester.actorId,
|
||||
domain: "database",
|
||||
mutationType,
|
||||
target: request.targetAction.resourceId || request.requester.actorId,
|
||||
metadata: {
|
||||
approvalRequestId: request.id,
|
||||
action,
|
||||
resourceId: request.targetAction.resourceId,
|
||||
requesterAgentId: request.requester.actorId,
|
||||
},
|
||||
runId: request.id,
|
||||
};
|
||||
if (request.taskId) event.taskId = request.taskId;
|
||||
if (request.runId) event.runId = request.runId;
|
||||
scopedStore.recordRunAuditEvent(event);
|
||||
}
|
||||
|
||||
async function resumeAfterDecision(params: {
|
||||
scopedStore: import("@fusion/core").TaskStore;
|
||||
request: import("@fusion/core").ApprovalRequest;
|
||||
@@ -210,6 +239,17 @@ export function registerApprovalRoutes(ctx: ApiRoutesContext): void {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (updated.targetAction.category === "agent_provisioning") {
|
||||
if (body.decision === "approve") {
|
||||
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });
|
||||
await agentStore.init();
|
||||
await executeApprovedAgentProvisioning(updated, { agentStore });
|
||||
emitProvisioningDecisionAudit({ scopedStore, request: updated, decision: "approved" });
|
||||
} else {
|
||||
emitProvisioningDecisionAudit({ scopedStore, request: updated, decision: "denied" });
|
||||
}
|
||||
}
|
||||
|
||||
await resumeAfterDecision({ scopedStore, request: updated, runtimeLogger });
|
||||
const history = approvalStore.getAuditHistory(requestId);
|
||||
const detail = toDetailDto(updated, history);
|
||||
|
||||
Reference in New Issue
Block a user