# GarraIA — Gateway de IA Multi-Canal

> Rust-based AI gateway: multi-channel, multi-provider LLM orchestration with mobile client.
> **Harness:** ClaudeMaxPower (branded localmente como "GarraIA SuperPowers") + official Superpowers plugin — hooks, agent teams, quality gates e subagents são parte do workflow obrigatório (ver `skills/`, `.claude/agents/`, `.claude/hooks/`).

## Identidade do Projeto

- **Nome:** GarraIA (GarraRUST)
- **Stack:** Rust (Axum 0.8) + Flutter + Tauri v2
- **Repo:** michelbr84/GarraRUST
- **Tracking:** tracker interno desde 2026-08-18 (Linear descontinuado; IDs `GAR-xxx` neste doc são registro histórico)

## Protocolo de início de sessão

1. Leia `TODO.md` (backlog operacional) e, se existir, `.garra-estado.md` (handoff local gerado por `garra max-power`; gitignored, ausente em clone novo)
2. Verifique `git status` e `git log --oneline -5`
3. Consulte a memória em `.claude/` se o contexto for relevante

## Estrutura de crates

Atualizado após plan 0145 / PR #396 (2026-05-18). **22 crates ativos** no workspace, mais o harness de benchmark `benches/agent-framework-comparison/` (fora do workspace, não é crate). Contagem ao vivo: `grep -c '^    "crates/' Cargo.toml`.

```text
crates/
  garraia-cli/        — binário "garraia" (clap), wizard, chat interativo, migrate,
                        `spinner.rs` (2026-08-30): indicador de atividade do
                        `garra chat`. Estado puro e sem relógio
                        (`SpinnerState::tick`), renderizado como um braço a mais
                        do `tokio::select!` em `stream_turn` — nunca uma task
                        própria, para não quebrar a drenagem concorrente do
                        canal limitado documentada em `chat.rs`. Escreve no
                        mesmo sink `io::Write` dos deltas, o que torna cada
                        quadro afirmável contra um `Vec<u8>`. `detect()`
                        devolve `None` (animação desligada) fora de TTY, sob
                        `NO_COLOR`, `TERM=dumb` ou `GARRAIA_NO_SPINNER`. O
                        cursor **nunca** é escondido — `\x1b[?25l` não é
                        emitido em lugar nenhum —, então nenhum caminho de
                        saída deixa o terminal sem cursor. Fallback ASCII
                        cobre quadro **e** texto (numa code page legada do
                        Windows o `ç`/`…` também vira mojibake). Proibido em
                        `ask.rs` e `mcp_server.rs`, com teste que varre o
                        fonte. `Ctrl+C` passou a cancelar o turno em vez de
                        matar o processo.
                        `config check` (GAR-379 slice 1) com validation + precedence
                        report + exit codes sysexits (0/2/65). Plan 0039 (GAR-413
                        Stage 1): novo subcomando `garraia migrate workspace
                        --from-sqlite … --to-postgres …` implementando users +
                        user_identities + PHC reassembly PBKDF2-SHA256 → PHC
                        format aceito por `garraia_auth::hashing::verify_pbkdf2`
                        + audit atômico in-tx. Plan 0040 (GAR-413 Stage 3) adiciona
                        groups + group_members: auto-cria (ou reusa) bucket
                        `--target-group-name` / `--target-group-type` (defaults
                        `'Legacy Personal Workspace'` / `'personal'`), primeiro user
                        migrado (`created_at ASC`) vira `owner`, demais `member`,
                        audit `groups.imported_from_sqlite` + N×
                        `group_members.imported_from_sqlite` atômico no mesmo tx
                        dos stages 1+2. Plan 0045 (GAR-413 Stage 5, sessão autônoma
                        Lote A-2 2026-04-22) adiciona chats + chat_members: amendment
                        normativo ao plan 0034 §7.5 (tabela legacy real é `sessions`,
                        não `conversations` — evidência em
                        `garraia-db/src/session_store.rs:105`), mapping
                        `sessions → chats` (type `'channel'`) + `chat_members`
                        (role `'owner'` para o `sessions.user_id` migrado), audit
                        `chats.imported_from_sqlite` + `chat_members.imported_from_sqlite`
                        atômico na mesma tx dos stages 1+2+3, `ChatMapping
                        { legacy_session_id → new_chat_id }` exposto em memória para
                        o stage 6 (messages) consumir em slice futuro. Stages 6+
                        (messages, memory, sessions, api_keys) em slices futuros.
  garraia-gateway/    — servidor HTTP/WS (Axum 0.8), admin API, MCP registry, router.
                        `webchat.html` (servido em `GET /` via `web_chat()` em
                        `crates/garraia-gateway/src/router.rs:382-387`) segue o design
                        system **Garra Glass** documentado em ADR 0009 (plan 0116):
                        tokens `--garra-*` canônicos, gold (`#ffd400`) para CTAs +
                        cyan (`#16d9ff`) para info/foco, glassmorphism (`backdrop-filter`)
                        em `app-header`/`chat-console`/`context-panel`, Inter 400-900 +
                        JetBrains Mono. Roll-out em 10 PRs sequenciais entregues
                        2026-05-14: #330 (foundation), #331 (app-header + chat),
                        #332 (right panel + Playwright), #333 (multi-page router +
                        Dashboard + Skins), #335 (`/api/health` + `/api/capabilities`),
                        #337 (Providers + `/api/providers/{test,default}`),
                        #338 (Channels + Sessions + `/api/channels`),
                        #339 (Settings Registry + `/api/settings/{schema,effective}` +
                        `PATCH /api/settings` dry-run),
                        #340 (Diagnostics + Logs + `/api/diagnostics`),
                        #341 (E2E + ROADMAP/README/CLAUDE). **Nunca** importar Bootstrap/AdminLTE/
                        Animate.css de CDN — ports inline (ADR 0009 §3). Endpoints
                        novos do Web Console (todos auth-free em `/api/*`, secret-free):
                        `/api/health` (Dashboard schema com `version`, `gateway_url`,
                        `uptime_secs`, `active_sessions`, `provider`, `model`,
                        `channels`, `warnings`, back-compat `checks`),
                        `/api/capabilities` (features/providers/models/channels/
                        commands/skins/experimental_flags), `/api/channels`
                        (10 known channels com status active/offline/optional),
                        `POST /api/providers/test`, `PATCH /api/providers/default`,
                        `/api/settings/schema` + `/api/settings/effective` (com secret
                        masking via `configured: bool` em vez de `value`),
                        `PATCH /api/settings` (validate + audit + dry-run; persistência
                        TOML em plan 0121a), `/api/diagnostics` (12 checks com
                        `next_step` quando não-OK).
  garraia-agents/     — LLM providers (OpenAI/OpenRouter/Anthropic/Ollama), AgentRuntime, tools
  garraia-auth/       — ✅ verify path real + extractor + endpoints (GAR-391a/b/c).
                        Tipos: IdentityProvider trait + InternalProvider + LoginPool/SignupPool
                        newtypes (private inner PgPool, validated via SELECT current_user, !Clone
                        enforced via static_assertions) + Role/Action enums + fn can() central
                        com 5×22=110-case table-driven test + Principal extractor (Axum
                        FromRequestParts) + RequirePermission struct method (NOT FromRequestParts
                        devido a const-generic limitation do Axum). Crypto: Argon2id (RFC 9106
                        m=64MiB,t=3,p=4) + PBKDF2 dual-verify + lazy upgrade transacional sob
                        FOR NO KEY UPDATE OF ui + constant-time anti-enumeration via DUMMY_HASH
                        em build.rs. JWT: HS256 access token (15min) + algorithm-confusion guards
                        + refresh token opaco com HMAC-SHA256 separado. PII: Credential.password
                        em SecretString + RedactedStorageError wrapper. Endpoints (default-on,
                        feature `auth-v1` REMOVIDA em 391c): POST /v1/auth/{login,refresh,logout,
                        signup} retornando 401 byte-identical em todos os modos de falha + 409
                        em duplicate signup. Audit em todos os terminals do login flow. Gateway
                        wiring via AuthConfig em garraia-config (4 env vars, fail-soft). Métricas
                        Prometheus baseline com bounded outcome enum. GAR-392 (pure RLS
                        matrix, 81 cenários, plan 0013 path C) ✅ entregue 2026-04-14 —
                        matriz table-driven contra pgvector/pg16 real exercitando
                        garraia_app (10 FORCE RLS tables × 4 TenantCtx + WITH CHECK
                        writes) + garraia_login e garraia_signup (grant layer allow/
                        denied). Oracle SQLSTATE distingue InsufficientPrivilege
                        (42501 grant) / PermissionDenied (42501 WITH CHECK) /
                        RlsFilteredZero (USING) / RowsVisible (any positive). GAR-391d
                        (app-layer cross-group via HTTP) entregue via plan 0014 em
                        `crates/garraia-gateway/tests/authz_http_matrix.rs` (50 cenários
                        sobre /v1/me, /v1/groups, /v1/groups/{id}). Epic GAR-391 fechado
                        em 2026-04-15.
                        Decisão: docs/adr/0005-identity-provider.md (com Amendment 2026-04-13).
  garraia-channels/   — Telegram, Discord, Slack, WhatsApp, iMessage
  garraia-db/         — SQLite (rusqlite), SessionStore, CRUD (dev/CLI single-user).
                        Plan 0036 (GAR-382): `update_mobile_user_hash` para lazy upgrade
                        de PBKDF2 → Argon2id sem mexer no schema.
  garraia-security/   — CredentialVault (AES-256-GCM), PBKDF2, RedactingWriter
  garraia-config/     — schema unificado de config (serde + validator + notify).
                        Plan 0035 (GAR-379 slice 1): novo módulo `check` com `run_check`
                        + `ConfigCheck`/`Finding`/`Severity`/`SourceReport` alimentando o
                        subcomando CLI `garraia config check [--json] [--strict]`.
                        Plan 0044 (GAR-395 slice 2) adiciona `StorageConfig` +
                        `StorageBackend` enum (`local` | `s3`) + `LocalFsConfig` +
                        `S3Config` com validações (staging_dir writable,
                        `max_patch_bytes` na faixa, S3 endpoint bem-formado, MIME
                        allow-list override via `allow_unsafe_mime_in_local_fs`),
                        4 unit tests na matriz em `check.rs`. Plan 0046 (GAR-379
                        slice 3, sessão autônoma Lote A-3 2026-04-22) adiciona
                        `AuthSection { jwt_algorithm, access_token_ttl_secs,
                        refresh_token_ttl_secs, metrics_token_ttl_hint_secs }` em
                        `AppConfig` — APENAS knobs não-secret (secrets seguem
                        env-only via `AuthConfig::from_env`, §5.1). `AuthConfig`
                        ganha fallback `GarraIA_VAULT_PASSPHRASE` (zero breaking
                        para deploys legacy). `config check` ganha 4 validações
                        (algoritmo aceito, TTL ranges, access ≤ refresh,
                        env-override Info). Redaction invariant: output (humano
                        + JSON) só reporta presença de secrets (`api_key_set:
                        true`), nunca valores.
  garraia-telemetry/  — ✅ OpenTelemetry + Prometheus baseline (GAR-384) — feature-gated
  garraia-workspace/  — ✅ Postgres 16 + pgvector multi-tenant — Fase 3 schema COMPLETO
                        (GAR-407 + GAR-386 + GAR-388 + GAR-389 + GAR-408 + GAR-390 + 391a/b/c
                        + GAR-387 + GAR-395). 37 tabelas em 33 migrations (a lista abaixo cobre as 14
                        iniciais), 32 sob FORCE RLS e 5 fora dela (users, roles,
                        permissions, role_permissions, group_invites):
                        • 001 users/groups/identities/sessions/api_keys/invites (tenant roots)
                        • 002 RBAC roles/permissions/63 role_permissions + audit_events + single-owner idx
                        • 003 folders/files/file_versions (GAR-387) — compound FK + object_key UNIQUE
                              + HMAC integrity + FORCE RLS com WITH CHECK explícito
                        • 004 chats/chat_members/messages (FTS) /message_threads com compound FK
                        • 005 memory_items/memory_embeddings (pgvector HNSW cosine)
                        • 006 tasks Tier 1 Notion-like (8 tabelas com RLS embedded + subtasks)
                        • 007 RLS FORCE wrap-up em 10 tabelas com NULLIF fail-closed
                        • 008 garraia_login NOLOGIN BYPASSRLS dedicated role (GAR-391a)
                        • 009 user_identities.hash_upgraded_at (GAR-391b prereq, plan 0011.5)
                        • 010 garraia_signup NOLOGIN BYPASSRLS + GRANT SELECT ON sessions/group_members
                              TO garraia_login (GAR-391c, Gaps A+B+C closed)
                        • 011 group_invites pending UNIQUE, 012 single-owner idx active-only,
                          013 audit_events WITH CHECK explícito (padrão seguido por 003).
                        • 014 tus_uploads (GAR-395 plan 0041) — ledger de upload tus 1.0 com
                              FORCE RLS + `tus_uploads_group_isolation` + CHECK `upload_length ≤ 5 GiB`
                              + `object_key` UNIQUE + índice parcial `expires_in_progress_idx`.
                        Handle PII-safe via skip(config) + custom Debug redaction.
                        Decisão: docs/adr/0003-database-for-workspace.md + 0004-object-storage.md.
  garraia-plugins/    — sandbox WASM inicial (wasmtime) — features adicionais na Fase 2.2
  garraia-voice/      — STT (Whisper) + TTS (Chatterbox/ElevenLabs/Kokoro)
  garraia-media/      — processamento de PDF, imagens, mídia
  garraia-skills/     — registry de skills para o agente
  garraia-learning/   — Fase 1.4 (GAR-641, ADR 0010 Accepted 2026-05-17 via PR #393 +
                        plan 0144) — Garra Learning Agent / Self-Improving Operations
                        Manual. Estado atual em main (após PR #393): 10 módulos
                        (`miner.rs`, `generator.rs`, `registry.rs`, `retriever.rs`,
                        `evaluator.rs`, `updater.rs`, `safety.rs` com gate hard-wall
                        funcional + 17 unit tests, `versioning.rs`, `skill_override.rs`,
                        `lib.rs`) + tipos `Skill`/`SkillScope`/`SkillSource` + frontmatter
                        `LearningSkillFrontmatter` com `score`/`locked`/`critical_paths_touched`/
                        `fail_count`. Sub-componentes GAR-643..GAR-651 entregues em
                        2026-05-18..20 (9/10: miner, generator, registry, evaluator,
                        auto-updater, safety gates, versioning git-backed, Web UI);
                        GAR-646 Retriever segue stub até a Fase 2.1 (garraia-embeddings)
                        e o CLI de override (`skill_override.rs`) também. Separação rígida: memória
                        (`workspace.memory_items`) ≠ skill (`learning.skills`) ≠ log
                        (`telemetry.traces`) ≠ manual distribuível (`garraia-skills` crate).
                        Nunca copiar código do Hermes Agent — Hermes é referência conceitual
                        de produto, arquitetura é própria.
  garraia-tools/      — tools compartilhadas (file ops, search, web)
  garraia-runtime/    — runtime helpers
  garraia-common/     — tipos + erros compartilhados
  garraia-glob/       — glob matching utilitário
  garraia-desktop/    — Tauri v2 app: bandeja + overlay do papagaio + Chat Bar
                        (Ctrl+Space); MSI/NSIS no Windows e .deb/AppImage no Linux
                        (v0.3.5), CLI como sidecar `binaries/garraia`
  garraia-gateway/    — Plan 0046 (GAR-379 slice 3, 2026-04-22) remove hardcoded
                        fallback inseguro `garraia-insecure-default-jwt-secret-change-me`
                        de `mobile_auth.rs` e introduz sentinel `AuthConfigMissing`
                        + getter `AppState::jwt_signing_secret() -> Result<SecretString,
                        AuthConfigMissing>`. `issue_jwt` / `issue_jwt_pub` propagam
                        `?` até handler, que converte em **503 fail-closed** (alinha
                        `/auth/*` com `/v1/auth/*` quando nenhum secret configurado).
                        Grep invariant: `std::env::var("GARRAIA_JWT_SECRET")` e
                        `std::env::var("GarraIA_VAULT_PASSPHRASE")` agora aparecem
                        SÓ em `crates/garraia-config/src/auth.rs` (oauth.rs e totp.rs
                        refactorados). `metrics_token` lido via `garraia-telemetry::config`
                        dedicado. Ver `docs/auth-config.md` para matriz de precedência.
                        Issue #824 (2026-08-17): as duas grafias da passphrase são
                        aceitas em TODOS os consumidores — cofre lê via
                        `garraia_security::vault_passphrase_from_env()` (all-caps
                        canônica > mixed-case deprecated com warning) e
                        `AuthConfig::from_env` ganha `GARRAIA_VAULT_PASSPHRASE`
                        como último fallback (`GARRAIA_JWT_SECRET` >
                        `GarraIA_VAULT_PASSPHRASE` > `GARRAIA_VAULT_PASSPHRASE`).
                        Leituras da mixed-case fora de auth.rs: presence-only em
                        check.rs/diagnostics_handler.rs + fallback do cofre em
                        garraia-security/credentials.rs (fonte única).
                        Fase 3.5 (GAR-395 slice 1 plan 0041 + slice 2 plan 0044 +
                        slice 3 plan 0047) adiciona `rest_v1::uploads` com `POST
                        /v1/uploads` (tus 1.0 Creation) + `HEAD /v1/uploads/{id}`
                        (Resume probe) + `PATCH /v1/uploads/{id}` (Core byte append)
                        + `DELETE /v1/uploads/{id}` (Termination) + `OPTIONS
                        /v1/uploads` (tus discovery) atrás de `Tus-Resumable:
                        1.0.0` precondition. Stored em `tus_uploads` (migration
                        014, FORCE RLS). Slice 2 wire `ObjectStore` em `AppState`
                        via novo `StorageConfig` (`garraia-config::model::StorageConfig`,
                        backend `local` ou `s3` feature-gated), staging FS local
                        append-only, commit two-phase ordering (blob-first via
                        `ObjectStore::put` + `files`/`file_versions` atomic + audit
                        `upload.completed` + `tus_uploads.status='completed'` →
                        `COMMIT` Postgres em seguida — plan 0044 §5.3.1). Cap
                        operacional `storage.max_patch_bytes` default 100 MiB.
                        Plan 0047 (GAR-395 slice 3, 2026-04-23 merged em `96f5c03`
                        via PR #62) fecha o epic GAR-395 adicionando: `DELETE
                        /v1/uploads/{id}` (Termination idempotente 204/404),
                        expiration worker dedicado em `uploads_worker.rs` (332 LOC)
                        + `uploads_worker_util.rs` purgando uploads `status='in_progress'`
                        expirados via `expires_in_progress_idx` com budget + jitter
                        configuráveis, e `ObjectStore::put_stream` em `LocalFs` para
                        patches grandes sem buffer integral em RAM. 752 LOC de
                        integration tests novos em `rest_v1_uploads_delete_worker.rs`.
                        GAR-395 movido para Done em 2026-04-23 17:38Z.
                        Plan 0050 Lote 2 (GAR-438, 2026-04-24 merged em `1828625`
                        via PR #64) corrige o pipeline CI `.github/workflows/ci.yml`:
                        `e2e` + `playwright` antes chamavam `./target/release/garraia-gateway`
                        (binário inexistente — `garraia-gateway` é biblioteca) e o
                        mascaramento via `continue-on-error: true` escondia o
                        `No such file or directory`. Fix cirúrgico: `cargo build
                        --bin garraia --release` + `./target/release/garraia start
                        --host 0.0.0.0 --port 3888` + `services: postgres:16.8-alpine`
                        + envs mínimas (`GARRAIA_JWT_SECRET`, `GARRAIA_REFRESH_HMAC_SECRET`,
                        `GARRAIA_LOGIN_DATABASE_URL`, `GARRAIA_SIGNUP_DATABASE_URL`
                        com `::add-mask::`). 4 de 7 `continue-on-error` removidos
                        permanentemente; 3 remanescentes rastreáveis por issue
                        (L286→GAR-444 mock LLM, L402→GAR-443 UI drift, L443→Lote 4
                        RUSTSEC).
                        Plan 0052 (GAR-443 Lote 4, 2026-04-24) migra
                        `tests/playwright/mcp-manager.spec.ts` para `getByTestId(...)`
                        ancorados em `data-testid` adicionados a `admin.html`
                        (`showMcpForm` + `renderMcpPage`). **Convenção**: specs
                        Playwright do admin DEVEM preferir `data-testid` estáveis
                        em vez de `placeholder*=` ou `getByRole(button,{name})` —
                        copy/placeholder são propriedade da UX e podem mudar; os
                        testids são contrato de teste. Remove `continue-on-error: true`
                        do step `Run Playwright tests` (`ci.yml:425`); CoE count
                        cai 2→1 (só RUSTSEC remanescente).
  garraia-embeddings/ — Fase 2.1 (GAR-372, ADR 0002 Accepted 2026-04-21, plan
                        0145 scaffold branch `feat/garraia-embeddings-scaffold`).
                        Public surface only: traits `EmbeddingProvider` (async,
                        `embed`/`embed_batch`/`model_id`) + `VectorStore`
                        (async, `insert`/`search`/`delete`, scoped by `Scope` +
                        `Option<Uuid> group_id`); strong types
                        `Scope`/`EmbeddingVector(768)`/`Document`/`Chunk`/
                        `SearchHit`; `HybridQuery` typed builder (rejects
                        cross-tenant via build-time scope ↔ group_id check);
                        `DeterministicProvider` (sha2-backed, default-on via
                        feature `testing-provider`) para unit tests downstream.
                        Sem `PgVectorStore` real, sem `MxbaiProvider`, sem
                        wiring em learning/agents — esses são slices futuros.
                        23 unit tests verdes.
  garraia-storage/    — Fase 3.5 (GAR-394 slice 1 plan 0037 + slice 2 plan 0038) —
                        trait ObjectStore + LocalFs baseline + path_sanitize. Slice 2
                        adiciona `S3Compatible` (aws-sdk-s3) atrás da feature
                        `storage-s3` com SSE-S3 obrigatório, MIME allow-list
                        compartilhada com LocalFs (ADR 0004 §Security 3), HMAC-SHA256
                        integrity sobre `{key}:{version_id}:{sha256_hex}` via
                        `PutOptions::hmac_secret` (ADR 0004 §Security 4), presigned
                        URLs reais com TTL range [30s, 900s]. MinIO coberto via
                        endpoint override. Integration tests: MinIO testcontainer
                        gated pela feature. Wiring no `garraia-gateway` +
                        `garraia-config::StorageConfig` fica para slice 3.
apps/
  garraia-mobile/     — Flutter Android client (Riverpod, go_router, Dio)
```

### Crates planejados (ROADMAP AAA Fases 2-3)

```text
(nenhum no momento — garraia-embeddings foi promovido em 2026-05-18 via plan 0145)
```

> `garraia-embeddings/` promovido de "planejado" para "ativo" em 2026-05-18 (scaffold per
> ADR 0002 + plan 0145; `PgVectorStore` real + `MxbaiProvider` ainda Backlog em
> GAR-372 sub-issues a criar).
>
> `garraia-learning/` promovido para "Crates ativos" em 2026-05-17 (ADR 0010 → Accepted,
> via PR #393 + plan 0144). Sub-componentes restantes (GAR-643..GAR-651) materializam
> miner / generator / registry full / retriever / evaluator / auto-updater / versioning /
> web UI uma issue por vez.

### Benchmarks fora do workspace

```text
benches/
  agent-framework-comparison/  — harness reprodutível (shell) que valida os claims
                     comparativos do README contra CLIs concorrentes. NÃO é crate
                     nem workspace member: são `run.sh` + `README.md` + `results/`.
                     Mede tamanho de binário, pico de RSS e cold start; constrói o
                     GarraIA do checkout atual e fixa os concorrentes por ref via
                     env var. Resultados entram versionados em `results/<data>-<host>/`.
```

> `benches/database-poc/` (GAR-373, Postgres vs SQLite) foi removido em 2026-08-16
> conforme o mandato original ("deletar depois que garraia-workspace estabilizar").
> Os números que ele produziu seguem citados em ADR 0003 e nas migrations 005/007.

## Convenções de código

### Rust

- `AppState` é `Arc<AppState>` — import via `crate::state::AppState`
- DB via `SessionStore` (rusqlite, sync, `tokio::sync::Mutex`)
- Axum 0.8: `FromRequestParts` usa AFIT nativo — **sem** `#[async_trait]`.
  Exceção documentada: traits que são usados como `dyn Trait` (ex.:
  `garraia_storage::ObjectStore`) usam `#[async_trait]` por causa de
  limitação de AFIT + `dyn` em Rust stable. Ver plan 0037 §5.1.
- Usar `?` operator para tratamento de erros (não `unwrap()` em produção)
- SQL queries via `params!` macro (nunca concatenar strings)
- `cargo check -p <crate>` antes de qualquer commit
- `cargo clippy --workspace` para linting

### Flutter

- State management: Riverpod + code generation
- Navigation: go_router com auth redirect
- HTTP: Dio com `_AuthInterceptor` (JWT bearer)
- Nunca usar `withOpacity()` — usar `withValues(alpha:)`

### Shell / Scripts

- `set -euo pipefail` em todos os scripts
- Usar `#!/usr/bin/env bash` (não `/bin/bash`)
- Paths devem funcionar cross-platform (usar `which` ou env vars)

### Convenção de datas

- **Project narrative dates** (ROADMAP, plans, ADRs, READMEs, commit prose, doc paragraphs como "entregue em YYYY-MM-DD") usam **America/New_York (Florida)** local time. Nunca usar UTC para data narrativa do projeto sem dizer explicitamente.
- **API timestamps, audit_events, log timestamps, JWT `iat`/`exp`, `expires_at` em response bodies** são sempre **UTC ISO 8601 com sufixo `Z`** — declaração explícita de UTC.
- Quando estiver em dúvida em prosa de doc/plan/commit, use o local time da Flórida. Se a referência for tecnicamente UTC (ex.: timestamp de log capturado), anote `(UTC)` inline.

### Commits

- Formato: Conventional Commits (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`)
- Imperativo: "adiciona feature" (não "adicionada feature")
- Limite 72 chars no assunto

### Changelog

- **NUNCA** editar o `CHANGELOG.md` direto num PR de feature/fix. Cada PR
  deixa um fragmento em `changelog.d/<seção>/<numero>-<slug>.md` — arquivos
  diferentes nunca conflitam, e era a colisão na seção `[Unreleased]` que
  fazia todo par de PRs paralelos precisar de um merge de resolução.
- Seções válidas: `added`, `changed`, `deprecated`, `removed`, `fixed`,
  `security` (Keep a Changelog 1.1.0). Texto sem acento, como o resto do
  `CHANGELOG.md`.
- `python3 scripts/changelog/assemble.py --check` valida os fragmentos;
  `--write` é **passo de release** (`docs/releasing.md` §1.3), não de PR.
- Ver `changelog.d/README.md`.

## Regras absolutas

1. **NUNCA** commitar `.env`, credenciais ou tokens
2. **NUNCA** `rm -rf /`, `rm -rf ~` ou fork bombs
3. **NUNCA** force push para `main`
4. **NUNCA** usar `unwrap()` em código de produção (apenas em testes)
5. **NUNCA** concatenar strings em SQL queries — `params!` (rusqlite) ou `sqlx::query!` (Postgres).
   Desde o sqlx 0.9 o compilador reforça isso: `sqlx::query()` / `query_as()` / `query_scalar()` /
   `raw_sql()` só aceitam `&'static str` (trait `SqlSafeStr`), então qualquer `format!()` vira erro
   `E0277`. **Exceção única e delimitada:** identificador SQL que o Postgres não aceita como bind
   parameter (nome de tabela/coluna, `ORDER BY` dinâmico, DDL). Nesses casos usar
   `sqlx::AssertSqlSafe` (note: `sqlx::AssertSqlSafe`, **não** `sqlx::sql_str::AssertSqlSafe`, que
   não é público) **com comentário de auditoria nomeando a origem fechada do valor**. Não é
   permitido em `crates/*/src/` — só em alvos de teste; hoje há exatamente 3 usos, todos em
   `tests/`. Valor de dado continua **sempre** por `.bind()`. Para GUC de RLS, use
   `SELECT set_config('app.current_group_id', $1, true)` com bind — nunca `SET LOCAL` interpolado.
6. **NUNCA** expor secrets/PII em logs (`GARRAIA_JWT_SECRET`, `GARRAIA_REFRESH_HMAC_SECRET`, `GARRAIA_METRICS_TOKEN`, `ANTHROPIC_API_KEY`, etc.)
7. **NUNCA** ignorar erros de compilação do `cargo check`
8. **SEMPRE** escrever ADR em `docs/adr/NNNN-*.md` antes de decisão arquitetural irreversível (Postgres vs SQLite, vector store, storage backend, etc.) — ver `ROADMAP.md` §3.1
9. **SEMPRE** migrations Postgres forward-only (colunas novas → backfill → NOT NULL depois)
10. **SEMPRE** testes de autorização cross-group antes de merge em qualquer rota nova de `garraia-workspace`/`garraia-auth`
11. **SEMPRE** usar a `garraia_login` BYPASSRLS dedicated role exclusivamente em paths de credential verification (login + lazy upgrade PBKDF2→Argon2id + extractor membership lookup + refresh token verify/revoke). Acesso ao role só via `garraia-auth::LoginPool` newtype — nunca raw `PgPool`. Documentado em `docs/adr/0005-identity-provider.md` (com Amendment 2026-04-13 cobrindo Gaps A/C).
12. **NUNCA** ler `user_identities.password_hash` no app pool role (`garraia_app`) — RLS filtra para 0 rows. Tratar 0 rows como "user not found" é anti-pattern (significa "RLS bloqueou"). Sempre usar `garraia_login` via login endpoint. Ver ADR 0005 §"Anti-patterns".
13. **SEMPRE** usar a `garraia_signup` BYPASSRLS dedicated role exclusivamente para o signup flow (`POST /v1/auth/signup`). Acesso só via `garraia-auth::SignupPool` newtype — nunca raw `PgPool`, nunca substituível pelo `LoginPool`. O role tem `INSERT` em `users`/`user_identities` mas NENHUM acesso a `sessions`, `messages`, `chats`, `memory_*`, `tasks*`, `groups`, `group_members` ou qualquer dado de tenant. Migration 010, ADR 0005 §"Amendment 2026-04-13" Gap B.
14. **SEMPRE** passar por `garraia_common::ssrf` (`vet_url` + `pinned_client`) qualquer requisição HTTP de saída cuja URL venha de um request, de config editável por request, ou de uma tool call de LLM. **NUNCA** `reqwest::get(url)` cru nesses caminhos. O guard faz allowlist de esquema, resolve o host uma vez e bloqueia faixas internas, pina os IPs vetados (`resolve_to_addrs`, anti-DNS-rebinding), desliga redirects e limita o corpo. Auth não substitui o guard: `plugins_handler` exige `Permission::ManagePlugins` **e** valida a URL. Onde o alvo legítimo é local (Ollama, MCP self-hosted), usar `IpScope::AllowPrivate` — que ainda bloqueia link-local (`169.254.169.254`), CGNAT, multicast e unspecified. Ver `docs/security/threat-model.md` §5.6.
15. **NUNCA** renomear ou remover os assets de release "crus" (`garraia-<os>-<arch>[.exe]`)
    nem seus `<asset>.sha256` irmaos. `crates/garraia-cli/src/update.rs:42-48` resolve o
    asset por nome exato e `:127` exige o `.sha256` irmao, entao qualquer renomeacao quebra
    o `garra update` de toda instalacao ja existente no momento em que ela pular para essa
    versao. Formatos novos (`.tar.gz`, `.zip`, `.msi`, `.deb`, `.rpm`, `.AppImage`) entram
    **aditivamente**, ao lado.
    O `select_checksum_line` do `install.sh` (e o `Select-ChecksumLine` do `install.ps1`)
    ancoram o nome em fim de linha justamente para que `garraia-linux-x86_64` nunca case
    com `garraia-linux-x86_64.tar.gz`.
16. **SEMPRE** manter `install.sh` e `install.ps1` em paridade de comportamento. Sao o mesmo
    contrato em dois sistemas operacionais (flags, env vars, precedencia env-vence-flag,
    verificacao SHA-256, encadeamento `init`/`start`); mudou um, muda o outro, e as suites
    em `tests/install_sh/` e `tests/install_ps1/` espelham-se uma a outra. A paridade
    vale também para o *serving*: cada instalador tem cópia estática + entrada no
    workflow de sync do repo do site (regra 17) e sondas espelhadas no
    `install-endpoints.yml`.
17. **O site `garraia.org` NÃO está neste repositório.** Ele vive em
    `michelbr84/garraia-74c335d5` (Vite + React + shadcn, publicado pelo Lovable).
    Editar `install.sh`/`install.ps1` aqui **não** muda o que `garraia.org` serve.
    O hosting da Lovable **IGNORA** `public/_redirects`: `/install.sh` e
    `/install.ps1` só funcionam porque existem **cópias estáticas em `public/`**
    daquele repo (o Vite copia `public/*` para a raiz do build), mantidas em
    sincronia com o raw do `main` daqui pelo workflow de sync do site
    (`sync-install-sh.yml`, cron diário). Publicar em produção é um passo
    **manual** do Lovable (Publish no editor, ou `deploy_project` via MCP) —
    merge no repo do site não muda o que garraia.org serve até alguém publicar.
    Essa separação já causou dois apagões do
    `irm https://garraia.org/install.ps1 | iex`: em 2026-08, HTML da home com
    HTTP 200 (o fallback SPA engolia a rota, na época em que se acreditava que
    `_redirects` valia); em 2026-08-31, 404 puro (faltava `public/install.ps1`).
    Ao adicionar um instalador novo, adicione **junto** o arquivo estático + a
    entrada no sync do repo do site, e republique. O `_redirects` e o
    `infra/nginx.conf` de lá são future-proofing para self-hosting, não o
    mecanismo vivo. O workflow `install-endpoints.yml` (agendado, não é gate de
    PR) sonda todas as URLs documentadas, imprime o código HTTP de cada sonda,
    roda todas antes de falhar e verifica o contrato real do Windows com
    `Invoke-RestMethod`. O Worker em `deploy/installer-worker/` está
    descontinuado e nunca serviu o `garraia.org`.
18. **NUNCA** cherry-pickar ou misturar arquivos de migration (`crates/garraia-workspace/migrations/`) entre repositórios diferentes sem verificar a numeração estrita e linear. Os esquemas e numerações de migrations são independentes e forward-only.

## Framework de Desenvolvimento: Superpowers

O projeto utiliza [Superpowers](https://github.com/obra/superpowers) como framework primário de workflow de desenvolvimento.

- **Config:** `.claude/superpowers-config.md` — contexto do projeto para o Superpowers
- **Bridge:** `skills/superpowers-bridge.md` — mapeamento entre skills locais e Superpowers
- **Regra:** Para features novas, bugs complexos e refactoring → usar workflow Superpowers (brainstorming → spec → plan → TDD → review → merge)
- **Skills locais** são usadas para operações específicas: pre-commit, generate-docs, translate, shell-explain

## Skills disponíveis

| Skill | Uso |
| ------- | ----- |
| `/superpowers-bridge` | Mapeamento skills locais ↔ Superpowers |
| `/review-pr` | Revisa PR com code-reviewer + security-auditor |
| `/tdd-loop` | Red-Green-Refactor automático |
| `/fix-issue` | Corrige issue GitHub via TDD |
| `/pre-commit` | Validação pré-commit (segredos, debug, lint) |
| `/refactor-module` | Refactoring seguro com testes |
| `/assemble-team` | Monta equipe de agentes coordenados |
| `/generate-docs` | Gera documentação automática |
| `/code-review` | Revisão de código inline |
| `/git-assist` | Ajuda com git workflow |
| `steward` | Guia para dirigir PR até o verde — quais falhas de CI são ambientais e quais são suas. Não é slash command: é lida automaticamente por um agente ao reagir a evento de CI/review num PR. |

## Agents disponíveis

| Agent | Papel |
| ------- | ------- |
| `code-reviewer` | Revisor sênior Rust/Flutter |
| `security-auditor` | Auditor OWASP, JWT, crypto |
| `doc-writer` | Escritor técnico PT-BR/EN |
| `team-coordinator` | Orquestrador de equipes de agentes |

## Ferramentas preferenciais

- Buscar arquivos: `Glob` (não `find`)
- Buscar conteúdo: `Grep` (não `grep`)
- Ler arquivos: `Read` (não `cat`)
- Editar arquivos: `Edit` (não `sed`)
- Testar Rust: `cargo test -p <crate>`
- Testar Flutter: `flutter test`
- Lint Rust: `cargo clippy --workspace`

## AI Quality Ratchet (`.quality/` + `scripts/quality/`)

Sistema de Quality Gates inspirado no padrão Catraca: métricas só sobem ou ficam, **nunca regridem**. Vide `plans/0064-quality-ratchet-pr1.md` para o scaffold inicial e `.quality/README.md` para a filosofia completa.

**Status atual: PR-1 — report-only.** Nenhum PR é bloqueado pelo ratchet ainda. Workflow `.github/workflows/quality-ratchet.yml` posta `quality-report.md` como comentário no PR e segue. Promoção a bloqueante (`compare.py --mode enforce`) entra em PR-4 com aprovação explícita.

### Comandos rápidos

```bash
# Coleta rápida (default — sob 10s):
bash scripts/quality/collect-metrics.sh > current-metrics.json

# Comparar contra baseline (report-only — sempre exit 0):
python3 scripts/quality/compare.py --mode report-only \
    .quality/baseline.json current-metrics.json

# Propor novo baseline (gera .proposed.json — NÃO commita):
python3 scripts/quality/freeze-baseline.py current-metrics.json

# Rodar testes dos parsers:
python3 -m pytest scripts/quality/tests/
```

### Regras absolutas (ratchet)

- **NUNCA** editar `.quality/baseline.json` manualmente para "passar" o ratchet — é fraude. Use `freeze-baseline.py` que gera `.quality/baseline.proposed.json` para review humano.
- **NUNCA** adicionar `continue-on-error: true` em workflows. Modo report-only é controlado pela flag `compare.py --mode report-only`.
- **NUNCA** desativar gates pré-existentes do `ci.yml` (fmt/clippy/test/audit/deny/etc.).
- Se o `/quality-babysit` propuser correção que toca segurança, auth, storage, RLS, secrets ou CI crítico → chamar `security-auditor` + `code-reviewer` agents antes de continuar (ver `.claude/commands/quality-babysit.md` §Guardrails).

## Referências

- @imports `.claude/agents/` para agentes especializados
- @imports `skills/` para workflows reutilizáveis
- @imports `TODO.md` (backlog operacional) e `.garra-estado.md` (handoff local, gitignored) para estado da sessão anterior
- @imports `ROADMAP.md` — plano AAA em 7 fases, fonte de verdade do planejamento
- @imports `deep-research-report.md` — base arquitetural da Fase 3 (Group Workspace multi-tenant)
- @imports `docs/adr/` — decisões arquiteturais: 16 ADRs (0001-0016), todas **Accepted**. Ver `docs/adr/README.md` para o índice.
- Tracking: tracker interno (o Linear foi descontinuado em 2026-08-18 — não criar/consultar issues lá; IDs `GAR-xxx` permanecem como registro histórico de entregas)
