# 04 — Tasks: SDD Bot do Slack

worktree: SDDL-2
branch-raiz: main

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

---

## Rastreabilidade

| Campo | Valor |
|---|---|
| Origem (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 |
| Ideia | `.worktree/SDDL-2/docs/sddl-2/01-idea.md` |
| Requisitos cobertos | RF-01..RF-24, RNF-01..RNF-09, RN-01..RN-09, CA-01..CA-13, S-01..S-07 |
| Próximo artefato | `docs/sddl-2/05-refinement.md` (risco ALTO) ou execução com aprovação humana |
| Checkpoint | CP-3 — aprovação humana antes de iniciar execução |

---

## Épicos

| ID | Nome | Bloco | Tasks |
|---|---|---|---|
| EP-A | Camada de persistência SQLite | A | TASK-01, TASK-02 |
| EP-B | Comandos `/bot` no Slack | B | TASK-03, TASK-04, TASK-05, TASK-06, TASK-07, TASK-08 |
| EP-C | Gate `/bloqueado` no CLI Rust | C | TASK-09, TASK-10 |
| EP-D | Instalador e limpeza de amarrações | D | TASK-11, TASK-12 |
| EP-E | Testes, segurança e evidências | E | TASK-13, TASK-14, TASK-15 |

---

## Backlog — Tabela Resumo

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

**Total estimado: 36 horas**

---

## Dependências

- TASK-01: nenhuma.
- TASK-02: TASK-01 — `CardRepository` depende de `db/index.ts`.
- TASK-03: TASK-02 — depende dos tipos definidos em TASK-02 (`types.ts`).
- TASK-04: TASK-02 — router importa `CardRepository` e tipos.
- TASK-05: TASK-03, TASK-04 — usa `channelKey` e o router registrado.
- TASK-06: TASK-04 — usa `assertOwner` do repo e o router.
- TASK-07: TASK-04 — idem.
- TASK-08: TASK-04 — handlers `list`, `view`, `help` dependem do router.
- TASK-09: nenhuma — Rust puro, paralelizável com Bloco B.
- TASK-10: TASK-09 — integra `block_gate` em `run_orchestration`.
- TASK-11: TASK-01 (schema DB), TASK-10 (gate validado antes do instalador).
- TASK-12: TASK-11 — limpeza após instalador funcional.
- TASK-13: TASK-08 — cobre todos os handlers e o repo.
- TASK-14: TASK-10 — testa gate integrado.
- TASK-15: TASK-13, TASK-14, TASK-12 — evidências finais de todos os blocos.

---

## Ordem sugerida

**Fase 1 — Bloco A:**
1. TASK-01 — Camada DB: migration SQL + abertura SQLite
2. TASK-02 — CardRepository: CRUD, ownership e stages

**Fase 2 — Blocos B e C em paralelo (após A):**

Trilha B (TypeScript):
3. TASK-03 — channelKey.ts
4. TASK-04 — Router `/bot` + reescrita index.ts
5. TASK-05 — Handler create.ts
6. TASK-06 — Handler update.ts
7. TASK-07 — Handler aproved.ts
8. TASK-08 — Handlers list, view e help

Trilha C (Rust, em paralelo com trilha B):
3. TASK-09 — block_gate.rs: lógica e unit tests
4. TASK-10 — Integração block_gate em run_orchestration

**Fase 3 — Bloco D:**
9. TASK-11 — Instalador sdd bot install + checagens
10. TASK-12 — Limpeza de amarrações em install/update

**Fase 4 — Bloco E:**
11. TASK-13 — Testes unitários TypeScript
12. TASK-14 — Testes unitários e integração Rust
13. TASK-15 — Auditoria de segurança e evidências finais

---

## Critérios de Aceite por Task

- TASK-01: DB abre sem erro; migration idempotente; WAL ativo; permissão 0600.
- TASK-02: CRUD correto; `assertOwner` lança `AccessDeniedError`; UPSERT sem duplicata; `listByUser` respeita filtros de status e usuário.
- TASK-03: Extrai key de tópico ou descrição; regex case-insensitive; lança `ChannelKeyMissingError` com mensagem orientativa.
- TASK-04: `ack()` < 3s; dispatch assíncrono; subcomandos inválidos caem no help; `index.ts` sem `/sdd`.
- TASK-05: Card criado no Jira; Card ID validado pós-API (S-05); persistência SQLite; `/bloqueado` adicionado; `execa` sem shell com env restrito (S-06); streaming de etapas; conteúdo > 3000 chars como arquivo.
- TASK-06: `assertOwner` antes de qualquer mutação (S-02); Jira e SQLite atualizados somente após ownership confirmado.
- TASK-07: Comentário `/aprovado` no Jira; `status = aprovado` no SQLite; confirmação ephemeral.
- TASK-08: `list` filtra por user e status sem IA; `view` sem IA com ownership; `help` exibe texto exato do §7.3.
- TASK-09: `is_blocked_marker` correto nos 6 casos do §16; `ensure_not_blocked` retorna `Ok` sem config Jira; `pub mod block_gate` declarado.
- TASK-10: Gate chamado no ponto correto pós-`assert_prerequisite_artifacts`; exit code != 0 em bloqueio; texto livre não consulta Jira.
- TASK-11: Config criado com 0600; `.gitignore` verificado; PATH do `sdd` verificado; `npm rebuild` executado.
- TASK-12: `sdd install` sem prompt de bot; `sdd update` sem prompt de bot; `sdd doctor` com bot opcional; `cargo test` verde.
- TASK-13: `npm test` verde cobrindo CA-01..CA-13; testes explícitos de S-03, S-04, S-05, S-06.
- TASK-14: `cargo test` completo verde; 6 casos de `is_blocked_marker` documentados e passando.
- TASK-15: `cargo audit` sem CVEs críticas/High; `gitleaks` sem secrets; checklist RF-24 com todos os 6 itens verificados; relatório `evidence-e2e.md` criado.

---

## Definition of Done (geral)

- Código implementado no escopo exato da task — sem antecipação de tasks futuras.
- Testes/checks focados executados com saída registrada como evidência.
- Nenhuma mutação em arquivos fora do escopo declarado.
- Secrets nunca em código-fonte, logs ou mensagens Slack.
- Relatório Markdown produzido ao final de cada task contendo: resumo, arquivos alterados, comandos executados, evidências, decisões, riscos e pendências.

---

## Estratégia de Testes

**Unitário (TypeScript):**
- `CardRepository` contra banco SQLite `:memory:` com migration aplicada em `beforeEach`.
- `channelKey` com mock de `client.conversations.info` — 3 cenários.
- Handlers com mocks de `JiraProvider`, `SlackProvider`, `LLMProvider`, `CardRepository`, `execa`.
- `security.test.ts`: redação de secrets (`SECRET_RE`), env restrito no execa, validação de Card ID.

**Unitário (Rust):**
- `is_blocked_marker` com 6 casos inline em `#[cfg(test)]`.
- `ensure_not_blocked` com mock de `load_jira_config` retornando `None`.

**Integração/contrato:**
- `execa` invocado sem `shell: true` e com env limitado verificado em teste.
- `run_orchestration` com Card ID e `/bloqueado` → exit != 0.
- `run_orchestration` com texto livre → sem consulta Jira.

**Regressão (RF-24):**
- `sdd install <proj>` — sem prompt de bot.
- `sdd update` — sem prompt de bot.
- `sdd doctor` — bot como componente opcional.
- `sdd orchestrator <texto-livre>` — executa normalmente.
- `sdd orchestrator <CARD-ID>` com comentário `!= /bloqueado` — executa normalmente.
- `cargo test` completo — verde.

**Segurança:**
- `cargo audit` — zero CVEs críticas ou High.
- `gitleaks detect --source . --no-git` — zero secrets.
- Permissão 0600 verificada em `sdd-bot.config.yaml` e `sdd-bot.db` no boot.

---

## Checklist de Regressão RF-24 (pré-merge)

- [ ] `sdd install <projeto>` — não pergunta sobre bot
- [ ] `sdd update` — não pergunta sobre bot
- [ ] `sdd doctor` — bot listado como componente opcional
- [ ] `sdd orchestrator <texto-livre>` — executa normalmente, sem consultar Jira
- [ ] `sdd orchestrator <CARD-ID>` com último comentário != `/bloqueado` — executa normalmente
- [ ] `cargo test` completo — verde
- [ ] `cargo audit` — zero CVEs críticas
- [ ] `gitleaks` — zero secrets

---

## Prompts Agent

### TASK-01 — Camada DB: migration SQL + abertura SQLite

**Bloco:** A
**Dependências:** nenhuma
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Esta task cria a infraestrutura de persistência SQLite do bot. Toda a lógica de estado (ownership, status de cards, conteúdo de etapas) depende desta camada. O banco fica em `.sdd/bot/sdd-bot.db`; WAL deve ser habilitado desde a abertura; permissão 0600 deve ser forçada no arquivo do banco após criação (S-03).

O módulo `db/index.ts` abre o banco com `better-sqlite3`, aplica a migration `001_init.sql` (DDL completo da Tech Spec §6) somente se `PRAGMA user_version = 0`, e garante WAL ativo. A permissão 0600 é verificada com `statSync` ao abrir — se divergir, emite `logger.warn`.

**Arquivos prováveis:**
- `bot/src/db/index.ts` — CRIAR: abre banco, aplica migration, verifica 0600.
- `bot/src/db/migrations/001_init.sql` — CRIAR: DDL completo (cards + card_stages + trigger + índices + `PRAGMA user_version = 1`).
- `bot/package.json` — MODIFICAR: adicionar `better-sqlite3` ^11.x e `@types/better-sqlite3` se ausentes.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Abertura do banco pela primeira vez
  Dado que o arquivo sdd-bot.db não existe
  Quando openDatabase() é chamado
  Então o arquivo sdd-bot.db é criado no caminho configurado
  E a tabela cards existe com todos os campos do schema
  E a tabela card_stages existe com todos os campos do schema
  E PRAGMA user_version retorna 1
  E PRAGMA journal_mode retorna wal
  E a permissão do arquivo é 0600 (em POSIX)

Cenário: Migration idempotente em reabertura
  Dado que o banco já existe com user_version = 1
  Quando openDatabase() é chamado novamente
  Então nenhuma alteração de schema é feita
  E nenhum erro é lançado

Cenário: Permissão insegura emite aviso de segurança
  Dado que o arquivo sdd-bot.db existe com permissão 0644
  Quando openDatabase() é chamado
  Então um aviso de segurança é emitido no logger
  E o banco continua operacional
```

**Definition of Done:**
- `db/index.ts` exporta `openDatabase(): Database`.
- `001_init.sql` contém DDL exato da Tech Spec §6 incluindo trigger e `PRAGMA user_version = 1`.
- `better-sqlite3` e `@types/better-sqlite3` no `package.json`.
- Verificação de 0600 implementada com aviso no logger.
- Migration idempotente via `PRAGMA user_version`.

**Estratégia de testes:**
- Banco em diretório temporário (`os.tmpdir()`), abrir duas vezes, verificar `user_version`, `journal_mode` e tabelas via `PRAGMA table_info`.
- Permissão verificada após criação (POSIX).

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-01` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §4, §6 e §10 (S-03) para obter o DDL completo, o caminho do banco e os requisitos de permissão. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\package.json` para verificar se `better-sqlite3` já está declarado. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\db\migrations\001_init.sql` com o DDL exato da Tech Spec §6. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\db\index.ts` que: (1) resolve o caminho do banco a partir de env `SDD_BOT_DB_PATH` ou padrão `.sdd/bot/sdd-bot.db` relativo à raiz; (2) abre com `better-sqlite3`; (3) aplica migration somente se `PRAGMA user_version = 0`; (4) executa `PRAGMA journal_mode = WAL`; (5) verifica permissão 0600 com `statSync` e emite `logger.warn` se divergir. Adicione `better-sqlite3` e `@types/better-sqlite3` ao `package.json` se ausentes. Não implemente `CardRepository`. Rode `npm run build` e registre a saída. Finalize com relatório Markdown: resumo, arquivos alterados, comandos executados, evidências, decisões, riscos e pendências.

---

### TASK-02 — CardRepository: CRUD, ownership e stages

**Bloco:** A
**Dependências:** TASK-01
**Owner sugerido:** backend-implementer
**Estimativa:** 3h

**Contexto:**
`CardRepository` é a única interface entre os handlers e o SQLite. Expõe os métodos exatos de §7.2 com prepared statements. `assertOwner` lança `AccessDeniedError` (erro tipado) quando o `slack_user_id` difere do criador — os handlers convertem isso em ephemeral amigável. UPSERT de stages usa `INSERT OR REPLACE INTO card_stages` para idempotência (AD-3). A invariante S-07 (sem tokens/secrets nas colunas) deve ser documentada como comentário no arquivo.

**Arquivos prováveis:**
- `bot/src/db/cardRepository.ts` — CRIAR: classe `CardRepository` completa.
- `bot/src/types.ts` — MODIFICAR: adicionar `Card`, `CardStage`, `CardStatus`, `StageName`, `SddCliResult`, `SDD_EXIT`, `AccessDeniedError`.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Inserir e recuperar card
  Dado um banco em memória com migration aplicada
  Quando insertCard é chamado com dados válidos
  Então getCard retorna o card com status "em_progresso" e deleted_at null

Cenário: assertOwner com dono correto
  Dado que card "PROJ-42" pertence ao usuário "U001"
  Quando assertOwner("PROJ-42", "U001") é chamado
  Então retorna o card sem exceção

Cenário: assertOwner com usuário diferente lança AccessDeniedError
  Dado que card "PROJ-42" pertence ao usuário "U001"
  Quando assertOwner("PROJ-42", "U002") é chamado
  Então AccessDeniedError é lançado com mensagem descritiva

Cenário: UPSERT de stage é idempotente
  Dado que card "PROJ-42" existe
  Quando upsertStage é chamado duas vezes com stage "idea" e conteúdos diferentes
  Então getStages retorna exatamente um registro para "idea"
  E o conteúdo é o da segunda chamada

Cenário: listByUser filtra corretamente
  Dado cards PROJ-10 (U001, em_progresso), PROJ-11 (U001, aprovado), PROJ-12 (U002, em_progresso)
  Quando listByUser("U001") é chamado
  Então retorna somente PROJ-10
```

**Definition of Done:**
- Todos os métodos de §7.2 implementados com prepared statements.
- `AccessDeniedError` como classe tipada.
- Invariante S-07 documentada como comentário.
- Testes passando contra banco `:memory:`.

**Estratégia de testes:**
- Banco `:memory:` com migration em `beforeEach`.
- Cobrir todos os métodos e cenários de borda.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-02` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.1 e §7.2. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\db\index.ts` para importar `openDatabase`. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\db\cardRepository.ts` com a classe `CardRepository` implementando todos os métodos de §7.2 via prepared statements do `better-sqlite3`. Defina `AccessDeniedError extends Error`. Adicione comentário declarando a invariante S-07. Atualize `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\types.ts` com os tipos de §7.1. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-03 — channelKey.ts: extração de chave do canal Slack

**Bloco:** B
**Dependências:** TASK-02
**Owner sugerido:** backend-implementer
**Estimativa:** 1h

**Contexto:**
Extrai a chave do projeto Jira (`PROJ`) do tópico ou da descrição do canal Slack via regex `key\s*:\s*([A-Z][A-Z0-9]+)` case-insensitive. Sem a chave, `/bot create` não pode determinar o projeto Jira (CA-02). Lança `ChannelKeyMissingError` com mensagem orientativa ao usuário se não encontrar.

**Arquivos prováveis:**
- `bot/src/channelKey.ts` — CRIAR.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Canal com key no tópico
  Dado que o tópico do canal contém "key: ACME"
  Quando extractProjectKey é chamado
  Então retorna "ACME"

Cenário: Canal com key na descrição
  Dado que tópico está vazio e a descrição contém "Key: myproj"
  Quando extractProjectKey é chamado
  Então retorna "MYPROJ"

Cenário: Canal sem key lança erro
  Dado que tópico e descrição não têm "key:"
  Quando extractProjectKey é chamado
  Então ChannelKeyMissingError é lançado com mensagem orientativa
```

**Definition of Done:**
- `extractProjectKey(client, channelId): Promise<string>` implementado conforme §7.7.
- `ChannelKeyMissingError` com mensagem orientativa.
- Retorno sempre em `toUpperCase()`.

**Estratégia de testes:**
- Mock de `client.conversations.info` — 3 cenários.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-03` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seção §7.7 para o contrato exato. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\channelKey.ts` com `extractProjectKey` e `ChannelKeyMissingError`. A regex deve ser `/key\s*:\s*([A-Z][A-Z0-9]+)/i`; retorno em `toUpperCase()`. Priorize tópico sobre descrição na busca. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-04 — Router `/bot` + reescrita de index.ts

**Bloco:** B
**Dependências:** TASK-02
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Substitui os registros de comandos antigos (`app.command('/sdd')`, `app.message(/^\/aprovado$/)`) pelo único slash command `/bot` com dispatch assíncrono. O `bot.ts` router faz parsing do subcomando, valida Card ID via regex `^[A-Z][A-Z0-9]+-\d+$` para subcomandos que exigem, e despacha para o handler correto. Subcomandos inválidos ou ausentes caem no `help`. O padrão `await ack()` + `void dispatch(...).catch(...)` garante resposta < 3s (RNF-01, AD-2).

**Arquivos prováveis:**
- `bot/src/commands/bot.ts` — CRIAR: router com dispatch.
- `bot/src/index.ts` — MODIFICAR: trocar registros de comandos; importar `bot.ts`.
- `bot/src/providers/slack.ts` — VERIFICAR/MODIFICAR: garantir `postEphemeral` e `uploadStringAsFile`.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: ack() chamado imediatamente em qualquer /bot
  Dado que o bot está rodando
  Quando qualquer /bot <subcomando> é recebido
  Então ack() é chamado antes de qualquer processamento assíncrono

Cenário: Subcomando inválido cai no help
  Dado que o usuário executa "/bot xyz"
  Quando o router processa
  Então help é invocado e ephemeral com subcomandos válidos é enviado

Cenário: Card ID em formato inválido retorna erro
  Dado que o usuário executa "/bot update proj-42 texto"
  Quando o router valida "proj-42"
  Então um ephemeral de erro de formato é enviado
  E nenhum handler de update é chamado

Cenário: index.ts não registra mais /sdd nem app.message(/aprovado/)
  Dado que o bot está inicializado
  Então não há listener para "/sdd" nem para padrão /\/aprovado/
```

**Definition of Done:**
- `bot.ts` exporta `router.dispatch(sub, rest, command, respond, client)`.
- Regex de Card ID: `^[A-Z][A-Z0-9]+-\d+$`.
- `index.ts` sem referências a `/sdd` e `app.message(/aprovado/)`.
- `SlackProvider` com `postEphemeral` e `uploadStringAsFile`.

**Estratégia de testes:**
- Instanciar router com mocks de handlers, verificar dispatch correto por subcomando.
- Subcomando inválido chama `help`.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-04` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\index.ts` para entender a estrutura atual dos registros de comandos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.3, §8 (AD-2, AD-6, AD-7). Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\bot.ts` com o router: (1) `await ack()` imediato; (2) parse de `command.text`; (3) validação de Card ID com `^[A-Z][A-Z0-9]+-\d+$` para update, aproved, view; (4) dispatch para handler correto ou `help`; (5) `.catch(err => logError(err))`. Modifique `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\index.ts` substituindo registros antigos por `app.command('/bot', ...)`. Verifique `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\providers\slack.ts` e adicione `postEphemeral` e `uploadStringAsFile` se ausentes. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-05 — Handler create.ts

**Bloco:** B
**Dependências:** TASK-03, TASK-04
**Owner sugerido:** backend-implementer
**Estimativa:** 4h

**Contexto:**
Handler mais complexo. Implementa o fluxo completo de `/bot create` conforme o sequence diagram da Tech Spec §9: extração de chave → LLM título → criação Jira → SQLite → `/bloqueado` → `execa` → streaming de etapas → UPSERT de stages. Todos os controles de segurança S-04, S-05, S-06 obrigatórios. O arquivo antigo `bot-criar.ts` não deve ser removido nesta task.

**Arquivos prováveis:**
- `bot/src/commands/create.ts` — CRIAR: handler completo.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: /bot create com sucesso completo
  Dado que o canal tem "key: PROJ" e o LLM retorna título válido
  E a API Jira cria o card retornando "PROJ-42"
  Quando o usuário executa "/bot create Implementar login OAuth"
  Então o card é inserido no SQLite com status "em_progresso"
  E o comentário "/bloqueado" é adicionado ao Jira antes de invocar o orquestrador
  E o orquestrador é invocado com "sdd orchestrator PROJ-42" sem shell e com env restrito
  E para cada etapa um ephemeral "gerando <etapa>..." é enviado
  E o conteúdo de cada etapa é persistido via upsertStage
  E conteúdo > 3000 chars é enviado como arquivo

Cenário: Card ID inválido da API Jira é rejeitado (S-05)
  Dado que a API Jira retorna card_id "proj42" em formato inválido
  Quando create.ts valida o retorno
  Então um erro é lançado e nenhuma persistência SQLite é feita

Cenário: Canal sem key retorna erro orientativo (CA-02)
  Dado que o canal não tem "key:" configurado
  Quando /bot create é executado
  Então ChannelKeyMissingError é capturado e ephemeral orientativo é enviado
  E nenhum card é criado no Jira

Cenário: Gate ativado — exit code 2 (CA-08)
  Dado que sdd orchestrator retorna exit code 2
  Quando create.ts processa o resultado
  Então ephemeral de bloqueio é enviado ao usuário
  E nenhuma etapa adicional é executada
```

**Definition of Done:**
- Fluxo completo conforme §9 (sequence diagram).
- S-04, S-05, S-06 aplicados.
- Conteúdo > 3000 chars como arquivo (RN-09).
- Texto não-vazio e máx. 4000 chars validados.
- `bot-criar.ts` não removido (escopo da TASK-12).

**Estratégia de testes:**
- Mocks: `JiraProvider`, `SlackProvider`, `LLMProvider`, `CardRepository`, `execa`.
- Cenários: sucesso, Card ID inválido da API, canal sem key, gate (exit 2), crash (exit 1).

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-05` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.3, §7.4, §9 (sequence diagram), §10 (S-04, S-05, S-06), §12 e §13. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\bot-criar.ts` para entender a implementação atual a ser refatorada. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\create.ts` implementando o fluxo completo. Use `execa` com array de args (sem shell), `env: { PATH: process.env.PATH, HOME: process.env.HOME, SDD_BOT_CONTEXT: 'true' }` e `reject: false`. Valide card ID retornado pela API com `CARD_ID_RE = /^[A-Z][A-Z0-9]+-\d+$/`. Aplique `redact()` definido na Tech Spec §10 (S-04) antes de qualquer log. Não remova `bot-criar.ts`. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-06 — Handler update.ts (com assertOwner obrigatório)

**Bloco:** B
**Dependências:** TASK-04
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Refatora `card-editar.ts` corrigindo o achado de segurança S-02: o arquivo atual não verifica ownership. `assertOwner` deve ser a primeira operação com efeito do handler — antes de qualquer chamada ao Jira ou SQLite. Após verificação: atualiza descrição Jira, adiciona `/bloqueado`, atualiza SQLite, invoca orquestrador. Subfluxo de streaming idêntico ao `create.ts`.

**Arquivos prováveis:**
- `bot/src/commands/update.ts` — CRIAR.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Atualizar como criador
  Dado que card "PROJ-42" pertence a "U001"
  Quando "U001" executa "/bot update PROJ-42 Nova descrição"
  Então assertOwner é chamado antes de qualquer operação Jira ou SQLite
  E a descrição do card Jira é atualizada
  E "/bloqueado" é adicionado no Jira
  E o SQLite é atualizado com novo texto e status "em_progresso"
  E o orquestrador é invocado com streaming de etapas

Cenário: Tentativa de update por não-criador (CA-04)
  Dado que card "PROJ-42" pertence a "U001"
  Quando "U002" executa "/bot update PROJ-42 Texto"
  Então AccessDeniedError é capturado
  E ephemeral amigável de acesso negado é enviado a "U002"
  E nenhuma operação Jira ou SQLite é executada
```

**Definition of Done:**
- `assertOwner` como primeira operação.
- `AccessDeniedError` converte em ephemeral sem stack trace.
- Jira e SQLite atualizados somente após ownership confirmado.
- `card-editar.ts` não removido (escopo da TASK-12).

**Estratégia de testes:**
- Mock de `CardRepository.assertOwner` — ok e `AccessDeniedError`.
- Verificar ordem: assertOwner → Jira → SQLite → execa.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-06` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\card-editar.ts` para entender o código atual. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.2, §10 (S-02) e §13. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\update.ts` com `assertOwner(cardId, userId)` como primeira operação. Capture `AccessDeniedError` e envie ephemeral amigável sem stack trace. O subfluxo de streaming deve ser idêntico ao `create.ts` — extraia para função utilitária compartilhada se fizer sentido, mas não altere `create.ts`. Não remova `card-editar.ts`. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-07 — Handler aproved.ts

**Bloco:** B
**Dependências:** TASK-04
**Owner sugerido:** backend-implementer
**Estimativa:** 1h

**Contexto:**
Refatora `aprovado.ts` para o novo padrão `/bot aproved <card-id>`. Verifica ownership, adiciona `/aprovado` no Jira e atualiza SQLite com `status = 'aprovado'`. Card some do `/bot list` após (RN-06). Confirma ao usuário via ephemeral.

**Arquivos prováveis:**
- `bot/src/commands/aproved.ts` — CRIAR.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Aprovar card como criador (CA-05)
  Dado que card "PROJ-42" pertence a "U001" com status "em_progresso"
  Quando "U001" executa "/bot aproved PROJ-42"
  Então assertOwner é verificado primeiro
  E o comentário "/aprovado" é adicionado ao Jira
  E o SQLite é atualizado: status = "aprovado"
  E um ephemeral de confirmação é enviado a "U001"

Cenário: Aprovação por não-criador (CA-04)
  Dado que card "PROJ-42" pertence a "U001"
  Quando "U002" executa "/bot aproved PROJ-42"
  Então AccessDeniedError é capturado
  E ephemeral amigável é enviado a "U002"
  E o status do card permanece inalterado
```

**Definition of Done:**
- `assertOwner` antes de qualquer mutação.
- Jira primeiro, SQLite após confirmação da API.
- Ephemeral de confirmação enviado.
- `aprovado.ts` não removido (escopo da TASK-12).

**Estratégia de testes:**
- Mock de `JiraProvider.addComment` e `CardRepository.updateStatus`.
- Verificar `AccessDeniedError` para não-criador.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-07` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\aprovado.ts`. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.2, §7.6 e §10 (S-02). Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\aproved.ts` com: (1) `assertOwner` primeiro; (2) `JiraProvider.addComment(cardId, '/aprovado')`; (3) `CardRepository.updateStatus(cardId, 'aprovado')`; (4) ephemeral de confirmação. Capture `AccessDeniedError`. Não remova `aprovado.ts`. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-08 — Handlers list.ts, view.ts e help.ts

**Bloco:** B
**Dependências:** TASK-04
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Três handlers que completam o conjunto. `list` e `view` consultam exclusivamente o SQLite sem IA (RF-14, RF-15, REST-04). `view` verifica ownership e envia como arquivo se > 3000 chars (RN-09). `help` exibe o texto exato aprovado em CP-1 (AD-6, §7.3).

**Arquivos prováveis:**
- `bot/src/commands/list.ts` — CRIAR.
- `bot/src/commands/view.ts` — CRIAR.
- `bot/src/commands/help.ts` — CRIAR.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: /bot list retorna apenas cards não aprovados do executor (CA-06)
  Dado cards PROJ-10 (U001, em_progresso) e PROJ-11 (U001, aprovado)
  Quando "U001" executa "/bot list"
  Então retorna "PROJ-10 — <título>" somente
  E PROJ-11 não aparece

Cenário: /bot list sem cards retorna mensagem informativa
  Dado que "U001" não tem cards em_progresso
  Quando "U001" executa "/bot list"
  Então uma mensagem informativa é enviada sem erro

Cenário: /bot view retorna conteúdo do SQLite sem IA (CA-07)
  Dado que card "PROJ-42" pertence a "U001" com stages no SQLite
  Quando "U001" executa "/bot view PROJ-42"
  Então o conteúdo é retornado do SQLite
  E nenhum LLMProvider é instanciado ou chamado

Cenário: /bot view conteúdo longo enviado como arquivo (CA-11)
  Dado que o conteúdo total das stages tem mais de 3000 chars
  Quando "U001" executa "/bot view PROJ-42"
  Então o conteúdo é enviado como arquivo Slack

Cenário: /bot help ou /bot sem args exibe subcomandos
  Quando "/bot" ou "/bot help" é executado
  Então um ephemeral com todos os subcomandos e sintaxes é enviado
```

**Definition of Done:**
- `list` usa `CardRepository.listByUser` sem IA.
- `view` usa `assertOwner` + `CardRepository.getStages` sem IA; arquivo se > 3000 chars.
- `help` exibe texto exato do §7.3.
- Nenhum dos três importa ou instancia `LLMProvider`.

**Estratégia de testes:**
- Mock de `CardRepository`, verificar ausência de chamadas a `LLMProvider`.
- Threshold de 3000 chars em `view`.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-08` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.2 (métodos do repo), §7.3 (help text exato) e §11 (autenticação/ownership). Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\list.ts` usando `CardRepository.listByUser`. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\view.ts` usando `assertOwner` + `CardRepository.getStages` — sem IA — enviando como arquivo se > 3000 chars. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\commands\help.ts` com o texto exato do §7.3. Nenhum dos três deve importar `LLMProvider`. Rode `npm run build`. Finalize com relatório Markdown.

---

### TASK-09 — block_gate.rs: lógica pura e unit tests Rust

**Bloco:** C
**Dependências:** nenhuma
**Owner sugerido:** backend-implementer
**Estimativa:** 3h

**Contexto:**
Cria `src/domain/orchestrator/block_gate.rs` com `is_blocked_marker` (apenas `text.trim() == "/bloqueado"`, case-sensitive, AD-5) e `ensure_not_blocked` (carrega config Jira, busca último comentário, chama `is_blocked_marker`; sem config retorna `Ok(())`). Confirma `reqwest` no `Cargo.toml` (R-4) e inclui unit tests inline com os 6 casos do §16.

**Arquivos prováveis:**
- `src/domain/orchestrator/block_gate.rs` — CRIAR.
- `src/domain/orchestrator/mod.rs` — MODIFICAR: `pub mod block_gate`.
- `Cargo.toml` — VERIFICAR/MODIFICAR: confirmar `reqwest`.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: is_blocked_marker "/bloqueado" exato
  Dado texto "/bloqueado"
  Quando is_blocked_marker é chamado
  Então retorna true

Cenário: is_blocked_marker "/BLOQUEADO" (maiúsculo)
  Dado texto "/BLOQUEADO"
  Quando is_blocked_marker é chamado
  Então retorna false

Cenário: is_blocked_marker "/BloquEado" (misto)
  Dado texto "/BloquEado"
  Quando is_blocked_marker é chamado
  Então retorna false

Cenário: is_blocked_marker "/bloqueado " (com espaço à direita)
  Dado texto "/bloqueado "
  Quando is_blocked_marker é chamado
  Então retorna true (trim remove o espaço)

Cenário: is_blocked_marker "/aprovado"
  Dado texto "/aprovado"
  Quando is_blocked_marker é chamado
  Então retorna false

Cenário: is_blocked_marker None
  Dado texto None
  Quando is_blocked_marker é chamado
  Então retorna false

Cenário: ensure_not_blocked sem config Jira
  Dado que não existe configuração Jira no root
  Quando ensure_not_blocked é chamado
  Então retorna Ok(()) sem consultar Jira
```

**Definition of Done:**
- `block_gate.rs` com `is_blocked_marker` e `ensure_not_blocked`.
- `pub mod block_gate` em `mod.rs`.
- `reqwest` confirmado ou adicionado.
- `cargo test domain::orchestrator::block_gate` — 6 casos passando.

**Estratégia de testes:**
- Unit tests inline com `#[cfg(test)]`.
- 6 casos de `is_blocked_marker` + `ensure_not_blocked` sem config.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-09` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §7.5, §8 (AD-4, AD-5) e §16 (casos de teste). Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\src\domain\orchestrator\mod.rs` para entender o módulo. Verifique `C:\alisson\sdd-layer\.worktree\SDDL-2\Cargo.toml` para `reqwest`; adicione `reqwest = { version = "0.12", features = ["rustls-tls", "json", "blocking"] }` se ausente. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\src\domain\orchestrator\block_gate.rs` com: (1) `pub fn is_blocked_marker(text: Option<&str>) -> bool` — somente `t.trim() == "/bloqueado"`; (2) `pub fn ensure_not_blocked(root: &Path, card_id: &str) -> Result<()>`; (3) `#[cfg(test)]` com os 6 casos. Adicione `pub mod block_gate;` em `mod.rs`. Rode `cargo test domain::orchestrator::block_gate` e registre saída. Finalize com relatório Markdown.

---

### TASK-10 — Integração block_gate em run_orchestration

**Bloco:** C
**Dependências:** TASK-09
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Insere `block_gate::ensure_not_blocked` no ponto correto de `run_orchestration` em `src/main.rs` — dentro do bloco `if worktree_dir(&root).exists()` e `if let Some(ref cid) = card_id`, após `assert_prerequisite_artifacts`. Texto livre não tem `card_id` — a invariante já existe e não deve ser alterada. O exit code não-zero em caso de bloqueio pode precisar de `std::process::exit(2)` explícito dependendo do harness — verificar e documentar.

**Arquivos prováveis:**
- `src/main.rs` — MODIFICAR: inserir chamada ao gate.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Gate ativa e aborta com exit não-zero (CA-08)
  Dado que o último comentário do card "PROJ-42" é "/bloqueado"
  Quando "sdd orchestrator PROJ-42" é executado
  Então ensure_not_blocked é chamado e retorna Err
  E o processo termina com exit code não-zero
  E a mensagem de erro menciona o bloqueio

Cenário: Gate não ativa quando comentário != "/bloqueado" (CA-09)
  Dado que o último comentário é "/aprovado"
  Quando "sdd orchestrator PROJ-42" é executado
  Então ensure_not_blocked retorna Ok
  E a orquestração continua normalmente

Cenário: Texto livre não consulta Jira (CA-10)
  Dado que o argumento é "implementar feature de login"
  Quando "sdd orchestrator" recebe o texto livre
  Então card_id é None e ensure_not_blocked não é chamado
```

**Definition of Done:**
- `ensure_not_blocked(&root, cid)?` inserido após `assert_prerequisite_artifacts`.
- `cargo test` completo verde após integração.
- Exit code != 0 em bloqueio documentado.

**Estratégia de testes:**
- `cargo test` completo — registrar saída.
- `cargo test domain::orchestrator::block_gate` — registrar saída.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-10` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\src\main.rs` linhas 4385-4415 para localizar o bloco `if worktree_dir(&root).exists()` e `if let Some(ref cid) = card_id` em `run_orchestration`. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seção §7.5 (pontos de inserção). Insira `crate::domain::orchestrator::block_gate::ensure_not_blocked(&root, cid)?;` após `assert_prerequisite_artifacts(&root, cid)?;`. Verifique se o `?` em `anyhow::Result` na main resulta em exit code != 0 — documente o comportamento encontrado e, se exit code 2 for exigido pelo bot TypeScript, adicione tratamento explícito com `std::process::exit(2)` no caminho de erro de bloqueio. Rode `cargo build` e `cargo test` e registre saídas. Finalize com relatório Markdown.

---

### TASK-11 — Instalador sdd bot install + checagens de segurança

**Bloco:** D
**Dependências:** TASK-01, TASK-10
**Owner sugerido:** backend-implementer
**Estimativa:** 3h

**Contexto:**
Estende `BotCommand::Install` em `run_bot` para guiar o usuário na criação de `sdd-bot.config.yaml` com 0600 (S-03), rodar `npm rebuild` para `better-sqlite3` (R-1), verificar se `sdd` está no PATH (RF-23) e se o config está no `.gitignore` (RF-22, CA-12).

**Arquivos prováveis:**
- `src/main.rs` — MODIFICAR: estender `run_bot` com lógica completa de `BotCommand::Install`.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: Config criado com permissão 0600 (S-03)
  Dado que "sdd bot install" é executado
  Quando o instalador gera sdd-bot.config.yaml
  Então o arquivo tem permissão 0600
  E nenhum token é gravado no repositório git

Cenário: Aviso quando config não está no .gitignore (CA-12)
  Dado que .gitignore não contém "sdd-bot.config.yaml"
  Quando o instalador verifica
  Então um aviso de segurança é emitido ao usuário

Cenário: Aviso quando sdd não está no PATH (RF-23)
  Dado que o binário "sdd" não é encontrado no PATH
  Quando o instalador verifica
  Então um aviso orientativo é emitido

Cenário: npm rebuild executado para better-sqlite3 (R-1)
  Dado que o instalador identifica o diretório bot/
  Quando a instalação conclui
  Então "npm rebuild" é executado no diretório bot/ e o resultado é reportado
```

**Definition of Done:**
- Config criado com `chmod 0600` (ou equivalente).
- `.gitignore` verificado com aviso.
- PATH do `sdd` verificado com aviso.
- `npm rebuild` executado em `bot/`.

**Estratégia de testes:**
- Teste manual em ambiente de teste com os 4 critérios verificados.
- Regressão: `sdd install <proj>` não pergunta sobre bot.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-11` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\src\main.rs` na região de `run_bot` para localizar `BotCommand::Install`. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §10 (S-03), §17 (R-1) e §6 (PRD RF-21, RF-22, RF-23). Estenda `run_bot` para: (1) guia interativo de geração de `sdd-bot.config.yaml` com `chmod 0600`; (2) verificação do `.gitignore` com aviso se ausente; (3) verificação de `sdd` no PATH com aviso se ausente; (4) execução de `npm rebuild` no diretório `bot/` com reporte do resultado. Rode `cargo build`. Finalize com relatório Markdown.

---

### TASK-12 — Limpeza de amarrações em install/update

**Bloco:** D
**Dependências:** TASK-11
**Owner sugerido:** backend-implementer
**Estimativa:** 2h

**Contexto:**
Remove os prompts interativos do bot dos comandos `sdd install` e `sdd update`. Mantém `print_bot_install_hint` como dica após install/update (AD-7). Garante que `sdd doctor` exibe bot como componente opcional. Após a limpeza, o checklist RF-24 completo deve ser executável.

**Arquivos prováveis:**
- `src/main.rs` — MODIFICAR: remover prompts de bot em install/update.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: sdd install sem prompt de bot (RF-24)
  Quando "sdd install <projeto>" é executado
  Então nenhum prompt interativo sobre bot é exibido
  E a instalação core conclui normalmente

Cenário: sdd update sem prompt de bot (RF-24)
  Quando "sdd update" é executado
  Então nenhum prompt interativo sobre bot é exibido

Cenário: sdd doctor lista bot como opcional (RF-24)
  Quando "sdd doctor" é executado
  Então o bot aparece como componente opcional

Cenário: Dica sdd bot install exibida após install/update
  Quando "sdd install <projeto>" conclui
  Então uma dica orientando "sdd bot install" é exibida
```

**Definition of Done:**
- Prompts de bot removidos de `install` e `update`.
- `print_bot_install_hint` presente ou criado.
- `sdd doctor` com bot como opcional.
- `cargo test` verde.

**Estratégia de testes:**
- Checklist RF-24 completo executado manualmente com evidências registradas.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-12` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\src\main.rs` nas regiões de `sdd install` e `sdd update` para identificar os prompts de instalação do bot. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seções §8 (AD-7) e §19 (checklist RF-24). Remova os prompts interativos de bot de `install` e `update`. Mantenha ou crie `print_bot_install_hint`. Verifique que `sdd doctor` exibe bot como componente opcional. Rode `cargo build` e `cargo test`. Execute cada item do checklist RF-24 do §19 e registre a saída. Finalize com relatório Markdown com evidências de cada item do checklist.

---

### TASK-13 — Testes unitários TypeScript (bot + repo)

**Bloco:** E
**Dependências:** TASK-08
**Owner sugerido:** test-implementer
**Estimativa:** 4h

**Contexto:**
Suite completa de testes unitários TypeScript cobrindo handlers, `CardRepository`, `channelKey` e controles de segurança. Banco `:memory:` para o repo; mocks para provedores externos. Testes explícitos de S-03 (0600), S-04 (redação de secrets), S-05 (Card ID da API), S-06 (env restrito no execa). Rastreabilidade aos critérios CA-01..CA-13.

**Arquivos prováveis:**
- `bot/src/__tests__/cardRepository.test.ts` — CRIAR.
- `bot/src/__tests__/channelKey.test.ts` — CRIAR.
- `bot/src/__tests__/create.test.ts` — CRIAR.
- `bot/src/__tests__/update.test.ts` — CRIAR.
- `bot/src/__tests__/aproved.test.ts` — CRIAR.
- `bot/src/__tests__/list.test.ts` — CRIAR.
- `bot/src/__tests__/view.test.ts` — CRIAR.
- `bot/src/__tests__/router.test.ts` — CRIAR.
- `bot/src/__tests__/security.test.ts` — CRIAR.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: npm test passa sem erros
  Dado que todos os handlers estão implementados
  Quando "npm test" é executado no diretório bot/
  Então todos os testes passam
  E há cobertura rastreável a CA-01..CA-13

Cenário: Teste de redação de secrets (S-04)
  Dado uma string com "xoxb-1234-secret"
  Quando redact() é chamada
  Então a saída contém "[REDACTED]"

Cenário: Teste de env restrito no execa (S-06)
  Dado que create.ts invoca o orquestrador
  Quando execa é chamado
  Então o env contém somente PATH, HOME e SDD_BOT_CONTEXT

Cenário: Teste de validação de Card ID da API (S-05)
  Dado que JiraProvider.createCard retorna "proj42" (inválido)
  Quando create.ts processa o retorno
  Então um erro é lançado sem persistência no SQLite
```

**Definition of Done:**
- `npm test` verde.
- Cobertura de CA-01..CA-13.
- Testes explícitos de S-03, S-04, S-05, S-06.

**Estratégia de testes:**
- Framework identificado via `package.json` (jest ou vitest).
- Banco `:memory:` em `beforeEach` para `CardRepository`.
- Mocks para todos os provedores externos e `execa`.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-13` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\package.json` para identificar o framework de testes. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\02-prd.md` seção §10 (CA-01..CA-13) e `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seção §16. Crie os arquivos de teste em `C:\alisson\sdd-layer\.worktree\SDDL-2\bot\src\__tests__\`. Para `CardRepository`: banco `:memory:` com migration em `beforeEach`. Para handlers: mock de todos os provedores e `execa`. Crie `security.test.ts` cobrindo S-03, S-04, S-05 e S-06 explicitamente. Rode `npm test` e registre saída completa. Finalize com relatório Markdown.

---

### TASK-14 — Testes unitários e integração Rust (gate)

**Bloco:** E
**Dependências:** TASK-10
**Owner sugerido:** test-implementer
**Estimativa:** 3h

**Contexto:**
Complementa os unit tests inline do `block_gate.rs` com a execução do `cargo test` completo para verificar regressão. Verifica que texto livre não consulta Jira (CA-10) e que o gate está no ponto correto de `run_orchestration`. Documenta o exit code observado em caso de bloqueio.

**Arquivos prováveis:**
- Sem novos arquivos — execução e registro de evidências.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: cargo test domain::orchestrator::block_gate — 6 casos passando
  Quando "cargo test domain::orchestrator::block_gate" é executado
  Então todos os 6 casos de is_blocked_marker passam

Cenário: cargo test completo verde após integração
  Quando "cargo test" é executado
  Então todos os testes existentes passam sem regressão

Cenário: Regressão RF-24 — texto livre não consulta Jira
  Dado argumento texto livre em sdd orchestrator
  Então card_id é None e ensure_not_blocked não é chamado
```

**Definition of Done:**
- `cargo test` completo verde.
- 6 casos de `is_blocked_marker` documentados e passando.
- Nenhuma regressão identificada.

**Estratégia de testes:**
- `cargo test` — saída registrada.
- `cargo test domain::orchestrator::block_gate` — saída registrada.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-14` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Leia `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\03-techspec.md` seção §16. Execute `cargo test` na raiz `C:\alisson\sdd-layer\.worktree\SDDL-2\` e registre a saída completa. Execute `cargo test domain::orchestrator::block_gate` e registre. Se algum teste existente falhou devido às tasks TASK-09 ou TASK-10, corrija — caso contrário documente a causa sem alterar código fora do escopo. Documente o exit code observado em caso de bloqueio. Finalize com relatório Markdown com as saídas dos comandos como evidência.

---

### TASK-15 — Auditoria de segurança e evidências finais

**Bloco:** E
**Dependências:** TASK-13, TASK-14, TASK-12
**Owner sugerido:** test-implementer
**Estimativa:** 2h

**Contexto:**
Task de fechamento. Executa `cargo audit`, `gitleaks`, `npm test` e `cargo test`, percorre o checklist RF-24 completo e cria o relatório de evidências `evidence-e2e.md` para o checkpoint CP-4. Este artefato será referenciado pela etapa `review`.

**Arquivos prováveis:**
- `docs/sddl-2/evidence-e2e.md` — CRIAR: relatório de evidências para CP-4.

**Critérios de aceite (Gherkin):**

```gherkin
Cenário: cargo audit sem CVEs críticas
  Quando "cargo audit" é executado
  Então zero CVEs com severidade Critical ou High são reportadas

Cenário: gitleaks sem secrets
  Quando "gitleaks detect --source . --no-git" é executado
  Então zero secrets são detectados

Cenário: Checklist RF-24 completo e verificado
  Dado que todos os blocos A, B, C, D, E foram concluídos
  Quando o checklist RF-24 do §19 é verificado
  Então todos os 6 itens estão marcados como concluídos com evidências

Cenário: npm test e cargo test verdes
  Quando "npm test" e "cargo test" são executados
  Então ambos terminam com zero falhas
```

**Definition of Done:**
- `cargo audit` sem CVEs críticas/High (ou com exceções documentadas e aceitas).
- `gitleaks` sem detecções.
- Checklist RF-24 com 6 itens verificados.
- `npm test` e `cargo test` verdes.
- `docs/sddl-2/evidence-e2e.md` criado com todas as evidências.

**Estratégia de testes:**
- `cargo audit` — saída completa registrada.
- `gitleaks detect --source . --no-git` — saída registrada.
- `npm test` no diretório `bot/` — saída registrada.
- `cargo test` na raiz — saída registrada.
- Checklist RF-24: executar cada comando manualmente e registrar saída.

**Prompt para LLM executor:**
Você é um agente de código executando a task `TASK-15` no workspace `.worktree/SDDL-2`. Use apenas caminhos absolutos. Esta é a task de evidências finais para CP-4. Execute em `C:\alisson\sdd-layer\.worktree\SDDL-2\`: (1) `cargo audit` — anote zero CVEs críticas ou documente exceções aceitas; (2) `gitleaks detect --source . --no-git` ou equivalente disponível — anote zero secrets; (3) `npm test` no diretório `bot\`; (4) `cargo test`. Percorra o checklist RF-24 do §19 da Tech Spec e execute/verifique cada item: `sdd install`, `sdd update`, `sdd doctor`, `sdd orchestrator <texto-livre>`, `sdd orchestrator <CARD-ID>` com comentário != `/bloqueado`, `cargo test`. Crie `C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\evidence-e2e.md` com: data/hora, versões das ferramentas, saída de cada comando, itens do RF-24 marcados, riscos residuais identificados. Finalize com relatório Markdown.

---

## Rastreabilidade Final

| Campo | Valor |
|---|---|
| Artefato | `docs/sddl-2/04-tasks.md` |
| Origem | `docs/sddl-2/03-techspec.md` — CP-2 aprovado |
| PRD referenciado | `docs/sddl-2/02-prd.md` — CP-1 aprovado |
| Ideia referenciada | `docs/sddl-2/01-idea.md` |
| Requisitos cobertos | RF-01..RF-24, RNF-01..RNF-09, RN-01..RN-09, CA-01..CA-13, S-01..S-07 |
| Total de tasks | 15 (5 épicos, 4 fases) |
| Total estimado | 36 horas |
| Próxima etapa | Refinement (risco ALTO) ou Execução com CP-3 aprovado |
| Estado | aguardando CP-3 — aprovação humana |

---

O arquivo foi gerado. O caminho de destino é:

**`C:\alisson\sdd-layer\.worktree\SDDL-2\docs\sddl-2\04-tasks.md`**

Salve o conteúdo acima nesse arquivo. Resumo do que foi produzido:

---

## Resumo do Backlog

**15 tasks atomicas em 5 epicos e 4 fases:**

| Fase | Bloco | Tasks | Paralelismo |
|---|---|---|---|
| 1 — Base | A | TASK-01, TASK-02 | Sequencial |
| 2 — Feature | B + C | TASK-03..08 (TS) + TASK-09..10 (Rust) | B e C em paralelo |
| 3 — Instalador | D | TASK-11, TASK-12 | Sequencial |
| 4 — Qualidade | E | TASK-13, TASK-14, TASK-15 | TASK-13 e 14 em paralelo |

**Total estimado: 36 horas**

**Destaques de segurança incorporados em cada task:**
- S-01 (gate case-sensitive) — TASK-09
- S-02 (`assertOwner` obrigatorio) — TASK-06, TASK-07, TASK-08
- S-03 (permissao 0600) — TASK-01, TASK-11, TASK-13
- S-04 (redacao de secrets) — TASK-05, TASK-13
- S-05 (Card ID validado pos-API) — TASK-05, TASK-13
- S-06 (execa sem shell + env restrito) — TASK-05, TASK-13
- S-07 (sem secrets no SQLite) — TASK-02

**Checklist RF-24 (regressao obrigatoria pre-merge):** executado em TASK-12 e consolidado em TASK-15 como evidencia para CP-4.