7.6 KiB
Impl: OPS52-media-guard — Guard de intenção explícita para escrita em bucket de produção
Status: aprovado (gate humano 2026-08-18) Atualizado em: 2026-08-18 Issue: #37 (OPS52-media-guard) Intenção: body da Issue #37 (spec — sem plano de intenção separado; achado do /simplify da #10) Appetite restante: pequeno (chore P3 — guard fail-closed de ~10 linhas + docs)
Leitura da intenção
- Outcome: o modo reconcile (padrão) do
pnpm media:recoversó escreve no bucket com intenção explícita —MEDIA_RECOVER_CONFIRM=1— e recusa com mensagem clara sem ela;--dry-rune--verifyseguem desprotegidos (não escrevem). O risco fechado: um worktree que receba credenciais S3_* de prod (cópia all-or-nothing do main) rodando reconcile contra rows locais (db:pullinclui media) escreveria covers no bucket de prod — hoje só o echo do alvo torna visível, não impede. - O que NÃO negociar: fail-closed (sem flag → sem escrita, em qualquer cenário de DB/alvo); dry-run/verify sem fricção nova; o runbook do homeserver continua sendo o contrato de execução; zero mudança de comportamento do reconcile além do novo gate.
- O que reavaliar: a forma da confirmação — a Issue propõe
"ex.:
MEDIA_RECOVER_CONFIRM=1ou prompt interativo". Prompt interativo é pior para este repo: o runbook roda via SSH no homeserver (potencialmente sem TTY), e o padrão de guard do repo é env flag (ALLOW_REMOTE_DB,isRemoteDbOverrideSet). Env flag vence (ver Opções).
Abordagem recomendada
flowchart LR
A[pnpm media:recover] -->|parse args| B{mode == reconcile?}
B -->|não dry-run/verify| C[segue desprotegido — zero escrita]
B -->|sim| D{MEDIA_RECOVER_CONFIRM true/1?}
D -->|não| E[die com o comando correto — sem S3 client, sem DB]
D -->|sim| F[guards existentes + echo do alvo + reconcile]
Opções consideradas:
- A — Env flag
MEDIA_RECOVER_CONFIRM(truthytrue|1) obrigatória no reconcile, com helper genéricoisTruthyEnvemscripts/lib/cli.mjseisRemoteDbOverrideSetdelegando para ele (sem twin). Recomendada. - B — Prompt interativo (
readline) antes do reconcile. - C — Flag CLI
--confirmno parsing de args.
Recomendação: A — paridade total com o guard de DB existente
(ALLOW_REMOTE_DB=true|1): funcionamento em runbook não-interativo (SSH,
set -a; source teqo-1313.env), sem fricção para dry-run/verify, e o parse de
args do script continua restrito aos modos mutuamente exclusivos
(--dry-run/--verify).
Rejeitadas: B (trava/falha sem TTY — o runbook roda por SSH no homeserver;
mais código; fora do padrão de guards do repo); C (mistura conceito de modo
com flag de confirmação; env var é o padrão e já é usada no runbook — o
comando set -a; source injeta as duas flags juntas).
Componentes / mudanças
isTruthyEnv(scripts/lib/cli.mjs, novo export puro):value === 'true' || value === '1'— exatamente a semântica atual deisRemoteDbOverrideSet(sem trim, sem lowercase:TRUE/yescontinuam recusados — não relaxar guard existente na refatoração).isRemoteDbOverrideSetpassa a delegar (isTruthyEnv(process.env[ALLOW_REMOTE_DB_FLAG])) — comportamento idêntico.scripts/recover-media.mjs: constanteMEDIA_RECOVER_CONFIRM; imediatamente após o parsing de modo (antes deassertLocalDatabase,resolveS3StorageEnve de qualquer rede):if (mode === 'reconcile' && !isTruthyEnv(process.env[MEDIA_RECOVER_CONFIRM])) die(...)— mensagem pt-BR com o comando correto (MEDIA_RECOVER_CONFIRM=1 pnpm media:recover) e a alternativa--dry-run. Bloco de comentário do header (safety model + modes) atualizado para documentar o guard. O gate vale para reconcile com QUALQUER alvo de DB (local ou remoto) — o risco é a escrita no bucket, não a origem dos dados.tests/unit/cliEnvFlags.unit.spec.ts(novo):isTruthyEnv(true/1passam;TRUE,yes,0, vazio, ausente recusam) e delegação deisRemoteDbOverrideSetpreservando o comportamento (mesmo casoALLOW_REMOTE_DB=TRUErecusado).- Docs: runbook do
docs/plans/ops52-media-recuperar-arquivos-impl.md(passo 3 do runbook ganhaMEDIA_RECOVER_CONFIRM=1+ nota do guard no cabeçalho do passo 2/3); nota curta no AGENTS.md (parágrafo da media —reconcile exige MEDIA_RECOVER_CONFIRM=1); entradadocs/changelog/2026-08-18-ops52-media-guard.md+pnpm changelog:build. - Migration: nenhuma. Access/Consent: intocados. UI: N/A.
package.json: sem mudança (a flag é env, não arg do script).
Fases verificáveis
- Helper + testes —
isTruthyEnvemcli.mjs+ refactor deisRemoteDbOverrideSet+tests/unit/cliEnvFlags.unit.spec.ts; rodarpnpm test:unit. - Guard no script —
recover-media.mjs(gate + header doc); conferir comportamento:pnpm media:recoversem flag → exit 1 com a mensagem (com S3_* ausente local o guard do confirm dispara antes do die de S3 — ordem verificada);--dry-run/--verifyseguem sem exigência. - Docs + changelog — runbook (passo 3 + nota), AGENTS.md,
docs/changelog/2026-08-18-ops52-media-guard.md,pnpm changelog:build+pnpm changelog:check. - Gates finais —
pnpm gate:fast,pnpm format:check,pnpm exec knip,pnpm check:cycles,pnpm test(unit+int),pnpm buildlocal.
Rabbit holes / Não escopo (engenharia)
- Testar o reconcile ponta a ponta — impossível por contrato (sem S3_* e
sem DB prod nesta máquina); a prova é o runbook do homeserver (padrão da
OPS52 fase 3). O que é testável (semântica do flag, preservação do
ALLOW_REMOTE_DB) é unit-testado. - Prompt interativo / TTY detection — rejeitado (Opção B).
- Guard para
--dry-run— não: não escreve; o Issue pede explicitamente mantê-los desprotegidos. - Renomear
ALLOW_REMOTE_DBou mexer em outros scripts — fora de escopo; o refactor deisRemoteDbOverrideSeté só delegação (uma ortografia do truthy-env, sem mudança de aceite). - Adiado com gatilho: banir re-escritas
=== '1'/=== 'true'de env-read nocodebaseConventions— quando um 4º site aparecer ou no próximo refactor dos 3 existentes (verdocs/plans/ops60-seed-posts-guard-escrita-s3.md, seção Explicitamente fora). - Registrado no simplify (OPS60, #49):
db:seed:postsescreve no bucket via adapter S3 sem flag de intenção — mesma classe de risco; Issue nova comdepends: [37].
Riscos e mitigação
- R1 — Refatorar
isRemoteDbOverrideSetrelaxa o guard de DB. Mitigação:isTruthyEnvcom a semântica EXATA atual (sem trim/lowercase) e teste de comportamento que pin o casoTRUErecusado. - R2 — Runbook desatualizado manda rodar reconcile sem a flag e o guard recusa (fricção na próxima execução de prod). Mitigação: runbook editado no mesmo PR; mensagem do die exibe o comando correto.
- R3 — Automação futura (CI/homeserver) esquece a flag. Mitigação: mensagem de erro autoexplicativa + dry-run disponível como pre-flight; o contrato é do runbook.
Aceite de engenharia
- Aceite de produto da intenção coberto: reconcile recusa sem
MEDIA_RECOVER_CONFIRM=1(fail-closed); dry-run/verify desprotegidos - Invariantes AGENTS/engineering-standards: identificadores em inglês;
guard reusado do dono (
cli.mjs) sem twin; zero migration/access/Consent - Testes de domínio:
isTruthyEnv+ preservação deisRemoteDbOverrideSet(unit); gates da checklist do AGENTS rodados - Docs: runbook atualizado (comando real com a flag) + nota AGENTS.md + changelog