fix: align backend API routes with console frontend contract
Some checks failed
CI — P3 Alert / test (push) Successful in 34s
CI — P4 Portal / test (push) Successful in 37s
CI — P5 Cost / test (push) Successful in 35s
CI — P6 Run / saas (push) Successful in 33s
CI — P5 Cost / build-push (push) Failing after 5s
CI — P6 Run / build-push (push) Failing after 4s
CI — P2 Drift (Go + Node) / agent (push) Successful in 1m5s
CI — P2 Drift (Go + Node) / saas (push) Successful in 37s
CI — P2 Drift (Go + Node) / build-push (push) Failing after 16s
CI — P3 Alert / build-push (push) Failing after 14s
CI — P4 Portal / build-push (push) Failing after 27s

This commit is contained in:
Max
2026-03-03 06:09:41 +00:00
parent 76715d169e
commit 47a64d53fd
8 changed files with 394 additions and 20 deletions

View File

@@ -33,7 +33,80 @@ export function registerApprovalRoutes(app: FastifyInstance) {
return { approvals: result.rows };
});
// Approve or reject a step
// Approve a step (console route: POST /api/v1/approvals/:id/approve)
app.post('/api/v1/approvals/:id/approve', async (req, reply) => {
const { id } = req.params as { id: string };
const reason = ((req.body as any)?.reason as string) ?? undefined;
const tenantId = (req as any).tenantId;
const userId = (req as any).userId;
const result = await withTenant(tenantId, async (client) => {
const entry = await client.query(
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
WHERE ae.id = $1 AND ae.status = 'awaiting_approval'`,
[id],
);
if (!entry.rows[0]) return null;
await client.query(
`UPDATE audit_entries SET status = 'approved', approved_by = $1, approval_method = 'api'
WHERE id = $2`,
[userId, id],
);
return entry.rows[0];
});
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
await bridge.sendApproval(tenantId, result.execution_id, id, 'approve');
logger.info({ stepId: id, decision: 'approve', userId }, 'Approval decision recorded');
return { step_id: id, decision: 'approve', reason };
});
// Reject a step (console route: POST /api/v1/approvals/:id/reject)
app.post('/api/v1/approvals/:id/reject', async (req, reply) => {
const { id } = req.params as { id: string };
const reason = ((req.body as any)?.reason as string) ?? undefined;
const tenantId = (req as any).tenantId;
const userId = (req as any).userId;
const result = await withTenant(tenantId, async (client) => {
const entry = await client.query(
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
WHERE ae.id = $1 AND ae.status = 'awaiting_approval'`,
[id],
);
if (!entry.rows[0]) return null;
await client.query(
`UPDATE audit_entries SET status = 'rejected', approved_by = $1, approval_method = 'api'
WHERE id = $2`,
[userId, id],
);
await client.query(
`UPDATE executions SET status = 'aborted', completed_at = now() WHERE id = $1`,
[entry.rows[0].execution_id],
);
return entry.rows[0];
});
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
await bridge.sendApproval(tenantId, result.execution_id, id, 'reject');
logger.info({ stepId: id, decision: 'reject', userId }, 'Approval decision recorded');
return { step_id: id, decision: 'reject', reason };
});
// Approve or reject a step (legacy route)
app.post('/api/v1/approvals/:stepId', async (req, reply) => {
const { stepId } = req.params as { stepId: string };
const body = approvalDecisionSchema.parse(req.body);
@@ -41,7 +114,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
const userId = (req as any).userId;
const result = await withTenant(tenantId, async (client) => {
// Get the audit entry and its execution
const entry = await client.query(
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
@@ -59,7 +131,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
[newStatus, userId, stepId],
);
// If rejected, abort the execution
if (body.decision === 'reject') {
await client.query(
`UPDATE executions SET status = 'aborted', completed_at = now() WHERE id = $1`,
@@ -72,7 +143,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
// Notify agent via Redis pub/sub
await bridge.sendApproval(tenantId, result.execution_id, stepId, body.decision);
logger.info({ stepId, decision: body.decision, userId }, 'Approval decision recorded');