Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ jobs:
- name: ESLint baseline gate (bloqueia apenas regressões novas)
run: npm run lint:baseline

- name: Zod pinning gate (issue #52 — barrel centralization)
run: npm run check:zod-pinning

# hooks já cobertos pelo job dedicado hooks-tests (timeout-minutes: 10).
# test:strict-ref e test:coverage omitidos aqui — rodam em ref-warning-suite
# e integration-tests respectivamente, evitando tripla execução da suite.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"lint:check": "eslint src --max-warnings=500",
"lint:baseline": "node scripts/check-eslint-baseline.mjs",
"lint:baseline:update": "node scripts/eslint-baseline-generate.mjs",
"check:zod-pinning": "node scripts/check-zod-pinning.mjs",
"typecheck": "node scripts/check-tsc-baseline.mjs",
"qa:lint": "eslint src --max-warnings=500",
"qa:typecheck": "tsc -p tsconfig.app.json --noEmit",
Expand Down
46 changes: 46 additions & 0 deletions scripts/check-zod-pinning.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env node
/**
* Guardrail anti-regressão #52 — pinning centralizado de Zod.
*
* Falha (exit 1) se encontrar imports diretos de Zod fora de
* `supabase/functions/_shared/contracts/`. Edge Functions devem importar
* `z` exclusivamente via barrel:
*
* import { z } from "../_shared/contracts/index.ts";
*
* Ref: https://github.com/adm01-debug/promo-gifts-v4/issues/52
*/
import { execSync } from 'node:child_process';
import { exit } from 'node:process';

const ALLOWED_ROOT = 'supabase/functions/_shared/contracts/';

// grep com -P (pcre) não disponível em todo lugar; usa -E e filtramos depois
let output = '';
try {
output = execSync(
'grep -rn "from[[:space:]]*[\\"\\\\\']https://.*zod" supabase/functions/ 2>/dev/null || true',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect all direct Zod specifiers in pinning gate

The new guard only searches for from ... "https://.*zod", so it misses other direct Zod imports (for example npm:zod) outside _shared/contracts, even though the script header says it should fail on any direct import outside that folder. In this repo, direct npm:zod imports already exist (e.g. supabase/functions/expert-chat/index.ts), so the gate can report a clean state while centralization is still bypassed and future regressions of the same form will not be blocked.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match multiline import specifiers in pinning gate

The regex is line-oriented and only matches when from and the URL are on the same line, so a direct import split across lines (e.g. import { z } from followed by the URL on the next line) is not detected and the script exits successfully. That allows straightforward bypasses of the CI guard while still importing Zod directly outside _shared/contracts.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate grep failures instead of forcing success

The command appends || true and suppresses stderr, which converts grep execution errors into a passing check. If the scan path is wrong, files are unreadable, or grep fails for any runtime reason, this gate still prints success and returns 0, so CI can silently skip enforcement instead of failing closed.

Useful? React with 👍 / 👎.

{ encoding: 'utf8' },
);
} catch (err) {
// grep retorna exit 1 quando não acha nada — tratamos como sucesso
output = '';
}

const violations = output
.split('\n')
.filter(Boolean)
.filter(line => !line.startsWith(ALLOWED_ROOT));

if (violations.length === 0) {
console.log('✓ Pinning de Zod centralizado (0 imports diretos fora de _shared/contracts/)');
exit(0);
}

console.error('❌ Imports diretos de Zod encontrados fora de _shared/contracts/:');
console.error(' (Zod deve ser importado via barrel: import { z } from "../_shared/contracts/index.ts")\n');
for (const v of violations) {
console.error(` ${v}`);
}
console.error('\n Veja https://github.com/adm01-debug/promo-gifts-v4/issues/52 para contexto.');
exit(1);
2 changes: 1 addition & 1 deletion supabase/functions/commemorative-dates/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getCorsHeaders } from '../_shared/cors.ts';
import { safeErrorResponse } from '../_shared/error-response.ts';
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { parseBodyWithSchema } from "../_shared/zod-validate.ts";

const ActionSchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/comparison-ai-advisor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { authenticateRequest, requireRole, authErrorResponse } from '../_shared/
// Comparison AI Advisor — Lovable AI Gateway
// Recebe lista slim de produtos e retorna 3-5 bullets + bestFor highVolume/fastDelivery/premium.

import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts';
import { z } from "../_shared/contracts/index.ts";
import { safeErrorFields } from '../_shared/log-safety.ts';

// Fallback CORS headers — sobrescritos per-request via getCorsHeaders(req).
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/connection-tester/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { safeErrorResponse } from "../_shared/error-response.ts";
// Reads credentials from `integration_credentials` (DB-first) with env fallback.
// Core ping/persistence logic lives in `_shared/connection-test-runner.ts`.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
import { z } from "../_shared/contracts/index.ts";
import { runConnectionTest } from "../_shared/connection-test-runner.ts";

const BodySchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/crm-db-bridge/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCorsHeaders, handleCorsPreflightIfNeeded } from '../_shared/cors.ts';
import { createClient, SupabaseClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { runBotProtection } from '../_shared/bot-protection.ts';
import { getBreaker, circuitOpenResponse, getAllBreakerStatuses } from '../_shared/circuit-breaker.ts';
import { AsyncLocalStorage } from "node:async_hooks";
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/dropbox-list/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCorsHeaders } from '../_shared/cors.ts';
import { authenticateRequest, requireRole, authErrorResponse } from '../_shared/auth.ts';
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { fetchWithBreaker, CircuitOpenError, circuitOpenResponse } from '../_shared/external-fetch.ts';

const BodySchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/elevenlabs-tts/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCorsHeaders } from '../_shared/cors.ts';
import { authenticateRequest, authErrorResponse } from '../_shared/auth.ts';
import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts';
import { z } from "../_shared/contracts/index.ts";
import { runBotProtection } from '../_shared/bot-protection.ts';
import { fetchWithBreaker, CircuitOpenError, circuitOpenResponse } from '../_shared/external-fetch.ts';

Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/external-db-bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type ServiceClient,
castSupabaseClient,
} from "../_shared/supabase-client-adapter.ts";
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
import { z } from "../_shared/contracts/index.ts";
import { buildPublicCorsHeaders, getCorsHeaders, handleCorsPreflightIfNeeded } from "../_shared/cors.ts";
import {
type Operation,
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/external-db-inspect/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCorsHeaders } from '../_shared/cors.ts';
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { runBotProtection } from '../_shared/bot-protection.ts';

const BodySchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/full-op-diagnostics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// token como usado — preserva a possibilidade de execução real depois.
// ----------------------------------------------------------------------------
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.0";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { getCorsHeaders, handleCorsPreflightIfNeeded } from "../_shared/cors.ts";

type CheckStatus = "pass" | "fail" | "skipped" | "error";
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/generate-ad-image/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "https://deno.land/std@0.224.0/dotenv/load.ts";
import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";

import { z } from "../_shared/contracts/index.ts";
// Mirror the schema from the function
const BodySchema = z.object({
productImageUrl: z.string().url(),
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/generate-ad-image/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getCorsHeaders, handleCorsPreflightIfNeeded } from '../_shared/cors.ts';
import { authenticateRequest, authErrorResponse } from '../_shared/auth.ts';
import { callAiWithTracking, QuotaExceededError } from '../_shared/ai-usage.ts';
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
import { z } from "../_shared/contracts/index.ts";
import { runBotProtection } from '../_shared/bot-protection.ts';

const BodySchema = z.object({
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/magic-up-score/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { getCorsHeaders } from '../_shared/cors.ts';
import { authenticateRequest, authErrorResponse } from '../_shared/auth.ts';
import { callAiWithTracking, QuotaExceededError } from '../_shared/ai-usage.ts';
import { runBotProtection } from '../_shared/bot-protection.ts';
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";

import { z } from "../_shared/contracts/index.ts";
const CriterionSchema = z.object({
id: z.string().min(1),
label: z.string().min(1),
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/mcp-keys-issue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
*/

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.95.0";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import {
KNOWN_SCOPES,
FULL_SCOPE,
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/mcp-keys-revoke/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
* payload_summary antes do trigger DB.
*/
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.95.0";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { getOrCreateRequestId, REQUEST_ID_HEADER } from "../_shared/request-id.ts";
import { writeAuditEntry, summarizePayload, extractRequestMeta } from "../_shared/audit-log.ts";
import { recordMcpViolation, mapViolationReason } from "../_shared/mcp-violations.ts";
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/mcp-keys-rotate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
*/

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.95.0";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import {
FULL_SCOPE_CONFIRMATION,
FULL_SCOPE_MIN_JUSTIFICATION,
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/mcp-keys-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
* Toda mudança é auditada com request_id, payload_summary, duração e status.
*/
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.95.0";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import {
KNOWN_SCOPES,
FULL_SCOPE_CONFIRMATION,
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/quote-sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
import { authenticateRequest, requireRole, authErrorResponse } from "../_shared/auth.ts";
/// <reference lib="deno.ns" />
import { createClient } from "npm:@supabase/supabase-js@2.49.1";
import { z } from "https://esm.sh/zod@3.23.8";
import { z } from "../_shared/contracts/index.ts";
import { parseBodyWithSchema } from "../_shared/zod-validate.ts";
import { resolveCredential } from "../_shared/credentials.ts";

Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/rate-limit-check/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { getCorsHeaders, handleCorsPreflightIfNeeded } from '../_shared/cors.ts';
import { safeErrorResponse } from '../_shared/error-response.ts';
import { logSecurityEvent } from '../_shared/security.ts';
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";

import { z } from "../_shared/contracts/index.ts";
const BodySchema = z.object({
endpoint: z.enum(['login', 'api', 'ai', 'approval']).default('api'),
}).partial();
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/secrets-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCorsHeaders } from "../_shared/cors.ts";
// Admin-only secrets manager for the Conexões hub.
// Persists values in `integration_credentials` and never returns plaintext to the client.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
import { z } from "../_shared/contracts/index.ts";
import {
invalidateCredentialCache,
getCredentialCacheMetrics,
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/verify-email/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { getCorsHeaders, handleCorsPreflightIfNeeded } from '../_shared/cors.ts';
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";

import { z } from "../_shared/contracts/index.ts";
const BodySchema = z.object({
token: z.string().min(1, "Token não fornecido"),
});
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/voice-agent/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCorsHeaders } from '../_shared/cors.ts';
import { authenticateRequest, authErrorResponse } from '../_shared/auth.ts';
import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts';
import { z } from "../_shared/contracts/index.ts";
import { callAiWithTracking, QuotaExceededError } from '../_shared/ai-usage.ts';
import { SYSTEM_PROMPT, VOICE_COMMAND_TOOL, TOOL_CHOICE } from './systemPrompt.ts';
import { parseAiResponse } from './parseAiResponse.ts';
Expand Down
Loading