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:
Fusion
2026-05-10 18:35:13 -07:00
committed by gsxdsm
parent 5b32c45393
commit 7d67dc3597
11 changed files with 392 additions and 5 deletions

View File

@@ -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);