fix(connections): destrava módulo /admin/conexoes — credenciais via DB-first, cron órfão, RLS dev#70
Conversation
…reto A função fazia early-return com 500 quando CRM_SUPABASE_URL não estava no ambiente, mesmo que o usuário tivesse salvado EXTERNAL_CRM_URL via /admin/conexoes (que persiste em integration_credentials). Agora usa resolveCredential() que tenta DB primeiro, depois env (com aliases CRM_SUPABASE_URL → EXTERNAL_CRM_URL). Isso desbloqueia toda seleção de empresas no front, que ficava travada em "Carregando..." indefinidamente porque o React Query reentava 3× em cada erro 500. Removido também o bloco de diagnóstico raw REST que rodava em toda request — adicionava ~200ms de latência sem trazer info nova além do que getCrmClient() já registra no warmup. https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
…solveCredential
Mesma classe de bug do crm-db-bridge: essas 3 funções liam
Deno.env.get('CRM_SUPABASE_URL') diretamente, ignorando credenciais
salvas via /admin/conexoes em integration_credentials.
- quote-sync: extrai helper getCrmCreds() chamado lazy em cada handler
(sync_all_pending, fetchQuoteFromCRM, updateCRMSyncStatus). Antes
resolvia no escopo de módulo, então rotação de secret nunca era
reaplicada sem cold restart do isolate.
- expert-chat: resolve dentro do bloco que usa, com fallback service→anon.
- quote-public-view: troca o '!' (que dava throw) por resposta 500
estruturada quando credenciais não estão configuradas.
Em todas: aliases CRM_SUPABASE_URL/KEY → EXTERNAL_CRM_URL/KEY já estão
configurados em _shared/credentials.ts, então env legado continua funcionando
sem migração de dados.
https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
CredentialsChangedBanner subscribe postgres_changes em
integration_credentials e mostra o nome do secret alterado. Mas estava
casteando para { name?: string }, e a tabela tem coluna `secret_name`.
Resultado: o banner aparecia ao mudar credencial mas o texto sempre
omitia qual secret havia mudado, frustrando o uso ('algo mudou, mas
o quê?').
https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
Migration 20260423184705 criou as helpers get/set_connections_auto_test_interval assumindo o job existia, mas nenhuma migration o criava. Resultado: auto-test nunca rodava em produção e AutoTestJobStatusCard ficava sempre "untested". Cria agora o job idempotentemente (default 15min, dentro da lista de intervalos permitidos pela helper). Ao final, a própria migration valida que get_connections_auto_test_interval() retorna 15. https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
A edge function secrets-manager autoriza apenas role 'dev', mas as RLS policies exigiam 'admin'. Funcionava via secrets-manager (service_role bypassa RLS), mas: - Auditoria/policy-checker mostrava divergência - Acesso direto via SQL editor com role dev falhava - Realtime podia bloquear payloads p/ subscribers dev Drop das 4 policies antigas (admin-only) e recreate como admin OR dev em SELECT/INSERT/UPDATE/DELETE. https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Operational Documentation docs/RUNBOOK_CONNECTIONS.md |
Adds comprehensive runbook for Conexões admin module covering crm-db-bridge troubleshooting, credential rotation, diagnostic endpoints, RLS validation, and structured logging event shapes. |
Frontend Credential UI src/components/admin/connections/CredentialsChangedBanner.tsx |
Updates banner to read credential name from payload.new/old.secret_name instead of name, adjusts string literals to single quotes, and refines event label/description templates. |
Edge Functions — Credential Resolution supabase/functions/crm-db-bridge/index.ts, supabase/functions/expert-chat/index.ts, supabase/functions/quote-public-view/index.ts, supabase/functions/quote-sync/index.ts |
Migrates from static env var reads to dynamic resolveCredential(...) lookups supporting DB-stored integration credentials with env fallback; crm-db-bridge adds new creds_health diagnostic endpoint; quote-sync introduces per-request credential resolution via getCrmCreds(). |
Database Scheduling supabase/migrations/20260429163414_schedule_connections_auto_test_cron.sql |
Creates idempotent connections-auto-test cron job running every 15 minutes, triggering edge function via HTTP POST with auth header and sanity-check validation. |
RLS Policy Updates supabase/migrations/20260429163441_align_integration_credentials_rls_with_dev.sql |
Expands integration_credentials SELECT/INSERT/UPDATE/DELETE policies from admin\-only to allow both admin and dev app roles via updated WITH CHECK and USING clauses. |
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
🐰 Credentials once scattered in env winds now nest in databases deep,
The bridge diagnostics awaken to whisper their health,
While admins and devs unlock RLS gates with shared keys,
A cron hops faithfully every fifteen moons,
And banners display the true secret names—resilience crafted from credentials reborn. 🔐✨
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly summarizes the main fix: unlocking the Conexões module through DB-first credentials, fixing an orphaned cron job, and updating RLS for dev role. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Description check | ✅ Passed | The pull request description is comprehensive and well-structured, covering all required template sections including detailed bug descriptions, validation steps, and a clear test plan. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
claude/fix-connections-module
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Review rate limit: 0/1 reviews remaining, refill in 35 minutes and 57 seconds.Comment @coderabbitai help to get the list of available commands and usage tips.
CI Status — todas as 5 falhas são herdadas de mainComparei os checks deste PR com PR #60 (lint cleanup, não modifica edge functions nem o módulo Conexões) — padrão idêntico:
Como PR #60 não toca em edge functions, isso comprova que o "Edge Functions — Deno typecheck" já estava quebrado em main antes deste PR. Mesma lógica vale para os outros 4 checks. Validação local feita antes do push:
Os 5 checks vermelhos seriam desbloqueados por:
Pronto para review. Quando os PRs base merger, faço rebase e os checks devem ficar verdes. Generated by Claude Code |
Resolve a lacuna de deploy: hoje após merge em main, edge functions ficam desatualizadas no Supabase Cloud até alguém rodar `supabase functions deploy` manualmente do laptop. Migrations são pior — aplicação manual via dashboard. Adiciona 2 workflows: 1. deploy-edge-functions.yml (auto, push em main) - Trigger: push em main tocando supabase/functions/** - Detecta funções alteradas via git diff (deploy só do que mudou) - Matrix paralelo (até 4 funções simultâneas, ~10s/fn) - workflow_dispatch também: deploy_all=true ou function_name=<x> - Requer secret: SUPABASE_ACCESS_TOKEN 2. apply-migrations.yml (manual, workflow_dispatch) - Deliberadamente manual — DDL pode lockar produção - dry_run=true por padrão (mostra plan sem aplicar) - Re-rodar com dry_run=false aplica de fato - Job summary com SQL aplicado + autor + commit pra auditoria - Requer secrets: SUPABASE_ACCESS_TOKEN, SUPABASE_DB_PASSWORD 3. docs/SUPABASE_DEPLOY_AUTOMATION.md - Setup inicial (gerar token + cadastrar secrets) — 5 min - Runbook por workflow + falhas comuns - Exemplo concreto usando este próprio PR (#70) como caso de uso Após este PR mergear, os 5 fixes do módulo Conexões + futuros PRs que toquem edge functions vão deployar sozinhos. Migrations continuam manuais mas com dry-run forçado pra revisão. https://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
🚀 Adicionado: workflows de auto-deployEstendi o PR pra também resolver o gap de como aplicar essas mudanças em produção (Supabase Cloud não auto-deploya edge functions nem migrations). 3 arquivos novos no commit
|
…upabase" This reverts commit dba68fc.
There was a problem hiding this comment.
Pull request overview
Fixes the /admin/conexoes “Connections” module end-to-end by making external CRM credentials DB-first (with env fallback), correcting realtime metadata used by the UI banner, and restoring missing operational wiring (cron + RLS) so auto-tests and dev access behave as intended.
Changes:
- Switch multiple edge functions to use
resolveCredential("EXTERNAL_CRM_*")(DB-first + alias/env fallback) instead of directDeno.env.get("CRM_SUPABASE_*"). - Fix
CredentialsChangedBannerto readsecret_namefrom realtime payloads so the banner shows which credential changed. - Add migrations to (a) schedule the orphaned
connections-auto-testcron job and (b) alignintegration_credentialsRLS policies to allowadmin OR dev.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/migrations/20260429163414_schedule_connections_auto_test_cron.sql | Adds idempotent scheduling for the connections-auto-test pg_cron job and validates helper output. |
| supabase/migrations/20260429163441_align_integration_credentials_rls_with_dev.sql | Replaces admin-only RLS policies with admin-or-dev policies for integration_credentials. |
| supabase/functions/quote-sync/index.ts | Lazily resolves CRM creds per request via resolveCredential to support UI-managed secrets + rotation. |
| supabase/functions/quote-public-view/index.ts | Resolves CRM creds DB-first and returns a structured 500 instead of throwing on missing env vars. |
| supabase/functions/expert-chat/index.ts | Resolves CRM creds DB-first when fetching client context from the external CRM DB. |
| supabase/functions/crm-db-bridge/index.ts | Migrates CRM credential sourcing to resolveCredential and adjusts runtime logging. |
| src/components/admin/connections/CredentialsChangedBanner.tsx | Fixes realtime payload mapping from name → secret_name and related UI text/formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SELECT cron.schedule( | ||
| 'connections-auto-test', | ||
| '*/15 * * * *', -- a cada 15 minutos (valor permitido pela helper de intervalo) | ||
| $$ | ||
| SELECT net.http_post( | ||
| url := 'https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test', | ||
| headers := jsonb_build_object( | ||
| 'Content-Type', 'application/json', | ||
| 'apikey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5tb2p3cGlobnNsa3NzbGpvd2poIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1Nzc0MjgsImV4cCI6MjA4ODE1MzQyOH0.49N47oFn_4O9EGZdoPOYDd_Iez7ft4s9eNITM8N1Eeg' | ||
| ), | ||
| body := '{"trigger":"cron"}'::jsonb, | ||
| timeout_milliseconds := 30000 | ||
| ) AS request_id; |
| } catch (diagErr) { | ||
| console.error(`[DIAG] Raw REST error:`, diagErr); | ||
| } | ||
| console.log(`[crm-db-bridge] CRM_URL prefix: ${CRM_URL.substring(0, 30)}..., using ${CRM_SERVICE_KEY ? 'SERVICE_KEY' : 'ANON_KEY'} (src=${CRM_SERVICE_KEY ? svcRes.source : anonRes.source}, len=${CRM_KEY.length}), anon_len=${CRM_ANON.length}, KEYS_MATCH=${keysMatch}, svc_last4=${CRM_KEY.slice(-4)}, anon_last4=${CRM_ANON.slice(-4)}`); |
| -- | ||
| -- Este patch: | ||
| -- - Faz drop das 4 policies antigas (admin-only) | ||
| -- - Recria como FOR ALL com USING/WITH CHECK aceitando admin OU dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 291f505f06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| url := 'https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test', | ||
| headers := jsonb_build_object( | ||
| 'Content-Type', 'application/json', | ||
| 'apikey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5tb2p3cGlobnNsa3NzbGpvd2poIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1Nzc0MjgsImV4cCI6MjA4ODE1MzQyOH0.49N47oFn_4O9EGZdoPOYDd_Iez7ft4s9eNITM8N1Eeg' |
There was a problem hiding this comment.
Parameterize cron target instead of hardcoding prod endpoint
Use environment-derived values for the function URL and API key here; as written, every environment that runs this migration will always call https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test with a fixed token, so staging/dev databases will trigger the production function instead of their own and can create cross-environment side effects (plus silent breakage if that key is rotated).
Useful? React with 👍 / 👎.
…-db-bridge
Sobe o módulo de Conexões para 10/10:
1) docs/RUNBOOK_CONNECTIONS.md — runbook operacional dedicado:
- Sintoma → causa → ação para "Carregando..." infinito, banner mudo,
auto-test sempre "untested"
- Procedimento de rotação de credenciais CRM
- Tabela de eventos estruturados emitidos por connections-auto-test
- Matriz de edge functions × credenciais consumidas (impacto se faltar)
2) crm-db-bridge: novo endpoint diag ?op=creds_health (bypass de auth, igual
a ping/diag/breaker_status). Retorna saúde da resolução das 3 credenciais
CRM (URL, SERVICE_ROLE, ANON) sem expor valores — só presença, source
(db/env/none), via_alias (bool), value_length, suffix4.
Health agregado:
- "healthy" : URL + 1 key presentes
- "degraded" : só URL OU só uma key (não conecta)
- "missing" : URL ausente — bridge fora do ar
Permite operadores e dashboards monitorarem o bridge sem precisar
invocar uma request autenticada (que seria bloqueada ou falharia
silenciosamente quando credenciais estão ausentes).
Validação:
- deno check supabase/functions/crm-db-bridge/index.ts ✅ exit 0
Espelha o padrão de diag.test.ts/ping.test.ts (integration test que bate
em deploy real). Cobre:
- Shape do payload (health enum, credentials.{url,service,anon} com
campos consistentes name/present/source/via_alias/value_length/suffix4)
- Invariante: present=true ⇒ value_length>0 e suffix4 com 4 chars
- Invariante: present=false ⇒ value_length=0 e suffix4=null
- Coerência health agregado vs presence (missing/degraded/healthy)
- Bypass de auth (igual a ping/diag) — status 200 ou 401 do gateway
deno check ✅ exit 0
Antes: a cada request a função emitia uma linha de log com prefix da URL, source das credenciais, e last4 das keys. Em rajada (50 req/s) poluía logs sem agregar info nova — todos os valores são derivados das mesmas credenciais resolvidas do isolate. Agora: - Log só se wasCold (1ª request real do isolate) OU se LOG_CRM_BRIDGE_VERBOSE=on (escape hatch para debug) - Format é JSON estruturado (evt:'crm-creds-resolved') igual aos outros eventos do connections-auto-test, em vez de string concatenada - Snapshot sob demanda continua disponível via GET ?op=creds_health (introduzido no commit anterior) deno check ✅ exit 0
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
supabase/functions/crm-db-bridge/index.ts (1)
58-71:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCached CRM client is not refreshed when credentials rotate.
Once
cachedCrmClientis set, new DB credentials are ignored. After key rotation, requests can keep failing until isolate restart.Suggested fix
let cachedCrmClient: SupabaseClient | null = null; +let cachedCredFingerprint: string | null = null; export async function getCrmClient(): Promise<SupabaseClient | null> { - if (cachedCrmClient) return cachedCrmClient; const [{ value: url }, { value: serviceKey }, { value: anonKey }] = await Promise.all([ resolveCredential("EXTERNAL_CRM_URL"), resolveCredential("EXTERNAL_CRM_SERVICE_ROLE_KEY"), resolveCredential("EXTERNAL_CRM_ANON_KEY"), ]); const key = serviceKey ?? anonKey; if (!url || !key) return null; + const fingerprint = `${url}|${key}`; + if (cachedCrmClient && cachedCredFingerprint === fingerprint) return cachedCrmClient; cachedCrmClient = buildCrmClient(url, key); + cachedCredFingerprint = fingerprint; return cachedCrmClient; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@supabase/functions/crm-db-bridge/index.ts` around lines 58 - 71, getCrmClient currently returns a single cachedCrmClient forever, so rotated credentials are ignored; update getCrmClient to detect credential changes by resolving EXTERNAL_CRM_URL and the service/anon key (via resolveCredential) and comparing them to the credentials used to create the cached client (store lastCrmUrl and lastCrmKey alongside cachedCrmClient); if url or key differ, recreate cachedCrmClient via buildCrmClient(url, key) and update the stored lastCrmUrl/lastCrmKey before returning. Ensure you reference and update cachedCrmClient, lastCrmUrl, lastCrmKey, resolveCredential and buildCrmClient in the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/RUNBOOK_CONNECTIONS.md`:
- Around line 100-105: The fenced code block containing the GET endpoint lines
(e.g., "GET /functions/v1/crm-db-bridge?op=ping", "GET ...?op=diag", "GET
...?op=breaker_status", "GET ...?op=creds_health") is missing a language tag and
triggers MD040; add a language tag (e.g., ```text) to the opening fence so the
block becomes ```text and the linter will accept it.
In `@src/components/admin/connections/CredentialsChangedBanner.tsx`:
- Around line 48-50: The current logic in CredentialsChangedBanner (using
(payload.new as ...) ?? (payload.old as ...)) can pick an empty payload.new on
DELETE and lose the old secret_name; change the selection to prefer payload.old
for DELETE events or to fall back to payload.old when payload.new is empty.
Update the code that computes row/name (the payload, row, and name variables and
any place reading secret_name) to either check payload.action === 'DELETE' and
use payload.old, or use a check like "use payload.new if it has keys/secret_name
else payload.old" so the old secret_name isn't dropped.
In `@supabase/functions/crm-db-bridge/index.ts`:
- Around line 188-232: The buildCredsHealthSnapshot function currently returns
sensitive fingerprinting fields (suffix4, value_length, source, via_alias,
resolved_name) derived from resolveCredential; remove these from the public,
unauthenticated response and only emit non-sensitive flags (e.g., present
boolean) and the overall health, or alternatively gate the detailed fields
behind an admin-auth check so that only authenticated admin/dev requests receive
source/alias/length/suffix information; update the returned object in
buildCredsHealthSnapshot (and the same logic referenced at lines ~870-872) to
exclude those sensitive properties unless an explicit admin auth flag is
present.
In `@supabase/migrations/20260429163414_schedule_connections_auto_test_cron.sql`:
- Around line 28-32: The migration currently hardcodes the Supabase project URL
and API key in the scheduled job payload (see url, headers and
jsonb_build_object for the 'connections-auto-test' call); remove these secrets
and instead build the url and headers from runtime configuration or a secrets
store (e.g. SELECT from an app_config/secrets table or current_setting-style
server settings) so each environment uses its own project and key; update the
SQL that sets url := ... and headers := jsonb_build_object(...) to pull values
from those secure/config sources rather than embedding the literal URL and
apikey.
---
Outside diff comments:
In `@supabase/functions/crm-db-bridge/index.ts`:
- Around line 58-71: getCrmClient currently returns a single cachedCrmClient
forever, so rotated credentials are ignored; update getCrmClient to detect
credential changes by resolving EXTERNAL_CRM_URL and the service/anon key (via
resolveCredential) and comparing them to the credentials used to create the
cached client (store lastCrmUrl and lastCrmKey alongside cachedCrmClient); if
url or key differ, recreate cachedCrmClient via buildCrmClient(url, key) and
update the stored lastCrmUrl/lastCrmKey before returning. Ensure you reference
and update cachedCrmClient, lastCrmUrl, lastCrmKey, resolveCredential and
buildCrmClient in the fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b761ba01-9ba9-4865-a19b-6c380da71daf
📒 Files selected for processing (8)
docs/RUNBOOK_CONNECTIONS.mdsrc/components/admin/connections/CredentialsChangedBanner.tsxsupabase/functions/crm-db-bridge/index.tssupabase/functions/expert-chat/index.tssupabase/functions/quote-public-view/index.tssupabase/functions/quote-sync/index.tssupabase/migrations/20260429163414_schedule_connections_auto_test_cron.sqlsupabase/migrations/20260429163441_align_integration_credentials_rls_with_dev.sql
| ``` | ||
| GET /functions/v1/crm-db-bridge?op=ping → liveness | ||
| GET /functions/v1/crm-db-bridge?op=diag → boot/runtime metrics | ||
| GET /functions/v1/crm-db-bridge?op=breaker_status → circuit breaker | ||
| GET /functions/v1/crm-db-bridge?op=creds_health → resolução das credenciais | ||
| ``` |
There was a problem hiding this comment.
Add a language tag to the fenced code block.
This block is missing a fence language and can trigger markdown lint failures (MD040).
Suggested fix
-```
+```text
GET /functions/v1/crm-db-bridge?op=ping → liveness
GET /functions/v1/crm-db-bridge?op=diag → boot/runtime metrics
GET /functions/v1/crm-db-bridge?op=breaker_status → circuit breaker
GET /functions/v1/crm-db-bridge?op=creds_health → resolução das credenciais</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>
[warning] 100-100: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @docs/RUNBOOK_CONNECTIONS.md around lines 100 - 105, The fenced code block
containing the GET endpoint lines (e.g., "GET
/functions/v1/crm-db-bridge?op=ping", "GET ...?op=diag", "GET
...?op=breaker_status", "GET ...?op=creds_health") is missing a language tag and
triggers MD040; add a language tag (e.g., text) to the opening fence so the block becomes text and the linter will accept it.
</details>
<!-- fingerprinting:phantom:medusa:grasshopper:f4d4f2f5-e163-443a-8ee4-509830b7f93d -->
<!-- d98c2f50 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| (payload.new as { secret_name?: string } | null) ?? | ||
| (payload.old as { secret_name?: string } | null); | ||
| const name = row?.secret_name ?? null; |
There was a problem hiding this comment.
DELETE events can still hide the changed secret_name.
Using payload.new ?? payload.old can prefer an empty new object on deletes, so the old row name is dropped.
Suggested fix
- const row =
- (payload.new as { secret_name?: string } | null) ??
- (payload.old as { secret_name?: string } | null);
+ const row =
+ event === 'DELETE'
+ ? (payload.old as { secret_name?: string } | null)
+ : ((payload.new as { secret_name?: string } | null) ??
+ (payload.old as { secret_name?: string } | null));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| (payload.new as { secret_name?: string } | null) ?? | |
| (payload.old as { secret_name?: string } | null); | |
| const name = row?.secret_name ?? null; | |
| event === 'DELETE' | |
| ? (payload.old as { secret_name?: string } | null) | |
| : ((payload.new as { secret_name?: string } | null) ?? | |
| (payload.old as { secret_name?: string } | null)); | |
| const name = row?.secret_name ?? null; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/admin/connections/CredentialsChangedBanner.tsx` around lines
48 - 50, The current logic in CredentialsChangedBanner (using (payload.new as
...) ?? (payload.old as ...)) can pick an empty payload.new on DELETE and lose
the old secret_name; change the selection to prefer payload.old for DELETE
events or to fall back to payload.old when payload.new is empty. Update the code
that computes row/name (the payload, row, and name variables and any place
reading secret_name) to either check payload.action === 'DELETE' and use
payload.old, or use a check like "use payload.new if it has keys/secret_name
else payload.old" so the old secret_name isn't dropped.
| url := 'https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test', | ||
| headers := jsonb_build_object( | ||
| 'Content-Type', 'application/json', | ||
| 'apikey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5tb2p3cGlobnNsa3NzbGpvd2poIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1Nzc0MjgsImV4cCI6MjA4ODE1MzQyOH0.49N47oFn_4O9EGZdoPOYDd_Iez7ft4s9eNITM8N1Eeg' | ||
| ), |
There was a problem hiding this comment.
Remove hardcoded project URL and key from scheduled job payload.
This pins every environment to a single Supabase project and commits credential material into git. In staging/dev, the cron can accidentally call production.
Suggested fix
- url := 'https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test',
+ url := current_setting('app.settings.supabase_url', true) || '/functions/v1/connections-auto-test',
headers := jsonb_build_object(
'Content-Type', 'application/json',
- 'apikey', '...hardcoded...'
+ 'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key', true)
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| url := 'https://nmojwpihnslkssljowjh.supabase.co/functions/v1/connections-auto-test', | |
| headers := jsonb_build_object( | |
| 'Content-Type', 'application/json', | |
| 'apikey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5tb2p3cGlobnNsa3NzbGpvd2poIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1Nzc0MjgsImV4cCI6MjA4ODE1MzQyOH0.49N47oFn_4O9EGZdoPOYDd_Iez7ft4s9eNITM8N1Eeg' | |
| ), | |
| url := current_setting('app.settings.supabase_url', true) || '/functions/v1/connections-auto-test', | |
| headers := jsonb_build_object( | |
| 'Content-Type', 'application/json', | |
| 'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key', true) | |
| ), |
🧰 Tools
🪛 Betterleaks (1.1.2)
[high] 31-31: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@supabase/migrations/20260429163414_schedule_connections_auto_test_cron.sql`
around lines 28 - 32, The migration currently hardcodes the Supabase project URL
and API key in the scheduled job payload (see url, headers and
jsonb_build_object for the 'connections-auto-test' call); remove these secrets
and instead build the url and headers from runtime configuration or a secrets
store (e.g. SELECT from an app_config/secrets table or current_setting-style
server settings) so each environment uses its own project and key; update the
SQL that sets url := ... and headers := jsonb_build_object(...) to pull values
from those secure/config sources rather than embedding the literal URL and
apikey.
A lógica de snapshot de saúde introduzida em crm-db-bridge?op=creds_health
era específica de 3 nomes hardcoded (URL/SERVICE_ROLE/ANON do CRM). Para
permitir que outras edge functions exponham endpoints similares sem
duplicar código, extraio para _shared/credentials.ts:
buildCredentialsHealth(names, { urlSuffixes?, serviceClient? })
→ CredentialsHealthSummary { ok, ts, health, credentials[] }
Mudanças:
- Forma do payload muda de objeto-com-keys-fixas para array genérica:
ANTES: { credentials: { url, service, anon } }
DEPOIS: { credentials: [{ name, present, source, via_alias, ... }] }
Mais geral, cada entry traz seu próprio `name` — endpoints podem
consumir N credenciais sem schema rígido.
- Health agregado segue 3 estados:
"missing" → nenhum nome com sufixo *_URL presente (sem URL nada
conecta — pivô de saúde do bridge)
"degraded" → URL presente mas todas as keys ausentes
"healthy" → URL presente E ao menos 1 key presente
urlSuffixes default = ["_URL"]; pode ser sobrescrito por edge fns
com nomenclatura diferente.
- crm-db-bridge agora chama o helper compartilhado, mantendo o endpoint
?op=creds_health funcional com a mesma semântica.
- Atualiza creds_health.test.ts para validar a nova forma array.
- Adiciona 4 unit tests em credentials.test.ts cobrindo:
1. tudo presente em DB → healthy
2. URL ausente → missing (mesmo com keys presentes)
3. URL presente, keys ausentes → degraded
4. alias legado em env → via_alias=true e source='env'
- Atualiza docs/RUNBOOK_CONNECTIONS.md com a nova forma de payload.
Validação:
- deno check (3 arquivos modificados) ✅ exit 0
- deno test _shared/credentials.test.ts ✅ 24/24 passa (era 20/20)
Antes: o script chamava \`deno check <files>\` sem nunca passar o import map per-função, então qualquer função com deno.json local que importa bare specifiers (npm:/jsr:) falhava com: Relative import path "X" not prefixed with / or ./ or ../ Atinge atualmente: - mcp-server (importa "hono", "mcp-lite", "@supabase/supabase-js") - quote-sync (futuro-prova) Fix: detectar \`<fnDir>/deno.json\` e adicionar \`--config <path>\` aos argumentos do \`deno check\` quando presente. Validação local após fix: node scripts/typecheck-edge-functions.mjs → ✅ All 85 edge function(s) typecheck cleanly Antes do fix: 84/85 (mcp-server falhava com import quebrado).
…ente Estes arquivos eram criados a cada execução do typecheck-edge-functions.mjs e poluíam git status sem agregar valor: - ./deno.lock (raiz, gerado quando script roda em modo multi-config) - supabase/functions/mcp-server/deno.lock - supabase/functions/quote-sync/deno.lock Não são commitados em main, e edge functions são deployadas pelo Lovable Cloud sem pinning reproduzível — CI faz fresh resolution via deno.json.
Sumário
Fix completo do Módulo Conexões (
/admin/conexoes), que estava parcialmente quebrado: empresas do CRM não carregavam no front, banner de auto-refresh não exibia metadata e o auto-test cron nunca rodava.5 bugs identificados e corrigidos em commits atômicos. Nenhuma migração de dados necessária — aliases já estavam configurados em
_shared/credentials.ts.Após validação, +4 commits de polish elevaram o módulo para 10/10 (runbook, observabilidade, testes, refactor reusável).
Bugs corrigidos
🔴 #1 —
crm-db-bridgeignorava credenciais salvas pela UICausa raiz do "Carregando..." infinito reportado pelo usuário.
supabase/functions/crm-db-bridge/index.ts:848-852liaDeno.env.get("CRM_SUPABASE_URL")direto e retornava 500 se não estivesse no env, mesmo que o usuário tivesse cadastradoEXTERNAL_CRM_URLvia/admin/conexoes(que persiste emintegration_credentials). Como o React Query reentrava 3× em cada erro 500, o front ficava em "Carregando..." por ~15-20s antes de finalmente mostrar vazio.Fix: migrar para
resolveCredential("EXTERNAL_CRM_URL")que tenta DB primeiro e cai pro env como fallback (com aliases).🔴 #2 —
quote-sync/expert-chat/quote-public-viewcom mesmo bugMesma classe de bug em 3 outras edge functions — todas liam
Deno.env.get("CRM_SUPABASE_URL")diretamente.quote-sync/index.ts: lia em escopo de módulo, então rotações de secret nunca eram reaplicadas sem cold restart do isolate. Extraído helpergetCrmCreds()chamado lazy em cada handler.expert-chat/index.ts:615: dentro do bloco que usa.quote-public-view/index.ts:88: trocava o!(que dava throw) por resposta 500 estruturada.🔴 #3 —
CredentialsChangedBannerlia coluna inexistentesrc/components/admin/connections/CredentialsChangedBanner.tsx:44-47casteava o payload realtime para{ name?: string }, mas a coluna na tabela ésecret_name. Resultado: o banner aparecia ao mudar credencial mas o texto sempre omitia qual secret havia mudado.🔴 #4 — Cron
connections-auto-testórfão (nunca rodava)Migration
20260423184705criou helpersget/set_connections_auto_test_intervalassumindo o job existia, mas nenhuma migration o criava. Resultado: auto-test nunca rodava em produção eAutoTestJobStatusCardficava sempre "untested".Nova migration
20260429163414_*agenda o job idempotentemente (default 15min, dentro da lista de intervalos permitidos pela helper) e valida o resultado viaget_connections_auto_test_interval().🔴 #5 — Mismatch RLS vs lógica de permissão
A edge function
secrets-managerautoriza roledev, mas as RLS policies deintegration_credentialsexigiamadmin. Funcionava via secrets-manager (service_role bypassa RLS), mas:devfalhava silenciosamentedevNova migration
20260429163441_*recria as 4 policies comoadmin OR dev.Polish para 10/10
📘 Runbook operacional dedicado (
docs/RUNBOOK_CONNECTIONS.md)connections-auto-test🩺 Endpoint
?op=creds_healthnocrm-db-bridgeBypass de auth (igual a
ping/diag/breaker_status) — operadores precisam diagnosticar mesmo com JWT/secrets quebrados. Retorna saúde da resolução das 3 credenciais CRM (URL, SERVICE_ROLE, ANON) sem expor valores — sópresent,source(db/env/none),via_alias,value_length,suffix4.Health agregado:
healthy: URL + 1 key presentesdegraded: só URL OU só uma key (não conecta)missing: URL ausente — bridge totalmente fora do ar🧰 Helper
buildCredentialsHealthextraído para_shared/credentials.tsReusável por outras edge functions (futuro: aplicar a
quote-sync,expert-chat,quote-public-view). Forma genérica via array denames+urlSuffixesopcional.🧪 Testes
?op=creds_health(shape, invariantes, bypass de auth)buildCredentialsHealth: healthy, missing, degraded, alias legado🪵 Log de resolução de credenciais — só na 1ª request do isolate
Antes corria a cada request; agora só em cold-start ou se
LOG_CRM_BRIDGE_VERBOSE=on. Format JSON estruturado (evt:'crm-creds-resolved') em vez de string concatenada.Validação
npx tsc --noEmit✅ exit 0npx vitest run tests/components/admin/connections✅ 30/30 passadeno check(3 arquivos modificados) ✅ exit 0deno test _shared/credentials.test.ts✅ 24/24 passa (era 20/20)isInstrumentationPaused is not definede drift em testes não-relacionados — mesmas falhas que PR test: fix 9 drifted test files; suite is now 100% green #28 visa corrigir). Confirmado viagit stash && checkout mainque essas falhas existem com zero alterações deste branch.Test plan
EXTERNAL_CRM_URL+EXTERNAL_CRM_SERVICE_ROLE_KEYvia/admin/conexoes→ o seletor de empresas no front deve carregar empresas em <3s (era infinito)CredentialsChangedBannermostra agora o nome do secret alterado ao salvar nova credencialSELECT public.get_connections_auto_test_interval();→ deve retornar15devconsegue rodarSELECT * FROM public.integration_credentials;direto no SQL editorquote-public-view,quote-sync,expert-chat(todas 3 deveriam continuar funcionando com env legado, e agora também com creds salvas via UI)GET /functions/v1/crm-db-bridge?op=creds_healthretorna{ health: "healthy", credentials: [...] }sem precisar de JWT autenticadohealthy → degraded → missingno endpointhttps://claude.ai/code/session_014iuvdUkCn9qJirLVr3XVWk
Generated by Claude Code