FASE 28 — a pasta do usuário

Este documento é o recorte da fase: o que ela persegue, o que ela recusa, e em que ordem. As decisões moram nos ADR 0146 e ADR 0147 — o que estiver aqui e não estiver lá é intenção, não decisão.

O problema

O produto tem três modos de execução desde o ADR 0104, e nenhum deles entrega a coisa mais simples que um usuário espera: abrir o editor e ver o trabalho acontecendo numa pasta dele.

modo pasta do usuário o que custa
container não existe o código mora num volume que ele nunca abre
mounted sim, sob BRABO_PROJECTS_BASE invisível na prática: sem a variável o modo nem é oferecido, e nada no produto pede que ela seja configurada
runner sim, pasta real binário, chmod +x manual, pareamento, e Docker como pré-requisito duro (RN-507/508)

O mounted é o caso mais instrutivo. O ADR 0141 construiu a base única e o ADR 0142 tornou possível sugerir um caminho que ainda não existe — mas nenhuma das duas coisas chegou à tela. GET /workspaces/:workspaceId/projects-base existe, é consumido por ninguém no web, e por isso o modo que já estava pronto continua fora do alcance de quem instala o produto.

O desenho, em três camadas

Cada camada com um mecanismo só, e nenhuma inventando autoridade nova:

camada onde mecanismo
consentimento pnpm bootstrap, uma vez, no host escolhe a base, grava BRABO_PROJECTS_BASE, prova o compartilhamento do Docker Desktop
pasta viva dentro da base bind por identidade (ADR 0141) — zero sincronização
destino fora da base opcional, por projeto capacidade espelho do agente local: uma direção, nunca apaga

A terceira camada existe por uma razão física: bind mount não atravessa rede nem alcança caminho fora do que foi montado. Para escrever numa pasta arbitrária do host só há três saídas — rodar api/engine nativamente, ter um processo vivo no host, ou o navegador escrever pela File System Access API. A segunda é a única contínua, e o brabo-runner já é esse processo.

Consentimento de configuração ≠ aprovação de ação

O ponto que os dois ADRs precisam declarar, e que decide o desenho do espelho:

A escrita do espelho não passa por proposed_action, porque não é um agente pedindo para agir — é o sistema escrevendo num caminho que o próprio usuário declarou ao criar o projeto. Sincronizar por comando de terminal seria o contrário: cairia no escopo de caminho do ADR 0055, viraria fila de aprovações rotineiras, e corroeria justamente o teto que dá sentido ao clique.

Numeração — contada, não herdada

As contagens em prosa do repositório estão em deriva conhecida. CONTRIBUTING.md diz "68 ADRs"; o frontmatter de docs/adr/index.md diz "108". Nenhuma sessão desta fase confia em número escrito em prosa.

O método, que toda sessão desta fase repete antes de reservar qualquer número:

# ADRs — arquivos, não prosa
ls docs/adr/ | grep -cE '^[0-9]{4}-.*\.md$'

# RNs — cabeçalhos, nos três arquivos que compartilham a numeração
git grep -h -E '^### RN-' -- 'docs/business-rules.md' 'docs/business-rules/*.md' \
  | grep -oE 'RN-[0-9]+' | sort -u | wc -l

Medido no início da sessão 1, contra dev: 144 ADRs (maior 0145, com um buraco em 0062) e 371 RNs (maior 509, zero âncoras duplicadas). Daí 0146 e 0147.

A faixa reservada para a fase é RN-511 a RN-520, e o salto sobre a RN-510 é deliberado. Contar apenas dev devolveria 510 — mas a RN-510 já estava alocada numa branch de trabalho que ainda não tinha mergeado. Reservá-la aqui recriaria exatamente a colisão que aconteceu com a RN-505, quando duas PRs alocaram o mesmo número com dois minutos de diferença e a âncora {#rn-505} ficou duplicada por semanas.

Vale registrar por que aquela colisão sobreviveu tanto tempo: docs:build não pega âncora duplicada. onBrokenAnchors: 'throw' reprova link para âncora que não existe; com duas âncoras iguais o Docusaurus resolve a primeira e a segunda fica inalcançável, em silêncio. Contar só o que está em dev é condição necessária e não suficiente — é preciso contar também o que está em voo.

As sessões

Uma entregável cada, na ordem. O prompt completo de cada sessão é escrito ao final da anterior, com o que ela tiver descoberto.

# entregável depende de
1 este documento + ADR 0146 + ADR 0147 + faixa de RN reservada —
2 base consentida no bootstrap.sh/preflight.mjs, .env.example, prova de compartilhamento 1
3 wizard oferece mounted por padrão quando a base existe 2
4 broker no compose local conforme o veredito do ADR 0146 2
5 capacidades declaradas no protocolo do agente local (sem espelho ainda) 1
6 capacidade espelho: escopo próprio, uma direção, nunca apaga 5
7 instalação como serviço de usuário, parar/remover, revogação server-side 6
8 estado do espelho na web (última sync, contagem, erro) 6
9 E2E, docs:check, docmap e CHANGELOG fechados 8

O que a fase entregou, e onde ela desviou do plano

A tabela acima é o plano. O que segue é o registro do que aconteceu — as diferenças estão aqui porque um plano que se reescreve para bater com o resultado deixa de ensinar qualquer coisa.

# RN/ADR como saiu
1 ADR 0146, ADR 0147 como planejado; faixa RN-511..520 reservada, com o salto sobre a RN-510 (já alocada em branch não mergeada)
2 ADR 0146 pontos 1–2 como planejado — base padrão $HOME/projetos-brabo, gravada por script Node porque item de menu do bootstrap.sh roda com stdin em /dev/null e por construção não consegue perguntar
3 RN-513 as duas metades já eram regra (RN-500/RN-501) e nunca tinham chegado à tela; NOVO foi só a pré-seleção
4 RN-512, ADR 0146 ponto 3 o broker sai do profiles no compose LOCAL e só nele — os dois composes divergem de propósito
5 RN-514, ADR 0147 ponto 1 como planejado; a recusa por capacidade faltante nasceu implementada e testada sem disparar — só a RN-516 lhe deu o primeiro disparo real
6 RN-515/RN-516, ADR 0147 pontos 2/3/4/8 partida em duas metades (destino × cópia), decisão tomada durante a sessão
7 RN-518, RN-519/520, ADR 0147 pontos 5–6 entregue depois da sessão 8 — as duas dependiam só da 6, e nada as ordenava entre si
8 RN-517, ADR 0147 ponto 7 como planejado
9 — fechamento: o E2E que faltava, a regra de docmap para apps/runner/** e este registro

Três coisas saíram diferentes do que o plano supunha, e as três estão declaradas onde importa:

  • A sessão 7 rodou depois da 8. As duas dependiam da 6 e de mais nada, e nenhuma dependia da outra — a ordem da tabela era leitura, não precedência.
  • BRB-031 não morreu. O ADR 0147 esperava que a instalação como serviço matasse o chmod +x manual do ADR 0118. Não mata: quem chega a rodar brabo-runner service install já precisou tornar o binário executável para chegar ao comando. O efeito colateral dependia de a instalação vir do bootstrap versionado do ADR 0146, que esta fase não ligou.
  • A revogação alcança {projeto, usuário}, nunca {chave}. O ponto 6 do ADR não prometia precisão por credencial, mas era a leitura natural dele. Ela não existe: runner_socket_tickets guarda project_id/user_id/kind, e a identidade da credencial morre no PatAuthGuard. O custo está escrito na RN-520 e em security-surface.md em vez de disfarçado.

O que esta fase NÃO toca

Declarado nos dois ADRs, e repetido aqui porque é o que dá sentido ao resto:

  • decide.ts e os tetos absolutos — nenhum teto ganha exceção, nem configurável
  • proposed_action como origem de todo efeito externo de agente
  • a imutabilidade do event log
  • o portão da imagem nos três modos (ADR 0135)
  • as cinco operações e a spec computada do broker (ADR 0128/0130)
  • o enum project_execution_mode — nenhum modo novo nasce nesta fase

Lacunas que a fase encosta e não resolve

  • credencial de git descartada no caminho docker exec em modo runner — apps/runner/src/index.ts:318-320; packages/docker-port não tem campo env, por decisão do ADR 0130. Declarada nos ADRs 0130/0145, não corrigida aqui.
  • symlink de dentro do projeto apontando para fora não é detectado por decide(), que é puro e não faz I/O — ADR 0055. apps/runner/src/guard.ts:9-31 declara-se best-effort pelo mesmo motivo.
  • BRB-005 — os artefatos publicados (imagens do GHCR, binários do runner) não são assinados. Um instalador de uma linha sem assinatura é pior que o download atual, e é por isso que a sessão 7 não ganha instalador de uma linha.
  • BRB-031 — chmod +x manual na configuração pelo navegador. Some como efeito colateral se a instalação passar a vir do bootstrap versionado.
  • exclusividade {project_id, machine_id} adiada até haver um segundo dev simultâneo real — ADR 0137.