Add drift report submission + stack deletion endpoints to P2
All checks were successful
CI — P2 Drift (Go + Node) / saas (push) Successful in 25s
CI — P2 Drift (Go + Node) / agent (push) Successful in 43s

This commit is contained in:
2026-03-02 05:03:34 +00:00
parent 9f46b84257
commit c537022fa8

View File

@@ -1,7 +1,59 @@
import { FastifyInstance } from 'fastify'; import { FastifyInstance } from 'fastify';
import { z } from 'zod';
import crypto from 'crypto';
import { withTenant } from '../data/db.js'; import { withTenant } from '../data/db.js';
const submitReportSchema = z.object({
stack_name: z.string().min(1).max(200),
stack_fingerprint: z.string().min(1),
agent_version: z.string().default('manual'),
scanned_at: z.string().datetime().optional(),
state_serial: z.number().int().min(0),
lineage: z.string().default('manual'),
total_resources: z.number().int().min(0),
drift_score: z.number().min(0).max(100),
raw_report: z.record(z.unknown()).default({}),
});
export async function registerApiRoutes(app: FastifyInstance) { export async function registerApiRoutes(app: FastifyInstance) {
// Submit a drift report (from agent or manual)
app.post('/api/v1/reports', async (request, reply) => {
const tenantId = (request as any).tenantId;
const pool = (app as any).pool;
const body = submitReportSchema.parse(request.body);
const nonce = crypto.randomUUID();
const result = await withTenant(pool, tenantId, async (client) => {
const { rows } = await client.query(
`INSERT INTO drift_reports (tenant_id, stack_name, stack_fingerprint, agent_version, scanned_at, state_serial, lineage, total_resources, drift_score, nonce, raw_report)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, stack_name, drift_score, scanned_at`,
[tenantId, body.stack_name, body.stack_fingerprint, body.agent_version,
body.scanned_at ?? new Date().toISOString(), body.state_serial, body.lineage,
body.total_resources, body.drift_score, nonce, JSON.stringify(body.raw_report)]
);
return rows[0];
});
return reply.status(201).send({ report: result });
});
// Delete all reports for a stack
app.delete('/api/v1/stacks/:stackName', async (request, reply) => {
const tenantId = (request as any).tenantId;
const { stackName } = request.params as { stackName: string };
const pool = (app as any).pool;
await withTenant(pool, tenantId, async (client) => {
await client.query(
'DELETE FROM drift_reports WHERE tenant_id = $1 AND stack_name = $2',
[tenantId, stackName]
);
});
return reply.status(200).send({ status: 'deleted', stack_name: stackName });
});
// List stacks // List stacks
app.get('/api/v1/stacks', async (request, reply) => { app.get('/api/v1/stacks', async (request, reply) => {
const tenantId = (request as any).tenantId; const tenantId = (request as any).tenantId;