Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
-- ============================================================================
-- Onda 9.1 — Vault Healthcheck Migration (idempotent)
-- ============================================================================
-- Cria infraestrutura de monitoramento contínuo do estado do Supabase Vault
-- para detectar corrupções de ciphertext (problema da Onda 9 — pgsodium key
-- substituída em 2026-04-29 corrompeu 31 secrets ao postgres reiniciar).
--
-- Componentes:
-- - public.vault_healthcheck_log (tabela append-only)
-- - public.fn_vault_healthcheck() (função read-only que valida 24/24)
-- - public.fn_vault_healthcheck_run() (executa + persiste log)
-- - public.fn_vault_healthcheck_cleanup() (retenção 30d)
-- - public.v_vault_health (view top-20 últimas leituras)
-- - cron 'vault_healthcheck' (a cada 15min)
-- - cron 'vault_healthcheck_cleanup' (4am diário)
--
-- Idempotência: 100% — pode rodar 2x sem efeito colateral.
-- - Tabela: CREATE TABLE IF NOT EXISTS (preserva dados históricos)
-- - Funções/View: CREATE OR REPLACE
-- - Cron: cron.unschedule() em DO...EXCEPTION + cron.schedule()
--
-- Ref: /workspace/notes/onda-9-vault-recovery-2026-05-09.md
-- ============================================================================

-- ----------------------------------------------------------------------------
-- 1) Tabela de log do healthcheck (idempotente — preserva dados)
-- ----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.vault_healthcheck_log (
id bigserial PRIMARY KEY,
checked_at timestamptz NOT NULL DEFAULT now(),
status text NOT NULL,
ok_count integer NOT NULL,
fail_count integer NOT NULL,
defer_count integer NOT NULL,
failed_names text[] NOT NULL DEFAULT '{}',
full_result jsonb NOT NULL
);
Comment thread
adm01-debug marked this conversation as resolved.

CREATE INDEX IF NOT EXISTS idx_vault_healthcheck_log_checked_at
ON public.vault_healthcheck_log (checked_at DESC);

COMMENT ON TABLE public.vault_healthcheck_log IS
'Onda 9.1 — log do healthcheck do Supabase Vault. Append-only, retenção 30d via cron.';

-- ----------------------------------------------------------------------------
-- 2) Função core: fn_vault_healthcheck — read-only, retorna jsonb
-- ----------------------------------------------------------------------------
-- Itera sobre vault.secrets, tenta decifrar via vault.decrypted_secrets,
-- conta ok/fail/defer (description LIKE 'DEFER%' = pendência conhecida).
-- Cada decryption em sub-bloco EXCEPTION pra capturar invalid ciphertext
-- sem abortar o loop completo.
CREATE OR REPLACE FUNCTION public.fn_vault_healthcheck()
RETURNS jsonb
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public', 'vault', 'extensions'
AS $function$
Comment thread
adm01-debug marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
DECLARE
rec RECORD;
v_decrypted text;
v_ok int := 0;
v_fail int := 0;
v_defer int := 0;
v_failed_names text[] := '{}';
BEGIN
FOR rec IN SELECT name, description FROM vault.secrets ORDER BY name
LOOP
IF rec.description LIKE 'DEFER%' THEN
v_defer := v_defer + 1;
ELSE
BEGIN
SELECT decrypted_secret INTO v_decrypted FROM vault.decrypted_secrets WHERE name = rec.name;
IF v_decrypted IS NOT NULL AND v_decrypted != '' THEN
v_ok := v_ok + 1;
ELSE
v_fail := v_fail + 1;
v_failed_names := v_failed_names || rec.name;
END IF;
EXCEPTION WHEN OTHERS THEN
v_fail := v_fail + 1;
v_failed_names := v_failed_names || rec.name;
END;
END IF;
END LOOP;

RETURN jsonb_build_object(
'checked_at', now(),
'ok', v_ok,
'fail', v_fail,
'defer', v_defer,
'failed_names', to_jsonb(v_failed_names),
'status', CASE WHEN v_fail = 0 THEN 'healthy' ELSE 'degraded' END
);
END
$function$;

COMMENT ON FUNCTION public.fn_vault_healthcheck() IS
'Onda 9.1 — retorna jsonb com ok/fail/defer/status. Read-only, safe pra polling.';

-- Hardening (least privilege para SECURITY DEFINER):
-- read-only, mas eleva privilégios pra ler vault.decrypted_secrets — restringir.
REVOKE ALL ON FUNCTION public.fn_vault_healthcheck() FROM public, anon;
GRANT EXECUTE ON FUNCTION public.fn_vault_healthcheck() TO authenticated, service_role;
Comment thread
adm01-debug marked this conversation as resolved.

-- ----------------------------------------------------------------------------
-- 3) Função wrapper: fn_vault_healthcheck_run — executa + persiste log
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.fn_vault_healthcheck_run()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public', 'extensions'
Comment thread
adm01-debug marked this conversation as resolved.
AS $function$
DECLARE
v_result jsonb;
BEGIN
v_result := public.fn_vault_healthcheck();
INSERT INTO public.vault_healthcheck_log (
checked_at, status, ok_count, fail_count, defer_count, failed_names, full_result
) VALUES (
(v_result->>'checked_at')::timestamptz,
v_result->>'status',
(v_result->>'ok')::int,
(v_result->>'fail')::int,
(v_result->>'defer')::int,
ARRAY(SELECT jsonb_array_elements_text(v_result->'failed_names')),
v_result
);
END
$function$;

COMMENT ON FUNCTION public.fn_vault_healthcheck_run() IS
'Onda 9.1 — wrapper invocado pelo cron. Executa o check e persiste em vault_healthcheck_log.';

-- Hardening (least privilege para SECURITY DEFINER):
-- escreve em vault_healthcheck_log + lê vault — restringir a service_role (cron usa essa role).
REVOKE ALL ON FUNCTION public.fn_vault_healthcheck_run() FROM public, anon;
GRANT EXECUTE ON FUNCTION public.fn_vault_healthcheck_run() TO service_role;

-- ----------------------------------------------------------------------------
-- 4) Função de limpeza: fn_vault_healthcheck_cleanup — retenção 30 dias
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.fn_vault_healthcheck_cleanup()
RETURNS void
LANGUAGE sql
AS $function$
DELETE FROM public.vault_healthcheck_log WHERE checked_at < now() - interval '30 days';
$function$;

COMMENT ON FUNCTION public.fn_vault_healthcheck_cleanup() IS
'Onda 9.1 — retenção: deleta logs com mais de 30 dias.';

-- Hardening (least privilege; função é SECURITY INVOKER, mas restringimos surface):
-- DELETE em log — apenas service_role pode disparar.
REVOKE ALL ON FUNCTION public.fn_vault_healthcheck_cleanup() FROM public, anon;
GRANT EXECUTE ON FUNCTION public.fn_vault_healthcheck_cleanup() TO service_role;

-- ----------------------------------------------------------------------------
-- 5) View: v_vault_health — top 20 últimas leituras com idade calculada
-- ----------------------------------------------------------------------------
CREATE OR REPLACE VIEW public.v_vault_health AS
Comment thread
adm01-debug marked this conversation as resolved.
SELECT
l.checked_at,
l.status,
l.ok_count,
l.fail_count,
l.defer_count,
l.failed_names,
age(now(), l.checked_at) AS age
FROM public.vault_healthcheck_log l
ORDER BY l.id DESC
LIMIT 20;

COMMENT ON VIEW public.v_vault_health IS
'Onda 9.1 — atalho de leitura: top 20 últimas leituras do healthcheck.';

-- ----------------------------------------------------------------------------
-- 6) Cron jobs (idempotentes via DO...EXCEPTION)
-- ----------------------------------------------------------------------------
-- 6a) vault_healthcheck — a cada 15min
DO $$
BEGIN
PERFORM cron.unschedule('vault_healthcheck')
WHERE EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'vault_healthcheck');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

SELECT cron.schedule(
'vault_healthcheck',
'*/15 * * * *',
$$ SELECT public.fn_vault_healthcheck_run(); $$
);
Comment thread
adm01-debug marked this conversation as resolved.

-- 6b) vault_healthcheck_cleanup — diário às 4am UTC
DO $$
BEGIN
PERFORM cron.unschedule('vault_healthcheck_cleanup')
WHERE EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'vault_healthcheck_cleanup');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

SELECT cron.schedule(
'vault_healthcheck_cleanup',
'0 4 * * *',
$$ SELECT public.fn_vault_healthcheck_cleanup(); $$
);

-- ----------------------------------------------------------------------------
-- 7) Smoke test (não falha se vault tiver DEFER — só documenta estado)
-- ----------------------------------------------------------------------------
DO $$
DECLARE
v_result jsonb;
BEGIN
v_result := public.fn_vault_healthcheck();
RAISE NOTICE 'Onda 9.1 smoke test: %', v_result;
Comment thread
adm01-debug marked this conversation as resolved.
END $$;
Loading