sdd-layer 0.25.3

Spec-Driven Development CLI and agent harness
# 05 — Refinement: SDD Bot do Slack

worktree: SDDL-2
branch-raiz: main

**Orquestração:** SDDL-2 — SDD Bot do Slack
**Data:** 2026-06-24
**Agente:** refinement (Tech Lead / Grooming)
**Estado:** pronto para execução — aguardando CP-3
**Artifact store:** `.worktree/SDDL-2/docs/sddl-2/`
**Risco:** ALTO

---

## Rastreabilidade

| Campo | Valor |
|---|---|
| Origem (Tasks) | `.worktree/SDDL-2/docs/sddl-2/04-tasks.md` |
| Tech Spec | `.worktree/SDDL-2/docs/sddl-2/03-techspec.md` — CP-2 aprovado |
| PRD | `.worktree/SDDL-2/docs/sddl-2/02-prd.md` — CP-1 aprovado |
| Requisitos cobertos | RF-01..RF-24, RNF-01..RNF-09, RN-01..RN-09, CA-01..CA-13, S-01..S-07 |
| Próxima etapa | Execução (CP-3 — aprovação humana antes de iniciar) |

---

## Resumo executivo

O SDD Bot do Slack hoje expõe múltiplos slash commands frágeis (`/sdd bot-criar`, `/sdd card-editar`, `/aprovado`) sem persistência local de estado ou controle de ownership. Esta feature unifica a interface num único `/bot` com cinco subcomandos, introduz SQLite como fonte de verdade de ownership e estado de cards, e adiciona um gate de bloqueio no CLI Rust que aborta a orquestração quando o último comentário Jira de um card for exatamente `/bloqueado`. O valor entregue é rastreabilidade completa de ownership no Slack, prevenção de orquestrações em cards bloqueados e desacoplamento do bot dos comandos `sdd install`/`sdd update`.

**Top-3 riscos:**
1. `better-sqlite3` binário nativo — `npm rebuild` obrigatório por plataforma (mitigado pelo instalador)
2. Exit code do gate Rust — confirmar que `?` em `anyhow::Result` produz saída com exit code != 0 (TASK-10)
3. `reqwest` ausente no `Cargo.toml` — confirmar antes de iniciar TASK-09 (+0,5h se ausente)

---

## Solução proposta

O daemon Node.js/TypeScript (`bot/`) recebe comandos via Socket Mode Bolt, um router central (`bot.ts`) faz dispatch assíncrono com `ack()` imediato e repassa para handlers dedicados (`create`, `update`, `aproved`, `list`, `view`, `help`). Toda mutação de estado passa por `CardRepository` (SQLite com `better-sqlite3`), que centraliza ownership via `assertOwner`. O CLI Rust recebe uma nova função `block_gate::ensure_not_blocked` inserida em `run_orchestration` após `assert_prerequisite_artifacts` — exclusivamente quando há `card_id`; texto livre não consulta Jira. O instalador `sdd bot install` é o único ponto de provisionamento do bot; `sdd install`/`sdd update` deixam de interagir com o bot.

Para detalhes de contratos de módulo, DDL do banco e sequência de mensagens Slack/Jira, ver Tech Spec §6, §7, §9 e §16.

---

## Pontos de observação

- **`better-sqlite3` nativo:** binário compilado por plataforma — `npm rebuild` obrigatório no `sdd bot install`. Falha silenciosa em ambiente com Node diferente do build pode quebrar toda a camada de persistência.
- **Gate case-sensitive (AD-5 confirmado CP-2):** `is_blocked_marker` compara `text.trim() == "/bloqueado"` exato. `/BLOQUEADO` e `/BloquEado` **não** ativam o gate — testes devem cobrir esses casos explicitamente. Qualquer normalização Unicode introduzida sem deliberação quebra o contrato.
- **S-02 — `assertOwner` ausente em `card-editar.ts` atual:** é um bug de segurança conhecido. TASK-06 corrige; nenhuma task anterior a ela deve invocar `update` sem ownership.
- **Exit code do gate Rust:** `?` em `anyhow::Result` na main pode não produzir exit code 2. TASK-10 deve verificar e documentar — o bot TypeScript depende de exit code != 0 para exibir ephemeral de bloqueio correto.
- **`reqwest` no `Cargo.toml` (R-4):** confirmar presença antes de iniciar TASK-09; ausência exige adição com feature `rustls-tls` + `json` + `blocking`.
- **Regressão RF-24:** `sdd install`, `sdd update` e `sdd doctor` não podem apresentar prompts do bot após TASK-12. Checklist de 6 itens deve ser executado e evidenciado antes do merge.
- **Redação de secrets em logs (S-04):** `redact()` deve sanitizar `xox*` e `Basic *` antes de qualquer `logger.*`. CI deve ter grep para garantir ausência de `shell: true` em chamadas `execa`.
- **Paralelismo B ‖ C:** blocos B (TypeScript) e C (Rust) são independentes após A. Dois implementers podem trabalhar simultaneamente — não avançar para TASK-11 sem TASK-10 verde.
- **Arquivos legados:** `bot-criar.ts`, `card-editar.ts`, `aprovado.ts` não devem ser removidos até TASK-12. Remoção prematura quebra referências no index atual.

---

## Checklist macro de execução

- [ ] **Bloco A concluído:** `db/index.ts` e `CardRepository` implementados, `npm run build` verde, migration idempotente verificada.
- [ ] **Gate A → B/C:** `better-sqlite3` no `package.json`, WAL ativo, permissão 0600 documentada.
- [ ] **Bloco B concluído:** router `/bot`, cinco handlers e `channelKey` implementados, `npm run build` verde, `assertOwner` em todos os handlers mutadores.
- [ ] **Bloco C concluído:** `block_gate.rs` com unit tests passando, `ensure_not_blocked` integrado em `run_orchestration`, exit code documentado.
- [ ] **Gate B/C → D:** confirmar que blocos B e C estão verdes antes de iniciar instalador.
- [ ] **Bloco D concluído:** `sdd bot install` funcional, prompts de bot removidos de `sdd install`/`sdd update`, checklist RF-24 parcial verde.
- [ ] **Bloco E concluído:** `npm test` e `cargo test` verdes, `cargo audit` sem CVEs Critical/High, `gitleaks` sem detecções, checklist RF-24 completo com evidências, `evidence-e2e.md` criado.
- [ ] **CP-4 aprovado:** relatório de evidências revisado e aprovação humana antes do merge.

---

## Subtasks

| ID | Título | Bloco | Estimativa | Owner |
|---|---|---|---|---|
| TASK-01 | Camada DB: migration SQL + abertura SQLite | A | 2h | backend-implementer |
| TASK-02 | CardRepository: CRUD, ownership e stages | A | 3h | backend-implementer |
| TASK-03 | channelKey.ts: extração de chave do canal | B | 1h | backend-implementer |
| TASK-04 | Router `/bot` + reescrita de index.ts | B | 2h | backend-implementer |
| TASK-05 | Handler create.ts | B | 4h | backend-implementer |
| TASK-06 | Handler update.ts (assertOwner obrigatório) | B | 2h | backend-implementer |
| TASK-07 | Handler aproved.ts | B | 1h | backend-implementer |
| TASK-08 | Handlers list.ts, view.ts e help.ts | B | 2h | backend-implementer |
| TASK-09 | block_gate.rs: lógica pura e unit tests Rust | C | 3h | backend-implementer |
| TASK-10 | Integração block_gate em run_orchestration | C | 2h | backend-implementer |
| TASK-11 | Instalador sdd bot install + checagens | D | 3h | backend-implementer |
| TASK-12 | Limpeza de amarrações em install/update | D | 2h | backend-implementer |
| TASK-13 | Testes unitários TypeScript (bot + repo) | E | 4h | test-implementer |
| TASK-14 | Testes unitários e integração Rust (gate) | E | 3h | test-implementer |
| TASK-15 | Auditoria de segurança e evidências finais | E | 2h | test-implementer |

**Total: 36h | Caminho crítico: 31h**

---

## Definition of Done

- [ ] `npm run build` sem erros TypeScript em `bot/`
- [ ] `cargo build` e `cargo test` verdes sem regressão
- [ ] `npm test` verde com cobertura rastreável a CA-01..CA-13
- [ ] `assertOwner` presente antes de qualquer mutação em `update.ts`, `aproved.ts` e `view.ts`
- [ ] Gate `is_blocked_marker` com casos `/bloqueado` (true), `/BLOQUEADO` (false), `/BloquEado` (false), texto com espaço, `/aprovado`, vazio — todos passando
- [ ] `execa` invocado sem `shell: true` e com env restrito (`PATH`, `HOME`, `SDD_BOT_CONTEXT`)
- [ ] Secrets jamais aparecem em logs — `redact()` aplicado antes de qualquer `logger.*` que receba dados externos
- [ ] `sdd bot install` cria `sdd-bot.config.yaml` com permissão 0600 e executa `npm rebuild`
- [ ] `sdd install <proj>`, `sdd update` e `sdd doctor` sem prompts do bot — checklist RF-24 com evidências
- [ ] `cargo audit` sem CVEs Critical ou High (exceções documentadas)
- [ ] `gitleaks detect --source . --no-git` sem detecções
- [ ] `sdd-bot.config.yaml` e `sdd-bot.db` no `.gitignore` — verificado
- [ ] `docs/sddl-2/evidence-e2e.md` criado com saídas de todos os comandos de auditoria
- [ ] Arquivos legados (`bot-criar.ts`, `card-editar.ts`, `aprovado.ts`) removidos somente em TASK-12

---

## Flags e configurações

| Item | Valor | Onde |
|---|---|---|
| Feature flag | Nenhuma | Ativado por `sdd bot install` |
| `SLACK_BOT_TOKEN` | xoxb-... | `sdd-bot.config.yaml` (0600), nunca em código |
| `SLACK_APP_TOKEN` | xapp-... | `sdd-bot.config.yaml` (0600) |
| `JIRA_BASE_URL` / `JIRA_EMAIL` / `JIRA_API_TOKEN` | credenciais Jira | `sdd-bot.config.yaml` |
| `SDD_BOT_DB_PATH` | padrão `.sdd/bot/sdd-bot.db` | env opcional |
| `SDD_BOT_CONTEXT` | `'true'` | injetado pelo bot no `execa` env |