Pular para conteúdo

Decisões — dev-memory

ARQUIVO HISTÓRICO / LEGADO. Preservado para memória, auditoria e contexto de decisões. Não usar como documentação operacional atual.

Decisões — Time Dev

(append-only — decisões relevantes ficam aqui, mais recente em cima)


2026-07-07 — Gate arquitetural: Automação do pacote pós-kickoff CS (8 Epics, projeto ff96e3e2)

Contexto: CS executou manualmente, com envio real a cliente pagante (OP Odontopenha), o pacote pós-validação de prompt (manual + roteiro de testes + formulário feedback + envio email/WhatsApp + link agendamento + credenciais + registro CRM). Objetivo: automatizar dentro do wizard /ativar-cliente. Gate Vitor antes da Amélia executar (Modo A, branch única feature/automacao-pos-kickoff-cs).

Verificação do estado real (não confiei nas premissas do brief): - Reais na main: _shared/email_templates.py (render/send, canais email+whatsapp), _shared/evo_client.py, _shared/cliente_registry.py, templates 05a-validacao-prompt (email+whatsapp) já commitados. - tally_builder.py NÃO está em _shared/ — vive em ~/.claude/scripts/tally_builder.py (script global, não versionado no framework, não importável por worker). - merge_template.py (DEV-1227) só na branch feat/dev-1227, contaminada com trabalho do motor-deploy (Dockerfile, session_lock, motor_run, new_isolated_session). Não é PR limpo.

Decisões:

  1. Orquestrar wrappers existentes — NÃO construir engines novas. Aprovado. O trabalho é encadear peças prontas, não reinventar. Rejeitado qualquer "engine" nova de email/tally/whatsapp.

  2. Lar da orquestração = módulo-biblioteca times/cs/workers/pos_kickoff/ (pacote), NÃO dentro de /ativar-cliente nem StandingOrder cron. É invocado interativamente pelo wizard, não por cron → não é StandingOrder. O wizard chama uma fachada fina; a lógica fica em funções-passo idempotentes, testáveis fora do wizard. Regra 5 do Time (Modo B proibido) já se aplica porque os Epics compartilham esse módulo → branch única obrigatória (confirma Modo A).

  3. Contrato entre Epics = 1 dataclass PosKickoffContext (dados) + assinatura de cada função-passo (função). Contexto carrega tenant_id, slug, contato, URLs (form Tally, docs Quartz, agendamento Cal.com, credenciais). Cada Epic entrega uma função-passo (ctx) -> ctx'; DEV-1219 apenas fia no wizard.

  4. Idempotência + resumabilidade obrigatórias (precedente: wizard/demo já foi interrompido no meio). Checkpoint por cliente em times/cs/state/pos-kickoff/<slug>.json (estado de framework, versionado como STATE.md). Cada passo: checa estado → skip se feito → executa → grava. Sem infra nova.

  5. tally_builder promovido a _shared/tally_builder.py consumindo token via _shared.secrets (SECRETS-PATTERN) — nunca op subprocess direto. Deixa de ser script solto e vira dependência importável.

  6. DEV-1227 rescopado + saneado: (a) entrega muda de PDF/HTML → publicação Obsidian/Quartz (Mermaid renderiza nativo, validado); (b) extrair da branch contaminada só _shared/merge_template.py + _shared/test_merge_template.py + times/cs/foundation/templates-documentos/* via cherry-pick para a feature branch limpa — o resto de feat/dev-1227 (motor-deploy) NÃO entra.

  7. Rebaixamentos: DEV-1237 → story/chore (só wrap do tally_builder + criar_form_feedback(ctx); 3 stories over-decompostas). DEV-1241 permanece Epic mas simplificado (orquestração pura de wrappers prontos; 5 stories é excesso — consolidar). Os demais (1232/1247/1252/1258) seguem como Epic.

  8. Gates duros não-negociáveis: (a) DEV-1241 (envio) passa por cliente_registry antes de qualquer send — anti-envio-a-lead; (b) qualquer chamada a cadencia-cli recarrega token via op a cada invocação (gotcha DEV-1164 — env stale 401); (c) DEV-1232 se tocar migration/schema → reviews críticos completos (§6), não é trivial.

Ordem de execução aprovada: 1237 → 1227(rescopado) → 1241 → 1232 → 1247 → 1252 → 1258 → 1219 (mantida a proposta; 1232 é backbone dos blocos de fase seguintes, fica antes de 1247/1252/1258 ✓).

Reviews: cada Epic ao fechar → /openrouter-review; feature consolidada antes de merge → /claude-review + gate Vitor; DEV-1232 se schema → cascata crítica. Merge em main só com autorização textual do Felipe (abre PR + notifica).

Quem decidiu: Vitor (gate arquitetural) + Felipe (Modo A autorizado).


2026-07-07 — DEV-1213: max_rows do PostgREST sobrepõe .limit() do client — regra vale pra qualquer produto

Resumo: Kanban de Oportunidades do Cadencia perdia opportunities silenciosamente (tenant com 1655 linhas, cap real do PostgREST em max_rows=1000, .limit(5000) do client não tem efeito nenhum acima desse cap). Fix: paginação real com .range() em loop. Detalhe técnico completo em times/dev/vitor/memory/decisions.md (2026-07-07).

Regra pra qualquer squad de produto: antes de escrever .select(...).limit(N) numa tabela que cresce por tenant (contacts, opportunities, activities, content_ideas etc.), checar se N pode superar 1000 — se sim, paginar com .range(), nunca assumir que .limit() alto resolve.

Quem decidiu: Vitor + Felipe.

2026-07-07 — Fila do Motor separada (own:motor) da fila de bug reativo (own:agente) — DEV-1218

Contexto: ao decidir apontar o motor pra um backlog real (Central de Observabilidade), a leitura da doc revelou que o motor consumia a mesma label own:agente que o autofix_worker (CAD-689) — o executor que a Central já usa pra bug de código (health_check fix:issueown:agente → autofix abre PR). Dois executores concorrentes na mesma fila. Não era "bug vs feature": era colisão de fila.

Decisão (Felipe, opção B — coexistir, não unificar): duas filas, dois executores, dois propósitos. - own:agenteautofix_worker (plantão de bug reativo, /15min VPS Dev) — intacto. - own:motorMotor (trabalho planejado/proativo — "issues chatas que tomam tempo e não geram valor") — nova label workspace (668bc0c4-2c88-41b4-8bed-c5b815ca71a2).

Rationale (dele): analogia de dois times — um cuida de bug, outro faz issue chata. Fila única faria trabalho relevante esperar atrás de uma fila longa; duas filas é mais eficiente. Rejeitado unificar (opção A) mesmo o Motor sendo tecnicamente a generalização do autofix_worker.

Implementação: motor_select.py usa own:motor em fila/candidatos/enqueue; auto-enqueue ganha denylist (tipo:bug → autofix, own:agente/own:felipe/own:luiz/aguardando-felipe → outro dono) — mas eligible_queue NÃO filtra a denylist: curadoria manual pode aplicar own:motor até num bug (override intencional). A skill /planejar-fila-motor (DEV-1215, já na main pelo #36) foi convertida pra own:motor. Salvaguarda de overlap: linear_claims impede dois donos ativos. Docs: MOTOR-AUTONOMO.md + health_check/README.md.

Gotcha da sessão: meu 1º PR (#37) partiu de uma main antiga (pré-#35/#36/#31) e conflitou — a DEV-1215 (fila manual + skill) já tinha sido mergeada nesse meio-tempo, em own:agente. Refeito sobre a main atual, absorvendo a fila manual e convertendo skill+manual-queue pra own:motor. Lição: em worktree de sessão longa, conferir se a main avançou antes de assumir o PR mergeável.

Multi-repo (clone sob demanda no container, pra o motor rodar em cadencia-app etc.): Felipe quer, mas é issue de deploy à parte — não entra aqui.

Quem decidiu: Felipe (guiado, com a doc da Central lida antes — a leitura mudou o diagnóstico de "bug/feature" pra "colisão de fila").


2026-07-06 — Deploy do Motor Autônomo: 5 decisões de arquitetura (VPS Dev, padrão framework)

Contexto: motor rodava local no Windows (teste noturno), abrindo consoles e comendo recursos da máquina do Felipe. Decidido tirar do local e dar um lar adequado. A conversa passou por isolamento (user/container), capacidade (VPS Dev era KVM1 saturada), e como integrar ao padrão de deploy da PD sem virar exceção. Antes de extrair o motor pra repo próprio, Felipe pediu relembrar por que ele mora no framework — e a razão invalidou a extração.

Decisões (5):

  1. Motor permanece em _core/ — NÃO extrair pra repo próprio. Razão: é fundação runtime-agnóstica (D5 do brainstorm ruflo — agente↔estado via CLI, operável por Claude/Codex/OpenCode/workers Master), reusa toda a plumbing (_core/runtime/*, hooks, MODEL-MAP, budget_guard, outcomes, linear_claims, squad_resolver) sob o invariante "diff zero em _core/", e o kill switch é a branch motor-state do próprio repo. Extrair quebraria os três. Rejeitada a proposta inicial de repo pd-motor.

  2. Deploy = padrão do FRAMEWORK, não de PRODUTO. A PD tem 2 padrões: produtos (repo próprio + Coolify on-commit) e framework (repo único + git pull + serviço). O motor é framework → container na VPS Dev + auto-git pull da main por ciclo + kill switch OFF. Rejeitado Coolify on-commit (torrente de commits rebuildaria à toa; motor commita em si mesmo → loop; _core/ é classe crítica → contornaria alçada). Auto-pull respeita a alçada naturalmente: o gate é no merge pra main, o pull só pega o aprovado.

  3. VPS Dev upgraded KVM1→KVM2 (2 vCPU / 8 GB). Motor roda em container com --cpus=1 --memory=2g — confina a 1 core, deixa o outro pro dev interativo do Felipe/Luiz. Causa da limitação de CPU da Hostinger era 1 vCPU saturado (load 7); resolvida pela capacidade. Upgrade preserva dados (resize, não reinstala — desde que não troque o SO).

  4. Fallback na VPS = claude → codex (ambos assinatura, logados no user felipe). opencode-local (Ollama/free) fica só pra execução local — a VPS Dev não tem GPU. opencode instalado lá mas dormente.

  5. Migrar Coolify da Master → PROJETO SEPARADO. Não misturar produção (Coolify orquestra 17 containers) com agente tool-use (motor) no mesmo box — viola a separação determinística/agente do SECURITY.md §1 (mesmo daemon Docker = motor alcançaria containers de produção). Master saturada é problema real, mas se resolve à parte, com brief próprio.

Próximo passo: PRD do mini-projeto de deploy (containerização + runtime VPS Dev + auto-pull + secrets/auth + cap de recursos + validação OFF), dentro do projeto "PD Framework V2.0 — Motor Autônomo 24/7".

Gotchas da sessão: pkill -f "device-auth" casa a própria conexão SSH inline → mata a si mesmo (exit 255); usar critério que não case o comando. SSH inline sem bash -lc não carrega PATH do nvm (claude/codex "command not found"). codex login --device-auth evita o callback localhost:1455 (que exigiria túnel SSH). Vault 1P Hostinger VPS existe na conta do op da VPS Dev (não no SA local do Felipe). Token GHL do Luiz exposto em texto claro no ps (arg de comando) — vale corrigir pra stdin/env.

Quem decidiu: Felipe (todas guiadas, com confronto adversarial em cada opção).


2026-07-06 — Trigger do corpus (DEV-1164) vive no C6 close_session, não num git-push hook

Contexto: a issue pedia "hook no git push" pra sincronizar o corpus, mas não existe hook de git push no framework (só Claude Code hooks). Precisava ser agnóstico de runtime (Felipe: "tem que ser lido por qualquer harness — Codex, OpenCode, não só Claude").

Decisão: o trigger dispara em _core/hooks/stop-session-branch.py (C6 close_session), o ponto de merge-pra-main onde os 3 runtimes convergem — Codex (stop.pyclose_session.pystop-session-branch.py) e OpenCode (lifecycle.tsclose_session.py→idem) delegam ao mesmo script. Um ponto de inserção cobre todos. Dispara destacado (não-bloqueante), best-effort.

Alternativas descartadas: .git/hooks/pre-push (não versionado, cada clone precisa setup; push é separado do merge local) · instrumentar cada skill (frágil).

Impacto: padrão pra qualquer capacidade que rode "ao consolidar trabalho" — inserir no C6 compartilhado, não num hook de runtime específico. Gotcha do PAT na memória reference-corpus-framework-supabase (env stale sombreia o mapa 1P → alias SUPABASE_HUBPD_PAT; sempre _shared.secrets, nunca op direto).

Quem decidiu: Felipe (requisito agnóstico) + análise da cadeia de delegação dos adapters.


2026-07-06 — Slack por squad (DEV-1165): consulta-primária → Claude Tag + MCP read-only; bridge adiado

Contexto: 1 canal Slack por time. Decisão em aberto: Claude Tag nativo (pronto, cego ao framework) vs bridge self-hosted (integrado, infra a manter). Felipe esclareceu: uso é consulta ("conversar com cada time como membros da empresa"), NÃO ação — coda via Termius, Slack pra codar é horrível.

Decisão: quando construir → Claude Tag nativo + MCP read-only do framework (STATE do squad + query no corpus DEV-1164 + lookup). Consulta read-only → some o buraco de segurança que rejeitava o Claude Tag (ação bypassando guards). Bridge só se surgir necessidade real de ação via Slack. Fica em backlog — uso indefinido; construir >1 semana sobre uso incerto viola análise-antes-de-codar.

Alternativas descartadas (por ora): bridge self-hosted agora (overkill) · Claude Tag puro sem MCP (cego aos squads).

Impacto: pré-requisito quando destravar = MCP read-only do framework (transporte-agnóstico). Detalhe no comentário da DEV-1165.

Quem decidiu: Felipe.


2026-07-04 — Sofia ganha skills Astryx para planejamento e bancada Storybook

Contexto: depois de rodar o Storybook do Astryx localmente, Felipe definiu que o Time Dev deve aproveitar componentes/bibliotecas prontas, mas adaptando com identidade Cadência em vez de copiar visual genérico.

Decisão: criar skills no Squad Sofia: /astryx-storybook para subir/abrir/testar componentes no Storybook e /astryx-planejar-ui para planejamento UX React/Next com Astryx-first. Storybook vira bancada visual da Sofia para mostrar componentes a Felipe durante planejamento.

Validação usada como base: pnpm install, pnpm -F @astryxdesign/core build, pnpm storybook em http://localhost:6006/ com HTTP 200. Gotchas Windows registrados nas skills.

Impacto: em próximas UIs Cadência, Sofia deve primeiro consultar Astryx para componentes/templates e depois especificar tema/ajuste de identidade Cadência antes de handoff para Vitor/Amélia.

Quem decidiu: Felipe + Sofia/Vitor.


2026-07-04 — Astryx incorporado ao Time Dev como referência UX da Sofia

Contexto: Felipe forkou facebook/astryx em felipeluissalgueiro/astryx e pediu que o repo fosse clonado localmente e na VPS Dev, além de incorporar Astryx no modo de trabalho da Sofia para próximas UIs.

Decisão: astryx vira repo de referência do Time Dev para UX/UI React/Next, sob responsabilidade da Sofia. Local: C:/dev/astryx; VPS Dev user Felipe: /home/felipe/astryx; label operacional: repo:astryx em _core/REPO-MAP.md. Sofia aplica Astryx-first em novas UIs, com gate de compatibilidade por produto e sem migração automática de telas existentes.

Impacto: próximos planos de UI devem consultar times/dev/sofia/context/astryx-ui-standard.md antes de escolher componentes/templates. Vitor ainda decide stack; Amélia/Luiz implementam; Sofia define fluxo, consistência e critério visual.

Quem decidiu: Felipe + Sofia/Vitor.


2026-07-03 — Auto-cost em delta por fonte + estado canônico no claims + decay fiado no Stop (validação viva)

Contexto: Validação e2e do Motor v2 nos 3 harnesses (via codex exec e opencode run headless) expôs 6 bugs, 3 deles com decisão de design embutida.

Decisão: (1) outcomes.py --auto-cost grava delta desde o último evento da mesma fonte — o cumulativo fica em cost.session_cumulative pra encadear; o cost_capture continua lendo cumulativos (fonte não muda), a conversão é responsabilidade do append. (2) linear_claims._state_id prefere nome canônico dentro do tipo (started→"In Progress", unstarted→"Todo") — um team pode ter vários estados do mesmo tipo e a ordem da API não é confiável. (3) apply_decay fiado no Stop de Claude e Codex via _core/hooks/stop-memory-decay.py (best-effort, idempotente por dia via last_decay_at) — mecanismo sem gatilho não é ciclo de vida.

Alternativas consideradas: deltificar no cost_capture (rejeitado: capture é leitura pura, sem estado); corrigir report em vez do dado (rejeitado: dado errado no log contamina qualquer consumidor futuro); decay via cron VPS (rejeitado por ora: Stop cobre o caso local sem infra nova).

Impacto: relatórios de custo por issue ficam honestos daqui pra frente (histórico migrado com backup); claim nunca mais cai em In Review; memória decai sozinha sem intervenção. Gotcha registrado: commit com Refs DEV-X faz o Linear auto-mover a issue via integração GitHub — transições "de graça" que o motor 24/7 pode aproveitar (ou precisa prever).

Quem decidiu: Felipe (validação autorizada) + agente.


2026-07-03 — Outcomes v1 como sinal estruturado append-only do Motor Autônomo (DEV-1103)

Contexto: O projeto PD Framework v2.0 / Motor Autônomo precisa de sinal confiável antes de cost tracker, budget guard, model-map e loop 24/7. O estudo ruflo mostrou que aprendizado de agente depende mais da qualidade do sinal de sucesso do que de mecanismo sofisticado.

Decisão: Criar o contrato pd.outcome.v1 em _core/OUTCOMES.md e helper _core/outcomes.py, com eventos JSONL append-only em .pd/outcomes/outcomes.jsonl (ignorado pelo git). O schema captura tarefa, squad, executor, runtime/harness/modelo, resultado, custo quando houver, evidências e intervenção humana.

Alternativas consideradas: Banco desde já (rejeitado por overengineering e sem frota concorrente); eventos versionados em git (rejeitado por poluir histórico com runtime state); dependência externa de JSON Schema (rejeitada para manter core determinístico e portável).

Impacto: DEV-1104 pode preencher cost sem mudar o contrato; DEV-1105/1106 consomem o mesmo sinal para budget/report; fases futuras do motor aprendem com outcomes estruturados em vez de inferir de texto solto.

Quem decidiu: Felipe + Vitor.


2026-07-02 — Adapter Codex #3: e2e FINAL PASSOU + 2 causas raiz do fail-open (Vitor)

Teste fim-a-fim que faltava, rodado em clone isolado (git cloneC:\tmp, origin = bare dummy, um agente por vez — resolve o gap de método de 2026-07-01). Resultado: fluxo contratual completo validado sob codex real (0.142.5)apply_patch na main → hook: PreToolUse Blocked → agente cria session/* com escalonamento → patch na branch → Stop auto-commita e mergeia na main. Antes de passar, o e2e reprovou e expôs 2 causas raiz que o smoke (subprocess direto) não pega:

1. Aspas no command do hooks.json quebram o spawn no Windows. O Codex spawna hooks via cmd.exe /C "<command>" (fonte: codex-rs/hooks/src/engine/command_runner.rs); o Rust escapa aspas internas como \", que o cmd não parseia → hook morre no spawn → Failedfail-open silencioso (nenhum hook rodava de verdade). Fix: command sem aspas (C:\Python314\python.exe adapters/codex/pretooluse.py — path sem espaços).

2. Exit 2 + stderr NÃO bloqueia na 0.142.5 Windows (contradiz a doc). Mesmo com o hook rodando e retornando exit 2 + stderr, o Codex marca Failed e executa a tool. O único bloqueio que funciona é o JSON hookSpecificOutput.permissionDecision:"deny" no stdout com exit 0 (hook: PreToolUse Blocked, isolado com hooks mínimos A/B). Fix: deny() do _common.py agora emite o JSON deny (reason também no stderr como diagnóstico); hardening de exceção idem — fail-closed via JSON, nunca exit 1/2.

Bugs colaterais corrigidos: stop-session-branch.py não checava o rc do auto-commit — commit falho (ex.: clone sem git identity) mergeava branch vazia e reportava ✅ falso; agora aborta com exit 2 e mensagem. run_smoke.py crashava no print do resumo em console cp1252 (reconfigure utf-8).

Gotchas de teste (pra próxima vez): hook precisa drenar o stdin (sair antes = "failed to write hook stdin" → Failed/fail-open); codex exec com stdin-pipe aberto pende esperando EOF (usar `