Skip to content

v4.2 backlog: 3 hardening follow-ups from v4.1.0-rc.1 adversarial review #2

@fernandoxavier02

Description

@fernandoxavier02

Contexto

Durante o release de v4.1.0-rc.1 (resolve NI-3/NI-4/NI-5 — ver CHANGELOG), três items foram identificados pelo reviewer adversarial como non-blocking mas reais e diferidos para v4.2.

Cada um é endereçável independentemente. Recomendo executar na ordem listada abaixo (menor → maior complexidade).


Item 1 — Pruning de gate-decisions.jsonl

Sintoma: gate-decisions.jsonl cresce monotonicamente ao longo da vida de um projeto. Toda invocação de pipeline adiciona entradas (EXEC_WINDOW_OPEN, EXEC_WINDOW_CLOSE, outros gates). Não há política de limpeza.

Impacto:

  • O(N) scan linear em findPairingEntry (.claude/hooks/edit-guard-hook.cjs) a cada Edit/Write hook event.
  • Em projetos com 100+ pipeline runs, a latência passa a ser perceptível.
  • Amplifica o risco de stale-OPEN reuse (apesar da mitigação intermediate-CLOSE do v4.1 — a janela de +60s mais ainda existe).

Proposta:

  • Rotação por tamanho (ex: quando .jsonl > 10MB, mover para gate-decisions.{YYYY-MM}.archive.jsonl e iniciar novo).
  • OU truncation por idade (entradas >30 dias vão para archive).
  • findPairingEntry lê apenas o arquivo ativo (não o archive) — reduz scan mas mantém forensics.

Tests: adicionar contract test validando que pairing ainda funciona após rotação; e benchmark smoke que mede latência antes/depois.

Files: .claude/hooks/edit-guard-hook.cjs, novo hook Stop que dispara pruning (ou cron), tests em __tests__/.


Item 2 — buildBlockMessage com hint de pairing

Sintoma: quando getActiveExecWindow rejeita uma janela por ausência de pairing (ou qualquer motivo silencioso), o edit-guard-hook retorna o block message genérico. Controllers usando o path raw Write (não os helpers openExecWindow) não sabem que o motivo foi pairing — têm que inspecionar stderr.

Impacto: UX pobre para debugging. Hook é silencioso sobre o motivo real.

Proposta:

  • getActiveExecWindow retorna tupla {window, rejection_reason?} em vez de apenas window | null.
  • shouldBlock usa rejection_reason para enriquecer buildBlockMessage.
  • Exemplo de mensagem enriquecida:

    "Pipeline session is active. Your exec-window was found BUT rejected: missing paired EXEC_WINDOW_OPEN audit entry. If using raw Write path, also append an audit line to .pipeline/docs/Pre-*/*/gate-decisions.jsonl. Or call openExecWindow() helper which handles both."

Tests: adicionar tests por cada rejection reason verificando que a mensagem contém a hint específica.

Files: .claude/hooks/edit-guard-hook.cjs, tests em __tests__/edit-guard-hook.test.cjs.


Item 3 — Bloquear NaN/Infinity no opts.ttl_minutes explicitamente

Sintoma: hoje:

  • Read side: getActiveExecWindow calcula declaredTtl = expires_at - opened_at. Se qualquer um for NaN, o resultado é NaN, que não é > 0 nem <= 60min — rejeitado por acidente via short-circuit. Funciona mas é fragilidade de linguagem.
  • Write side (helper): openExecWindow hoje valida Number.isFinite(ttlMinutes) && ttlMinutes > 0 && ttlMinutes <= 60. Já rejeita NaN explicitamente.
  • Write side (raw Write controller): nenhum check — um controller pode escrever .exec-window com expires_at: NaN. Read side rejeita silentemente.

Impacto: defense-in-depth ausente no read side. Inconsistência entre helper (strict) e raw-Write (leniente-por-acidente).

Proposta:

  • Adicionar Number.isFinite() check explícito em getActiveExecWindow para opened_at e expires_at antes de calcular declaredTtl.
  • Stderr log se detectado: edit-guard: skipping window with non-finite opened_at/expires_at.
  • Test que escreve window com opened_at: NaN e valida rejeição.

Files: .claude/hooks/edit-guard-hook.cjs (~5 linhas), 1 novo test.


Estimativa de esforço

  • Item 1 (pruning): MÉDIA — precisa definir política (tamanho vs idade), implementar rotação, testar migração. ~2-3h.
  • Item 2 (hint message): SIMPLES — refactor de 1 função + enriquecer testes. ~30min.
  • Item 3 (NaN finite check): TRIVIAL — 5 linhas + 1 test. ~10min.

Sugiro fazer Item 3 primeiro (cheap win), depois Item 2, depois Item 1 (maior design decision).

Critérios de aceite para v4.2.0-rc.1

  • Item 1 implementado + test de rotação + benchmark smoke
  • Item 2 implementado + tests por rejection reason + docs atualizados
  • Item 3 implementado + test NaN
  • Adversarial review passes CLEAN (max 1 fix pass)
  • CHANGELOG entry para v4.2
  • Tag v4.2.0-rc.1 + push + claude plugin update verifica

Referência: v4.1.0-rc.1 CHANGELOG seção "Known limitations remaining (deferred to v4.2)".

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions