20. Crate garraia-hardware — abstração de dispositivos físicos
- Status: Accepted (2026-09-12, decisão do dono — opção A, ver nota abaixo)
- Deciders: @michelbr84 (decisão final) + Claude (levantamento, sessão autônoma 2026-09-11)
- Date: 2026-09-11
- Tags: hardware, iot, plataforma, arquitetura, seguranca
- Supersedes: none
- Superseded by: none
- Links:
- Epic: #1124 — "roadmap: garraia-hardware — observar, entender e agir no mundo físico"
- Issues-filhas: #1125
(crate +
trait Device), #1126 (adapter MQTT), #1127 (adapter Home Assistant), #1128 (motor de automações), #1129 (modelo de risco R0-R5), #1130 (Serial/USB + GPIO), #1131 (hardware skills) - Regra absoluta 8 do
CLAUDE.md: decisão arquitetural irreversível pede ADR - Precedente de processo: ADR 0018 — mesmo padrão de "ADR Proposed escrito numa sessão autônoma, execução aguarda aceite"
Context and Problem Statement
O epic #1124 (crédito: "conselho Hera+Garra", 2026-09-10) propõe posicionar o GarraIA para atuar no mundo físico — não como feature isolada de GPIO, mas como camada de plataforma: dispositivos IoT/hardware viram mais uma superfície que o agente enxerga e opera, ao lado de arquivos, chat e ferramentas web. A alavancagem citada é real: adaptando MQTT e Home Assistant, o Garra herda descoberta de milhares de dispositivos sem escrever driver por fabricante.
O epic já chegou com 7 issues-filhas sequenciadas pelo dono
(comentário em #1124), acceptance criteria concretos em cada uma (ex.: #1125
pede cargo test -p garraia-hardware com MockDevice, tool exposta em
GET /api/mcp/health), e um modelo de risco (#1129) desenhado para reusar o
safety_gate/ToolApproval existentes em vez de duplicá-los. Isto não é
brainstorm — é plano de execução.
O que falta é o documento que a regra absoluta 8 exige antes de a crate
nascer: hoje CLAUDE.md lista o workspace como "sem crates planejados no
momento", e toda crate ativa do repo (garraia-embeddings incluída, via ADR
0002) tem uma decisão arquitetural registrada antes do primeiro cargo new.
Uma crate nova — com trait pública, modelo de segurança próprio e adapters
que falam com rede local e serviços de terceiros — é exatamente o tipo de
decisão que o índice de ADRs (docs/adr/README.md) cita como exemplo:
"escolha de vector store", "protocolo de autenticação", "runtime de sandbox".
Hardware físico half-implica todas as três: é superfície nova de I/O, sujeita
a autorização, e roda fora do sandbox de processo (PR_SET_DUMPABLE/Landlock,
ADR 0019) porque fala com o mundo via rede/serial, não com o filesystem local.
Decision Drivers
- Reversibilidade real, mas não trivial. Ao contrário de
garraia-embeddings(crate sem consumidores, remoção =git revert), uma vez quegarraia-hardwareganhe adapters e a tool for exposta ao agente via MCP/tool nativa, desfazer significa também revisar qualquer automação ou memória que já referencie umdevice_id. A decisão de nascer bem — risco embutido naCapabilitydesde o primeiro commit (#1129 "transversal, deve nascer com o Device trait") — evita a alternativa cara: adicionar controle de acesso depois que dispositivos reais já estão conectados. - Superfície de segurança nova, não delegável a review tardia. Hardware
real tem ações irreversíveis no mundo físico (destrancar porta, mover
braço robótico) — categoria de risco que o repo não tinha até agora.
ToolApprovale o tier risky dosafety_gate(GAR-187/GAR-497) cobrem comandos de shell; estender o mesmo gate paradevice_executeé reuso deliberado, não um sistema paralelo — mas precisa estar decidido, não descoberto issue a issue. - Não é dívida técnica especulativa. Diferente do crate de embeddings (escrito 4 meses antes de ter consumidor), aqui as issues-filhas têm aceite testável e ordem de dependência explícita. O ADR não está tentando prever um caso de uso — está formalizando um que já tem 7 issues detalhadas.
- Escopo do core. O epic é explícito: "sem drivers no core" — a crate
define só
trait Device+ registry + política; MQTT/HA/Serial vivem em crates/adapters separados, adicionados aditivamente. Isso limita o raio da decisão irreversível ao trait em si, não a cada transporte.
Considered Options
A. Criar a crate garraia-hardware agora, como especificado nas issues-filhas
Nasce com trait Device (id/capabilities/read/execute,
#[async_trait] por ser usado como dyn Trait — mesma exceção documentada em
CLAUDE.md para garraia_storage::ObjectStore), Capability com risk class
R0-R5 embutida, registry em Arc<dyn Device>, e o gate de execução ligado ao
ToolApproval/safety_gate existente desde o primeiro commit. Adapters
(MQTT, Home Assistant, Serial/GPIO) entram depois, um por issue, cada um
aditivo ao workspace.
- A favor: segue o plano já sequenciado pelo dono; risco nasce embutido
em vez de vir depois; adapters isolados mantêm o core pequeno e testável
(
MockDevicein-memory, sem hardware real no CI); reusa infraestrutura de autorização existente em vez de duplicá-la. - Contra: adiciona superfície de I/O física nova ao produto — a primeira categoria de ação do agente com efeito fora do processo/filesystem/rede já conhecidos. Compromisso de manutenção de longo prazo (7+ issues, múltiplos adapters).
- Mitigação do contra: o modelo R0-R5 (#1129) é fail-closed por padrão — R5 é deny-by-default com allowlist explícita, e nenhuma capability roda sem classificação. O escopo inicial (#1125+#1129 juntos) não conecta hardware real nenhum; só depois de MQTT/HA (#1126/#1127) existe qualquer I/O físico de verdade, e cada um é seu próprio issue/PR/review.
B. Dobrar em garraia-tools em vez de crate nova
Hardware seria só mais um grupo de tools dentro do crate de tools compartilhadas existente, sem trait/registry dedicados.
- A favor: zero crate nova, zero linha no
Cargo.tomlraiz. - Contra:
garraia-toolshoje é file ops/search/web — tools request-response, sem noção de dispositivo com estado (online/offline,last_seen) nem de descoberta dinâmica. Forçar isso lá dentro mistura domínios e contradiz o próprio desenho das issues-filhas (registry + adapters como conceito de primeira classe). Reverter essa mistura depois é mais caro que separar desde o início.
C. Adiar — não decidir agora, esperar caso de uso mais concreto
Mesmo raciocínio que motivou o ADR 0002 a não implementar garraia-embeddings
cedo demais.
- A favor: zero risco imediato; nenhuma superfície nova até haver certeza.
- Contra: ao contrário do cenário de embeddings, aqui já existe o caso de uso concreto — 7 issues com aceite testável, dispositivo mock incluso no critério de aceite do primeiro (#1125). Adiar não elimina ambiguidade (não há ambiguidade: o plano está escrito); só atrasa o trabalho que o dono já pediu.
Decision Outcome
Recomendação: opção A — criar a crate garraia-hardware seguindo a
sequência já definida pelo dono em #1124 (fundação #1125+#1129 juntos →
#1126 → #1127 → #1128 → #1130 → #1131; ROS2 fora do escopo inicial, de
propósito).
O argumento decisivo é que o epic não é uma proposta em aberto — é um plano com critérios de aceite testáveis, ordem de dependência explícita e um modelo de segurança desenhado para reusar (não duplicar) o gate de risco que o repo já tem para bash/tools. O que faltava para essa primeira issue (#1125) poder começar sob a regra 8 era este documento.
Aceito em 2026-09-12 (opção A) — decisão do dono na sessão de implementação: "Sim — aceita e implementa". A implementação começou na mesma sessão: #1125 (crate
garraia-hardware) + #1129 (risco R0–R5 embutido naCapabilitye noHardwareGate) entregues juntos, conforme a sequência cravada pelo epic #1124.
Consequences
Se aceito (A):
crates/garraia-hardware/nasce como novo membro do workspace, seguindo #1125:trait Device+Capability(nome, risk class R0-R5, schema leve de args, read-only flag) + registryArc<dyn Device>+MockDevicede teste.CLAUDE.md§"Estrutura de crates" ganha a entrada e a contagem sobe de 22 para 23; a nota "sem crates planejados no momento" sai. Este ADR não implementa nada — a implementação é o trabalho de #1125.- #1129 (risco R0-R5) nasce junto com #1125, não depois — a
Capabilityjá carrega risk class desde o primeiro commit, e o gate reusaToolApproval/safety_gateem vez de um sistema paralelo. - Adapters (#1126 MQTT, #1127 Home Assistant, #1130 Serial/GPIO) entram aditivamente, cada um seu próprio PR/review, sem tocar o core.
- Nenhum dispositivo físico real é conectado por este ADR nem por #1125 — isso só começa em #1126/#1127, cada um com seu próprio risco a avaliar (rede local, credenciais de terceiros) no momento do PR correspondente.
Se recusado (B ou C):
- O epic #1124 fica bloqueado sem crate — as 7 issues-filhas continuam abertas e nenhuma pode começar sob a regra 8 sem um novo ADR substituindo este.
- Se a escolha for B (dobrar em
garraia-tools), as issues-filhas precisam ser reescritas — o desenho de registry/adapter delas assume crate própria.
Verificação
A afirmação central deste ADR — "o plano já está sequenciado e tem aceite testável" — é verificável relendo #1124 (comentário de sequenciamento) e #1125/#1129 (critérios de aceite), e deve ser reconferida antes de #1125 começar, caso as issues tenham sido editadas desde 2026-09-11.
Desfecho (2026-09-13)
O epic #1124 fechou com as sete issues-filhas entregues, e o que este ADR decidiu se sustentou na execução:
| Issue | Entrega |
|---|---|
| #1125 + #1129 | crate, trait Device, Capability com risco embutido, registry, gate |
| #1126 | adapter MQTT (rumqttc sobre TCP, sem a cadeia TLS que o GAR-455 tirou do lock) |
| #1127 | adapter Home Assistant (REST + WebSocket, URL pelo guard de SSRF) |
| #1128 | motor de automações sob a policy do runtime, sem bypass |
| #1130 | Serial/USB e GPIO, sobre a tabela fechada de periféricos |
| #1131 | hardware skills — lista fechada de transportes, risco que só sobe |
Duas decisões deste ADR provaram seu valor durante a execução, e viraram invariantes com teste:
- Risco nasce com o tipo (a razão de #1129 vir junto de #1125, e não
depois). Quando o transporte serial chegou (#1130), a pergunta "de onde vem
o risco de uma placa que ninguém autenticou?" já tinha resposta estrutural:
não do dispositivo. A tabela de
perifericosé fechada no código. - O core não conhece fabricante. Foi o que permitiu que Zigbee e Matter entrassem como preset sobre o Home Assistant (#1131) em vez de stack própria — e que Modbus/ROS2 fiquem de fora sem bloquear nada: um manifesto que os declare carrega inerte.
Nenhuma consequência prevista na seção anterior precisou ser revista. O
DeviceRegistry continua vazio em produção até um adapter ser configurado, e
todos os transportes seguem atrás de feature desligada por default.
Visão geral da plataforma entregue: docs/hardware.md.