feat(bin): per-home Herdr workspace label override - #4662
rafaelreis-r wants to merge 4 commits into
Conversation
Add config/herdr-workspace-label (local, gitignored, not inherited by secondmate homes): when present and non-empty, its trimmed content overrides the Herdr workspace label resolved by fm_backend_herdr_workspace_label, while a secondmate's marker-derived label still takes precedence over this override. Without this, every primary home resolves to the fixed "firstmate" label, which collides in Herdr's spaces sidebar when more than one primary-role home runs on the same machine (for example a fork development checkout alongside a production checkout). A local label override lets each such home present distinctly without touching the shared default. Documents the new file in AGENTS.md, docs/configuration.md, and docs/herdr-backend.md, and adds three tests in tests/fm-backend-herdr.test.sh covering a trimmed override, the fallback when the file is absent or empty, and the secondmate-marker precedence.
|
Speaking as Kun's firstmate: First look on HEAD Attestation: MATCH (body Contract-class: opt-in — VISION.md per-rule
Workflow approval this pass: yes — Require no-mistakes CI / NM: waiting on post-approval green. Mergeable recomputing after unrelated main merges. Note (not blocking while waiting CI): custom labels are not yet taught to Firstmate flag: no (not otherwise-ready — waiting CI). Do not merge/rebase. No closing issue link. Security: none withhold. |
Intent
O firstmate usado aqui é o fork rafaelreis-r/firstmate (remote origin); o projeto original é kunchenguid/firstmate (remote upstream). Decisão do capitão (2026-09-16): todas as mudanças próprias do fork vão como proposta ao upstream. São três, já mergeadas na main do fork: PR #1 do fork (merge c14d749) "rótulo da workspace do Herdr configurável por home" (config/herdr-workspace-label lido por fm_backend_herdr_workspace_label em bin/backends/herdr.sh, docs e teste); PR #2 do fork (merge ce941fe) "stop the exit-1 cascade, name every failure, retire gone endpoints" (.omp/extensions/fm-primary-omp-watch.ts rearma como cold start após ciclo falho; bin/fm-watch.sh registra motivo em stderr durável nos traps e appends que falham; registros .stale-* de endpoint comprovadamente inexistente são aposentados sem wake; docs e testes); PR #3 do fork (merge f14d46f) "code root que é home primário não é fork de backlog" (detect_code_root_backlog_fork em bin/fm-bootstrap.sh silencia quando o code root tem state/.lock próprio; doc e teste). O CONTRIBUTING do upstream exige que toda PR contra a main dele seja aberta pelo pipeline no-mistakes com o fork como push target (no-mistakes init --fork-url), e o check "Require no-mistakes" recusa PR sem essa atestação.
What Changed
fm_backend_herdr_workspace_labelinbin/backends/herdr.shnow reads an optional localconfig/herdr-workspace-labeland uses its trimmed contents as the home's Herdr workspace label. The secondmate marker still takes precedence; a file that is empty, spans more than one line (the label has to round-trip through a singleparent_label=journal line), or cannot be read falls back to thefirstmatedefault, and the read is guarded so an unreadable file does not abort aset -eucaller such asfm-spawn.sh.AGENTS.md,docs/configuration.md, anddocs/herdr-backend.md, including that it is local, gitignored, not inherited, applies only to workspaces created after it is set, and that the existing label-collision warning now covers whatever label the override produces.tests/fm-backend-herdr.test.shgains six cases for the override (applied with internal spaces, absent, multi-line, surrounded by blank lines, unreadable underset -e, and losing to the secondmate marker). The Herdr smoke and prune-safety e2e suites now exportFM_HOMEto a scratch primary home so the ambient checkout's own config cannot retarget the labels they assert on.Risk Assessment
✅ Low: A mudança é bem delimitada: uma leitura de config opcional com fall-through determinístico para o default
firstmate, protegida contra rótulo multilinha e arquivo ilegível, mais isolamento de FM_HOME em duas suítes e-2-e; nenhum caminho de produção existente muda quando o arquivo está ausente.Testing
Dirigi o recurso contra o herdr 0.9.0 real, com sessões de laboratório isoladas e o
bin/fm-spawn.shreal: seis spawns cobrindo rótulo customizado, rótulo padrão, precedência do secondmate e as duas guardas adversariais, mais o contrato do journal de projeção comparado entre a build sob teste e a build pré-guarda, mais as duas suítes reais rodadas com umconfig/herdr-workspace-labelno checkout. Tudo o que pude dirigir passou, incluindo a reprodução da regressão relatada: na build pré-guarda o arquivo de duas linhas produz um journal de 13 linhas e o relançamento seguinte é recusado commalformed herdr presentation journal, e na build sob teste isso não acontece. Duas limitações do host, ambas anteriores a esta mudança e verificadas com o rótulo padrão intocado: o herdr 0.9.0 renderiza o rótulo de uma workspace viva com índice ([1] firstmate), o que quebra a busca por rótulo exato — por isso nenhuma projeção acontece via fm-spawn (cenário reportado como untested) e as suítesfm-backend-herdr-prune-safety-e2eefm-backend-herdr-smokejá falham no commit baseaf1f2ea. As asserções dos meus drivers normalizam esse prefixo de índice e o transcript mostra rótulo cru e normalizado lado a lado. A superfície de usuário aqui é o rótulo da workspace na sidebar do Herdr; capturei a leitura de estado do próprio produto (herdr workspace list/pane get) em vez de screenshot, porque fotografar a sidebar exigiria subir o viewer GUI do laboratório e a permissão de Gravação de Tela do macOS para este processo, que não está concedida.herdr pane getaponta w1 eherdr workspace listmostra o rótulo 'Mate Raiz'; a captura do pane traz olive-label-ok…herdr workspace listconfirma que nenhum rótulo contém newlinefm-spawn.shsai com status 0 e a tarefa nasce em 'firstmate'parent_label=Mate Raiznum journal v2 de 12 linhas e fm_backend_herdr_projection_recovery_allows_flat devolve rc 0Evidence: Colocação ao vivo: 6 spawns reais e o rótulo de workspace resultante
Source: Colocação ao vivo: 6 spawns reais e o rótulo de workspace resultante
Evidence: Journal de projeção: parent_label customizado, build sob teste vs build pré-guarda
Source: Journal de projeção: parent_label customizado, build sob teste vs build pré-guarda
Evidence: Override ambiente: suítes reais com config/herdr-workspace-label no checkout, antes e depois do pinning
Source: Override ambiente: suítes reais com config/herdr-workspace-label no checkout, antes e depois do pinning
Evidence: Por que o caminho de projeção não é acionável neste host (com rótulo padrão)
Source: Por que o caminho de projeção não é acionável neste host (com rótulo padrão)
Evidence: Baseline: as duas suítes reais já falham no commit base af1f2ea
Source: Baseline: as duas suítes reais já falham no commit base af1f2ea
Evidence: Casos unitários de fm_backend_herdr_workspace_label
Source: Casos unitários de fm_backend_herdr_workspace_label
Evidence: Resumo da colocação observada no herdr real
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 5 issues found → auto-fixed (2) ✅
bin/backends/herdr.sh:375- O trim com sed é por linha e não restringe o rótulo a uma única linha: um config/herdr-workspace-label com linha em branco inicial (ou duas linhas) produz um rótulo contendo \n (verificado:printf '\nMate Raiz\n'-> $'\nMate Raiz'). Esse rótulo é propagado como HERDR_PARENT_LABEL (bin/fm-spawn.sh:2994) e gravado por fm_backend_herdr_projection_journal_write_v2 comoparent_label=<label>, gerando um journal de 13 linhas; fm_backend_herdr_projection_journal_snapshot só aceita o par version:lines2:12, então no spawn de recuperação seguinte fm_backend_herdr_projection_recovery_allows_flat emite 'malformed herdr presentation journal for <id>; refusing duplicate launch' e o fm-spawn.sh sai com 1 — a task só volta a subir depois de alguém apagar o journal manualmente. (Se o herdr sanear o rótulo no create, o desfecho alternativo é live_binding_matches falhar e a task ficar sem binding exato de restart.) Os demais leitores de config do adapter (fm_backend_herdr_presentation_preference, leitura do marcador de secondmate) são imunes porque aplicamtr -d '[:space:]'no arquivo inteiro. Remédio mecânico, sem estado novo: ler a primeira linha não vazia e trimar só as pontas, preservando os espaços internos que o teste novo exige.bin/backends/herdr.sh:1510- A mudança introduz uma terceira forma de rótulo de home (texto arbitrário), mas o classificador jq de fm_backend_herdr_projection_order_best_effort segue reconhecendo sófirstmatee^2ndmate-[^/]+$em is_top_level_parent (linhas 1510-1512). Sequência concreta: primary com config/herdr-workspace-label='Mate Raiz' e um secondmate '2ndmate-x' na mesma sessão herdr; o secondmate abre um crewmate projetado e a lista de workspaces fica [2ndmate-x, filho, Mate Raiz, filho, criado]; na caminhada do remainder o workspace 'Mate Raiz' cai no ramo else (active_parent=null) e o is_new_child seguinte marca valid=false, então a análise volta vazia e a função emite 'herdr presentation ordering found an ambiguous workspace layout; leaving worker in Herdr's current order' — o workspace projetado fica no fim da sidebar em vez de logo abaixo do pai. Falha segura (não corrompe estado), mas degrada o presentation space exatamente para quem adota o recurso. O remédio mexe em comportamento de produto (como o classificador passa a reconhecer rótulos arbitrários de home, ou se o rótulo customizado deve ser restrito a um formato reconhecível), então precisa de decisão do autor.bin/backends/herdr.sh:367- Em uma home secondmate o config/herdr-workspace-label é lido como inexistente (o marcador retorna antes) e nenhum aviso é emitido: o capitão escreve o arquivo, nada muda e nada explica. A precedência está documentada em docs/herdr-backend.md e docs/configuration.md, mas a linha nova do inventário do AGENTS.md ('optional per-home Herdr workspace label override') não menciona que homes secondmate ignoram o arquivo. Decisão do autor: aceitar o silêncio, avisar em stderr, ou ao menos registrar a exceção na linha do AGENTS.md.AGENTS.md:83- Todo item config/* do inventário aponta para uma seção dona nomeada (por exemplodocs/configuration.md "Stow pass horizon",docs/herdr-backend.md "Presentation spaces"). A entrada nova é a única que diz apenas 'see docs/configuration.md', e o texto correspondente vive embutido na seção## Runtime backend (config/backend / FM_BACKEND)(docs/configuration.md:162), sem heading próprio — o leitor não tem como localizar o item pelo ponteiro. Remédio: criar## Herdr workspace label (config/herdr-workspace-label)em docs/configuration.md e citá-lo no AGENTS.md, ou apontar explicitamente para a seção existente.docs/configuration.md:162- Trocar o rótulo com tasks em voo deixa os endpoints antigos no workspace 'firstmate' enquanto os novos nascem no workspace renomeado; além do que a doc já diz ('applies only to workspaces created from that point on'), as buscas por rótulo (fm_backend_herdr_workspace_find / fm_backend_herdr_list_live, bin/backends/herdr.sh:1719 e :3562) deixam de enxergar as tasks antigas — hoje sem impacto de produção porque list_live só tem chamador em testes, e teardown/peek endereçam o pane id gravado no meta. Tradeoff consciente e documentado; registrado apenas como nota.🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:
tests/fm-backend-herdr-prune-safety-e2e.test.sh:117- As duas suítes de herdr real usam o FM_HOME AMBIENTE, que cai em $FM_ROOT (bin/fm-backend.sh:54) — a raiz do checkout, exatamente onde vive o config/ lido pelo novo override. Sequência concreta: o capitão escreve config/herdr-workspace-label='Mate Raiz' no home e roda as suítes. (1) prune-safety-e2e cria a workspace de colisão com --label firstmate (linha 68) e chama fm_backend_herdr_container_ensure sem fixar FM_HOME (linha 117); o ensure agora resolve 'Mate Raiz', não acha match, CRIA workspace nova, e a linha 120 falha com 'container_ensure should have ADOPTED the pre-existing label-colliding workspace' — o teste acusa regressão do incidente de 2026-07-02 que não existe. (2) smoke.test.sh:250 procura select(.label == "firstmate") para achar a workspace que ele mesmo criou; POST_PRIMARY_ID volta vazio e a asserção falha com 'the primary workspace id did not survive the restart'. Todas as outras suítes de herdr já fixam FM_HOME em home de scratch (launcher-workspace-e2e:132, presentation-e2e:411, workspace-per-home-e2e:133) e a suíte unitária documenta esse exato perigo no cabeçalho ('a secondmate-marked checkout ... would silently change placement behavior'); essas duas ficaram sem a proteção, e a mudança amplia a exposição de 'home marcado como secondmate' para 'qualquer home primário que adote o recurso'. Remédio mecânico, só em teste: derivar o rótulo esperado de fm_backend_herdr_workspace_label (nos três pontos: prune-safety 68/78/120 e smoke 250) ou exportar um FM_HOME de scratch antes de sourcear bin/fm-backend.sh.bin/backends/herdr.sh:380-custom=$(sed ... "$label_file" 2>/dev/null)não tem guarda de status. Verificado: sobset -e, uma atribuição cuja substituição de comando falha aborta o shell (bash -c 'set -e; v=$(sed s/x/y/ <arquivo-sem-permissão> 2>/dev/null); echo survived'sai 1 sem imprimir nada).[ -f ]passa para arquivo regular ilegível, então um config/herdr-workspace-label existente mas sem permissão de leitura (criado por outro usuário, umask restritivo, setup com sudo) mata fm-spawn.sh — que rodaset -eudesde a linha 364 e chama o rótulo em HERDR_PARENT_LABEL=$(...) na linha 2994 — com exit 1 e NENHUMA mensagem. O leitor irmão do mesmo arquivo já se protege exatamente assim: fm_backend_herdr_presentation_preference fazvalue=$(tr ... ) || value=""(bin/backends/herdr.sh:172). Remédio:|| custom=na linha 380.bin/backends/herdr.sh:381- O refuse de rótulo multilinha é silencioso: o capitão escreve duas linhas no config/herdr-workspace-label, a workspace continua nascendo como 'firstmate' e nada explica por quê — o caso típico é justamente um erro de digitação (newline sobrando com texto depois). O leitor irmão do mesmo arquivo tomou a decisão oposta e documentou o motivo em comentário: fm_backend_herdr_presentation_preference avisa em stderr num valor não reconhecido 'rather than failing a spawn over a purely visual setting, so a typo is visible instead of silently deciding anything' (bin/backends/herdr.sh:164-167, warning na linha 177). Emitir warning é saída visível ao usuário, então a decisão é do autor: manter o silêncio ou seguir a convenção do arquivo.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
herdr pane getaponta w1 eherdr workspace listmostra o rótulo 'Mate Raiz'; a captura do pane traz olive-label-ok…herdr workspace listconfirma que nenhum rótulo contém newlinefm-spawn.shsai com status 0 e a tarefa nasce em 'firstmate'parent_label=Mate Raiznum journal v2 de 12 linhas e fm_backend_herdr_projection_recovery_allows_flat devolve rc 0bash tests/fm-backend-herdr.test.sh(suíte unitária dona dos 6 casos novos de fm_backend_herdr_workspace_label)driver ao vivo: 6 spawns reais viabin/fm-spawn.sh --backend herdrnuma sessão de laboratório isolada (rótulo customizado, segunda tarefa do mesmo home, home sem override, arquivo de duas linhas, arquivo modo 000, home secondmate com override), conferindoherdr pane get+herdr workspace listdriver ao vivo do journal de projeção:fm_backend_herdr_projection_journal_create/bind+fm_backend_herdr_projection_recovery_allows_flatcontra herdr real, comparando a build sob teste (dd5c81a) com a build pré-guarda (git archive 3c032f5) sobre o MESMO arquivo de rótulo de duas linhasdriver ao vivo do override ambiente:tests/fm-backend-herdr-prune-safety-e2e.test.shetests/fm-backend-herdr-smoke.test.shrodados comconfig/herdr-workspace-labelpresente no checkout, na build sob teste e na build pré-pinning (git archive c98699d), observando de fora os rótulos de workspace que cada run criabaseline: as duas suítes reais rodadas a partir degit archive af1f2ea(commit base) para separar falha pré-existente de regressãoprobe de disponibilidade da projeção: spawn comconfig/herdr-presentation-spaces=one rótulo PADRÃO, ancorado numa workspace já existente✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.