sdd-layer 0.25.3

Spec-Driven Development CLI and agent harness
# Tech Spec - DDD Effect Rust Providers CLI

## Rastreabilidade
- Orquestração: DDD Effect Rust Providers CLI
- Slug: ddd-effect-rust-providers-cli
- Stage: techspec
- Estado atual: approved
- Origem: PRD aprovado em `02-prd.md`, risk classification em `00-risk-classification.md`, discovery em `00-project-discovery.md`.
- Decisão: aprovado pelo humano em 2026-06-06; prosseguir para Tasks e Refinement obrigatório.
- Observação de Agent Team: subagentes não foram acionados porque o ambiente só permite spawn quando o usuário pede explicitamente delegação; esta Tech Spec incorpora revisões de arquitetura, segurança, contratos e testes no próprio documento.

## Visão técnica
A evolução deve preservar o CLI Rust como harness determinístico e transformar a arquitetura por fatias. A primeira fatia separa domínio, aplicação e infraestrutura dentro da mesma crate para reduzir risco. A segunda fatia introduz provider registry, config versionada e secret redaction. A terceira adiciona memória incremental auditável. A bridge Node+TypeScript com Effect.ts fica atrás de adapter opcional e não entra no caminho padrão offline.

A inspiração em Effect.ts deve ser traduzida para idioms Rust, não copiada literalmente. O core usa `Result<T, SddError>`, ports/traits, composition root e layers de runtime. Para composição assíncrona e middleware, a direção técnica é usar `tokio` para runtime async e avaliar `tower::Service`/`tower::Layer` para retry, timeout, telemetry e redaction ao redor de providers. Effect.ts entra no lado TypeScript apenas quando houver orquestração JS que se beneficie de Layers, resource safety e tracing do ecossistema Effect.

## Arquitetura
### Estado atual
- `src/main.rs` concentra parsing CLI, domínio, IO, rendering, artifact store, installer, clients, hooks e testes unitários.
- `Cargo.toml` expõe apenas um binário `sdd`.
- `build.rs` empacota arquivos gerenciados e ignora `docs/<orquestracao>/`.
- Não há workspace Node/TypeScript.

### Arquitetura alvo incremental
Primeira entrega mantém uma crate para evitar churn de packaging:

```text
src/
  main.rs                  # composition root e Cli::parse
  cli/                     # clap args, command dispatch fino
  domain/
    artifact.rs            # ArtifactStage, ArtifactState, ArtifactIndex
    orchestration.rs       # OrchestrationId, Checkpoint, Stage
    risk.rs                # RiskResult, RiskFactor, RiskPolicy
    provider.rs            # ProviderId, ModelId, Capability, AuthRef
    memory.rs              # MemoryEvent, Learning, EvidencePointer
  application/
    artifact_service.rs
    orchestration_service.rs
    provider_service.rs
    memory_service.rs
    client_surface_service.rs
  runtime/
    effect.rs              # SddRuntime, Layer, Scope, Clock, Telemetry
    errors.rs              # SddError e Result alias
    redaction.rs           # Secret<T>, RedactedDisplay, scrubbers
  infrastructure/
    fs_store.rs
    process_runner.rs
    http_client.rs
    providers/
      openai_responses.rs
      claude_agent.rs
      gemini_generate_content.rs
      opencode_gateway.rs
      custom_openai.rs
    node_effect_bridge.rs
  packaging/
    embedded.rs
    install.rs
    update.rs
```

Depois que essa divisão estabilizar, avaliar workspace:

```text
crates/sdd-core
crates/sdd-cli
crates/sdd-provider
crates/sdd-packaging
bridges/effect-ts
```

### Bounded contexts
- `cli`: entrada humana e resolução de flags/env/config.
- `orchestration`: fluxo SDD, checkpoints, estados e estágios.
- `artifact_store`: arquivos `docs/<slug>/`, schemas e traceability map.
- `risk`: classificação, políticas e evidências mínimas.
- `providers`: registry, capabilities, auth refs, requests/responses e adapters.
- `memory`: index derivado de artefatos aprovados/registrados.
- `clients`: Codex, Claude Code, opencode, Cursor, Devin, Antigravity, Trae.
- `install_update`: pacote embutido, preservação de estado local e migração.
- `hooks`: guardrails determinísticos e auditoria local.

### Runtime Effect-like em Rust
- `SddRuntime` agrega ports: filesystem, process runner, clock, telemetry, provider registry, artifact store e memory index.
- `Layer` representa dependências construíveis e substituíveis em teste.
- `Scope` registra cleanup explícito para arquivos temporários, processos e streams; não depender de async drop implícito.
- `SddError` substitui `anyhow` gradualmente nas camadas de domínio/aplicação; `anyhow` pode permanecer no boundary CLI até a migração fechar.
- Middleware de provider: redaction, timeout, retry, budget, telemetry e rate-limit.

### Bridge Node+TypeScript com Effect.ts
- Diretório opcional: `bridges/effect-ts/`.
- Protocolo: JSONL ou JSON request/response sobre stdin/stdout via `ProcessRunner`.
- Envelope estável:

```json
{
  "schema_version": "sdd.bridge.v1",
  "operation": "orchestration_step",
  "provider": "claude",
  "model": "configured-model",
  "input": {},
  "capabilities": ["tools", "streaming"],
  "redaction": { "secrets": "env-only" }
}
```

- O CLI deve funcionar sem Node quando `bridge.enabled=false`.
- A bridge não lê secrets diretamente de arquivos do SDD; recebe apenas nomes de env vars ou tokens injetados em memória pelo processo pai quando estritamente necessário.

## Contratos
### CLI
Adicionar flags compartilhadas em comandos que podem chamar modelo:

```bash
sdd orchestration --provider claude --model "<model-id>" "<ideia>"
sdd prd --provider codex --model "<model-id>" --name "<ciclo>" "<entrada>"
sdd providers list
sdd providers doctor
sdd providers doctor --json
sdd memory status
sdd memory learn --name "<ciclo>"
```

Precedência:

```text
flags CLI > variáveis de ambiente > sdd.config.yaml > defaults sem rede
```

### Config
Adicionar bloco backward-compatible em `sdd.config.yaml` e presets:

```yaml
providers:
  default: codex
  offline_default: deterministic
  profiles:
    codex:
      kind: openai_responses
      base_url: https://api.openai.com/v1
      auth_env: OPENAI_API_KEY
      default_model: ""
      capabilities: [text, json_schema, tools, streaming]
      timeout_ms: 60000
      retries: 2
      token_budget: 12000
      enabled: true
    claude:
      kind: claude_agent_sdk
      auth_env: ANTHROPIC_API_KEY
      default_model: ""
      capabilities: [text, tools, streaming]
      enabled: false
    gemini:
      kind: gemini_generate_content
      auth_env: GEMINI_API_KEY
      default_model: ""
      capabilities: [text, multimodal]
      enabled: false
    opencode:
      kind: opencode_gateway
      auth_env: OPENCODE_API_KEY
      default_model: ""
      capabilities: [text, tools]
      enabled: false
    custom:
      kind: openai_compatible
      base_url: ""
      auth_env: SDD_CUSTOM_PROVIDER_API_KEY
      default_model: ""
      capabilities: [text]
      enabled: false

runtime:
  node_effect_bridge:
    enabled: false
    command: pnpm
    args: ["--dir", "bridges/effect-ts", "start"]

memory:
  learning_enabled: true
  index_path: .sdd/memory/learnings.jsonl
  sources: [approved_artifacts, recorded_artifacts]
  redact_secrets: true
```

### Provider port
Contrato Rust proposto:

```rust
pub trait ModelProvider {
    fn id(&self) -> &ProviderId;
    fn capabilities(&self) -> &[Capability];
    fn complete(&self, request: ProviderRequest) -> ProviderFuture;
}
```

`ProviderFuture` deve ser object-safe sem forçar macro no primeiro passo:

```rust
pub type ProviderFuture =
    Pin<Box<dyn Future<Output = Result<ProviderResponse, ProviderError>> + Send>>;
```

### Provider request/response
- `ProviderRequest`: stage, orchestration id, prompt, system/developer context, model, token budget, tool policy, output format, timeout.
- `ProviderResponse`: text, structured payload opcional, usage opcional, provider metadata redigida, finish reason.
- `ProviderError`: auth_missing, auth_invalid, rate_limited, timeout, unsupported_capability, invalid_request, provider_unavailable, unsafe_output, unknown.

### Memory contracts
- Fonte canônica continua sendo `docs/<slug>/` e `traceability-map.yaml`.
- `.sdd/memory/learnings.jsonl` é cache derivado e reconstruível.
- Cada learning precisa de `source_artifact`, `source_hash`, `confidence`, `created_at`, `summary`, `recommendation`, `applies_to`.
- Não armazenar prompt completo de usuário se ele não estiver em artefato aprovado/registrado.

## Dados
### Persistência
- `sdd.config.yaml`: novo bloco `providers`, `runtime` e `memory`.
- `sdd.config.example.yaml` e `presets/*.yaml`: defaults seguros, providers desabilitados quando exigirem secret.
- `schemas/provider-config.schema.json`: novo schema opcional para validar provider config.
- `.sdd/memory/learnings.jsonl`: índice derivado; pode ser removido e reconstruído.
- `docs/<slug>/traceability-map.yaml`: registrar provider/model usado somente por identificador, nunca segredo.

### Compatibilidade
- Configs antigas sem `providers` seguem válidas.
- `sdd doctor` deve emitir warning informativo, não failure, quando não houver provider remoto e o comando não exigir rede.
- `sdd update` preserva blocos `providers`, `runtime` e `memory` existentes, como já preserva estado local.

### Consumers
- CLI humano.
- Surface agents que chamam `sdd`.
- Hooks e `sdd ci`.
- Projetos instalados que versionam `sdd.config.yaml`, `.sdd/`, `AGENTS.md` e superfícies de clientes.

## Segurança
- Secrets são representados por `AuthRef::Env("OPENAI_API_KEY")`, nunca por valor.
- `Secret<T>` não implementa `Display`; logs usam `RedactedDisplay`.
- Scrubber central remove padrões de API key conhecidos e valores lidos de env antes de gravar stdout, artifacts, events ou memory.
- `providers doctor --json` retorna `present: true|false`, `source: env`, `name: OPENAI_API_KEY`; nunca retorna valor.
- Não ler arquivos de auth de terceiros por padrão. Para opencode, preferir env var ou subprocesso isolado; qualquer leitura de auth store local exige design próprio e checkpoint de segurança.
- Node bridge recebe somente envelope redigido; se precisar token em runtime, ele entra como env do subprocesso e não no payload.
- Testes negativos obrigatórios: secret em env não aparece em stdout, stderr, traceability, memory, review, events ou JSON.

## Performance
- Comandos determinísticos (`init`, `doctor`, `clients`, `artifact`, `validate-artifact`, `ci`) não devem inicializar HTTP client nem Node bridge.
- Provider calls usam timeout configurável e retry limitado; default sem rede deve ser rápido.
- Memory learning deve ser incremental por hash de artefato para evitar reprocessar tudo.
- Node bridge usa subprocesso sob demanda inicialmente; daemon persistente só depois de métricas provarem necessidade.
- `tower::Layer`/middleware pode centralizar timeout/retry/rate-limit sem espalhar lógica por adapter.

## Observabilidade
- Introduzir eventos estruturados redigidos:

```json
{
  "event": "provider.request",
  "provider": "claude",
  "model": "configured-model",
  "stage": "techspec",
  "orchestration": "ddd-effect-rust-providers-cli",
  "duration_ms": 1234,
  "status": "ok",
  "redacted": true
}
```

- `sdd providers doctor --json` deve ser estável para CI.
- `sdd memory status --json` mostra número de learnings, fontes indexadas, arquivos pendentes e último erro redigido.
- `sdd ci` deve validar JSON/schema/provider config sem chamar rede.

## Testes
- Unitários:
  - parsing de `ProviderId`, `ModelId`, `Capability`, `AuthRef`.
  - precedência flags/env/config/default.
  - redaction de secrets reais e padrões conhecidos.
  - serialização/deserialização de provider config.
  - risk classifier reconhecendo `token`, `tokens`, `provider`, `api key`, `secret`.
- Integração CLI:
  - `sdd providers list` sem config.
  - `sdd providers doctor --json` com env presente/ausente.
  - `sdd orchestration --provider custom --model test-model --dry-run`.
  - comandos offline existentes continuam passando sem Node.
  - `sdd update` preserva providers customizados.
- Contrato provider:
  - fake provider local para sucesso, timeout, rate limit, auth missing e unsupported capability.
  - nenhum teste CI depende de rede real.
- Bridge Node:
  - teste ignorado ou feature-gated quando Node indisponível.
  - teste de envelope JSON e falha redigida.
- Memory:
  - indexa artefatos registrados/aprovados.
  - ignora drafts quando configurado para fontes aprovadas.
  - reconstrói cache por hash.

Comandos mínimos antes de merge:

```bash
cargo fmt --check
cargo clippy -- -D warnings
cargo test
cargo build --release
target/release/sdd ci
```

## Riscos
- Refactor amplo: mitigar com módulos internos antes de workspace.
- Drift de APIs externas: isolar adapters e capabilities; documentação de provider fica em referência, não espalhada.
- Vazamento de secrets: bloquear com tipos e testes negativos.
- Node obrigatório sem querer: manter bridge desabilitada e comandos offline sem dependência Node.
- Provider "codex" ambíguo: documentar como perfil OpenAI/Codex inicialmente; separar `codex_cli` no futuro se houver contrato diferente.
- Memória opaca: memory index precisa ser derivado, auditável, reconstruível e apontar para artifacts.

## Plano de implementação
1. Preparar domínio e runtime mínimo sem mudar comportamento:
   - Criar módulos `domain`, `application`, `runtime`, `infrastructure`.
   - Mover tipos puros de artifact/risk/stage gradualmente.
   - Manter comandos existentes e testes verdes.
2. Introduzir provider config e registry offline:
   - Adicionar structs, parser, defaults e schema.
   - Implementar `sdd providers list` e `sdd providers doctor`.
   - Adicionar redaction central e testes negativos.
3. Conectar provider selection aos stages em modo dry-run/determinístico:
   - Flags `--provider`, `--model`, `--offline`.
   - Registrar provider/model redigidos no artifact metadata.
   - Não chamar rede ainda sem adapter explícito.
4. Implementar adapters conhecidos por trás de feature/config:
   - `openai_responses`/Codex-compatible.
   - `gemini_generate_content`.
   - `claude_agent_sdk` ou adapter equivalente aprovado no Refinement.
   - `custom_openai`.
   - `opencode_gateway` como integração isolada e conservadora.
5. Implementar memória incremental:
   - `sdd memory status`, `sdd memory learn`.
   - JSONL derivado com hashes e evidence pointers.
   - Recomendações integradas a `sdd context recommend`.
6. Adicionar bridge Node+Effect.ts opcional:
   - Criar `bridges/effect-ts` somente quando os adapters Rust não cobrirem o caso.
   - Definir envelope JSON e testes feature-gated.
7. Atualizar pacote e docs:
   - README, docs, clients, presets, schemas, examples.
   - `sdd update` preservando blocos novos.
8. Refinement obrigatório:
   - Quebrar em tasks pequenas, estimar, definir rollout e rollback.
   - Confirmar quais adapters entram no MVP.

## Referências técnicas
- Effect.ts: https://effect.website/docs/getting-started/introduction/
- Effect Layers: https://effect.website/docs/requirements-management/layers/
- Tower Service/Layer: https://docs.rs/tower/latest/tower/
- Tokio runtime: https://docs.rs/tokio/latest/tokio/
- Reqwest HTTP client: https://docs.rs/reqwest/latest/reqwest/
- Serde derive: https://serde.rs/derive.html
- OpenAI Responses API: https://platform.openai.com/docs/api-reference/responses/object
- Claude Agent SDK: https://code.claude.com/docs/en/agent-sdk/overview
- Gemini generateContent: https://ai.google.dev/api/generate-content
- opencode providers: https://opencode.ai/docs/providers/