From cc22ca008818306329131b87f7b8a481c490c117 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 16:03:30 -0300 Subject: [PATCH] fix(quota,i18n): close B26 audit gap on DELETE plan + pt-BR translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two gaps found in second-pass code review of Group B: 1. B26 violation: DELETE /api/quota/plans/[connectionId] did not emit logAuditEvent. Per master plan B26, every plan mutation must audit. Now emits quota.plan.updated with metadata.reverted=true to mark the revert-to-auto/catalog semantic. Test integration extended with assertion that audit event is present after DELETE. 2. pt-BR / pt locales had "costsSection": "Costs" (English label) instead of the Portuguese "Custos". Other section labels in the same block are left in English intentionally (analytics, monitoring) — they are project-wide untranslated terms; "Custos" is the established repo translation for the Costs section title. Validation: - npm run typecheck:core: clean - tests/integration/quota-plans-crud.test.ts: 10/10 pass (includes new assertion on DELETE → audit event) - eslint --no-warn-ignored on touched files: clean --- .../api/quota/plans/[connectionId]/route.ts | 18 +++++++++++++++--- src/i18n/messages/pt-BR.json | 2 +- src/i18n/messages/pt.json | 2 +- tests/integration/quota-plans-crud.test.ts | 13 +++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) 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 () => {