292 lines
9.3 KiB
TypeScript
292 lines
9.3 KiB
TypeScript
import { Router, type Request, type Response } from "express";
|
|
import { governanceService } from "./service";
|
|
import { jobQueueService } from "./jobQueue";
|
|
import { runPolicyTests } from "./policyTests";
|
|
|
|
const router = Router();
|
|
|
|
router.get("/stats", async (_req: Request, res: Response) => {
|
|
try {
|
|
const stats = await governanceService.getGovernanceStats();
|
|
res.json(stats);
|
|
} catch (error) {
|
|
console.error("Error fetching governance stats:", error);
|
|
res.status(500).json({ error: "Failed to fetch governance stats" });
|
|
}
|
|
});
|
|
|
|
router.get("/policies", async (_req: Request, res: Response) => {
|
|
try {
|
|
const policies = await governanceService.getPolicies();
|
|
res.json(policies);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch policies" });
|
|
}
|
|
});
|
|
|
|
router.post("/policies", async (req: Request, res: Response) => {
|
|
try {
|
|
const { name, scope, target, effect, conditions, priority, description } = req.body;
|
|
if (!name || !scope || !target || !effect) {
|
|
return res.status(400).json({ error: "name, scope, target, effect are required" });
|
|
}
|
|
const policy = await governanceService.createPolicy({
|
|
name, scope, target, effect,
|
|
conditions: conditions || {},
|
|
priority: priority || 100,
|
|
description: description || null,
|
|
});
|
|
res.status(201).json(policy);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to create policy" });
|
|
}
|
|
});
|
|
|
|
router.post("/evaluate", async (req: Request, res: Response) => {
|
|
try {
|
|
const { agent, action, target, context } = req.body;
|
|
if (!agent || !action || !target) {
|
|
return res.status(400).json({ error: "agent, action, target are required" });
|
|
}
|
|
const result = await governanceService.evaluatePolicy(agent, action, target, context);
|
|
res.json(result);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to evaluate policy" });
|
|
}
|
|
});
|
|
|
|
router.get("/contracts", async (_req: Request, res: Response) => {
|
|
try {
|
|
const contracts = await governanceService.getContracts();
|
|
res.json(contracts);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch contracts" });
|
|
}
|
|
});
|
|
|
|
router.post("/contracts", async (req: Request, res: Response) => {
|
|
try {
|
|
const { name, action, description, inputSchema, outputSchema, requiredPermissions, category } = req.body;
|
|
if (!name || !action) {
|
|
return res.status(400).json({ error: "name, action are required" });
|
|
}
|
|
const contract = await governanceService.registerContract({
|
|
name, action,
|
|
description: description || null,
|
|
inputSchema: inputSchema || null,
|
|
outputSchema: outputSchema || null,
|
|
requiredPermissions: requiredPermissions || [],
|
|
category: category || null,
|
|
});
|
|
res.status(201).json(contract);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to create contract" });
|
|
}
|
|
});
|
|
|
|
router.get("/tools", async (_req: Request, res: Response) => {
|
|
try {
|
|
const tools = await governanceService.getTools();
|
|
res.json(tools);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch tools" });
|
|
}
|
|
});
|
|
|
|
router.get("/skills", async (req: Request, res: Response) => {
|
|
try {
|
|
const status = req.query.status as string | undefined;
|
|
const skills = await governanceService.getSkills(status);
|
|
res.json(skills);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch skills" });
|
|
}
|
|
});
|
|
|
|
router.post("/skills", async (req: Request, res: Response) => {
|
|
try {
|
|
const { name, version, description, steps, tools, inputSchema, outputSchema, createdBy } = req.body;
|
|
if (!name || !version) {
|
|
return res.status(400).json({ error: "name, version are required" });
|
|
}
|
|
const skill = await governanceService.createSkill({
|
|
name, version,
|
|
description: description || null,
|
|
steps: steps || null,
|
|
tools: tools || [],
|
|
inputSchema: inputSchema || null,
|
|
outputSchema: outputSchema || null,
|
|
createdBy: createdBy || "system",
|
|
});
|
|
res.status(201).json(skill);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to create skill" });
|
|
}
|
|
});
|
|
|
|
router.get("/skills/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
if (isNaN(id)) return res.status(400).json({ error: "Invalid ID" });
|
|
const skill = await governanceService.getSkill(id);
|
|
if (!skill) return res.status(404).json({ error: "Skill not found" });
|
|
res.json(skill);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch skill" });
|
|
}
|
|
});
|
|
|
|
router.delete("/skills/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
if (isNaN(id)) return res.status(400).json({ error: "Invalid ID" });
|
|
await governanceService.deactivateSkill(id);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to deactivate skill" });
|
|
}
|
|
});
|
|
|
|
router.get("/audit", async (req: Request, res: Response) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit as string) || 50;
|
|
const agentName = req.query.agent as string | undefined;
|
|
const trail = await governanceService.getAuditTrail(limit, agentName);
|
|
res.json(trail);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch audit trail" });
|
|
}
|
|
});
|
|
|
|
router.get("/jobs", async (req: Request, res: Response) => {
|
|
try {
|
|
const status = req.query.status as string | undefined;
|
|
const limit = parseInt(req.query.limit as string) || 50;
|
|
const jobs = await jobQueueService.getJobs(status, limit);
|
|
res.json(jobs);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch jobs" });
|
|
}
|
|
});
|
|
|
|
router.get("/jobs/stats", async (_req: Request, res: Response) => {
|
|
try {
|
|
const stats = await jobQueueService.getJobStats();
|
|
res.json(stats);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch job stats" });
|
|
}
|
|
});
|
|
|
|
router.post("/jobs", async (req: Request, res: Response) => {
|
|
try {
|
|
const { type, priority, payload, scheduledAt, maxAttempts, createdBy, parentJobId, metadata } = req.body;
|
|
if (!type) return res.status(400).json({ error: "type is required" });
|
|
const job = await jobQueueService.enqueue({
|
|
type,
|
|
priority: priority || 50,
|
|
payload: payload || null,
|
|
scheduledAt: scheduledAt ? new Date(scheduledAt) : null,
|
|
maxAttempts: maxAttempts || 3,
|
|
createdBy: createdBy || "api",
|
|
parentJobId: parentJobId || null,
|
|
metadata: metadata || null,
|
|
});
|
|
res.status(201).json(job);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to enqueue job" });
|
|
}
|
|
});
|
|
|
|
router.post("/jobs/:id/cancel", async (req: Request, res: Response) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
if (isNaN(id)) return res.status(400).json({ error: "Invalid ID" });
|
|
await jobQueueService.cancelJob(id);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to cancel job" });
|
|
}
|
|
});
|
|
|
|
router.post("/jobs/:id/retry", async (req: Request, res: Response) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
if (isNaN(id)) return res.status(400).json({ error: "Invalid ID" });
|
|
const retried = await jobQueueService.retryJob(id);
|
|
res.json({ success: retried });
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to retry job" });
|
|
}
|
|
});
|
|
|
|
router.get("/metrics", async (req: Request, res: Response) => {
|
|
try {
|
|
const agent = req.query.agent as string | undefined;
|
|
const limit = parseInt(req.query.limit as string) || 100;
|
|
const metrics = await jobQueueService.getAgentMetrics(agent, limit);
|
|
res.json(metrics);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch metrics" });
|
|
}
|
|
});
|
|
|
|
router.get("/metrics/summary", async (_req: Request, res: Response) => {
|
|
try {
|
|
const summary = await jobQueueService.getAgentSummary();
|
|
res.json(summary);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch metrics summary" });
|
|
}
|
|
});
|
|
|
|
router.get("/policy-tests", async (_req: Request, res: Response) => {
|
|
try {
|
|
const results = await runPolicyTests();
|
|
res.json(results);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to run policy tests" });
|
|
}
|
|
});
|
|
|
|
router.post("/tools/:id/rbac", async (req: Request, res: Response) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
if (isNaN(id)) return res.status(400).json({ error: "Invalid ID" });
|
|
const { allowedAgents } = req.body;
|
|
if (!Array.isArray(allowedAgents)) {
|
|
return res.status(400).json({ error: "allowedAgents must be an array" });
|
|
}
|
|
await governanceService.updateToolRBAC(id, allowedAgents);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to update tool RBAC" });
|
|
}
|
|
});
|
|
|
|
router.get("/dashboard", async (_req: Request, res: Response) => {
|
|
try {
|
|
const [govStats, jobStats, agentSummary, recentAudit, policies, skills] = await Promise.all([
|
|
governanceService.getGovernanceStats(),
|
|
jobQueueService.getJobStats(),
|
|
jobQueueService.getAgentSummary(),
|
|
governanceService.getAuditTrail(20),
|
|
governanceService.getPolicies(),
|
|
governanceService.getSkills("active"),
|
|
]);
|
|
|
|
res.json({
|
|
governance: govStats,
|
|
jobs: jobStats,
|
|
agents: agentSummary,
|
|
recentAudit,
|
|
policies,
|
|
skills,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Failed to fetch dashboard data" });
|
|
}
|
|
});
|
|
|
|
export default router;
|