Skip to content

fix: guard Lavish boards against lost form answers - #38

Merged
MrGTV-love merged 3 commits into
mainfrom
fm/fm-lavish-board-answers-lost
Oct 7, 2026
Merged

MrGTV-love merged 3 commits into
mainfrom
fm/fm-lavish-board-answers-lost

Conversation

@MrGTV-love

Copy link
Copy Markdown
Owner

Intent

the lavish board is not capturing my responses.

No -- I entered the answers, and it submit. when I ddi that, I recieved a notice to reload the page that it was no longer current. However, reloadiung the page did not remediate the issue

Context: the board is the 2026-10-06 20:30 CDT status report, ~/firstmate/data/status-report-20261006/report.html (built by build.py in that folder; decision forms call window.lavish.queuePrompt), opened with lavish-axi <file> as session http://127.0.0.1:4387/session/c6efbeaf94c38d45 and armed with bin/fm-procevent-lavish.sh arm <file> (procevent source lavish-c6efbeaf94c38d45). Evidence: report.html unchanged since 20:20:32; lavish lists the session open with 0 pending prompts and an agent-listener; the procevent runner relaunched at 21:31 with an empty claim output; no result file was ever written for that source, so no answer reached firstmate. The 2026-10-04 report used the same queuePrompt forms and its answers did arrive. The captain's answers were later given in chat, so nothing is pending on this board now.

What Changed

  • Add a syntax-only check command for inline event handlers and classic scripts, and run it on the physical artifact before every arm or re-arm, refusing registration when checks fail and reporting source locations.
  • Warn when inline form handlers lack a guard, and provide a drop-in guard that cancels otherwise uncancelled submissions, displays an alert, and attempts to queue a form-error prompt.
  • Document the checking and guard contracts, and add regression coverage for malformed scripts, script exclusions, guard behavior, and symlink-based arming and re-arming.

Risk Assessment

✅ Low: The changes are bounded to compile-only validation and an optional native-submit guard, and the comment-extraction and symlink fixes preserve the intended caller and ownership behavior.

Testing

After inspecting the change and CLI controls, exercised six targeted scenarios against an isolated real Lavish server and production process-event commands. Reproduced the stale-submit/reload failure, verified refusal and successful answer capture, and confirmed guard, comment, script-type, and symlink boundaries. Screenshots and persisted product outputs were collected; all scenarios passed and disposable resources were removed. No full suite, linters, formatters, or static analysis ran.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Arm an apostrophe-broken decision form: refuse it before registration instead of silently losing answers ✅ pass live Live CLI transcript names the broken form and rejects both ownership paths without a source or claim. broken-board-stale-after-reload.png reproduces the reported failure when the unguarded board is su…
Submit a safely quoted answer through a symlink-opened firstmate board: persist the choice and retain one listener on re-arm ✅ pass live sound-answer-queued.png; lavish-22db081534d6fd27.1.result.txt; CLI transcript reports the canonical artifact and still-listening on alias re-arm.
Submit a form whose handler throws: keep the review page usable, display an error, and deliver form-error rather than a choice ✅ pass live guard-visible-error.png; lavish-d1cacd3667e08982.1.result.txt; browser-observations.json retains the token-bearing artifact URL; answers returned empty output.
Arm and answer a board containing commented-out broken scripts and a commented-out guard: ignore inactive code but retain the missing-guard warning ✅ pass live CLI transcript shows successful check/arm with the missing-guard warning; commented-scripts-answer-queued.png and lavish-df2c0f1fd526d127.1.result.txt show successful answer delivery.
Check script extraction boundaries: reject broken live JavaScript containing HTML comment markers while accepting non-classic script elements ✅ pass live CLI transcript rejects script-comments.html with an inline-script diagnostic and no registration. The JSON/module/external-script board arms and delivers the answer recorded in lavish-599b428a1f81ce1b…
Arm a task-owned board through a symlink: refuse premature re-arm, then acknowledge captured feedback and capture the next round ✅ pass live CLI transcript shows canonical task registration, no-captured-round refusal, successful re-arm, and listening state. task-inbox-before-rearm.json, task-round-acknowledgment.json, and lavish-a0e6a52469…
Evidence: Live CLI commands, refusal diagnostics, ownership transitions, and teardown

Source: Live CLI commands, refusal diagnostics, ownership transitions, and teardown

[
  {
    "command": [
      "lavish-axi",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html",
      "--no-open",
      "--no-gate"
    ],
    "rc": 0,
    "stdout": "session:\n  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html\n  url: \"http://127.0.0.1:56033/session/dd298740f52a1688?no-gate=1\"\n  status: opened\nnext_step: \"Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html --reopen`.\"\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "check",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html"
    ],
    "rc": 1,
    "stdout": "",
    "stderr": "error: this board has page scripts that do not parse, so an answer entered on it would be lost: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html\n  <form> question d5-branch-pointer onsubmit (line 1): Unexpected identifier 's'\nfix: never paste text into an inline handler string; build the handler with addEventListener or read the text from a data attribute\n"
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "arm",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html"
    ],
    "rc": 1,
    "stdout": "",
    "stderr": "error: this board has page scripts that do not parse, so an answer entered on it would be lost: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html\n  <form> question d5-branch-pointer onsubmit (line 1): Unexpected identifier 's'\nfix: never paste text into an inline handler string; build the handler with addEventListener or read the text from a data attribute\n"
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "source-id",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html"
    ],
    "rc": 0,
    "stdout": "lavish-dd298740f52a1688\n",
    "stderr": ""
  },
  {
    "command": [
      "lavish-axi",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/script-comments.html",
      "--no-open",
      "--no-gate"
    ],
    "rc": 0,
    "stdout": "session:\n  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/script-comments.html\n  url: \"http://127.0.0.1:56033/session/aef46366ddbe4b80?no-gate=1\"\n  status: opened\nnext_step: \"Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/script-comments.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/script-comments.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 p

... [25126 bytes truncated] ...

-validation:0",
      "fm-board-worker"
    ],
    "rc": 0,
    "stdout": "",
    "stderr": ""
  },
  {
    "command": [
      "lavish-axi",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html",
      "--no-open",
      "--no-gate"
    ],
    "rc": 0,
    "stdout": "session:\n  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html\n  url: \"http://127.0.0.1:56033/session/a0e6a52469642be0?no-gate=1\"\n  status: opened\nnext_step: \"Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.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/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html --reopen`.\"\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "arm",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html",
      "--for",
      "board-worker"
    ],
    "rc": 0,
    "stdout": "registered: lavish-a0e6a52469642be0 (lavish, task=board-worker)\narmed: lavish-a0e6a52469642be0\nartifact: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html\nowner-task: board-worker\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "source-id",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html"
    ],
    "rc": 0,
    "stdout": "lavish-a0e6a52469642be0\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "arm",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html",
      "--for",
      "board-worker"
    ],
    "rc": 1,
    "stdout": "",
    "stderr": "error: cannot re-arm source lavish-a0e6a52469642be0: task board-worker already holds this board and no captured round is waiting to be acknowledged\n"
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "arm",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html",
      "--for",
      "board-worker"
    ],
    "rc": 1,
    "stdout": "",
    "stderr": "error: this board has page scripts that do not parse, so an answer entered on it would be lost: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/broken.html\n  <form> question d5-branch-pointer onsubmit (line 1): Unexpected identifier 's'\nfix: never paste text into an inline handler string; build the handler with addEventListener or read the text from a data attribute\n"
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "arm",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html",
      "--for",
      "board-worker"
    ],
    "rc": 0,
    "stdout": "registered: lavish-a0e6a52469642be0 (lavish, task=board-worker)\narmed: lavish-a0e6a52469642be0\nartifact: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html\nowner-task: board-worker\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent.sh",
      "list"
    ],
    "rc": 0,
    "stdout": "SOURCE                       ADAPTER      OWNER      PENDING\nlavish-a0e6a52469642be0      lavish       task:board-worker/listening 0\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "retire",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html"
    ],
    "rc": 1,
    "stdout": "",
    "stderr": "error: cannot retire task-owned source lavish-a0e6a52469642be0 while a captured round for task board-worker is unacknowledged; acknowledge it with bin/fm-procevent.sh handled lavish-a0e6a52469642be0 <sequence>\n"
  },
  {
    "command": [
      "bin/fm-procevent.sh",
      "handled",
      "lavish-a0e6a52469642be0",
      "2"
    ],
    "rc": 0,
    "stdout": "handled: lavish-a0e6a52469642be0 2\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent-lavish.sh",
      "retire",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-alias.html"
    ],
    "rc": 0,
    "stdout": "retired: lavish-a0e6a52469642be0\n",
    "stderr": ""
  },
  {
    "command": [
      "bin/fm-procevent.sh",
      "list"
    ],
    "rc": 0,
    "stdout": "no sources registered\n",
    "stderr": ""
  },
  {
    "command": [
      "tmux",
      "-S",
      "~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-socket",
      "kill-server"
    ],
    "rc": 0,
    "stdout": "",
    "stderr": ""
  },
  {
    "command": [
      "lavish-axi",
      "stop",
      "--port",
      "56033"
    ],
    "rc": 0,
    "stdout": "server:\n  status: stopped\n  port: 56033\n",
    "stderr": ""
  }
]
![Broken form still shows stale-artifact notice after reload and resubmit](https://github.com/user-attachments/assets/6dc8914e-cdbb-4ff1-9aff-94372e72e2f2) - Evidence: [Broken form still shows stale-artifact notice after reload and resubmit](https://github.com/MrGTV-love/firstmate/blob/f0eed7f11e22897a47f81bbac9f459a566d135d4/.no-mistakes/evidence/fm/fm-lavish-board-answers-lost/broken-board-stale-after-reload.png) ![Correctly quoted answer queued without a guard error](https://github.com/user-attachments/assets/1bb13cf6-cf90-48d4-bbe1-6bf04188f66b) - Evidence: [Correctly quoted answer queued without a guard error](https://github.com/MrGTV-love/firstmate/blob/f0eed7f11e22897a47f81bbac9f459a566d135d4/.no-mistakes/evidence/fm/fm-lavish-board-answers-lost/sound-answer-queued.png)
Evidence: Successful firstmate answer persisted by the real runner

Source: Successful firstmate answer persisted by the real runner

session:
  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/sound.html
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "1","Decision: Repair it\n\nContext data:\n{\n  \"question\": \"d5-branch-pointer\",\n  \"answer\": \"Repair it\"\n}",html > body > form > button,choice,"Repair lane d5's branch pointer: Repair it"
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/sound.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/sound.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: "uid=2 body \"Decision: repair lane d5's branch pointer Choose how to handle the broken pointe\"\n  uid=3 h1 \"Decision: repair lane d5's branch pointer\"\n  uid=4 form \"Choose how to handle the broken pointer. Repair it Leave it Queue answer\"\n    uid=5 p \"Choose how to handle the broken pointer.\"\n    uid=6 label \"Repair it\"\n      uid=7 input\n    uid=8 label \"Leave it\"\n      uid=9 input\n    uid=1 button \"Queue answer\"\n  uid=10 script \"(function () { document.addEventListener('submit', function (event) { if (event.\"\n  uid=11 script"
![Runtime handler failure shows visible alert and queues form-error](https://github.com/user-attachments/assets/5caff34f-49b0-451d-8e17-2ef9bdb6ff96) - Evidence: [Runtime handler failure shows visible alert and queues form-error](https://github.com/MrGTV-love/firstmate/blob/f0eed7f11e22897a47f81bbac9f459a566d135d4/.no-mistakes/evidence/fm/fm-lavish-board-answers-lost/guard-visible-error.png)
Evidence: Form-error delivered to the real process-event result

Source: Form-error delivered to the real process-event result

session:
  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/guard-failure.html
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "1","Form d5-branch-pointer failed: its page script is broken and no answer was queued\n\nContext data:\n{\n  \"question\": \"d5-branch-pointer\",\n  \"error\": \"handler-broken\"\n}",html > body > form,form-error,"Form failed: d5-branch-pointer"
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/guard-failure.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/guard-failure.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: "uid=2 body \"Decision: repair lane d5's branch pointer Choose how to handle the broken pointe\"\n  uid=3 h1 \"Decision: repair lane d5's branch pointer\"\n  uid=1 form \"Choose how to handle the broken pointer. Repair it Leave it Queue answer This an\"\n    uid=4 p \"Choose how to handle the broken pointer.\"\n    uid=5 label \"Repair it\"\n      uid=6 input\n    uid=7 label \"Leave it\"\n      uid=8 input\n    uid=9 button \"Queue answer\"\n    uid=10 div \"This answer was NOT recorded: the form's page script is broken. Tell the agent (\"\n  uid=11 script \"(function () { document.addEventListener('submit', function (event) { if (event.\"\n  uid=12 script"
![Board containing commented-out broken scripts successfully queues an answer](https://github.com/user-attachments/assets/4020a9e3-7c85-4861-8990-a694a7f3fabe) - Evidence: [Board containing commented-out broken scripts successfully queues an answer](https://github.com/MrGTV-love/firstmate/blob/f0eed7f11e22897a47f81bbac9f459a566d135d4/.no-mistakes/evidence/fm/fm-lavish-board-answers-lost/commented-scripts-answer-queued.png)
Evidence: Answer captured from board with JSON, module, and external script elements

Source: Answer captured from board with JSON, module, and external script elements

session:
  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/skipped.html
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "1","Decision: Repair it\n\nContext data:\n{\n  \"question\": \"d5-branch-pointer\",\n  \"answer\": \"Repair it\"\n}",html > body > form > button,choice,"Repair lane d5's branch pointer: Repair it"
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/skipped.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/skipped.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: "uid=2 body \"Decision: repair lane d5's branch pointer Choose how to handle the broken pointe\"\n  uid=3 h1 \"Decision: repair lane d5's branch pointer\"\n  uid=4 script \"{'title':'not JS'}\"\n  uid=5 script \"export const liveModule = 1;\"\n  uid=6 script\n  uid=7 form \"Choose how to handle the broken pointer. Repair it Leave it Queue answer\"\n    uid=8 p \"Choose how to handle the broken pointer.\"\n    uid=9 label \"Repair it\"\n      uid=10 input\n    uid=11 label \"Leave it\"\n      uid=12 input\n    uid=1 button \"Queue answer\"\n  uid=13 script \"(function () { document.addEventListener('submit', function (event) { if (event.\"\n  uid=14 script"
![Task-owned board queues a second answer after symlink re-arm](https://github.com/user-attachments/assets/53601318-b7d1-4904-86ec-4c21278ecb34) - Evidence: [Task-owned board queues a second answer after symlink re-arm](https://github.com/MrGTV-love/firstmate/blob/f0eed7f11e22897a47f81bbac9f459a566d135d4/.no-mistakes/evidence/fm/fm-lavish-board-answers-lost/task-second-round-queued.png)
Evidence: Second task-owned answer persisted after re-arm

Source: Second task-owned answer persisted after re-arm

session:
  file: ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html
  status: feedback
prompts[1]{uid,prompt,selector,tag,text}:
  "1","Decision: Leave it\n\nContext data:\n{\n  \"question\": \"d5-branch-pointer\",\n  \"answer\": \"Leave it\"\n}",html > body > form > button,choice,"Repair lane d5's branch pointer: Leave it"
next_step: "Apply the requested changes to ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.html. Do not respond to the user just yet. Now you must run `lavish-axi poll ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/boards/task-sound.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: "uid=2 body \"Decision: repair lane d5's branch pointer Choose how to handle the broken pointe\"\n  uid=3 h1 \"Decision: repair lane d5's branch pointer\"\n  uid=4 form \"Choose how to handle the broken pointer. Repair it Leave it Queue answer\"\n    uid=5 p \"Choose how to handle the broken pointer.\"\n    uid=6 label \"Repair it\"\n      uid=7 input\n    uid=8 label \"Leave it\"\n      uid=9 input\n    uid=1 button \"Queue answer\"\n  uid=10 script \"(function () { document.addEventListener('submit', function (event) { if (event.\"\n  uid=11 script"
Evidence: Task inbox delivery before re-arm

Source: Task inbox delivery before re-arm

[
  {
    "name": "001.msg",
    "content": "schema=fm-task-inbox.v1\nat=2026-10-07T04:06:57Z\n--\nLavish review feedback is captured for task board-worker at ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/state/procevent-inbox/lavish-a0e6a52469642be0.1.result. Read it with bin/fm-procevent-lavish.sh read ~/.no-mistakes/worktrees/32d18ed9638d/01M4A7STHEHGBXVCC8GXB2J2BP/.fm-lab-validation-t70aaac1/state/procevent-inbox/lavish-a0e6a52469642be0.1.result, apply the round, and re-arm the board with the reply."
  }
]
Evidence: Observed stale notice and guarded versus successful frame state

Source: Observed stale notice and guarded versus successful frame state

{
  "brokenAfterReload": "This artifact load is no longer current. Reload Lavish to continue.",
  "guard": {
    "url": "http://127.0.0.1:56033/artifact/d1cacd3667e08982/index.html?artifact_revision=1&artifact_load_token=xMmz_io6prwCSuXirpIFZfi8KcA-UmAy",
    "alerts": [
      "This answer was NOT recorded: the form's page script is broken. Tell the agent (form d5-branch-pointer)."
    ]
  },
  "sound": {
    "url": "http://127.0.0.1:56033/artifact/22db081534d6fd27/index.html?artifact_revision=1&artifact_load_token=He5GED21eQC5OPBKOjxBR3qECjp8fuPD",
    "alerts": []
  }
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 bin/fm-procevent-lavish.sh:269 - Script extraction scans the original HTML before comments are removed. A valid board containing &lt;!-- &lt;script&gt;const broken = ;&lt;/script&gt; --&gt; therefore has the inactive script compiled and is refused by arm, despite its live answer handlers working. The same invariant is violated at bin/fm-procevent-lavish.sh:267, where a commented-out guard counts as installed and suppresses the warning at :359. Comment removal at :279 happens too late for either operation. Make the shared extraction boundary distinguish live script elements from comment contents for both compilation and guard detection, without stripping HTML-like comments inside live JavaScript. Related changed sites: docs/verification/process-event-sources.md:131 promises commented-out scripts are ignored; tests/fm-procevent.test.sh:5032 and :5062 exercise only a commented-out form, leaving this script case uncovered.
  • 🚨 bin/fm-procevent-lavish.sh:394 - The new gate checks the caller's path before canonicalization, while cmd_check rejects final-component symlinks at bin/fm-procevent-lavish.sh:311. Opening a sound board through a symlink and then arming that same path now fails before registration. Previously, arm resolved the path and passed the physical file to its listener; source identity still explicitly supports this alias at :202–208, with existing coverage in tests/fm-procevent.test.sh:3156–3159. Resolve the artifact before invoking the checker and check the same canonical file used by the listener at :395–397. This must hold for both registration paths, :400 (task-owned) and :406 (firstmate-owned), including re-arms.

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

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Arm an apostrophe-broken decision form: refuse it before registration instead of silently losing answers ✅ pass live Live CLI transcript names the broken form and rejects both ownership paths without a source or claim. broken-board-stale-after-reload.png reproduces the reported failure when the unguarded board is su…
Submit a safely quoted answer through a symlink-opened firstmate board: persist the choice and retain one listener on re-arm ✅ pass live sound-answer-queued.png; lavish-22db081534d6fd27.1.result.txt; CLI transcript reports the canonical artifact and still-listening on alias re-arm.
Submit a form whose handler throws: keep the review page usable, display an error, and deliver form-error rather than a choice ✅ pass live guard-visible-error.png; lavish-d1cacd3667e08982.1.result.txt; browser-observations.json retains the token-bearing artifact URL; answers returned empty output.
Arm and answer a board containing commented-out broken scripts and a commented-out guard: ignore inactive code but retain the missing-guard warning ✅ pass live CLI transcript shows successful check/arm with the missing-guard warning; commented-scripts-answer-queued.png and lavish-df2c0f1fd526d127.1.result.txt show successful answer delivery.
Check script extraction boundaries: reject broken live JavaScript containing HTML comment markers while accepting non-classic script elements ✅ pass live CLI transcript rejects script-comments.html with an inline-script diagnostic and no registration. The JSON/module/external-script board arms and delivers the answer recorded in lavish-599b428a1f81ce1b…
Arm a task-owned board through a symlink: refuse premature re-arm, then acknowledge captured feedback and capture the next round ✅ pass live CLI transcript shows canonical task registration, no-captured-round refusal, successful re-arm, and listening state. task-inbox-before-rearm.json, task-round-acknowledgment.json, and lavish-a0e6a52469…
  • Inspected the target diff and ran lavish-axi --help to establish the runtime and isolation controls.
  • Created a marked disposable home with bin/fm-lab-home.sh create &lt;workspace-local-lab&gt; and launched the real lavish-axi server --port 56033 --verbose with isolated state.
  • Ran bin/fm-procevent-lavish.sh check &lt;board&gt; and arm &lt;board&gt; against broken inline handlers, broken classic scripts, sound forms, commented-out scripts/guards, and JSON/module/external script elements.
  • Drove real Lavish sessions in Chromium: selected answers, clicked Queue answer and Send to Agent, and inspected persisted process-event results and firstmate notifications.
  • Submitted an unguarded apostrophe-broken form, reloaded Lavish, and submitted again to reproduce the repeated stale-artifact notice.
  • Submitted a guarded form whose handler throws; captured its visible alert, delivered form-error result, and verified bin/fm-procevent-lavish.sh answers &lt;result&gt; returned no choice.
  • Opened and armed a sound board through a symlink; re-armed the firstmate-owned alias and observed still-listening.
  • Ran task-owned symlink arm/re-arm against a disposable private tmux endpoint: refused premature re-arm, captured feedback in the task inbox, acknowledged round one on re-arm, and captured a second answer.
  • Captured browser screenshots, CLI transcripts, persisted feedback, inbox state, and round acknowledgment evidence; retired all sources, stopped the private server and tmux instance, and removed disposable workspace files.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…orm guard

A decision board form whose inline onsubmit held text pasted into a quoted
JavaScript string (an apostrophe in a title) failed to parse. The browser then
submitted the form natively, the artifact frame landed on Lavish's 409 'no
longer current' page, and a reload could not repair it, so answers were lost.

fm-procevent-lavish.sh gains a check command that compiles every inline event
handler and inline classic script, and arm now runs it first and refuses a
board that would silently lose answers. A drop-in form guard asset cancels any
uncancelled native submit, shows a visible alert, and queues a form-error
prompt for the agent.
@MrGTV-love
MrGTV-love merged commit 2000884 into main Oct 7, 2026
21 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