# Tech Spec - Orquestrador Autônomo de Demandas SDD

## Rastreabilidade
- Orquestração: Orquestrador Autônomo de Demandas SDD
- Slug: orquestrador-autônomo-de-demandas-sdd
- Stage: techspec
- Estado atual: in_review
- Origem: requisitos aprovados em `02-prd.md` + classificação de risco high (`00-risk-classification.md`).
- Próximo artefato: backlog técnico em `04-tasks.md`.
- Revisão: Agent Team (architecture-reviewer, security-reviewer, data-contract-reviewer). Esta é a **v2**, que incorpora os achados Críticos/Altos. Ver `## Revisão (Agent Team) — decisões`.
- Escopo: Fase 1 (planejamento autônomo). Execução autônoma é fronteira projetada, não ativada.

## Visão técnica
O orquestrador é a *cola* do `pipeline.html` §07: uma **máquina de estados durável** que dirige o planejamento por demanda. A revisão de arquitetura corrigiu a premissa central da v1: **o motor de geração headless por provider já existe e está completo** em `src/tui/runner.rs` (Codex/Claude/Opencode/Cursor/Antigravity; `codex exec`, `build_provider_prompt`, `collect_artifact_context`, `ensure_adapter_ready`, parse de usage, checagem de auth). Portanto a Fase 1 **não reimplementa geração**; ela:

1. **Adiciona o motor durável (`engine`)**: fila + estado + `tick()` que decide a próxima ação, avança o planejamento e **pausa nos gates**, de forma idempotente — reusando a navegação `tui::stage::can_navigate` e a leitura `tui::orchestration::parse_index`.
2. **Extrai o núcleo de geração** de `tui::runner` para um módulo neutro de runtime, consumido **tanto pela TUI quanto pelo engine** (um único ponto de montagem de comando headless, redação e timeout — sem segundo caminho de regressão de segurança).

Tudo o mais é **reuso, não reescrita**: o que já é determinístico (`stage`, `orchestration`, `persist::write_atomic`) e a redação (`runtime::redaction`). As integrações entram como **adapters plugáveis** com implementação **local/Markdown como default operante**.

Princípio inegociável (RN-02, `pipeline.html` §02): **autoria do comportamento separada da autoria do código** — o motor **não tem caminho de código que aprove um gate**; humano decide PRD/TechSpec/Refinamento (controle técnico em `## Segurança`).

## Arquitetura

### Fronteiras de módulo (alinhadas às 3 camadas existentes: `domain` / `runtime` / `tui`)
```
src/
  domain/
    orchestrator/           # PURO / IO determinístico, testável sem rede nem terminal
      mod.rs
      demand.rs             # modelo da demanda + slug validado (anti-traversal)
      queue.rs              # fila durável (.sdd/queue/), enqueue, dedup, claim, list
      state.rs              # estado de runtime (.sdd/state/<slug>.json): cursor, lock, approvals, schema_version
      engine.rs             # tick(): decide próxima ação, avança/pausa, idempotente
      gate.rs               # checkpoint: marca awaiting, monta pedido, registra decisão
      approval.rs           # registro/validação de decisão (autor, canal, allowlist)
  runtime/
    stage_runner.rs         # EXTRAÍDO de tui::runner: geração headless com event sink genérico
    adapters/               # EFEITO (rede): seams de integração, default local
      mod.rs                # traits: WorkItemAdapter, DocAdapter, ChatAdapter, RepoAdapter
      local.rs              # default Markdown/local (sempre disponível)
      slack.rs              # Chat: pedido + callback de aprovação (com verificação de assinatura)
      jira.rs               # WorkItem (estado/itens) — habilitado por config
      confluence.rs         # Doc (página) — habilitado por config
  tui/
    runner.rs               # passa a CONSUMIR runtime::stage_runner (sem duplicar geração)
```
> Decisão (resolve achado A4): o motor puro vive em `domain/`; runner headless e adapters de rede são **efeito** e vivem em `runtime/`. Não há top-level novo, preservando o padrão de 3 camadas.

### Refactors pré-requisito (declarados como tasks; não são "reuso pronto")
1. **Extrair geração de `tui::runner` → `runtime::stage_runner`** com um *event sink* genérico (trait/closure) no lugar de `Sender<StageEvent>`; TUI e engine passam a consumir o mesmo núcleo (resolve C1/A1-arch).
2. **Expor a validação de seções**: `required_sections()` (hoje privado em `main.rs:1406`) movido para um módulo compartilhado e reusado pelo engine (resolve B1-arch).
3. **Extrair a mutação do map**: `update_stage_state` (hoje privado em `main.rs:1199`, string-replace) → função reusável com round-trip estruturado (serde_yaml) e teste; o engine escreve **in-process**, não por subprocesso `sdd artifact save` (resolve M2/M3-arch).
4. **Propagar timeout/retries**: `ProviderSelection` (`domain/providers.rs:174`) passa a expor `timeouts`/`retries` de `ProviderDefinition` (resolve A2-arch).
5. **Estender `validate_state`** (`main.rs:1326`) e/ou o mapeamento de estados para cobrir o que o engine grava (ver `## Dados`, resolve achado ALTO de data-contract).

### Superfície de CLI (`src/main.rs`)
Novo grupo, sem colidir com o fluxo manual:
- `sdd demand enqueue --id <ID> --type <epic|story|task|bug> --title <T> [--desc <D>] [--source <S>]`
- `sdd demand list [--json]` / `sdd demand status [<ID>] [--json]`
- `sdd demand approve <ID> --gate <prd|techspec|refinement> --by <autor> [--note <n>]` / `... reject ... --reason <r>`
- `sdd auto tick [--max <N>]` — **um** passo do motor (idempotente, testável, agendável).
- `sdd auto run [--watch] [--interval <s>] [--max-concurrency <N>]` — laço fino sobre `tick` (evento de filesystem ou intervalo; nunca busy-poll).
- `sdd auto status [--json]`.

> `auto run` é deliberadamente fino: toda a lógica vive em funções puras + `tick`, o que permite rodar o motor por **scheduled task** sem daemon dedicado, preservando neutralidade de harness (decisão confirmada como acertada pela revisão).

### Fluxo de um tick
1. Carrega fila + estados (`queue`, `orchestration::parse_index`, `state`).
2. **Ingestão**: demanda nova → dedup por `id` (RF-12) → cria `docs/<slug>/` (parte reusável de `run_orchestration`, in-process) → estado `idea`.
3. **Avanço**: demanda elegível → próxima etapa via `current_stage`+`can_navigate` → `runtime::stage_runner` gera o artefato (prompt = `build_provider_prompt`) → **redige** → **valida seções** (`required_sections`) → grava artefato no nome canônico + atualiza map in-process (`recorded`/`in_progress`).
4. **Gate**: etapa concluída ∈ {PRD, TechSpec, Refinamento} → `gate::request()` grava `awaiting_approval` **no `.sdd/state`** (não no campo de artefato do map; ver `## Dados`), emite pedido (`notify` + `ChatAdapter`), **não avança**.
5. **Aprovação**: sinal **humano** (CLI `demand approve` ou callback Slack verificado) grava `Decision` em `state.approvals`; próximo tick retoma na etapa seguinte. Reprovação reabre a etapa (RF-09).
6. **Handoff**: aprovar Tasks/Refinamento → estado `ready_for_exec` no `.sdd/state` (RF-11), expondo o contrato consumido pelo fluxo de execução existente (manual no MVP).

### Reuso explícito (corrigido pela revisão)
| Necessidade | Reuso REAL | Observação |
|---|---|---|
| Ordem/gate das etapas | `tui::stage::{can_navigate, current_stage}` (`stage.rs:143/172`) | direto |
| Leitura de estado | `tui::orchestration::parse_index` (`orchestration.rs:78`) | ignora chaves desconhecidas (serde) |
| **Geração headless** | `tui::runner` → extrair p/ `runtime::stage_runner` | **C1**: não reimplementar |
| Prompt por etapa | `tui::runner::build_provider_prompt` (`runner.rs:1764`) | **não** `main.rs::stage_prompt` (é só delegação) |
| Comando headless por provider | `tui::runner::DirectAdapter` (`runner.rs`) | verbo (`-p`/`exec`/`run`) está aqui, não em `auth_methods.command` |
| Validação de seções | `required_sections()` (`main.rs:1406`, expor) | não via hook payload |
| Escrita atômica | `tui::persist::write_atomic` | direto |
| Mutação do map | extrair `update_stage_state` (`main.rs:1199`) | round-trip serde + in-process |
| Provider/modelo/effort | `domain::providers` (`stage_models`, `stage_efforts`) | propagar timeouts/retries |
| Redação | `runtime::redaction::redact_text` (`redaction.rs:37`) | **expandir cobertura** (ver Segurança) |
| Guardrails | `sdd hook bash-guard` | defesa-em-profundidade, não barreira primária |
| Base de execução futura | `tui::parallel`, `tui::execution` | sairão de `tui::` quando a execução for ativada |

## Contratos

### StageRunner (abstração sobre o núcleo extraído)
```rust
pub struct StageRequest { pub slug: String, pub stage: Stage, pub prompt: Secret<String>,
    pub provider: String, pub model: String, pub effort: String,
    pub timeout_ms: u64, pub max_attempts: u32 }
pub struct StageArtifact { pub stage: Stage, pub markdown: String, pub provider_meta: ProviderMeta }
pub trait StageRunner { fn run(&self, req: &StageRequest, sink: &mut dyn FnMut(StageProgress)) -> Result<StageArtifact, RunnerError>; }
```
- Impl real = wrapper fino sobre `runtime::stage_runner` (extraído). `FakeRunner` determinístico para testar todo o motor sem provider/rede.
- `prompt` é `Secret<String>` (resolve B3-sec). Saída passa por **redação → validação de seções → deny-check de conteúdo** (ver Segurança) **antes** de tocar disco/log.
- Provider sem headless confiável → `RunnerError::NotHeadless` → estado `manual:<etapa>` no `.sdd/state` (humano/agente preenche). Mantém neutralidade (`provider-compatibility`).

### Adapters (seams; default local)
```rust
pub trait WorkItemAdapter { fn upsert(&self, item:&WorkItem)->Result<ExternalRef>; fn set_state(&self,r:&ExternalRef,s:&str)->Result<()>; }
pub trait DocAdapter      { fn upsert_page(&self, doc:&ArtifactDoc)->Result<ExternalRef>; }
pub trait ChatAdapter     { fn post_gate(&self, req:&GateRequest)->Result<ChatRef>; fn verify_and_parse(&self, raw:&InboundCallback)->Result<Decision>; }
pub trait RepoAdapter     { fn link_branch_pr(&self, slug:&str)->Result<Vec<ExternalRef>>; }
```
- `LocalAdapter` implementa todos via `docs/<slug>/` + map + jsonl. **Sempre o default**; nada do núcleo depende de adapter externo.
- **Nenhuma credencial externa é lida na Fase 1** (M4-sec). Quando habilitados por config, credenciais vêm de env dedicada, escopo mínimo, lidas por um único ponto que passa por redação.

### Decision (paridade CLI ↔ Slack — RF-06/07)
```rust
pub struct Decision { pub slug:String, pub gate:Stage, pub decision:DecisionKind, // approve|reject
    pub author:String, pub channel:Channel, pub ts:String, pub reason:Option<String> }
```
- Inclui `slug` (resolve B-data). Ambos os canais produzem o **mesmo** registro. O motor só **lê**; **nunca escreve `approve`** (controle em Segurança).

## Dados

### Demanda (`.sdd/queue/<id>.json`)
```json
{ "schema_version": 1, "id": "DEM-1", "type": "story", "title": "...", "description": "...",
  "source": "cli|jira|webhook", "priority": "normal", "created_at": "<iso>",
  "slug": "<derivado-e-validado>", "status": "queued" }
```
- `schema_version` obrigatório; versão divergente → erro claro, sem leitura silenciosa (resolve ALTO-data).
- `slug` derivado do título, **validado contra path traversal** (sem `..`, sem separadores/symlink/abs) e sem colidir com palavras de `ARTIFACT_HINTS` (defensivo; ver Riscos).
- Dedup por `id` (RF-12).

### Estado do motor (`.sdd/state/<slug>.json`) — fonte primária de verdade
```json
{ "schema_version": 1, "slug":"...", "cursor":"prd",
  "status":"queued|orchestrating|awaiting_approval|ready_for_exec|paused|error|manual:techspec",
  "lock": { "owner":"tick-<pid>", "acquired_at":"<iso>" },
  "approvals": [ {"slug":"...","gate":"prd","decision":"approve","author":"alan@...","channel":"cli","ts":"<iso>"} ],
  "last_tick":"<iso>", "attempts": {"techspec":1} }
```
- **Escrita atômica** (write-temp + rename); `lock` por demanda evita avanço concorrente (RNF-01).
- **Mapeamento de estados (resolve ALTO-data, Opção A)**: os estados ricos do motor (`awaiting_approval`, `ready_for_exec`, `manual:*`) vivem **apenas** aqui. No `traceability-map.yaml`, o campo `state` por-artefato só recebe valores que `StageStatus::from_yaml` entende (`recorded|in_progress|approved|skipped`) — assim `can_navigate`/`current_stage` da TUI **não quebram** (não viram `Unknown`). O `orchestration.state` do topo do map (string repassada por `parse_index` sem `from_yaml`) carrega o status grosso para display.

### Fonte de verdade por decisão (resolve M1-arch / MED-data)
- **Avanço de etapa**: estado por-artefato do map (lido por `parse_index` → `can_navigate`).
- **Gate aguardando / cursor / approvals**: `.sdd/state/<slug>.json` (primário).
- **Display**: `orchestration.state` do map = cache, escrito pelo motor após confirmar a transição.
- **Divergência (crash entre dois writes)**: reconstrução a partir do `.sdd/state` (primário) na inicialização; o map é reconciliado a partir dele.

### Extensões ao `traceability-map.yaml`
- Bloco `demand:` (id, type, source, priority) + em `artifacts.<stage>`: `approved_by`, `approved_channel`; `external_links` por artefato. **Leitura** é backward-compatible (`parse_index` ignora desconhecidos). **Escrita** passa pelo `update_stage_state` extraído com round-trip serde + teste (M2-arch).

### Auditoria (`.sdd/orchestrator.jsonl`)
- Append-only: `{ts, slug, event, detail}` (`enqueue|stage_done|gate_request|decision|resume|error`), **redigido**. Mesma família dos jsonl existentes.

## Segurança
> A revisão de segurança elevou esta seção: redação e bash-guard são **defesa-em-profundidade por regex/lista**, não barreiras suficientes por si. As barreiras primárias são: (a) o motor **não executa** nada do provider na Fase 1 (só captura Markdown e valida); (b) o motor **não tem caminho que aprove gate**; (c) escrita **confinada** a paths permitidos.

- **Entrada não confiável / injeção de prompt (resolve C1-sec, RN-02)**: a demanda (`title`/`description`, possivelmente de Jira/webhook) é **dado não confiável**. Ela entra no prompt **em bloco delimitado e rotulado como dados**, nunca como instrução; instrução de sistema fixa **proíbe** o modelo emitir comandos shell, segredos ou instruções de release; campos têm **limite de tamanho** e sanitização. O gate de artefato ganha **deny-check de conteúdo** (blocos com `git push|merge|deploy`, `curl ... | sh`, padrões de segredo) além da checagem de seções.
- **Redação real e ampliada (resolve C2-sec)**: hoje `redact_text` cobre só `Bearer`, alguns pares chave-valor e `sk-ant-`/`sk-`/`AIza`. Será **expandida** para Slack (`xoxb-`/`xoxp-`), GitHub (`ghp_`/`gho_`/`github_pat_`), GitLab (`glpat-`), JWT (`eyJ…`), AWS (`AKIA…`), PEM, URL com credencial (`https://user:pass@`) e e-mail/PII. Aplicada em **todos** os novos writers (queue json, state json, orchestrator.jsonl, map, transcript de subagent), não só na saída do runner. Teste de não vazamento **por formato**.
- **Sem auto-aprovação (resolve A1-sec, RN-02)**: o motor (`auto tick/run`) **não possui** função que escreva `decision: approve`. Só `sdd demand approve/reject` (humano) e `ChatAdapter.verify_and_parse` (callback humano verificado) criam `Decision`. Validação: `channel ∈ {cli, slack}` e `author` ∈ allowlist e ≠ owner do lock. Teste: "tick não cria aprovação".
- **Allowlist de aprovadores (resolve A2-sec)**: novo bloco `approvers` em `sdd.config.yaml` (`cli: [git-email/usuário]`, `slack: [Uxxxx]`). Autor CLI derivado de `git config user.email` (não confiar só em `--by`) e checado contra a allowlist. **Default seguro**: `approvers` vazio ⇒ gate **não** é aprovável (nega), nunca "qualquer um".
- **Verificação de assinatura Slack no MVP (resolve A3-sec)**: como a aprovação por Slack é da Fase 1, `ChatAdapter` (Slack) valida `X-Slack-Signature` (HMAC com signing secret), janela de timestamp ≤ 5 min (anti-replay) e mapeia `user-id → approver`. Callback sem assinatura válida é rejeitado e auditado.
- **Path allowlisting no motor (resolve M2-sec)**: toda escrita de `engine`/`runner`/`queue`/`state` é **confinada** a `root/docs/<slug>/` e `root/.sdd/`; paths que escapam (canonicalização + checagem de prefixo; barra `..`, symlink, abs) são rejeitados.
- **bash-guard (resolve M1-sec)**: continua bloqueando `git push|merge|reset --hard|clean`, `gh pr merge`, deploys — mas é **defesa-em-profundidade** (blocklist por regex, evitável; pode não interceptar comandos rodados por provider headless que não seja o Claude Code). A barreira primária da Fase 1 é o motor **não executar** nada do provider.
- **notify sem injeção (resolve M3-sec)**: dados de demanda/externos enviados ao `notify`/AppleScript são escapados/passados por argumento, nunca interpolados em string de shell.
- **Credenciais de adapter (resolve M4-sec)**: nenhuma lida na Fase 1; quando habilitadas, env dedicada + escopo mínimo (Slack só `chat:write` + assinatura; repo **sem** permissão de push no caminho autônomo) + ponto único com redação.
- **Auth no caminho quente (resolve B1-sec)**: usar `ProviderAuthCheckMode::Fast` (pula `check_command`) no tick; check completo só em `providers doctor`.
- **`sdd.config.yaml` é superfície de confiança**: quem edita a config executa código (`check_command`/`login_command`); documentado.
- **Auditoria** (B2-sec): jsonl append simples é falsificável por quem tem FS; limitação registrada; encadeamento de hash fica como evolução futura.

## Performance
- **Observador sem busy-poll** (RNF-05): `auto run --watch` usa eventos de filesystem (crate `notify`) sobre `.sdd/queue/`; fallback `--interval`.
- **Concorrência limitada** (`--max-concurrency`, default 2); cada demanda é serial (lock).
- **Timeouts/retries por provider** propagados a `ProviderSelection` evitam travar o tick.
- **Auth Fast** no caminho quente (sem `check_command` por etapa).
- **Escritas pequenas e atômicas** (estado por demanda em arquivo próprio).

## Observabilidade
- `.sdd/orchestrator.jsonl` (transições, gates, decisões, erros) **redigido** + reuso de `hook trace-log`/`hook subagent-audit`.
- `sdd demand status --json` e `sdd auto status --json` para máquinas/dashboards.
- **TUI esteira**: estende `scan_orchestrations` para incluir demandas em fila e destacar `awaiting_approval` com cor de alerta (`pipeline.html`).
- `hook notify` (escapado) dispara o pedido de aprovação no canal configurado.

## Testes
- **Unitários (puros)**: `queue` (enqueue/dedup/claim), `state` (escrita atômica, lock, reconstrução, `schema_version` divergente → erro), `engine::tick` (ingestão→idea, avanço, pausa em gate, retomada idempotente, reprovação reabre), `approval` (paridade CLI/Slack, allowlist, default-deny), `slug` (anti-traversal + anti-colisão de hint).
- **Mapeamento de estado (não regressão de `stage.rs`)**: garantir que o map nunca recebe `awaiting_approval`/`ready_for_exec` no campo por-artefato (Opção A); `from_yaml` permanece coberto pelos valores conhecidos.
- **Integração com `FakeRunner`**: ciclo completo Idea→…→gate→approve→resume **sem provider real**; cenário de **restart no meio** (mata e recria; estado retoma do `.sdd/state`).
- **Segurança**: injeção de prompt (payload adversarial → artefato sem comando de release/segredo; deny-check bloqueia); não vazamento **por formato** (e-mail, `xoxb-`, `ghp_`, JWT, `AKIA`, URL `user:pass@`) em todos os writers; auto-aprovação (tick não aprova); anti-spoof Slack (assinatura inválida/expirada/replay rejeitada); path traversal (`title`/`slug` com `../` → escrita confinada).
- **Provider runner real**: teste `#[ignore]` gated por disponibilidade do CLI (montagem de comando headless + timeout + redação com provider stub/echo).
- **Não regressão**: suíte atual da TUI/CLI verde; gates de CI (`cargo test`, `cargo clippy -- -D warnings`, `cargo check`).

## Riscos
- **Regressão na extração de `tui::runner`** (geração compartilhada) → fazer a extração com testes de equivalência TUI antes de plugar o engine; sink genérico testado.
- **Injeção de prompt via demanda externa** → entrada como dado delimitado + instrução de sistema restritiva + deny-check de conteúdo (C1-sec).
- **Cobertura de redação insuficiente** → expansão de formatos + aplicação em todos os writers + teste por formato (C2-sec).
- **Corrida/avanço duplicado** → `lock` por demanda + transições idempotentes + escrita atômica.
- **Perda/corrupção de estado** → write-temp+rename, `schema_version`, reconstrução do `.sdd/state` como primário.
- **Guardrail headless fora do hook** → barreira primária = motor não executa nada do provider na Fase 1; bash-guard é defesa-em-profundidade (M1-sec).
- **Colisão de hint do `infer_artifact`** → **mitigado**: a detecção já é filename-first via `CANONICAL_ARTIFACT_FILES` (`main.rs:108`); o runner grava direto no nome canônico (sem arquivo temporário não-canônico). Risco rebaixado a Baixo.
- **Escopo escorregando para execução** → NO1/NO2; `runner`/`parallel`/`execution` ficam atrás de estado de handoff inerte.

## Plano de implementação
Ordem sugerida (detalhada em `04-tasks.md`):
1. **Refactors pré-requisito**: extrair `runtime::stage_runner` de `tui::runner` (sink genérico); expor `required_sections()`; extrair `update_stage_state` (serde round-trip, in-process); propagar `timeouts/retries` a `ProviderSelection`; estender `validate_state`. Cada um com teste de não regressão.
2. **Fundação de dados**: `demand` + `queue` + `state` (atômico, lock, dedup, `schema_version`, slug validado).
3. **Motor**: `engine::tick` (ingestão→idea + avanço via `can_navigate`) com `FakeRunner`.
4. **Gate + aprovação CLI**: `gate` + `approval` (allowlist, default-deny, sem auto-aprovação) + `sdd demand approve/reject` + retomada idempotente.
5. **Plugar geração**: engine consome `runtime::stage_runner`; redação ampliada + deny-check + validação no caminho; grava nome canônico.
6. **CLI da esteira**: `sdd demand enqueue/list/status`, `sdd auto tick`.
7. **Observador**: `sdd auto run` (watch/intervalo, concorrência limitada) + `.sdd/orchestrator.jsonl` redigido.
8. **Adapters seam**: traits + `LocalAdapter` default; `SlackAdapter` com verificação de assinatura (pedido + decisão); `Jira/Confluence` atrás de config (stub que falha claro se não configurado).
9. **TUI esteira** + estado de fila/gate.
10. **Endurecimento de segurança + não regressão**: testes de injeção, não vazamento por formato, path traversal, anti-spoof; suíte completa verde.

Refinamento (obrigatório por risco) consolidará flags, ordem fina e DoD antes da execução.

## Revisão (Agent Team) — decisões
| Achado | Sev | Decisão nesta v2 |
|---|---|---|
| arch-C1: StageRunner duplica `tui::runner` | Crítico | Extrair `runtime::stage_runner` e reusar em TUI+engine; não reimplementar |
| sec-C1: injeção de prompt da demanda | Crítico | Entrada como dado delimitado + system prompt restritivo + deny-check de conteúdo |
| sec-C2: redação não cobre "toda saída" | Crítico | Expandir formatos + aplicar em todos os writers + teste por formato |
| arch-A1/A2/A3: prompt/timeout/comando headless | Alto | Corrigidos na tabela de reuso + refactors pré-requisito |
| arch-A4: fronteira de módulo | Alto | `domain/orchestrator` (puro) + `runtime/` (efeito); sem top-level novo |
| sec-A1: auto-aprovação | Alto | Motor sem caminho que escreva `approve`; só humano/callback verificado |
| sec-A2: allowlist `approvers` | Alto | Novo bloco de config + autor de `git user.email` + default-deny |
| sec-A3: assinatura Slack | Alto | Trazida para o MVP (HMAC + timestamp + replay + user→approver) |
| data-ALTO: estados novos viram `Unknown` | Alto | Opção A: estados ricos só no `.sdd/state`; map só com valores de `from_yaml` |
| data-ALTO: sem `schema_version` | Alto | `schema_version: 1` em queue/state + rejeição de versão divergente |
| sec-M1/M2/M3/M4 | Médio | bash-guard = defesa-em-profundidade; path allowlist no motor; notify escapado; sem credencial de adapter na Fase 1 |
| data-MED: `validate_state`/divergência | Médio | Estender `validate_state`; `.sdd/state` primário + map cache + reconciliação |
| arch/data-Baixo: hint collision | Baixo | Mitigado (filename-first já existe); runner grava nome canônico |

## Diagramas

Marcador canônico: ```mermaid. Esta seção versiona componentes/fronteiras, tick do motor e geração de etapa com reuso do runner extraído.

### Componentes e fronteiras (núcleo de geração compartilhado)
```mermaid
flowchart TB
    subgraph CLI[sdd CLI]
      D[demand enqueue/list/status/approve]
      A[auto tick/run/status]
    end
    subgraph DOM[domain/orchestrator - puro]
      Q[queue] --> E[engine.tick]
      S[state .sdd/state - verdade primaria] --> E
      E --> G[gate]
      E --> AP[approval - so humano]
    end
    subgraph RT[runtime - efeito]
      SR[stage_runner - extraido de tui.runner]
      RED[redaction ampliada]
      ADP[adapters: local default + slack/jira/confluence]
    end
    subgraph REUSE[reuso domain/tui]
      ST[stage.can_navigate]
      OR[orchestration.parse_index]
      PROV[domain.providers]
    end
    TUI[tui.runner] --> SR
    D --> Q
    A --> E
    E --> ST
    E --> OR
    E --> SR
    SR --> PROV
    SR --> RED
    G --> ADP
    AP --> S
```

### Tick do motor
```mermaid
flowchart TD
    T[auto tick] --> N{demanda nova?}
    N -- sim --> I[dedup + cria docs/&lt;slug&gt; + estado idea]
    N -- nao --> P{elegivel?}
    I --> P
    P -- awaiting/gate --> X[nao avanca]
    P -- elegivel --> NS[proxima etapa via can_navigate]
    NS --> RUN[runtime.stage_runner gera]
    RUN --> SEC[redige + deny-check + valida secoes]
    SEC --> SAVE[grava nome canonico + map in-process]
    SAVE --> GK{etapa e gate?}
    GK -- sim --> REQ[awaiting no .sdd/state + notify + ChatAdapter]
    GK -- nao --> P
    REQ --> X
```

### Geração de etapa (reuso do runner extraído)
```mermaid
sequenceDiagram
    participant E as engine.tick
    participant Pr as domain.providers
    participant Sr as runtime.stage_runner (ex tui.runner)
    participant Cli as Provider CLI (claude/codex/...)
    participant Rd as redaction ampliada
    participant Vd as required_sections + deny-check
    E->>Pr: resolve(stage) -> provider, model, effort, timeout
    E->>Sr: run(StageRequest{prompt=build_provider_prompt, dados delimitados})
    Sr->>Cli: exec headless (DirectAdapter; timeout/retries)
    Cli-->>Sr: markdown do artefato
    Sr->>Rd: redige segredos (todos os formatos)
    Rd-->>Sr: markdown seguro
    Sr->>Vd: valida secoes + deny-check de conteudo
    Vd-->>E: StageArtifact (ok) ou RunnerError
```

> Excalidraw editável do mapa de adapters/fronteiras pode ser salvo em `docs/orquestrador-autonomo-de-demandas-sdd/assets/` se a colaboração visual ajudar no Refinamento.
