diff --git a/src/app/api/quota/plans/[connectionId]/route.ts b/src/app/api/quota/plans/[connectionId]/route.ts index 517db505dc..54bbd55577 100644 --- a/src/app/api/quota/plans/[connectionId]/route.ts +++ b/src/app/api/quota/plans/[connectionId]/route.ts @@ -5,7 +5,7 @@ * * Auth: requireManagementAuth * Zod: PlanUpsertSchema from @/shared/schemas/quota (PUT only) - * Audit: quota.plan.updated on PUT (B26) + * Audit: quota.plan.updated on PUT and DELETE (B26 — DELETE reverts override to auto/catalog) * Sanitization: all error responses via buildErrorBody (Hard Rule #12, B25) * * For PUT: provider is derived from the connectionId via getProviderConnectionById. @@ -115,9 +115,21 @@ export async function DELETE(request: Request, { params }: RouteParams): Promise try { const { connectionId } = await params; - // deleteProviderPlan returns true if a row was deleted, false if not found - // We return 204 either way (idempotent delete) + + const existing = getProviderPlan(connectionId); + const provider = existing?.provider ?? (await resolveProvider(connectionId)); + deleteProviderPlan(connectionId); + + const ctx = getAuditRequestContext(request); + logAuditEvent({ + action: "quota.plan.updated", + target: connectionId, + metadata: { provider, source: "auto", reverted: true }, + ipAddress: ctx.ipAddress ?? undefined, + requestId: ctx.requestId, + }); + return new Response(null, { status: 204 }); } catch (err) { const message = err instanceof Error ? err.message : "Failed to delete plan"; diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 2341b3494d..91a5e25613 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -819,7 +819,7 @@ "agentsAiSection": "Agents & AI", "cacheContextSection": "Cache & Context", "analyticsSection": "Analytics", - "costsSection": "Costs", + "costsSection": "Custos", "monitoringSection": "Monitoring", "auditSecuritySection": "Audit & Security", "devtoolsSection": "Dev Tools", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 2935420b9e..beb976af8c 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -819,7 +819,7 @@ "agentsAiSection": "Agents & AI", "cacheContextSection": "Cache & Context", "analyticsSection": "Analytics", - "costsSection": "Costs", + "costsSection": "Custos", "monitoringSection": "Monitoring", "auditSecuritySection": "Audit & Security", "devtoolsSection": "Dev Tools", diff --git a/tests/integration/quota-plans-crud.test.ts b/tests/integration/quota-plans-crud.test.ts index 8f603e5ade..c580c3613d 100644 --- a/tests/integration/quota-plans-crud.test.ts +++ b/tests/integration/quota-plans-crud.test.ts @@ -238,6 +238,19 @@ test("DELETE /api/quota/plans/[connectionId] clears override → 204; GET revert // After delete, should fall back to catalog or empty (source=auto or manual-empty) // For a connectionId with no catalog match, source=manual + dimensions=[] assert.ok(["auto", "manual"].includes(body.plan.source)); + + // B26: DELETE must emit logAuditEvent with quota.plan.updated + metadata.reverted=true + const logs = compliance.getAuditLog({ action: "quota.plan.updated", limit: 20 }); + const events = Array.isArray(logs) ? logs : []; + const deleteEvt = events.find( + (e) => + typeof e === "object" && + e !== null && + (e as Record).action === "quota.plan.updated" && + (e as Record).target === connectionId && + (e as { metadata?: { reverted?: boolean } }).metadata?.reverted === true + ); + assert.ok(deleteEvt, "quota.plan.updated audit event (reverted=true) must be present after DELETE"); }); test("DELETE /api/quota/plans/[connectionId] is idempotent → 204 even when not found", async () => {