agentleFS
Sign inSign up

GarraRUST

michelbr84/GarraRUST/CLAUDE.md

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/). 22 crates ativos no workspace (contagem ao vivo: grep -c '^ "crates/' Cargo.toml), mais o harness benches/agent-framework-comparison/ (fora do workspace, não é crate). O histórico de entrega (plans, PRs, datas, IDs GAR-xxx) vive em plans/, docs/adr/ e CHANGELOG.md — aqui fica só…

CLAUDE.md18 starsChanged 14 days ago
  • Reads credentials
  • Deletes or force-pushes
# 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

**22 crates ativos** no workspace (contagem ao vivo: `grep -c '^    "crates/' Cargo.toml`),
mais o harness `benches/agent-framework-comparison/` (fora do workspace, não é crate).
O histórico de entrega (plans, PRs, datas, IDs `GAR-xxx`) vive em `plans/`, `docs/adr/`
e `CHANGELOG.md` — aqui fica só o estado atual e os invariantes que um agente precisa
respeitar.

```text
crates/
  garraia-cli/        — **Dois nomes, ambos certos, cada um no seu contexto** — não
                        "consertar" um para casar com o outro: o **alvo de cargo** é
                        `garra` (`[[bin]]` em `crates/garraia-cli/Cargo.toml:10`, e o
                        clap idem), então é `cargo build --bin garra`; o **pacote** é
                        `garraia`; e o **asset de release / binário instalado** é
                        `garraia` (`install.sh:86`), com `garra` chegando como symlink
                        relativo ao lado dele (`install_garra_alias`, #1328; no Windows
                        o shim `garra.cmd`). Numa instalação os dois comandos funcionam,
                        e é por isso que `garraia config check` nos docs e `garra config
                        check` no `docs/execution-profiles.md` estão os dois corretos.
                        Wizard, chat interativo, `config check`
                        (plan 0035; exit codes sysexits 0/2/65), `migrate workspace
                        --from-sqlite --to-postgres` (plans 0039/0040/0045: users +
                        identities + groups + chats, audit atômico in-tx; stages 6+
                        pendentes). `spinner.rs`: estado puro sem relógio, renderizado
                        como braço do `tokio::select!` em `stream_turn` — nunca task
                        própria; `detect()` devolve `None` fora de TTY / `NO_COLOR` /
                        `TERM=dumb` / `GARRAIA_NO_SPINNER`; nunca esconde o cursor;
                        fallback ASCII; proibido em `ask.rs` e `mcp_server.rs` (teste
                        varre o fonte). `Ctrl+C` cancela o turno, não o processo.
                        `desktop.rs` (#1181 M1): `garra desktop [--status]
                        [--no-launch]` localiza e lança o app instalado via
                        `garraia_desktop_core::locate` — exit 0/69/70
                        (sysexits). **Zero dependência de Tauri na CLI**
                        (teste varre o fonte); "já está rodando" fica para o
                        M2, com canal de instância única na casca.
  garraia-gateway/    — servidor HTTP/WS (Axum 0.8), admin API, MCP registry, router.
                        `webchat.html` (`GET /`) segue o design system Garra Glass
                        (ADR 0009): tokens `--garra-*`, gold `#ffd400` para CTAs, cyan
                        `#16d9ff` para info/foco, Inter + JetBrains Mono. **Nunca**
                        importar Bootstrap/AdminLTE/Animate.css de CDN — ports inline.
                        Web Console em `/api/*` (auth-free, secret-free): `/api/health`,
                        `/api/capabilities`, `/api/channels`, `POST /api/providers/test`,
                        `PATCH /api/providers/default`, `/api/settings/{schema,effective}`
                        (secrets como `configured: bool`, nunca valor), `PATCH /api/settings`
                        (validate + audit + dry-run), `/api/diagnostics` (checks com
                        `next_step`; `voice.tts`/`voice.stt` sondam servidores locais
                        com modo voz ligado).
                        Perfil de execução (ADR 0024, #1329): `bootstrap::execution`
                        traduz `execution.profile` (`standard` | `isolated-pod`, env
                        `GARRAIA_EXECUTION_PROFILE` vence, inválido = boot recusado)
                        em política pura — raiz do MCP `filesystem` por perfil
                        (nunca `$HOME`) e anúncio de boot; **nunca** inferir de
                        `/.dockerenv`/cgroup (testes varrem o fonte). Superfícies:
                        checks `execution.profile` + `mcp.filesystem_root`, linha
                        `security.execution_profile`; `docs/execution-profiles.md`.
                        Auth: sem fallback de JWT secret hardcoded — `AppState::
                        jwt_signing_secret() -> Result<SecretString, AuthConfigMissing>`
                        e handlers respondem **503 fail-closed** sem secret (plan 0046).
                        `std::env::var("GARRAIA_JWT_SECRET")` / `("GarraIA_VAULT_PASSPHRASE")`
                        só em `crates/garraia-config/src/auth.rs`; matriz de precedência
                        em `docs/auth-config.md`. Passphrase do cofre: as duas grafias
                        são aceitas via `garraia_security::vault_passphrase_from_env()`
                        (all-caps canônica > mixed-case deprecated com warning); em
                        `AuthConfig::from_env` a ordem é `GARRAIA_JWT_SECRET` >
                        `GarraIA_VAULT_PASSPHRASE` > `GARRAIA_VAULT_PASSPHRASE` (#824).
                        Uploads tus 1.0 em `rest_v1::uploads` (`POST/HEAD/PATCH/DELETE
                        /v1/uploads[/{id}]` + `OPTIONS`, precondition `Tus-Resumable:
                        1.0.0`), ledger `tus_uploads` (migration 014, FORCE RLS),
                        `ObjectStore` em `AppState` via `StorageConfig`, commit two-phase
                        blob-first (plan 0044 §5.3.1), cap `storage.max_patch_bytes`
                        default 100 MiB, worker de expiração em `uploads_worker.rs`.
                        CI (`.github/workflows/ci.yml`): o binário é `garra`
                        (`cargo build --bin garra --release`; `garraia-gateway` é lib);
                        zero `continue-on-error` ativo (higiene #1094). Specs Playwright
                        do admin DEVEM usar `data-testid` estáveis (contrato de teste),
                        não `placeholder*=` nem `getByRole(button,{name})` (plan 0052).
  garraia-agents/     — LLM providers (OpenAI/OpenRouter/Anthropic/Ollama), AgentRuntime, tools
  garraia-auth/       — IdentityProvider trait + InternalProvider; `LoginPool`/`SignupPool`
                        newtypes (inner PgPool privado, validado via SELECT current_user,
                        !Clone via static_assertions); Role/Action enums + `can()` central
                        (teste table-driven 5×22); `Principal` extractor (FromRequestParts)
                        + `RequirePermission` como método de struct (limitação
                        const-generic 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` + anti-enumeração constant-time via
                        `DUMMY_HASH` (build.rs). JWT HS256 (15 min) com guards de
                        algorithm-confusion + refresh opaco HMAC-SHA256. PII:
                        `Credential.password` em `SecretString`. Endpoints default-on:
                        `POST /v1/auth/{login,refresh,logout,signup}` — 401 byte-identical
                        em toda falha, 409 em signup duplicado, audit em todos os
                        terminais. Matriz RLS (81 cenários, pgvector/pg16 real) e matriz
                        HTTP cross-group (`tests/authz_http_matrix.rs`, 50 cenários) são
                        o gate. Decisão: `docs/adr/0005-identity-provider.md`.
  garraia-channels/   — Telegram, Discord, Slack, WhatsApp, iMessage
  garraia-db/         — SQLite (rusqlite), SessionStore, CRUD (dev/CLI single-user).
                        `update_mobile_user_hash` faz lazy upgrade PBKDF2 → Argon2id
                        sem mexer no schema.
  garraia-security/   — CredentialVault (AES-256-GCM), PBKDF2, RedactingWriter
  garraia-config/     — schema unificado de config (serde + validator + notify). Módulo
                        `check` (`run_check` + `ConfigCheck`/`Finding`/`Severity`/
                        `SourceReport`) alimenta `garraia config check [--json] [--strict]`.
                        `StorageConfig` + `StorageBackend` (`local` | `s3`) + `LocalFsConfig`
                        + `S3Config` com validações (staging_dir gravável, faixa de
                        `max_patch_bytes`, endpoint S3, MIME allow-list override via
                        `allow_unsafe_mime_in_local_fs`). `AuthSection` em `AppConfig` só
                        com knobs não-secret (algoritmo, TTLs, hint de métricas) —
                        secrets seguem env-only via `AuthConfig::from_env`. Invariante
                        de redaction: `config check` (humano + JSON) só reporta presença
                        (`api_key_set: true`), nunca valores.
                        Boot gate (#1247, `boot_gate`): o mesmo `run_check` roda em todo `start`/`restart`/`start -d`; so `Error` da allowlist fechada `BLOQUEIA_O_BOOT` (hoje: TLS pela metade) recusa (exit 78; escotilha `GARRAIA_ALLOW_INVALID_CONFIG=1`, exatamente `1`).
  garraia-telemetry/  — OpenTelemetry + Prometheus baseline — feature-gated
  garraia-workspace/  — Postgres 16 + pgvector multi-tenant (Fase 3 schema completo).
                        37 tabelas em 33 migrations; 32 sob FORCE RLS e 5 fora (users,
                        roles, permissions, role_permissions, group_invites). Marcos:
                        001 tenant roots (users/groups/identities/sessions/api_keys/
                        invites) · 002 RBAC + audit_events · 003 folders/files/
                        file_versions (compound FK, object_key UNIQUE, HMAC integrity)
                        · 004 chats/chat_members/messages (FTS)/threads · 005 memory +
                        pgvector HNSW cosine · 006 tasks Notion-like · 007 FORCE RLS
                        wrap-up com NULLIF fail-closed · 008 role `garraia_login`
                        NOLOGIN BYPASSRLS · 010 role `garraia_signup` · 013 audit WITH
                        CHECK explícito · 014 `tus_uploads` (CHECK ≤ 5 GiB, índice
                        parcial `expires_in_progress_idx`). Handle PII-safe via
                        skip(config) + Debug redigido. ADRs 0003 e 0004.
  garraia-plugins/    — sandbox WASM inicial (wasmtime) — features adicionais na Fase 2.2
  garraia-voice/      — STT (Whisper) + TTS (Chatterbox/Hibiki/LM Studio
                        OpenAI-compativel). Adaptadores ElevenLabs e Kokoro
                        vivem em `garraia-channels::voice_channel` (feature
                        `voice`, que nenhuma crate liga) e nao chegam ao
                        gateway — `server.rs` so casa `hibiki`/`lmstudio`,
                        o resto cai no Chatterbox.
  garraia-media/      — processamento de PDF, imagens, mídia
  garraia-skills/     — registry de skills para o agente. Frontmatter com `kind`
                        (`instruction` default · `hardware-adapter` ·
                        `hardware-preset`) + bloco `provides` (#1131): a crate
                        e de DADOS — ela nao conhece `RiskClass`, para que
                        conteudo empacotado nao classifique o proprio risco.
                        Varredura desce em subdiretorios (`hardware/<slug>/
                        SKILL.md`), ignora symlink e tem teto de profundidade.
  garraia-learning/   — Garra Learning Agent / Self-Improving Operations Manual (ADR
                        0010). 10 módulos: miner, generator, registry, retriever (stub
                        até garraia-embeddings real), evaluator, updater, safety (gate
                        hard-wall), versioning (git-backed), skill_override (CLI stub),
                        lib. Frontmatter `LearningSkillFrontmatter` com `score`/`locked`/
                        `critical_paths_touched`/`fail_count`. Separação rígida: memória
                        (`workspace.memory_items`) ≠ skill (`learning.skills`) ≠ log
                        (`telemetry.traces`) ≠ manual distribuível (crate `garraia-skills`).
                        Nunca copiar código do Hermes Agent — referência só conceitual.
  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,
                        CLI como sidecar `binaries/garraia`
  garraia-desktop-core/ — ADR 0021 (epic #1181, M0): núcleo do Control Center
                        **sem Tauri**, e por isso dentro dos gates obrigatórios
                        — a casca `garraia-desktop` roda com `--exclude` em
                        clippy/build/test e tem zero cobertura. Três módulos:
                        `state` (ligado/desligado como estado puro, sem relógio
                        nem I/O, no padrão do `spinner.rs`; `Desired` = o que o
                        usuário pediu, `Power` = o que de fato acontece, então
                        queda vira `Failed` sem apagar a intenção), `detect`
                        (agentes GarraIA/Hermes/OpenClaw/Claude Code/AgentDeck
                        por leitura de `PATH` + config, **nunca executando** o
                        binário: nome na PATH não prova identidade — sem
                        corroboração fica `Ambiguous`, e um teste varre o
                        próprio fonte atrás de `Command::new`/`.spawn()`) e
                        `supervise` (launch/restart/kill extraídos do
                        `gateway.rs` da casca, sem `unwrap` em lock, sem
                        `sleep` por dentro — `RestartPolicy::backoff` devolve o
                        intervalo e quem tem o relógio espera — e com o filho
                        morrendo junto com o supervisor via `Drop`) mais
                        `locate` (M1, #1181): onde está o aplicativo desktop
                        instalado — instalador da plataforma > `PATH` >
                        diretório da própria CLI, com a recusa explícita de
                        resolver para o próprio executável, que no `.deb` é
                        irmão do aplicativo. Consumida hoje pela
                        `garraia-cli` (`garra desktop`); a casca ainda não.
  garraia-embeddings/ — Fase 2.1 (ADR 0002; ADR 0018 Proposed). Só superfície pública:
                        traits `EmbeddingProvider` + `VectorStore` (scoped por `Scope` +
                        `Option<Uuid> group_id`), tipos `Scope`/`EmbeddingVector(768)`/
                        `Document`/`Chunk`/`SearchHit`, `HybridQuery` builder (rejeita
                        cross-tenant em build-time), `DeterministicProvider` (feature
                        `testing-provider`). `PgVectorStore` real e `MxbaiProvider`
                        ainda não existem; sem wiring em learning/agents.
  garraia-storage/    — trait `ObjectStore` (`#[async_trait]`, usado como `dyn`) +
                        `LocalFs` + `path_sanitize`; `S3Compatible` (aws-sdk-s3) atrás
                        da feature `storage-s3` com SSE-S3 obrigatório, MIME allow-list
                        compartilhada, HMAC-SHA256 sobre `{key}:{version_id}:{sha256_hex}`
                        via `PutOptions::hmac_secret`, presigned URLs com TTL [30s, 900s].
                        MinIO via endpoint override; testcontainer gated pela feature.
garraia-hardware/   — ADR 0020 (epic #1124; #1125+#1129): abstração de dispositivos
                        físicos. `trait Device` (async, `dyn`) + `Capability { name,
                        risk R0-R5, read_only, args_schema }` com invariante
                        leitura↔R0 (`leitura()`/`acao()`/`validar()`), `RiskClass`
                        com tabela fail-closed `decisao()` (R0/R1 auto · R2 policy ·
                        R3 confirmação humana · R4 aprovação explícita · R5 deny
                        salvo allowlist), `HardwareGate` (`decide()` consulta a
                        tabela; R5 via allowlist do operador), `DeviceRegistry`
                        (`Arc<dyn Device>` por id), `DeviceStateStore` (presença
                        online/last_seen em SQLite, best-effort) e `MockDevice`
                        (fixture de teste, default-on). Integração com o runtime:
                        tools `device_list`/`device_read`/`device_execute` em
                        `garraia-agents` (duas camadas: `ToolGate` dos modos nega
                        `device_execute` nos read-only; dentro da tool, `HardwareGate`
                        usa o fluxo GAR-187 `ToolApproval::Granted(fingerprint)` para
                        R3/R4 e fail-closed sem canal de confirmação). Registry começa
                        vazio em produção — adapters (#1126/#1127/#1130) registram
                        dispositivos e é cada um que traz o teto R2.
                        Hardware skills (#1131, feature `skills`,
                        `docs/hardware-skills.md`): `CatalogoDeSkills` le os
                        manifestos e aplica as duas regras que o manifesto NAO
                        escolhe — lista fechada de transportes (`mqtt`,
                        `home_assistant`, `serial`, `gpio`; outro carrega
                        inerte) e risco efetivo = `max(adapter, skill)`, ou
                        seja, um skill so SOBE risco. Leitura continua R0
                        (invariante de `Capability`). Seis skills oficiais em
                        `skills/hardware/`; Zigbee/Matter como preset sobre o
                        Home Assistant, nunca stack propria.
apps/
  garraia-mobile/     — Garra Mobile (Flutter, Riverpod 3, go_router, Dio). v0.4.0
                        (ADR 0016): home "Garra Neon" + `lib/runtime/` (`GarraConnection`:
                        Termux local em 127.0.0.1:3888 / outro Garra na LAN / Garra Cloud)
                        + negociação de capabilities via `/api/capabilities`. Codegen:
                        `dart run build_runner build` (`*.g.dart` gitignored). APK sai do
                        CI (`mobile.yml` em PR, `build-android-apk` na release) — este
                        container não alcança o Android SDK.
benches/
  agent-framework-comparison/ — harness shell (`run.sh` + `README.md` + `results/`),
                        NÃO é crate nem workspace member. Mede binário, pico de RSS e
                        cold start contra CLIs concorrentes fixados por ref via env var;
                        resultados versionados em `results/<data>-<host>/`.
```

> `benches/database-poc/` foi removido em 2026-08-16;
> seus números 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`.
    **O corpo da release vem do CHANGELOG, nao da lista de commits.** O
    `release.yml` roda `scripts/release/notes.py "$VERSION" CHANGELOG.md` e passa o
    resultado como `body_path`; o script extrai a *prosa de abertura* da secao da
    versao (o que vem antes da primeira subsecao `###`) e o softprops anexa a lista
    gerada embaixo. A secao inteira nao serve: a da v0.3.9 tem 1298 linhas e 90 KB,
    perto do teto de 125 KB do GitHub. Apagar o script quebra a release inteira — ele
    roda **antes** do `Create Release`, e o job usa `set -euo pipefail`.
    **E cuidado com `.gitignore` sem ancora:** `release/` sem a barra inicial casa em
    QUALQUER profundidade e ja engoliu `scripts/release/` uma vez, com o `git add -A`
    pulando o arquivo em silencio e a release referenciando um script inexistente. Os
    padroes de artefato de build sao `/release/` e `/dist/`, ancorados na raiz.
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 |
| ------- | ----- |
| `/max-power` | Ativação do harness em um comando: verifica markers, repara via git se algo falta, oferece o plugin Superpowers, valida o setup, imprime o menu e roteia por goal |
| `/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, selecionada por risco R0-R5 |
| `/repo-autopilot` | Varredura autonoma: triagem de issues/PRs, correcoes e relatorio de saude |
| `/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 | Modelo | Papel |
| ------- | ------ | ------- |
| `team-coordinator` | anthropic/claude-fable-5.1 | Orquestração, delegação e decisão de merge. Nao implementa |
| `repo-analyst` | anthropic/claude-opus-5 | Diagnostico de issues/PRs, causa raiz, duplicadas. Nao escreve codigo |
| `implementer` | anthropic/claude-opus-5 | Implementacao Rust/Flutter em worktree isolada |
| `test-engineer` | anthropic/claude-opus-5 | fmt/check/clippy/test e teste de regressao |
| `code-reviewer` | anthropic/claude-opus-5 | Revisao independente e gate MERGE_READY |
| `security-auditor` | anthropic/claude-opus-5 | auth, JWT, crypto, RLS, SSRF, secrets. Convocado em R4 |
| `doc-writer` | anthropic/claude-opus-5 | README/SETUP/CHANGELOG, docstrings e higiene do repo |

Roster em Claude desde 2026-09-14 (decisão do dono): Opus 5 nos papéis operacionais, Fable 5.1 no coordinator. A independência entre quem escreve e quem julga vem do **contexto separado** (agentes distintos, prompts distintos, worktrees isoladas), não mais de modelos distintos — o Reviewer nunca lê o relatório do Implementer, só o diff.
Selecao por risco (R0-R5) em `skills/assemble-team.md`; varredura autonoma em
`skills/repo-autopilot.md`. R5 (release, secrets, destrutivo) sempre escala ao humano.

## 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: 25 ADRs (0001-0025). As 0001-0017, a **0019** (confinamento das tools, #1084), a **0020** (crate `garraia-hardware`, epic #1124 — aceita 2026-09-12, opção A) e a **0021** (Desktop Control Center, epic #1181 — aceita 2026-09-14, opção D: crate `garraia-desktop-core` sem Tauri no CI, casca Tauri fina, abas sobre `/api/*`, aba Agents cliente do AgentDeck), a **0022** (identidade do LLM padrão — `z-ai/glm-5.3-flash` via OpenRouter, local como segunda opção, #1180 — aceita 2026-09-13), a **0024** (perfis de execução `standard` | `isolated-pod`, #1329 — aceita 2026-09-21: poder total dentro do pod, nada implícito fora; perfil explícito, nunca inferido de container; `docs/execution-profiles.md`) e a **0025** (classes de capacidade, teto por principal e Access Policy v2 do WhatsApp, #1385/#1388/#1392 — aceita 2026-09-26: toda tool declara o que faz em classes fechadas, o teto vem do principal, e `channels.whatsapp_linked.access` é o motor único de CLI + API admin + Web Console) estão **Accepted**; a **0018** (crate `garraia-embeddings`, #949) está **Proposed** — a decisão é do dono, e aceitá-la é o gatilho da remoção. 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)

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.