Skip to content

fix: retire finished Lavish board listeners - #78

Merged
MrGTV-love merged 14 commits into
mainfrom
fm/fm-board-listener-retire
Oct 9, 2026
Merged

MrGTV-love merged 14 commits into
mainfrom
fm/fm-board-listener-retire

Conversation

@MrGTV-love

@MrGTV-love MrGTV-love commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Intent

I would like all bugs to be fixed so tomorrow can be focused entirely to Vernant and not problems preventing Vernant from getting built. A recommended architecture should come with proof.

Context for this change: Lavish board listeners were never retired. Each armed Lavish review board keeps a resident listener (runner, owner guard, poll wrapper, node poll) for as long as its Lavish session lives, and a captain's choice answer does not end the session. 25 listeners were live on the host; 15 of them belonged to boards answered 8-13 days earlier, and 2 pointed at board files that no longer exist, so reconcile relaunched them forever. The change retires listeners whose boards have ended or are older than their work. Hard requirement: never retire a listener whose board still has an open captain card.

What the change does: adds bin/fm-procevent-lavish.sh sweep [--dry-run], run automatically at the end of each good bin/fm-bearings-board.sh build. It retires a listener when the board file is gone, Lavish has no session for it, or the session status is ended; otherwise only when the board has been idle for FM_BOARD_LISTENER_IDLE_HOURS (default 48, range 1..8760) and no card key on the board names an open captain call (checked with fm-captain-hold.sh open; exit 0 open or exit 2 cannot-tell both keep). It always keeps the standing Bearings board, worker-owned boards, boards with queued feedback or status feedback, boards with an unacknowledged captured result, and every board when the Lavish store cannot be read. Every uncertain read fails toward keeping the listener. It never ends the Lavish session, and arm brings a listener back. Design was compared against a manual one-off retire, a generic check in every reconcile cycle, and a self-expiring poll timer; the sweep was chosen because it stops regrowth at lower process cost. Proof is in data/fm-board-listener-retire/proof.md.

Known deliberate limits: a card key held only in a secondmate's backlog is invisible to the main backlog (the idle window is the second safeguard); the watchdog's own 9 execs per tick belong to the separate fm-supervision-fork-budget work and are not changed here; retiring the live fleet's listeners is not part of this change. Tests: two behavioral tests in tests/fm-captain-hold-lifecycle.test.sh fail on the old code (unknown command: sweep) and pass now. tests/fm-procevent.test.sh has one host-only failure (setsid missing on macOS) in code this change does not touch.

What Changed

  • Add fm-procevent-lavish.sh sweep [--dry-run] and invoke it after successful Bearings builds to retire eligible missing, ended, sessionless, or idle board listeners. Configure the idle window with FM_BOARD_LISTENER_IDLE_HOURS (default 48).
  • Require confirmed closed captain cards for present-board retirement using source-bound exact, legacy, and migrated key resolution. Preserve standing and worker-owned boards, pending feedback or captures, and listeners with uncertain evidence without ending Lavish sessions.
  • Add registration-identity checks and locked inbox revalidation to conditional retirement, plus lifecycle regression coverage and documentation for sweep eligibility, activity accounting, and safety limits.

Risk Assessment

✅ Low: The retirement path conservatively preserves uncertain evidence, the latest fix distinguishes binding absence from read failures, and no new material defects were substantiated within the authorized scope.

Testing

Live CLI checks verified retirement, process shutdown and revival, automatic build sweeping, protected cards and feedback, activity windows, and fail-toward-keeping errors. All eight focused regression selectors completed across two commands after an initial timeout; a pre-existing quoted-path build limitation required an ordinary-path fixture. Product transcripts, persisted session state, and captured feedback were saved. Successful Beads routing remains untested live, but its unavailable consumer kept safely. No rendered UI changed; no screenshots were needed.

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
Preview retirement without changing registrations or running polls ✅ pass live Sweep outcomes and process evidence: dry-run verdicts and unchanged exact poll PIDs.
Sweep missing, ended, sessionless, and idle resolved boards ✅ pass live Live product command transcript: all four eligibility paths reported retired and removed their registrations.
Stop a real resident listener without ending its session, prevent regrowth, and re-arm it ✅ pass live Sweep outcomes and process evidence: poll disappearance, unchanged session-store SHA256, reconcile without regrowth, and replacement poll after arm.
Build Bearings to sweep finished listeners while retaining the standing board ✅ pass live Live product command transcript: successful ordinary-path build stopped an old real listener; a later sweep retained the aged standing Bearings board.
Keep exact and legacy open captain cards until their answers are recorded ✅ pass live Sweep outcomes and process evidence: open cards protected idle, ended, and sessionless registrations; exact and legacy answered cards subsequently retired.
Keep worker-owned boards, queued feedback, and unacknowledged captured answers ✅ pass live Live product command transcript and captured-feedback.txt: ownership and feedback keep reasons, plus locked refusal for a real unacknowledged capture.
Keep boards whose complete card set cannot be established ✅ pass live Live product command transcript: dynamic-script, empty, event-handler, and unresolved-key boards remained registered with explicit uncertainty reasons.
Respect recent activity, literal home paths, and configured idle-window bounds ✅ pass live Live product command transcript: fresh and recently handled boards stayed active; a six-hour board became eligible under the one-hour window; invalid settings were refused.
Keep listeners on unreadable store, inbox, stat, or board evidence ✅ pass live Sweep outcomes and process evidence: real permission errors produced logged keep decisions and locked refusals; an unrelated healthy board retired, and confirmed inbox absence allowed retirement.
Treat inaccessible decision bindings as unknown while preserving ordinary unbound behavior ✅ pass live Live product command transcript: binding lookup EACCES returned exit 2 and kept the listener; confirmed missing binding returned the closed verdict and permitted retirement.
Refuse stale-generation retirement and retirement with newly captured feedback ✅ pass live Sweep outcomes and process evidence: old identity refused against a real re-armed listener; a new real capture blocked identity-conditional retirement without deleting its result.
Resolve migrated Beads card identities against a real Beads-capable task consumer ⏸️ untested no Beads was absent from PATH, so a verified workspace-local Beads runtime and real embedded-Dolt graph were provisioned. Installed tasks-axi 0.2.6 rejected Beads list and hold operations. The npm regist…
Evidence: Live product command transcript

Source: Live product command transcript

Isolated real Lavish server: {"ok": true, "app": "lavish-axi", "version": "0.1.79", "state_id": "074509f7b7e81dbe", "state_dir": "~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/lavish-state", "hosts": ["127.0.0.1"], "requested_hosts": ["127.0.0.1"], "listeners": []}
$ tmux -L fm-lab new-session -d -s validation -x 120 -y 40 -c <worktree> sleep 3600
error connecting to ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/tmux/tmux-501/fm-lab (File name too long)
[exit=1]
$ tmux -S .sweep-validation/s new-session -d -s validation -x 120 -y 40 -c <worktree> sleep 3600
[exit=0]
$ tasks-axi add closed-card Closed ordinary work item
ok: added closed-card -> Queued
task:
  id: closed-card
  title: Closed ordinary work item
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: "-"
  priority: "-"
  created: 2026-10-09
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start closed-card` to move it to in flight
  - Run `tasks-axi block closed-card --by <other>` to record a dependency
[exit=0]
$ <worktree>/bin/fm-captain-hold.sh hold open-card --title Unanswered captain card --reason Choose a route --repo sample
open-card
[exit=0]
$ <worktree>/bin/fm-captain-hold.sh hold legacy-origin-decision-third-choice --title Legacy captain card --reason Legacy decision pending --repo sample --origin legacy-origin
legacy-origin-decision-third-choice
[exit=0]
$ lavish-axi <worktree>/.sweep-validation/fm home [literal]/boards/idle.html --no-open
session:
  file: "~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html"
  url: "http://127.0.0.1:53341/session/e93884a4a83f6df8"
  status: opened
self_paint_warning: "This artifact never paints its own page surface: no background on html/body/:root, no bg-* class or data-theme on html/body, and no stylesheet that could set one. Lavish injects no design system, so text that assumes a dark or light host surface can render invisible. Set an explicit background and readable text."
next_step: "First fix the unpainted page surface flagged in self_paint_warning and save - Lavish live-reloads the artifact automatically, so you do not need to re-run `lavish-axi ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html`. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html`. This command long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag \"layout-warnings\" prompt. Do not pass --timeout-ms during normal agent use. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervisor. Do not tell the user the artifact is being monitored until that wake path is live. If the poll gets killed or times out before feedback arrives, re-run it - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely. If it returns browser_disconnected, the review window stayed disconnected past its reconnect grace period but the session remains resumable; ask the user whether to reopen or end it, and do neither uninvited. After applying feedback, run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html --agent-reply \"<message for the user>\"` without --timeout-ms to show a concise response in Lavish Editor and wait for more feedback. Keep the reply concise. Only when a longer reply is genuinely necessary, use Markdown structure - blank-line paragraphs, `- ` / `1. ` lists, `## ` headings - so it renders scannably instead of a wall of text, and pass that body with `--agent-reply-file <path>` (`-` reads stdin) so newlines survive quoting. The Conversation panel's Markdown subset is in `lavish-axi poll --help` and README. If the user ends the session, stop polling and do not reopen it by re-running `lavish-axi ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html` unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run `lavish-axi ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html --reopen`."
[exit=0]
$ <worktree>/bin/fm-procevent-lavish.sh arm <worktree>/.sweep-validation/fm home [literal]/boards/idle.html
registered: lavish-e93884a4a83f6df8 (lavish)
armed: lavish-e93884a4a83f6df8
artifact: ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/idle.html
[exit=0]
$ lavish-axi <worktree>/.sweep-validation/fm home [literal]/boards/open.html --no-open
session:
  file: "~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html"
  url: "http://127.0.0.1:53341/session/f9d10ab03e356480"
  status: opened
self_paint_warning: "This artifact never paints its own page surface: no background on html/body/:root, no bg-* class or data-theme on html/body, and no stylesheet that could set one. Lavish injects no design system, so text that assumes a dark or light host surface can render invisible. Set an explicit background and readable text."
next_step: "First fix the unpainted page surface flagged in self_paint_warning and save - Lavish live-reloads the artifact automatically, so you do not need to re-run `lavish-axi ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html`. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html`. This command long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag \"layout-warnings\" prompt. Do not pass --timeout-ms during normal agent use. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervi

... [167537 bytes truncated] ...

=1]
PASS: stale identity retirement refused; replacement real poll remains registered and resident: {"2798": "bash ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/bin/fm-procevent-lavish.sh poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html", "6511": "node ~/.local/bin/lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html", "6547": "bash ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/bin/fm-procevent-lavish.sh poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html"}
POST /api/74b654497ddc617e/prompts
{"status": "queued", "pending_prompts": 1, "chat": [], "ack_ids": [], "chat_revision": 0}
$ <worktree>/bin/fm-procevent.sh retire lavish-74b654497ddc617e --if-identity 16777232:170267275
error: cannot retire source lavish-74b654497ddc617e while a captured round is unacknowledged
[exit=1]
New real capture preserved:
session:
  file: "~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html"
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "","","",message,Second unacknowledged capture; never lose this
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html --agent-reply \"<message for the user>\"` without --timeout-ms unless the user ended the session. Keep the reply concise. Only when a longer reply is genuinely necessary, use Markdown structure - blank-line paragraphs, `- ` / `1. ` lists, `## ` headings - so it renders scannably instead of a wall of text, and pass that body with `--agent-reply-file <path>` (`-` reads stdin) so newlines survive quoting. The Conversation panel's Markdown subset is in `lavish-axi poll --help` and README. The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervisor. Do not tell the user the artifact is being monitored until that wake path is live. If the poll gets killed or times out before feedback arrives, re-run it - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely. If it returns browser_disconnected, the review window stayed disconnected past its reconnect grace period but the session remains resumable; ask the user whether to reopen or end it, and do neither uninvited."
dom_snapshot: ""

$ <worktree>/bin/fm-captain-hold.sh answer open-card --decision-file <worktree>/.sweep-validation/fm home [literal]/answer.txt
answered: open-card
[exit=0]
$ <worktree>/bin/fm-captain-hold.sh answer legacy-origin-decision-third-choice --decision-file <worktree>/.sweep-validation/fm home [literal]/answer.txt
answered: legacy-origin-decision-third-choice
[exit=0]
$ bin/fm-procevent-lavish.sh sweep (relevant rows)
retired: lavish-10b19d7bd45208a1 ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open-ended.html - the Lavish session has ended
retired: lavish-252ede3f6d62a0a0 ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/legacy.html - idle for 72 hours with no open captain call
retired: lavish-a1b1268c4078ce25 ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open-no-session.html - Lavish holds no session for the board
retired: lavish-f9d10ab03e356480 ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/open.html - idle for 72 hours with no open captain call
sweep: retired=4 kept=9
PASS: exact and legacy cards keep while held, then allow retirement after recorded answers.
UNTESTED migration resolution: bd was missing on PATH, so downloaded and SHA256-verified bd 1.3.1 into the worktree, initialized a real embedded-Dolt graph, and created raw/derived migration-note rows. Installed tasks-axi 0.2.6 rejects backend beads for list and hold (UNSUPPORTED). npm registry and upstream main both report 0.2.6; a Beads-capable real tasks-axi build is required for full live migrated-key routing. Markdown bound-key and binding EACCES paths were driven live instead.
$ <worktree>/bin/fm-procevent.sh retire lavish-74a0dd5488136fc3
retired: lavish-74a0dd5488136fc3
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-6ed2b6ef8e93888a
retired: lavish-6ed2b6ef8e93888a
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-42a31163843d3d49
retired: lavish-42a31163843d3d49
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-61f4d95b83a4e273
retired: lavish-61f4d95b83a4e273
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-88edec2828daec33
retired: lavish-88edec2828daec33
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-e60db1894600a55b
retired: lavish-e60db1894600a55b
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-c026ad3401cc910a
retired: lavish-c026ad3401cc910a
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-74b654497ddc617e
retired: lavish-74b654497ddc617e
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-d1e142d8e833c247
retired: lavish-d1e142d8e833c247
[exit=0]
$ <worktree>/bin/fm-procevent.sh retire lavish-692faba0dd70f4d6
retired: lavish-692faba0dd70f4d6
[exit=0]
$ lavish-axi stop --port 53341
server:
  status: stopped
  port: 53341
[exit=0]
$ tmux -S .sweep-validation/s kill-server
[exit=0]
Live-lab teardown complete: no registrations or resident polls in owned lab homes; isolated Lavish server and private tmux socket stopped. Targeted test fixture cleanup remains until its command completes.
$ <worktree>/bin/fm-procevent.sh register lavish lavish-ff37d5d5d6e1846e -- <worktree>/bin/fm-procevent-lavish.sh poll <worktree>/.sweep-validation/migration-home/boards/migrated.html
registered: lavish-ff37d5d5d6e1846e (lavish)
[exit=0]
$ <worktree>/bin/fm-captain-hold.sh bind lavish-ff37d5d5d6e1846e origin
bound: lavish-ff37d5d5d6e1846e -> origin
[exit=0]
$ <worktree>/bin/fm-captain-hold.sh open-bound lavish-ff37d5d5d6e1846e third-choice
fm-captain-hold: error: "Unsupported backend \"beads\" — P1 ships the markdown backend only"
[exit=2]
$ <worktree>/bin/fm-procevent-lavish.sh sweep
kept: lavish-ff37d5d5d6e1846e ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/migration-home/boards/migrated.html - card third-choice is an open captain call or cannot be checked
sweep: retired=0 kept=1
[exit=0]
PASS: unsupported real Beads consumer is uncertainty and keeps its listener; successful migrated-key resolution remains untested live.
$ <worktree>/bin/fm-procevent.sh retire lavish-ff37d5d5d6e1846e
retired: lavish-ff37d5d5d6e1846e
[exit=0]
Final cleanup: downloaded Beads runtime/archive, private graph, test fixtures, temporary homes and socket all removed from the worktree. Source and test files were not changed.
Evidence: Real captured feedback preserved by retirement refusal

Source: Real captured feedback preserved by retirement refusal

session:
  file: "~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html"
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "","","",message,Second unacknowledged capture; never lose this
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4G9E6HVJ3S8MS9V1BGPX94E/.sweep-validation/fm home [literal]/boards/captured.html --agent-reply \"<message for the user>\"` without --timeout-ms unless the user ended the session. Keep the reply concise. Only when a longer reply is genuinely necessary, use Markdown structure - blank-line paragraphs, `- ` / `1. ` lists, `## ` headings - so it renders scannably instead of a wall of text, and pass that body with `--agent-reply-file <path>` (`-` reads stdin) so newlines survive quoting. The Conversation panel's Markdown subset is in `lavish-axi poll --help` and README. The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervisor. Do not tell the user the artifact is being monitored until that wake path is live. If the poll gets killed or times out before feedback arrives, re-run it - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely. If it returns browser_disconnected, the review window stayed disconnected past its reconnect grace period but the session remains resumable; ask the user whether to reopen or end it, and do neither uninvited."
dom_snapshot: ""

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 1 issue found → auto-fixed ✅
  • ⚠️ docs/verification/process-event-sources.md - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Review** - 2 issues found → auto-fixed (3) ✅
  • 🚨 bin/fm-procevent-lavish.sh:1120 - The sweep can retire supported JavaScript-generated decks with open captain cards. Main's fix: route legacy Lavish captain answers safely #63 includes .live-lavish-validation/ordinary.html:10-28, which creates forms dynamically and submits row.data.question without any static data-lavish-question attribute. Arm accepts this valid script. With an unanswered captain-held question, an idle open session, and no pending capture, the changed hunk next unless exists $attr{&#34;data-lavish-question&#34;} discovers no keys; sweep therefore retires the listener. This contradicts "never retire a listener whose board still has an open captain card." Related changed sites: bin/fm-procevent-lavish.sh:1113 (static-only discovery), :1151 (empty discovery serialized as '-'), :1163 (sentinel skipped), :1276 (shared guard for ended, sessionless, and idle boards), and :1284 (retirement). Establish an exhaustive card-key set at sweep_facts, or classify unsupported/dynamic card discovery as unknown and keep the listener; zero static matches must not prove there are no cards.
  • 🚨 bin/fm-procevent-lavish.sh:1167 - The sweep checks a card's raw key rather than the task its answer would reach. Concrete supported sequence: bind an ordinary board to origin sample-scout, retain its legacy card key third-choice, and leave sample-scout-decision-third-choice captain-held while the board becomes idle. The changed hunk fm-captain-hold.sh open &#34;$key&#34; --distinguish-absent returns 3 for third-choice, allowing retirement although the answer intake resolves that key to the open derived task. This routing is documented in docs/captain-hold-lifecycle.md:468 and exercised at tests/fm-captain-hold-lifecycle.test.sh:2333-2345. Migrated Beads identities have the same mismatch through bin/fm-captain-hold.sh:739-755. This contradicts "never retire a listener whose board still has an open captain card." Related changed sites: bin/fm-procevent-lavish.sh:1164 and :1170 (cache keyed only by raw key), :1174 (absence authorizes retirement), and :1276 (caller supplies no source binding). Reuse the captain-hold owner's exact/legacy/migrated resolution precedence in a read-only bound-key predicate, keeping unresolved evidence; cache by resolved task or binding plus key.

🔧 Fix applied.
4 issues (2 errors, 2 warnings) still open:

  • 🚨 bin/fm-procevent-lavish.sh:1120 - The sweep can retire supported JavaScript-generated decks with open captain cards. Main's fix: route legacy Lavish captain answers safely #63 includes .live-lavish-validation/ordinary.html:10-28, which creates forms dynamically and submits row.data.question without any static data-lavish-question attribute. Arm accepts this valid script. With an unanswered captain-held question, an idle open session, and no pending capture, the changed hunk next unless exists $attr{&#34;data-lavish-question&#34;} discovers no keys; sweep therefore retires the listener. This contradicts "never retire a listener whose board still has an open captain card." Related changed sites: bin/fm-procevent-lavish.sh:1113 (static-only discovery), :1151 (empty discovery serialized as '-'), :1163 (sentinel skipped), :1276 (shared guard for ended, sessionless, and idle boards), and :1284 (retirement). Establish an exhaustive card-key set at sweep_facts, or classify unsupported/dynamic card discovery as unknown and keep the listener; zero static matches must not prove there are no cards.
  • 🚨 bin/fm-procevent-lavish.sh:1167 - The sweep checks a card's raw key rather than the task its answer would reach. Concrete supported sequence: bind an ordinary board to origin sample-scout, retain its legacy card key third-choice, and leave sample-scout-decision-third-choice captain-held while the board becomes idle. The changed hunk fm-captain-hold.sh open &#34;$key&#34; --distinguish-absent returns 3 for third-choice, allowing retirement although the answer intake resolves that key to the open derived task. This routing is documented in docs/captain-hold-lifecycle.md:468 and exercised at tests/fm-captain-hold-lifecycle.test.sh:2333-2345. Migrated Beads identities have the same mismatch through bin/fm-captain-hold.sh:739-755. This contradicts "never retire a listener whose board still has an open captain card." Related changed sites: bin/fm-procevent-lavish.sh:1164 and :1170 (cache keyed only by raw key), :1174 (absence authorizes retirement), and :1276 (caller supplies no source binding). Reuse the captain-hold owner's exact/legacy/migrated resolution precedence in a read-only bound-key predicate, keeping unresolved evidence; cache by resolved task or binding plus key.
  • ⚠️ tests/fm-captain-hold-lifecycle.test.sh:4885 - This run's Round 1 R13 fix leaves the recent-board-activity fixture unable to detect a regression. fresh has no static card keys, so it now remains registered because discovery is unknown even if sweep stops including the board's mtime in activity. Related sites: tests/fm-captain-hold-lifecycle.test.sh:4942 (session without updated_at), :4971 (recent-touch assertion), and :4985 (registration assertion). Give this fixture the existing resolved closed-card key, leaving recent board mtime as its only keep condition; verify the active-window reason so unknown discovery cannot satisfy the assertion.
  • ⚠️ bin/fm-procevent-lavish.sh:1175 - Simplification: Round 1's R14 fix changes the cache identity to source:key, but each source is visited once and its card keys are already deduplicated at :1137. Consequently, this cache cannot hit during an ordinary sweep and adds matching and accumulation without saving any predicate calls. No intent requirement needs this unused component. Remove the cache branches at :1175-1186 and initialization at :1196, directly retaining the predicate's exit-1 check. Also remove the caching claim at docs/configuration.md:2450.

🔧 Fix applied.
1 error still open:

  • 🚨 bin/fm-captain-hold.sh:2054 - Round 1's R14 fix leaves binding-lookup uncertainty behind. Concrete sequence: bind an idle static board keyed third-choice to origin; its Beads graph contains a closed migration-note row for third-choice and an open migration-note row for origin-decision-third-choice. With readable binding evidence, resolution is ambiguous and keeps the listener. Remove search permission from state/decision-bindings while leaving the backlog, registry and inbox accessible. read_binding's existence check at bin/fm-captain-hold.sh:1253 returns false for EACCES and succeeds with an empty origin. The new hunk origin=$(read_binding &#34;$source&#34;) || return 2 accepts that result; migration discovery considers only the closed raw-key row, and open-bound returns 1, permitting retirement. This contradicts "Every uncertain read fails toward keeping the listener." Related sites: bin/fm-captain-hold.sh:673 (origin-derived migration candidate), :753 (legacy candidate), :2055-2056 (resolution and closed verdict); bin/fm-procevent-lavish.sh:1175 (predicate consumption), :1274/:1276/:1279 (sessionless, ended and idle paths), :1281 (shared card guard), and :1289 (retirement). At the shared binding-read boundary, distinguish confirmed absence from lookup errors; preserve ordinary unbound behavior but return uncertainty for EACCES and other non-absence failures.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
Preview retirement without changing registrations or running polls ✅ pass live Sweep outcomes and process evidence: dry-run verdicts and unchanged exact poll PIDs.
Sweep missing, ended, sessionless, and idle resolved boards ✅ pass live Live product command transcript: all four eligibility paths reported retired and removed their registrations.
Stop a real resident listener without ending its session, prevent regrowth, and re-arm it ✅ pass live Sweep outcomes and process evidence: poll disappearance, unchanged session-store SHA256, reconcile without regrowth, and replacement poll after arm.
Build Bearings to sweep finished listeners while retaining the standing board ✅ pass live Live product command transcript: successful ordinary-path build stopped an old real listener; a later sweep retained the aged standing Bearings board.
Keep exact and legacy open captain cards until their answers are recorded ✅ pass live Sweep outcomes and process evidence: open cards protected idle, ended, and sessionless registrations; exact and legacy answered cards subsequently retired.
Keep worker-owned boards, queued feedback, and unacknowledged captured answers ✅ pass live Live product command transcript and captured-feedback.txt: ownership and feedback keep reasons, plus locked refusal for a real unacknowledged capture.
Keep boards whose complete card set cannot be established ✅ pass live Live product command transcript: dynamic-script, empty, event-handler, and unresolved-key boards remained registered with explicit uncertainty reasons.
Respect recent activity, literal home paths, and configured idle-window bounds ✅ pass live Live product command transcript: fresh and recently handled boards stayed active; a six-hour board became eligible under the one-hour window; invalid settings were refused.
Keep listeners on unreadable store, inbox, stat, or board evidence ✅ pass live Sweep outcomes and process evidence: real permission errors produced logged keep decisions and locked refusals; an unrelated healthy board retired, and confirmed inbox absence allowed retirement.
Treat inaccessible decision bindings as unknown while preserving ordinary unbound behavior ✅ pass live Live product command transcript: binding lookup EACCES returned exit 2 and kept the listener; confirmed missing binding returned the closed verdict and permitted retirement.
Refuse stale-generation retirement and retirement with newly captured feedback ✅ pass live Sweep outcomes and process evidence: old identity refused against a real re-armed listener; a new real capture blocked identity-conditional retirement without deleting its result.
Resolve migrated Beads card identities against a real Beads-capable task consumer ⏸️ untested no Beads was absent from PATH, so a verified workspace-local Beads runtime and real embedded-Dolt graph were provisioned. Installed tasks-axi 0.2.6 rejected Beads list and hold operations. The npm regist…
  • bash tests/fm-captain-hold-lifecycle.test.sh test_sweep_retires_only_finished_board_listeners test_sweep_keeps_recently_handled_boards_in_literal_home_paths test_sweep_keeps_unknown_inbox_evidence test_sweep_keeps_unknown_card_discovery test_sweep_resolves_bound_captain_keys test_sweep_resolves_migrated_captain_keys test_sweep_stops_a_live_listener_of_a_finished_board test_sweep_preserves_rearmed_registration_generations — the first four selectors passed before the 600-second command timeout.
  • bash tests/fm-captain-hold-lifecycle.test.sh test_sweep_resolves_bound_captain_keys test_sweep_resolves_migrated_captain_keys test_sweep_stops_a_live_listener_of_a_finished_board test_sweep_preserves_rearmed_registration_generations — remaining selectors passed with a longer command bound.
  • Started real lavish-axi server --port 53341 --verbose with workspace-local state, loopback binding, and browser opening disabled; drove the public Firstmate CLIs against disposable homes.
  • Ran fm-procevent-lavish.sh arm, sweep --dry-run, sweep, and fm-procevent.sh reconcile; observed exact resident poll processes, registration removal, unchanged Lavish session-store checksum, and revival through arm.
  • Ran fm-bearings-board.sh build against a disposable payload. An initial literal-path attempt hit the unchanged quoted-session-listing limitation; an ordinary-path fixture successfully exercised automatic retirement and standing-board protection.
  • Created exact and legacy captain holds through fm-captain-hold.sh, exercised idle, ended, and sessionless boards, recorded answers, and verified the subsequent retirement transitions.
  • Submitted feedback to the real Lavish prompts API; inspected durable captured results and exercised handled and identity-conditional retirement.
  • Exercised malformed session state, inbox mode 0300, an inbox-file stat EACCES, unreadable board contents, and decision-binding lookup EACCES; verified logged keep decisions and locked retirement refusals.
  • Exercised the default idle window, a one-hour override, accepted endpoints 1 and 8760, and rejected values 0, 8761, and invalid text.
  • Downloaded and SHA256-verified workspace-local Beads 1.3.1, initialized a real embedded-Dolt graph, and attempted Beads operations through tasks-axi 0.2.6. The consumer rejected that backend; the actual sweep retained its listener.
  • Stopped the isolated Lavish server and private tmux server, verified no owned resident polls remained, and removed all disposable fixtures and downloaded tools.
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

A listener lives as long as its Lavish session, and a choice answer never
ends the session, so every board ever armed kept its resident processes.
Add fm-procevent-lavish.sh sweep: it retires a listener whose board file or
session is gone, or whose board has been idle past FM_BOARD_LISTENER_IDLE_HOURS
with no open captain call, and keeps worker-owned, standing, queued-feedback
and unacknowledged boards. A board rebuild runs the sweep.
…e fm-backlog-read-bound.test.sh required incidental diagnostic word order, despite verify correctly rejecting a timed-out read. Removed wording-only assertions across hold, direct verify, reconcile-requests, and migrated-prefix verify; now check bounded completion, failure status, prohibited output/side effects, and no further backend reads after timeout. Updated verification documentation; product code is unchanged. Verification passed: the complete bounded-backlog behavior suite, bash -n, and shellcheck -x. Temporary fixtures were cleaned up. The full CI shard was not rerun
@MrGTV-love
MrGTV-love merged commit adcbf11 into main Oct 9, 2026
22 checks passed
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.

1 participant