Skip to content

feat(bin): per-home Herdr workspace label override - #4662

Open
rafaelreis-r wants to merge 4 commits into
kunchenguid:mainfrom
rafaelreis-r:contrib/herdr-workspace-label
Open

rafaelreis-r wants to merge 4 commits into
kunchenguid:mainfrom
rafaelreis-r:contrib/herdr-workspace-label

Conversation

@rafaelreis-r

Copy link
Copy Markdown

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_label in bin/backends/herdr.sh now reads an optional local config/herdr-workspace-label and 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 single parent_label= journal line), or cannot be read falls back to the firstmate default, and the read is guarded so an unreadable file does not abort a set -eu caller such as fm-spawn.sh.
  • Documented the setting in AGENTS.md, docs/configuration.md, and docs/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.sh gains six cases for the override (applied with internal spaces, absent, multi-line, surrounded by blank lines, unreadable under set -e, and losing to the secondmate marker). The Herdr smoke and prune-safety e2e suites now export FM_HOME to 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.sh real: 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 um config/herdr-workspace-label no 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 com malformed 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ítes fm-backend-herdr-prune-safety-e2e e fm-backend-herdr-smoke já falham no commit base af1f2ea. 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.

  • Live validation: ✅ go - 9 of 10 scenarios driven live against the product
Scenario Result Live Evidence
O capitão escreve 'Mate Raiz' em config/herdr-workspace-label e a tarefa que ele sobe nasce na workspace com esse nome ✅ pass live live-workspace-label-placement.txt, cenário 1: spawn real via bin/fm-spawn.sh, herdr pane get aponta w1 e herdr workspace list mostra o rótulo 'Mate Raiz'; a captura do pane traz o live-label-ok…
Uma segunda tarefa do mesmo home também nasce sob o rótulo customizado ✅ pass live live-workspace-label-placement.txt, cenário 2: segundo spawn real, rótulo normalizado 'Mate Raiz'
Um home que nunca criou o arquivo continua com a workspace 'firstmate', sem migração forçada ✅ pass live live-workspace-label-placement.txt, cenário 3: spawn real num home sem o arquivo, rótulo 'firstmate' e workspace distinta da do home customizado
Adversarial: um config/herdr-workspace-label com duas linhas é recusado e nenhum newline chega a um rótulo do herdr ✅ pass live live-workspace-label-placement.txt, cenário 4: spawn real com arquivo de duas linhas cai em 'firstmate', e a checagem jq sobre herdr workspace list confirma que nenhum rótulo contém newline
Adversarial: um config/herdr-workspace-label existente mas ilegível não mata o fm-spawn.sh sob set -eu ✅ pass live live-workspace-label-placement.txt, cenário 5: arquivo em modo 000, fm-spawn.sh sai com status 0 e a tarefa nasce em 'firstmate'
Precedência: um home secondmate com arquivo de rótulo próprio ainda nasce em 2ndmate-<id> ✅ pass live live-workspace-label-placement.txt, cenário 6: spawn real com --secondmate, rótulo '2ndmate-livesm1'
O rótulo customizado sobrevive ao journal de vínculo exato da projeção e é relido na recuperação ✅ pass live live-journal-parent-label.txt, caso C: contra herdr real, o bind grava parent_label=Mate Raiz num journal v2 de 12 linhas e fm_backend_herdr_projection_recovery_allows_flat devolve rc 0
Regressão relatada: na build pré-guarda, o mesmo arquivo de duas linhas quebra o journal e o relançamento seguinte é recusado ✅ pass live live-journal-parent-label.txt, caso B vs A: build 3c032f5 grava journal de 13 linhas com parent_label partido e a recuperação falha com 'malformed herdr presentation journal for mlB; refusing duplicat…
As duas suítes reais de herdr ficam imunes a um config/herdr-workspace-label no home ambiente do operador ✅ pass live live-ambient-home-label.txt: com o arquivo presente no checkout, a suíte smoke sob teste cria workspaces rotuladas 'firstmate' e produz a mesma saída do baseline sem o arquivo; a mesma suíte na build…
Um spawn projetado (presentation spaces ON) de um home com rótulo customizado nasce numa workspace filha sob a workspace pai renomeada ⏸️ untested no O herdr 0.9.0 instalado neste host renderiza o rótulo de uma workspace viva com prefixo de índice ('[1] firstmate'), então a busca por rótulo exato do adaptador não encontra o pai e todo spawn projeta…
Evidence: 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

\### Scenario 1: primary home with config/herdr-workspace-label = 'Mate Raiz'
    custom label                 pane=w1:p2    workspace=w1   herdr label='[1] Mate Raiz'        normalized='Mate Raiz'
ok - 1. custom label: the crewmate's tab is live in the workspace labeled 'Mate Raiz'
\### Scenario 2: a second task from the SAME home joins the same 'Mate Raiz' workspace
    custom label (2nd task)      pane=w2:p2    workspace=w2   herdr label='[2] Mate Raiz'        normalized='Mate Raiz'
    adoption: second task reused the first workspace? no (w2 vs w1)
ok - 2. custom label: a second task from the same home also lands under 'Mate Raiz'
\### Scenario 3: primary home with NO label file keeps 'firstmate'
    no override                  pane=w3:p2    workspace=w3   herdr label='[3] firstmate'        normalized='firstmate'
ok - 3. no override: an untouched home still lands in 'firstmate', in its own workspace
\### Scenario 4 (adversarial): a two-line label file is refused, not propagated
    two-line override            pane=w4:p2    workspace=w4   herdr label='[4] firstmate'        normalized='firstmate'
ok - 4. adversarial two-line override: refused - the task landed in 'firstmate' and no newline reached a herdr label
\### Scenario 5 (adversarial): an unreadable label file must not kill the 'set -eu' spawn
    fm-spawn.sh exit status with an unreadable label file: 0
    unreadable override          pane=w5:p2    workspace=w5   herdr label='[5] firstmate'        normalized='firstmate'
ok - 5. adversarial unreadable override: fm-spawn.sh survived (rc 0) and degraded to 'firstmate'
\### Scenario 6: a secondmate home's marker wins over its own label file
    secondmate + override        pane=w6:p2    workspace=w6   herdr label='[6] 2ndmate-livesm1'  normalized='2ndmate-livesm1'
ok - 6. precedence: a secondmate home carrying its own label file still lands in '2ndmate-livesm1'

\### Final workspace inventory in the isolated lab session
    w1	[1] Mate Raiz	(2 tabs)
    w2	[2] Mate Raiz	(1 tabs)
    w3	[3] firstmate	(1 tabs)
    w4	[4] firstmate	(2 tabs)
    w5	[5] firstmate	(1 tabs)
    w6	[6] 2ndmate-livesm1	(1 tabs)
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

\### the captain's config/herdr-workspace-label files
    single-line home : 0000000    M   a   t   e       R   a   i   z  \n                         0000012 
    two-line home    : 0000000    M   a   t   e       R   a   i   z  \n   S   e   g   u   n   d 0000020    a       L   i   n   h   a  \n                                 

\### A. build under test (dd5c81a) + two-line label file

    [under test / two-line] resolved home label: 0000000    f   i   r   s   t   m   a   t   e                             0000011 
    --- /private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-multiline/state/mlA.herdr-presentation ---
      version=2
      task_id=mlA
      projection_id=7ZujzPxs5UjJwnK26rba4g
      home=/private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-multiline
      session=fm-lab-journal-77296
      workspace_id=w2
      tab_id=w2:t1
      pane_id=w2:p1
      parent_workspace_id=w1
      parent_label=firstmate
      workspace_label=└ mlA · p:7ZujzPxs5UjJwnK26rba4g
      task_label=fm-mlA
      (line count: 12)
      recovery verdict: rc=0
      herdr-adapter: warning: quarantined herdr presentation for mlA is dead or agent-free; exact bound reclaim may proceed, otherwise spawning flat
ok - A. two-line override on the build under test: parent_label=firstmate, 12-line binding, recovery proceeds

\### B. pre-guard build (3c032f5) + the SAME two-line label file

    [pre-guard / two-line] resolved home label: 0000000    M   a   t   e       R   a   i   z  \n   S   e   g   u   n   d 0000020    a       L   i   n   h   a                                     
    --- /private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-multiline/state/mlB.herdr-presentation ---
      version=2
      task_id=mlB
      projection_id=Kn5VA-9EvXQE8tl4e8smeA
      home=/private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-multiline
      session=fm-lab-journal-77296
      workspace_id=w4
      tab_id=w4:t1
      pane_id=w4:p1
      parent_workspace_id=w3
      parent_label=Mate Raiz
      Segunda Linha
      workspace_label=└ mlB · p:Kn5VA-9EvXQE8tl4e8smeA
      task_label=fm-mlB
      (line count: 13)
      recovery verdict: rc=1
      herdr-adapter: error: malformed herdr presentation journal for mlB; refusing duplicate launch
ok - B. the SAME file on the pre-guard build writes a 13-line journal and the next launch is refused - the regression the guard removes

\### C. build under test + a legitimate single-line 'Mate Raiz'

    [under test / single-line] resolved home label: 0000000    M   a   t   e       R   a   i   z                             0000011 
    --- /private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-single/state/okC.herdr-presentation ---
      version=2
      task_id=okC
      projection_id=kn1lLGrRDHK5xwW0vb6OOw
      home=/private/var/folders/8g/ftp_r_353ggb5ccyc_f0t2x40000gn/T/fm-journal.wJgZYy/home-single
      session=fm-lab-journal-77296
      workspace_id=w6
      tab_id=w6:t1
      pane_id=w6:p1
      parent_workspace_id=w5
      parent_label=Mate Raiz
      workspace_label=└ okC · p:kn1lLGrRDHK5xwW0vb6OOw
      task_label=fm-okC
      (line count: 12)
      recovery verdict: rc=0
      herdr-adapter: warning: quarantined herdr presentation for okC is dead or agent-free; exact bound reclaim may proceed, otherwise spawning flat
ok - C. single-line 'Mate Raiz' round-trips: recorded verbatim, 12-line binding, recovery proceeds

\### Workspaces in the lab session
    w1	[1] firstmate
    w2	[2] └ mlA · p:7ZujzPxs5UjJwnK26rba4g
    w3	[3] Mate Raiz Segunda Linha
    w4	[4] └ mlB · p:Kn5VA-9EvXQE8tl4e8smeA
    w5	Mate Raiz
    w6	└ okC · p:kn1lLGrRDHK5xwW0vb6OOw
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


=== prune-safety / build under test / ambient override present ===
  ambient config/herdr-workspace-label: 'Mate Raiz'
  suite exit status: 1
  suite output:
    ok - repro setup: a pre-existing workspace labeled 'firstmate' collides with the primary home's own label
    ok - repro setup: a live long-running process is running in the startup workspace's single tab (label '1'), heartbeating to a marker file
    not ok - container_ensure should have ADOPTED the pre-existing label-colliding workspace (w1), got 'fm-lab-prune-safety-e2e-42498:w2'
    fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-prune-safety-e2e-42498'; refusing destructive calls
  workspace labels this run created in its lab session (observed live from outside):
    [1] firstmate
    firstmate

=== prune-safety / pre-pinning build / ambient override present ===
  ambient config/herdr-workspace-label: 'Mate Raiz'
  suite exit status: 1
  suite output:
    ok - repro setup: a pre-existing workspace labeled 'firstmate' collides with the primary home's own label
    ok - repro setup: a live long-running process is running in the startup workspace's single tab (label '1'), heartbeating to a marker file
    not ok - container_ensure should have ADOPTED the pre-existing label-colliding workspace (w1), got 'fm-lab-prune-safety-e2e-49710:w2'
    fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-prune-safety-e2e-49710'; refusing destructive calls
  workspace labels this run created in its lab session (observed live from outside):
    [1] firstmate
    firstmate

=== smoke / build under test / ambient override present ===
  ambient config/herdr-workspace-label: 'Mate Raiz'
  suite exit status: 1
  suite output:
    ok - real herdr: version_check accepts the installed binary's protocol
    ok - real herdr: container_ensure starts the isolated session's server, creates the firstmate workspace (fm-lab-backend-smoke-54811:w1), and reports its seeded default tab id (w1:t1)
    ok - real herdr: session status normalizes running and compatible
    ok - real herdr: container_ensure is idempotent (reuses/adopts the existing firstmate workspace, reports no seeded default tab on adoption)
    not ok - expected exactly 1 tab (the seeded default pruned) after the first real task tab, got 2: {"id":"cli:tab:list","result":{"tabs":[{"agent_status":"unknown","focused":true,"label":"[1] tmp › zsh","number":1,"pane_count":1,"tab_id":"w1:t1","workspace_id":"w1"},{"agent_status":"unknown","focused":false,"label":"fm-smoke1","number":2,"pane_count":1,"tab_id":"w1:t2","workspace_id":"w1"}],"type":"tab_list"}}
    fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-backend-smoke-54811'; refusing destructive calls
  workspace labels this run created in its lab session (observed live from outside):
    [1] firstmate
    firstmate

=== smoke / pre-pinning build / ambient override present ===
  ambient config/herdr-workspace-label: 'Mate Raiz'
  suite exit status: 1
  suite output:
    ok - real herdr: version_check accepts the installed binary's protocol
    ok - real herdr: container_ensure starts the isolated session's server, creates the firstmate workspace (fm-lab-backend-smoke-57038:w1), and reports its seeded default tab id (w1:t1)
    ok - real herdr: session status normalizes running and compatible
    ok - real herdr: container_ensure is idempotent (reuses/adopts the existing firstmate workspace, reports no seeded default tab on adoption)
    ok - real herdr: create_task prunes the freshly-created workspace's seeded default tab, leaving exactly one clean fm-<id> task tab
    ok - real herdr: create_task refuses a same-labeled tab whose pane hosts a genuinely live registered agent (unchanged behavior)
    ok - real herdr: create_task closes and replaces a same-labeled tab whose pane hosts no registered agent (the restored-husk shape), leaving the workspace intact
    ok - real herdr: a secondmate-shaped home (.fm-secondmate-home) gets its OWN herdr workspace, distinct from the primary's, in the SAME session
    ok - real herdr: the secondmate-shaped home's workspace is labeled 2ndmate-<secondmate-id> in herdr itself
    ok - real herdr: a task spawned into the secondmate-shaped home lands as a tab inside the secondmate's OWN workspace
    not ok - the secondmate-shaped home's list_live did not see its own task
    
    fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-backend-smoke-57038'; refusing destructive calls
  workspace labels this run created in its lab session (observed live from outside):
    Mate Raiz
    [1] Mate Raiz
    [2] 2ndmate-smoketest-sm1
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)

Why the fm-spawn.sh presentation-projection path could not be driven on this host.
herdr client: herdr 0.9.0

A home with NO label override at all (plain default 'firstmate'), presentation spaces ON,
after an anchor task already created the home workspace:

  anchor spawn (projection off) rc=0
  projected spawn (projection on) rc=0
  stderr:
    warning: herdr presentation parent is absent or ambiguous; using the ordinary flat layout without projection
  state dir (no *.herdr-presentation journal is published):
    anchor.meta
    home-summary.json
    shape.meta
  workspaces herdr reports (note the display-index prefix the adapter's exact-label lookup cannot match):
    w1	[1] firstmate
    w2	[2] firstmate
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

Pre-existing environment failures in the two real-herdr suites (herdr herdr 0.9.0)
Both suites already fail at BASE commit af1f2ea, before any part of this change:

--- tests/fm-backend-herdr-prune-safety-e2e.test.sh @ af1f2ea ---
  ok - repro setup: a pre-existing workspace labeled 'firstmate' collides with the primary home's own label
  ok - repro setup: a live long-running process is running in the startup workspace's single tab (label '1'), heartbeating to a marker file
  not ok - container_ensure should have ADOPTED the pre-existing label-colliding workspace (w1), got 'fm-lab-prune-safety-e2e-4666:w2'
  fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-prune-safety-e2e-4666'; refusing destructive calls

--- tests/fm-backend-herdr-smoke.test.sh @ af1f2ea ---
  ok - real herdr: version_check accepts the installed binary's protocol
  ok - real herdr: container_ensure starts the isolated session's server, creates the firstmate workspace (fm-lab-backend-smoke-12498:w1), and reports its seeded default tab id (w1:t1)
  ok - real herdr: session status normalizes running and compatible
  ok - real herdr: container_ensure is idempotent (reuses/adopts the existing firstmate workspace, reports no seeded default tab on adoption)
  not ok - expected exactly 1 tab (the seeded default pruned) after the first real task tab, got 2: {"id":"cli:tab:list","result":{"tabs":[{"agent_status":"unknown","focused":true,"label":"[1] tmp › zsh","number":1,"pane_count":1,"tab_id":"w1:t1","workspace_id":"w1"},{"agent_status":"unknown","focused":false,"label":"fm-smoke1","number":2,"pane_count":1,"tab_id":"w1:t2","workspace_id":"w1"}],"type":"tab_list"}}
  fm-herdr-lab: missing fleet-state tripwire for 'fm-lab-backend-smoke-12498'; refusing destructive calls
Evidence: Casos unitários de fm_backend_herdr_workspace_label

Source: Casos unitários de fm_backend_herdr_workspace_label

ok - fm_backend_herdr_workspace_label: a primary home (no marker) resolves to 'firstmate'
ok - fm_backend_herdr_workspace_label: a secondmate home (.fm-secondmate-home) resolves to '2ndmate-<id>'
ok - fm_backend_herdr_workspace_label: trims whitespace around the marker's secondmate id
ok - fm_backend_herdr_workspace_label: an empty marker file falls back to the primary label 'firstmate'
ok - fm_backend_herdr_workspace_label: two different secondmate homes get two different, non-colliding labels
ok - fm_backend_herdr_workspace_label: config/herdr-workspace-label overrides the label, preserving internal spaces
ok - fm_backend_herdr_workspace_label: an absent config/herdr-workspace-label falls back to 'firstmate'
ok - fm_backend_herdr_workspace_label: a multi-line config/herdr-workspace-label falls back to 'firstmate'
ok - fm_backend_herdr_workspace_label: blank lines around a single-line override are trimmed, not refused
ok - fm_backend_herdr_workspace_label: an unreadable config/herdr-workspace-label falls back to 'firstmate' instead of killing a 'set -e' caller
ok - fm_backend_herdr_workspace_label: the secondmate marker wins over a config/herdr-workspace-label override
Evidence: Resumo da colocação observada no herdr real
custom label pane=w1:p2 workspace=w1 herdr label='[1] Mate Raiz' normalized='Mate Raiz'
custom label (2nd task) pane=w2:p2 workspace=w2 herdr label='[2] Mate Raiz' normalized='Mate Raiz'
no override pane=w3:p2 workspace=w3 herdr label='[3] firstmate' normalized='firstmate'
two-line override pane=w4:p2 workspace=w4 herdr label='[4] firstmate' normalized='firstmate'
unreadable override pane=w5:p2 workspace=w5 herdr label='[5] firstmate' normalized='firstmate'
secondmate + override pane=w6:p2 workspace=w6 herdr label='[6] 2ndmate-livesm1' normalized='2ndmate-livesm1'

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 &#39;\nMate Raiz\n&#39; -> $'\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 como parent_label=&lt;label&gt;, gerando um journal de 13 linhas; fm_backend_herdr_projection_journal_snapshot só aceita o par version:lines 2: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 aplicam tr -d &#39;[:space:]&#39; 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ó firstmate e ^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 exemplo docs/configuration.md &#34;Stow pass horizon&#34;, docs/herdr-backend.md &#34;Presentation spaces&#34;). 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 ... &#34;$label_file&#34; 2&gt;/dev/null) não tem guarda de status. Verificado: sob set -e, uma atribuição cuja substituição de comando falha aborta o shell (bash -c &#39;set -e; v=$(sed s/x/y/ &lt;arquivo-sem-permissão&gt; 2&gt;/dev/null); echo survived&#39; 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 roda set -eu desde 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 faz value=$(tr ... ) || value=&#34;&#34; (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.

  • Live validation: ✅ go - 9 of 10 scenarios driven live against the product
Scenario Result Live Evidence
O capitão escreve 'Mate Raiz' em config/herdr-workspace-label e a tarefa que ele sobe nasce na workspace com esse nome ✅ pass live live-workspace-label-placement.txt, cenário 1: spawn real via bin/fm-spawn.sh, herdr pane get aponta w1 e herdr workspace list mostra o rótulo 'Mate Raiz'; a captura do pane traz o live-label-ok…
Uma segunda tarefa do mesmo home também nasce sob o rótulo customizado ✅ pass live live-workspace-label-placement.txt, cenário 2: segundo spawn real, rótulo normalizado 'Mate Raiz'
Um home que nunca criou o arquivo continua com a workspace 'firstmate', sem migração forçada ✅ pass live live-workspace-label-placement.txt, cenário 3: spawn real num home sem o arquivo, rótulo 'firstmate' e workspace distinta da do home customizado
Adversarial: um config/herdr-workspace-label com duas linhas é recusado e nenhum newline chega a um rótulo do herdr ✅ pass live live-workspace-label-placement.txt, cenário 4: spawn real com arquivo de duas linhas cai em 'firstmate', e a checagem jq sobre herdr workspace list confirma que nenhum rótulo contém newline
Adversarial: um config/herdr-workspace-label existente mas ilegível não mata o fm-spawn.sh sob set -eu ✅ pass live live-workspace-label-placement.txt, cenário 5: arquivo em modo 000, fm-spawn.sh sai com status 0 e a tarefa nasce em 'firstmate'
Precedência: um home secondmate com arquivo de rótulo próprio ainda nasce em 2ndmate-<id> ✅ pass live live-workspace-label-placement.txt, cenário 6: spawn real com --secondmate, rótulo '2ndmate-livesm1'
O rótulo customizado sobrevive ao journal de vínculo exato da projeção e é relido na recuperação ✅ pass live live-journal-parent-label.txt, caso C: contra herdr real, o bind grava parent_label=Mate Raiz num journal v2 de 12 linhas e fm_backend_herdr_projection_recovery_allows_flat devolve rc 0
Regressão relatada: na build pré-guarda, o mesmo arquivo de duas linhas quebra o journal e o relançamento seguinte é recusado ✅ pass live live-journal-parent-label.txt, caso B vs A: build 3c032f5 grava journal de 13 linhas com parent_label partido e a recuperação falha com 'malformed herdr presentation journal for mlB; refusing duplicat…
As duas suítes reais de herdr ficam imunes a um config/herdr-workspace-label no home ambiente do operador ✅ pass live live-ambient-home-label.txt: com o arquivo presente no checkout, a suíte smoke sob teste cria workspaces rotuladas 'firstmate' e produz a mesma saída do baseline sem o arquivo; a mesma suíte na build…
Um spawn projetado (presentation spaces ON) de um home com rótulo customizado nasce numa workspace filha sob a workspace pai renomeada ⏸️ untested no O herdr 0.9.0 instalado neste host renderiza o rótulo de uma workspace viva com prefixo de índice ('[1] firstmate'), então a busca por rótulo exato do adaptador não encontra o pai e todo spawn projeta…
  • bash 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 via bin/fm-spawn.sh --backend herdr numa 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), conferindo herdr pane get + herdr workspace list
  • driver ao vivo do journal de projeção: fm_backend_herdr_projection_journal_create/bind + fm_backend_herdr_projection_recovery_allows_flat contra 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 linhas
  • driver ao vivo do override ambiente: tests/fm-backend-herdr-prune-safety-e2e.test.sh e tests/fm-backend-herdr-smoke.test.sh rodados com config/herdr-workspace-label presente 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 cria
  • baseline: as duas suítes reais rodadas a partir de git archive af1f2ea (commit base) para separar falha pré-existente de regressão
  • probe de disponibilidade da projeção: spawn com config/herdr-presentation-spaces=on e rótulo PADRÃO, ancorado numa workspace já existente
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

rafaelreis-r and others added 4 commits September 16, 2026 09:59
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.
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

First look on HEAD 248b5bf96f7ee27cd604f558d6b5ac2d384ee0d5 (fork rafaelreis-r, FIRST_TIME_CONTRIBUTOR). Full thread + full diff reviewed. No .github/** edits. Authors not on blocked list.

Attestation: MATCH (body head_sha binds tip; intent/rebase/review/test/document/lint/push completed; ci pending). Live validation go 9/10.

Contract-class: opt-in — config/herdr-workspace-label is local/gitignored; absent file keeps the firstmate default byte-identical; multi-line/unreadable fall through; secondmate marker still wins. Unconfigured run unchanged.

VISION.md per-rule

  • One captain, one interface: aligns — presentation convenience as an option, not forced migration.
  • Authority is explicit and never inferred: aligns — override only when the captain writes the file.
  • Scripts own the mechanics, agents own the judgment: aligns — label read/guards are scripted.
  • A restart is a non-event: aligns — label re-read from FM_HOME each call.
  • Delegation with a spine: aligns / n/a.
  • The fleet outlives any vendor: aligns — Herdr adapter only; default path intact.
  • Scope: aligns — backend label + docs/tests.

Workflow approval this pass: yes — Require no-mistakes 35135082990 + CI 35135083049 (were action_required; approved after diff review).

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 is_top_level_parent presentation-order recognition; with presentation spaces ON, ordering may fall back to “ambiguous layout” (fail-safe, no state corruption). Acceptable for opt-in first land; can follow up separately.

Firstmate flag: no (not otherwise-ready — waiting CI). Do not merge/rebase. No closing issue link. Security: none withhold.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants