feat(bin): add captain inbox board - #1
Merged
Merged
Conversation
* feat: fence primary-session delegation outside the fleet A firstmate primary that delegates through Claude Code's built-in delegation tools creates work with no state/<id>.meta. Because fm-supervision-lib.sh counts *.meta and fm-turnend-guard.sh exits silently at zero, such work does not merely go unsupervised: it makes the whole guard stack structurally inert, and it dies with the primary session. On 2026-07-22 that cost two workers mid-flight and left supervision down for 73 minutes unnoticed. Layer 1, the primary fix: a permissions.deny list in .claude/settings.json removes the 18 delegation, scheduling, worktree, and task-tracking tools from the model's schema, so they are never offered. This is removal rather than interception, so there is no call to intercept and no fail-open path. The list is flat and in one file so its width stays reviewable; the captain owns that width. Layer 2, bin/fm-subagent-pretool-check.sh: a deny list is fail-open against tools that do not exist yet, and permissions.allow is a pre-approval list rather than an availability list, so there is no fail-closed allowlist to use instead. This backstop classifies the tool NAME by shape rather than against a fixed list, so a delegation tool that ships before the deny list is updated is still refused. It excludes mcp__* names and observe-or-stop operations, scopes itself to a genuine primary home via the shared fm_primary_scope_matches predicate so a crewmate's task worktree is unaffected, and offers one deliberate FM_ALLOW_SUBAGENT=1 escape hatch that must be set at launch. Verified live against Claude Code 2.1.217, including a deny-key A/B with a nonsense-name control, layer 2 denying an un-denied Workflow call, the same call allowed in a linked worktree, and the escape hatch. Corrects a prior finding: both Task and Agent work as deny keys, so both are pinned. Codex 0.144.1 verified to expose no delegation tool; grok, opencode, and pi are inspected and documented as not wired because those binaries are absent from this host and the repo requires live validation before trusting a harness hook. Evidence in docs/subagent-guard.md. * no-mistakes(review): Ship scoped Claude delegation guard * no-mistakes(test): Ship Claude delegation deny list * no-mistakes(document): Clarify PreToolUse guard ownership * no-mistakes(lint): Keep Claude deny list local
Reproduction: portable-parallel-2 completed successfully without tasks-axi while fm-decision-hold-lifecycle emitted a gate skip in 30 ms. The pre-shard lane installed tasks-axi and exercised the test fully. Installing tasks-axi is the smallest counterfactual and makes the representative shard execute the test with gate_skip=false in about 20 seconds. Both parallel jobs receive symmetric setup, while the exact 91-test inventory and coverage guard remain unchanged.
* feat: make dispatch profiles quota aware * no-mistakes(review): Fix quota window and Grok product scoping * no-mistakes(document): Document implicit quota-aware dispatch accurately
* fix: preserve mixed Bearings projections * no-mistakes(review): Enforce strict invalidity precedence for partial snapshots * no-mistakes(review): Enforce ownership for unknown child metadata * no-mistakes(document): Document partial structured Bearings projections * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes
* Add session-local Pi calm mode * no-mistakes(review): Preserve Pi HTML exports during calm mode * no-mistakes(review): Preserve calm exports across submit bindings and share * no-mistakes(document): Document calm-mode feasibility across supported harnesses
* fix(pi): limit watcher arm tool to recovery * no-mistakes(review): Strengthen Pi live re-arm regression coverage * no-mistakes(document): Document Pi first-cycle and recovery-only watcher arming
* fix(pi): clean up Calm transcript rendering * no-mistakes(review): Captain, preserve Calm exports and classify Pi launch briefs * no-mistakes(review): Captain, eliminate Calm gaps and verify exported conversations * no-mistakes(review): Restore Calm rows received while active * no-mistakes(review): Preserve diagnostics during Calm restoration * no-mistakes(document): Clarify Calm transcript behavior and injection paths
* fix: execute every PR body compliance event * no-mistakes(document): Document independent PR compliance events
* fix: distinguish operational input in ahoy * no-mistakes(review): Handle legacy Ahoy operational boundaries * no-mistakes(review): Narrow legacy Ahoy boundaries with live regressions * no-mistakes(document): Document Ahoy operational marker ownership * no-mistakes(lint): Suppress intentional literal fixture lint warnings
* fix: type canonical operational inputs * no-mistakes(document): Correct canonical operational-input documentation ownership
* fix: avoid generic secondmate acknowledgements * no-mistakes(document): Document sparse secondmate acknowledgement behavior * no-mistakes: apply CI fixes
* fix(pi): preserve calm presentation across sessions * no-mistakes(review): Fix Calm home fallback persistence * no-mistakes(document): Clarify Calm gapless and export contracts * no-mistakes: apply CI fixes
* fix: retire merged PR polls after notification * no-mistakes(review): Decouple PR retirement recovery from template updates * no-mistakes(review): Recover pending PR retirements before poll migration * no-mistakes(document): Document merged PR poll retirement contracts
* Clarify intake evidence and overlap handling * no-mistakes(review): Align scout guard with intake classification * no-mistakes(document): Clarify scout documentation and intake ownership
* fix(pi): preserve operational follow-up semantics in Calm * no-mistakes(document): Correct Calm operational-row visibility documentation
* perf(lint): shrink shell source graph * no-mistakes(review): Ensure lint workers terminate fully on cancellation * no-mistakes(document): Repair stale lint documentation ownership
* fix(pi): remove Calm hidden-block gaps * no-mistakes(review): Validate Calm geometry against current viewport * no-mistakes(review): Synchronize Calm geometry checks with reload completion
* fix: escalate ask-user contract expansion * no-mistakes(document): Point project management to authority owner
* fix(pi): hide Calm operational user rows * no-mistakes(review): Narrow Calm operational input suppression * no-mistakes(review): Avoid Calm replay classifier subprocesses * no-mistakes(document): docs: point Pi verification to Calm owner
* fix(session-start): relaunch missing second mates * fix(test): detect completed parallel workers * no-mistakes(review): Isolate session-start recovery test cleanup * no-mistakes(review): Complete backend-safe secondmate session recovery * no-mistakes(review): Resolve Zellij task ownership before recovery * no-mistakes(review): Recover relocated Zellij ghost tabs safely * no-mistakes(review): Restore conservative Zellij recovery boundary * no-mistakes(review): Reject malformed tmux recovery targets * no-mistakes(document): Align secondmate recovery documentation * no-mistakes: apply CI fixes
* fix(herdr): reclaim resumed task projections safely * no-mistakes(review): Enforce safe Herdr reclaim close boundaries * no-mistakes(document): docs: clarify Herdr restart projection contract
* docs: separate current guides from verification * no-mistakes(review): Restore Herdr 0.7.5 restart-reclaim verification evidence
* feat(claude): Stop-owned tokenless watcher continuity via asyncRewake auto-arm Claude primaries (main home and marked secondmate homes) no longer depend on the model remembering to re-arm the watcher after each wake. A tracked Stop asyncRewake hook (bin/fm-claude-stop-autoarm.sh, timeout 28800s) fires on every turn end, claims one home-scoped single-flight owner, foregrounds bin/fm-watch-arm.sh inside the hook-owned process tree, and translates an actionable close or typed watcher failure into exactly one exit-2 rewake. The hook scopes to genuine primary checkouts, requires the session lock to be held by its own harness ancestor, stays inert while AFK owns triage or the home is idle, and hands AFK transitions mid-cycle to the daemon without rewaking. The synchronous turn-end guard gains a --claude cooperative mode: it ignores stop_hook_active (true on every post-continuation stop, which is what re-opened the 2026-07-21 blind window), waits briefly for a watcher health proof, a live auto-arm owner claim, or a fresh rewake epoch, and re-blocks only when the auto-arm genuinely failed to establish - bounded to 3 consecutive blocks per session, safely below Claude Code's 8-block override, then a degraded allow with a visible systemMessage. Codex keeps the previous one-block loop guard byte-identically, and Pi, OpenCode, and Grok adapters are untouched. Continuity PreToolUse gate and durable wake queue are preserved; the gate's recovery guidance now names the Stop-owned re-arm and reserves manual background arms for auto-arm failure. Claude supervision protocol, harness-adapters facts, architecture, configuration, and continuity docs updated; docs/turnend-guard.md records the 2026-07-24 Claude 2.1.218 contract revalidation (tokenless multi-cycle rewake, no-dedup, timeout process-group kill, 8-block cap, interactive non-stall) and the 2.1.219 product live E2Es. Regression matrix: hermetic tests cover scope, identity, AFK, need, single-flight, translation, guard cooperation, budget, and registration; the new live E2E proves two full tokenless auto-arm rewake cycles with zero model arm commands; Pi and OpenCode Option B live E2Es pass unchanged. * no-mistakes(review): Fix Claude X-mode auto-arm continuity backstop * no-mistakes(review): Remove unsupported Claude contract-lab verification claims * no-mistakes(document): Update Claude auto-arm continuity documentation
* Clean stale Herdr projections at session start * no-mistakes(document): Document stale Herdr session-start projection cleanup * no-mistakes(review): Enforce locked exact Herdr projection cleanup * no-mistakes(review): Fail closed on unverified session lock ownership * no-mistakes(review): Serialize session lock acquisition atomically * no-mistakes(document): Align session-start and Herdr cleanup documentation * no-mistakes(document): Generalize lock-refusal diagnostics * no-mistakes(lint): Avoid reserved keyword in concurrency test * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes
bin/fm-inbox-view.sh writes one self-contained HTML board from live fleet state, so every open captain decision, review-ready pull request and stopped task is answerable from a single surface instead of arriving one chat interrupt at a time. It is a read-only projection in the same shape as fm-fleet-view.sh: it shells out to fm-fleet-snapshot.sh --json and `tasks-axi show --full`, and writes nothing but the board file. A test proves state/ and data/ stay byte-identical across a generation. Three selection defects the existing aggregated surface has are fixed here. Decisions are selected on the captain hold alone, because the snapshot's own captain_actionable flag also requires kind == "captain" and therefore hides every thread gated with `tasks-axi hold --kind captain`, which is where the oldest waiting decisions live. Decision text is read through tasks-axi, because the snapshot's metadata capture stops at the first comma and silently drops the options from any hold reason that lists them. A recorded pull request renders as unverified unless --verify-prs live-checks it, because locally recorded metadata cannot know a pull request was closed and a closed one must never read as ready to merge. Decision cards lead with plain English. An optional captain-private cards file supplies the plain question, what the captain is actually choosing, where it came from, firstmate's recommendation, a link to research a product or service, and a collapsed technical section. An item that is firstmate's own assumption rather than a choice the captain made is flagged as one. A decision with no card still renders and says so rather than looking complete. Answers use the Lavish input contract: native radios plus a free-text note, with exactly one queued prompt per question on submit. This change delivers the surface only; regenerating on a cadence, relaying answers back, and any escalation-routing change remain separate captain decisions.
…flow fix Fold in the captain's hard requirements from live use of the board. Free-form answers are now first-class. A card's predefined options are optional quick-picks with a clear-selection reset; the answer submits with just free text and no option selected. Previously the submit required picking an offered option, so a genuine free-form answer could not be sent at all. Each card gains a discuss path that sends a question back (DISCUSS <id>: ...) instead of an answer (DECISION <id>: ...), so a decision can be clarified rather than forced. Delivery is made reliable end to end. bin/fm-inbox-serve.sh is one operator command that regenerates the board, arms the answer relay, serves it on the Tailscale address, and verifies the link. bin/fm-inbox-arm.sh writes and registers state/inbox.check.sh, a bounded lavish poll bound by fm-check-register, so firstmate's supervision cycle relays answers on its normal check cadence without anyone remembering to poll. The relay fails closed: when the board is not served the poll errors fast and it stays silent. Submitting also sends immediately with a visible confirmation, closing the window where an answer looked silently eaten. Long titles and ids now wrap instead of overflowing horizontally, which Lavish's own layout audit flags as an error. No-project items get readable area names derived from their origin or id instead of a generic badge. Behavior is covered by tests/fm-inbox-view.test.sh (generator, read-only, free-form, discuss, immediate send, area names, wrapping) and tests/fm-inbox-arm.test.sh (the relay is a valid registered fail-closed check, silent while unserved, rejected after tampering). docs/inbox-board.md records the design and verification evidence.
ItsFlow
added a commit
that referenced
this pull request
Jul 28, 2026
* feat(bin): add captain inbox board (#1) * fix: block primary-session delegation outside the fleet (#854) * feat: fence primary-session delegation outside the fleet A firstmate primary that delegates through Claude Code's built-in delegation tools creates work with no state/<id>.meta. Because fm-supervision-lib.sh counts *.meta and fm-turnend-guard.sh exits silently at zero, such work does not merely go unsupervised: it makes the whole guard stack structurally inert, and it dies with the primary session. On 2026-07-22 that cost two workers mid-flight and left supervision down for 73 minutes unnoticed. Layer 1, the primary fix: a permissions.deny list in .claude/settings.json removes the 18 delegation, scheduling, worktree, and task-tracking tools from the model's schema, so they are never offered. This is removal rather than interception, so there is no call to intercept and no fail-open path. The list is flat and in one file so its width stays reviewable; the captain owns that width. Layer 2, bin/fm-subagent-pretool-check.sh: a deny list is fail-open against tools that do not exist yet, and permissions.allow is a pre-approval list rather than an availability list, so there is no fail-closed allowlist to use instead. This backstop classifies the tool NAME by shape rather than against a fixed list, so a delegation tool that ships before the deny list is updated is still refused. It excludes mcp__* names and observe-or-stop operations, scopes itself to a genuine primary home via the shared fm_primary_scope_matches predicate so a crewmate's task worktree is unaffected, and offers one deliberate FM_ALLOW_SUBAGENT=1 escape hatch that must be set at launch. Verified live against Claude Code 2.1.217, including a deny-key A/B with a nonsense-name control, layer 2 denying an un-denied Workflow call, the same call allowed in a linked worktree, and the escape hatch. Corrects a prior finding: both Task and Agent work as deny keys, so both are pinned. Codex 0.144.1 verified to expose no delegation tool; grok, opencode, and pi are inspected and documented as not wired because those binaries are absent from this host and the repo requires live validation before trusting a harness hook. Evidence in docs/subagent-guard.md. * no-mistakes(review): Ship scoped Claude delegation guard * no-mistakes(test): Ship Claude delegation deny list * no-mistakes(document): Clarify PreToolUse guard ownership * no-mistakes(lint): Keep Claude deny list local * fix: install tasks-axi in portable CI shards (#866) Reproduction: portable-parallel-2 completed successfully without tasks-axi while fm-decision-hold-lifecycle emitted a gate skip in 30 ms. The pre-shard lane installed tasks-axi and exercised the test fully. Installing tasks-axi is the smallest counterfactual and makes the representative shard execute the test with gate_skip=false in about 20 seconds. Both parallel jobs receive symmetric setup, while the exact 91-test inventory and coverage guard remain unchanged. * feat(bin): make dispatch profiles quota aware (#867) * feat: make dispatch profiles quota aware * no-mistakes(review): Fix quota window and Grok product scoping * no-mistakes(document): Document implicit quota-aware dispatch accurately * Add built-in ahoy recap skill (#873) * fix: preserve trustworthy Bearings data in partial snapshots (#875) * fix: preserve mixed Bearings projections * no-mistakes(review): Enforce strict invalidity precedence for partial snapshots * no-mistakes(review): Enforce ownership for unknown child metadata * no-mistakes(document): Document partial structured Bearings projections * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * feat(pi): add session-local calm mode (#884) * Add session-local Pi calm mode * no-mistakes(review): Preserve Pi HTML exports during calm mode * no-mistakes(review): Preserve calm exports across submit bindings and share * no-mistakes(document): Document calm-mode feasibility across supported harnesses * fix(pi): prevent redundant watcher re-arms (#885) * fix(pi): limit watcher arm tool to recovery * no-mistakes(review): Strengthen Pi live re-arm regression coverage * no-mistakes(document): Document Pi first-cycle and recovery-only watcher arming * fix(pi): clean up Calm transcript rendering (#895) * fix(pi): clean up Calm transcript rendering * no-mistakes(review): Captain, preserve Calm exports and classify Pi launch briefs * no-mistakes(review): Captain, eliminate Calm gaps and verify exported conversations * no-mistakes(review): Restore Calm rows received while active * no-mistakes(review): Preserve diagnostics during Calm restoration * no-mistakes(document): Clarify Calm transcript behavior and injection paths * fix: execute every PR body compliance event (#898) * fix: execute every PR body compliance event * no-mistakes(document): Document independent PR compliance events * fix: exclude operational injections from ahoy boundaries (#899) * fix: distinguish operational input in ahoy * no-mistakes(review): Handle legacy Ahoy operational boundaries * no-mistakes(review): Narrow legacy Ahoy boundaries with live regressions * no-mistakes(document): Document Ahoy operational marker ownership * no-mistakes(lint): Suppress intentional literal fixture lint warnings * fix: canonically classify operational inputs across harnesses (#909) * fix: type canonical operational inputs * no-mistakes(document): Correct canonical operational-input documentation ownership * fix: avoid generic secondmate start acknowledgements (#926) * fix: avoid generic secondmate acknowledgements * no-mistakes(document): Document sparse secondmate acknowledgement behavior * no-mistakes: apply CI fixes * fix(pi): make calm mode persistent and gapless (#927) * fix(pi): preserve calm presentation across sessions * no-mistakes(review): Fix Calm home fallback persistence * no-mistakes(document): Clarify Calm gapless and export contracts * no-mistakes: apply CI fixes * fix(watch): retire merged PR polls after durable notification (#932) * fix: retire merged PR polls after notification * no-mistakes(review): Decouple PR retirement recovery from template updates * no-mistakes(review): Recover pending PR retirements before poll migration * no-mistakes(document): Document merged PR poll retirement contracts * fix: refine scout intake and parallel dispatch (#934) * Clarify intake evidence and overlap handling * no-mistakes(review): Align scout guard with intake classification * no-mistakes(document): Clarify scout documentation and intake ownership * fix(pi): prevent duplicate assistant replies in Calm (#936) * fix(pi): preserve operational follow-up semantics in Calm * no-mistakes(document): Correct Calm operational-row visibility documentation * perf(bin): shrink the ShellCheck source graph (#939) * perf(lint): shrink shell source graph * no-mistakes(review): Ensure lint workers terminate fully on cancellation * no-mistakes(document): Repair stale lint documentation ownership * fix(pi): remove Calm hidden-block gaps (#942) * fix(pi): remove Calm hidden-block gaps * no-mistakes(review): Validate Calm geometry against current viewport * no-mistakes(review): Synchronize Calm geometry checks with reload completion * fix: enforce contract boundaries for ask-user findings (#945) * fix: escalate ask-user contract expansion * no-mistakes(document): Point project management to authority owner * docs: prefer direct operational paths (#946) * fix(pi): hide operational user rows in Calm mode (#948) * fix(pi): hide Calm operational user rows * no-mistakes(review): Narrow Calm operational input suppression * no-mistakes(review): Avoid Calm replay classifier subprocesses * no-mistakes(document): docs: point Pi verification to Calm owner * fix: relaunch missing second mates at session start (#950) * fix(session-start): relaunch missing second mates * fix(test): detect completed parallel workers * no-mistakes(review): Isolate session-start recovery test cleanup * no-mistakes(review): Complete backend-safe secondmate session recovery * no-mistakes(review): Resolve Zellij task ownership before recovery * no-mistakes(review): Recover relocated Zellij ghost tabs safely * no-mistakes(review): Restore conservative Zellij recovery boundary * no-mistakes(review): Reject malformed tmux recovery targets * no-mistakes(document): Align secondmate recovery documentation * no-mistakes: apply CI fixes * fix(herdr): reclaim resumed task projections after restart (#967) * fix(herdr): reclaim resumed task projections safely * no-mistakes(review): Enforce safe Herdr reclaim close boundaries * no-mistakes(document): docs: clarify Herdr restart projection contract * Teach Ahoy to surface open decisions (#968) * Require shipshape routine acknowledgement (#969) * docs: separate current guidance from verification evidence (#994) * docs: separate current guides from verification * no-mistakes(review): Restore Herdr 0.7.5 restart-reclaim verification evidence * fix: preserve Claude watcher continuity across Stop hooks (#997) * feat(claude): Stop-owned tokenless watcher continuity via asyncRewake auto-arm Claude primaries (main home and marked secondmate homes) no longer depend on the model remembering to re-arm the watcher after each wake. A tracked Stop asyncRewake hook (bin/fm-claude-stop-autoarm.sh, timeout 28800s) fires on every turn end, claims one home-scoped single-flight owner, foregrounds bin/fm-watch-arm.sh inside the hook-owned process tree, and translates an actionable close or typed watcher failure into exactly one exit-2 rewake. The hook scopes to genuine primary checkouts, requires the session lock to be held by its own harness ancestor, stays inert while AFK owns triage or the home is idle, and hands AFK transitions mid-cycle to the daemon without rewaking. The synchronous turn-end guard gains a --claude cooperative mode: it ignores stop_hook_active (true on every post-continuation stop, which is what re-opened the 2026-07-21 blind window), waits briefly for a watcher health proof, a live auto-arm owner claim, or a fresh rewake epoch, and re-blocks only when the auto-arm genuinely failed to establish - bounded to 3 consecutive blocks per session, safely below Claude Code's 8-block override, then a degraded allow with a visible systemMessage. Codex keeps the previous one-block loop guard byte-identically, and Pi, OpenCode, and Grok adapters are untouched. Continuity PreToolUse gate and durable wake queue are preserved; the gate's recovery guidance now names the Stop-owned re-arm and reserves manual background arms for auto-arm failure. Claude supervision protocol, harness-adapters facts, architecture, configuration, and continuity docs updated; docs/turnend-guard.md records the 2026-07-24 Claude 2.1.218 contract revalidation (tokenless multi-cycle rewake, no-dedup, timeout process-group kill, 8-block cap, interactive non-stall) and the 2.1.219 product live E2Es. Regression matrix: hermetic tests cover scope, identity, AFK, need, single-flight, translation, guard cooperation, budget, and registration; the new live E2E proves two full tokenless auto-arm rewake cycles with zero model arm commands; Pi and OpenCode Option B live E2Es pass unchanged. * no-mistakes(review): Fix Claude X-mode auto-arm continuity backstop * no-mistakes(review): Remove unsupported Claude contract-lab verification claims * no-mistakes(document): Update Claude auto-arm continuity documentation * fix(herdr): clean stale projections at session start (#996) * Clean stale Herdr projections at session start * no-mistakes(document): Document stale Herdr session-start projection cleanup * no-mistakes(review): Enforce locked exact Herdr projection cleanup * no-mistakes(review): Fail closed on unverified session lock ownership * no-mistakes(review): Serialize session lock acquisition atomically * no-mistakes(document): Align session-start and Herdr cleanup documentation * no-mistakes(document): Generalize lock-refusal diagnostics * no-mistakes(lint): Avoid reserved keyword in concurrency test * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * feat(inbox): add the captain's decision and review board generator bin/fm-inbox-view.sh writes one self-contained HTML board from live fleet state, so every open captain decision, review-ready pull request and stopped task is answerable from a single surface instead of arriving one chat interrupt at a time. It is a read-only projection in the same shape as fm-fleet-view.sh: it shells out to fm-fleet-snapshot.sh --json and `tasks-axi show --full`, and writes nothing but the board file. A test proves state/ and data/ stay byte-identical across a generation. Three selection defects the existing aggregated surface has are fixed here. Decisions are selected on the captain hold alone, because the snapshot's own captain_actionable flag also requires kind == "captain" and therefore hides every thread gated with `tasks-axi hold --kind captain`, which is where the oldest waiting decisions live. Decision text is read through tasks-axi, because the snapshot's metadata capture stops at the first comma and silently drops the options from any hold reason that lists them. A recorded pull request renders as unverified unless --verify-prs live-checks it, because locally recorded metadata cannot know a pull request was closed and a closed one must never read as ready to merge. Decision cards lead with plain English. An optional captain-private cards file supplies the plain question, what the captain is actually choosing, where it came from, firstmate's recommendation, a link to research a product or service, and a collapsed technical section. An item that is firstmate's own assumption rather than a choice the captain made is flagged as one. A decision with no card still renders and says so rather than looking complete. Answers use the Lavish input contract: native radios plus a free-text note, with exactly one queued prompt per question on submit. This change delivers the surface only; regenerating on a cadence, relaying answers back, and any escalation-routing change remain separate captain decisions. * feat(inbox): free-form answers, discuss path, reliable delivery, overflow fix Fold in the captain's hard requirements from live use of the board. Free-form answers are now first-class. A card's predefined options are optional quick-picks with a clear-selection reset; the answer submits with just free text and no option selected. Previously the submit required picking an offered option, so a genuine free-form answer could not be sent at all. Each card gains a discuss path that sends a question back (DISCUSS <id>: ...) instead of an answer (DECISION <id>: ...), so a decision can be clarified rather than forced. Delivery is made reliable end to end. bin/fm-inbox-serve.sh is one operator command that regenerates the board, arms the answer relay, serves it on the Tailscale address, and verifies the link. bin/fm-inbox-arm.sh writes and registers state/inbox.check.sh, a bounded lavish poll bound by fm-check-register, so firstmate's supervision cycle relays answers on its normal check cadence without anyone remembering to poll. The relay fails closed: when the board is not served the poll errors fast and it stays silent. Submitting also sends immediately with a visible confirmation, closing the window where an answer looked silently eaten. Long titles and ids now wrap instead of overflowing horizontally, which Lavish's own layout audit flags as an error. No-project items get readable area names derived from their origin or id instead of a generic badge. Behavior is covered by tests/fm-inbox-view.test.sh (generator, read-only, free-form, discuss, immediate send, area names, wrapping) and tests/fm-inbox-arm.test.sh (the relay is a valid registered fail-closed check, silent while unserved, rejected after tampering). docs/inbox-board.md records the design and verification evidence. * no-mistakes(review): Fix inbox relay safety and PR verification * no-mistakes(review): Gate inbox relay before polling * no-mistakes(document): Align inbox board docs * no-mistakes: apply CI fixes --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * test(session-start): fix fork sync merge artifacts * no-mistakes(review): Harden inbox relay and hooks * no-mistakes(review): Escape inbox shipped metadata * no-mistakes(document): refresh inbox-board documentation --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Ship the captain's decision-and-review board (firstmate shared tracked material). The board is one Lavish HTML surface that collects everything waiting on the captain (decisions to make, PRs to approve, stopped work) so it arrives in one place instead of scattered chat interruptions. Deliverables: bin/fm-inbox-view.sh (a pure READ-ONLY generator projecting bin/fm-fleet-snapshot.sh --json plus 'tasks-axi show --full'; must mutate nothing under state/ or data/, proven by a test), bin/fm-inbox-render.py (HTML renderer), bin/fm-inbox-arm.sh and bin/fm-inbox-serve.sh (serve + arm the answer relay), tests, and docs/inbox-board.md.
The captain approved the board shape yesterday and, from live use, stressed five HARD requirements folded into this change: (1) FREE-FORM ANSWER IS FIRST-CLASS - every decision card must accept a standalone free-text answer with NO forced option selection; predefined options are optional quick-picks with a clear-selection reset. (2) A per-card discuss/clarify path that sends a question back (DISCUSS : ...) instead of an answer (DECISION : ...). (3) RELIABLE DELIVERY - his prior answers never reached firstmate because nothing polled the board; fm-inbox-serve.sh now serves AND arms a registered watcher check (state/inbox.check.sh via fm-check-register.sh, same pattern as the Workflowy @go channel) that relays answers on the normal supervision cadence with no manual polling, and the submit sends immediately with visible confirmation. The relay fails closed: silent when the board is unserved. (4) Fix horizontal overflow so long titles wrap (Lavish's layout audit flagged it error-severity). (5) Clearer names for no-project items, derived from origin/id.
Deliberate design decisions a reviewer should know: The generator selects open captain decisions on hold_kind==captain ALONE, intentionally wider than the snapshot's captain_actionable flag (which also requires kind==captain and thus hides the captain's oldest holds). Full decision text is read via tasks-axi show --full because the snapshot truncates hold reasons at the first comma. Recorded PRs render as UNVERIFIED unless --verify-prs live-checks them, because local metadata cannot know a PR was closed. Plain-English card copy lives in an optional captain-private cards file (data/inbox-cards.md), not in tracked code. fm-inbox-view stays a pure read-only generator; the stateful arming lives in the separate fm-inbox-serve/fm-inbox-arm so the no-mutation guarantee and its test stay clean. This touches firstmate shared tracked material, so firstmate-coding-guidelines was followed (one sentence per line in Markdown, plain dashes, no agent co-author, shellcheck-clean scripts, colocated tests, evidence-backed doc). A pre-existing unrelated failure in tests/fm-bearings-snapshot.test.sh (an SSHHIP-child warning) exists on the base commit and is out of scope.
What Changed
state/inbox.check.shrelay, and deliver submitted answers through the normal supervision cadence while staying silent when unserved.Risk Assessment
✅ Low: The follow-up is narrow, source-verifiable, and addresses the prior relay-spawn and wake-count defects without introducing a material remaining risk I can substantiate.
Testing
After fixing an initial changed-test selector gap, I reran the runner and inbox behavior tests, generated and served a fixture board, verified read-only manifests and relay registration, captured desktop/mobile/interaction screenshots, and confirmed free-text answers, discuss prompts, immediate send confirmation, and no horizontal overflow all worked.
/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-rendered.png)/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-mobile-wrap.png)/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-interaction.png)Evidence: Generated inbox board HTML
Evidence: Browser interaction payload and layout check
Evidence: Serve and arm output
armed: state/inbox.check.sh relays answers for /var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board.html board: /var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board.html link: http://127.0.0.1:4891/session/31e87e96a7fed7be reachable: yes (HTTP 200 over Tailscale)Evidence: Relay registration check
registered=yes mode=700 relay=/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/fixture-home/state/inbox.check.shEvidence: Read-only manifest check
state/ and data/ manifests are byte-identical after fm-inbox-view.shPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (2) ✅
bin/fm-inbox-render.py:321- Whengh pr viewfails,fm-inbox-view.shrecords the state asunknown, but this branch treats every verified non-OPENstate as stale. With--verify-prs, a transient auth/network failure can move an open PR out of “Review and merge” and label it “already unknown”; keepunknownin the review list as unchecked instead.bin/fm-inbox-arm.sh:68- The registered relay runs barelavish-axi poll, so a board served with a non-defaultLAVISH_AXI_PORTis verified byfm-inbox-serve.shbut later polled on the watcher's default port unless that environment happens to match. Capture/export the served port in the generated check so non-default-port boards do not silently miss answers.bin/fm-inbox-arm.sh:58-cat > "$CHECK"opens the predictablestate/inbox.check.shpath before destination validation, so an existing symlink or hard link can be overwritten outside the intended state file beforefm-check-register.shrejects it. Publish through a private temp file instate/and a validated rename, matching the existing check-publication pattern.bin/fm-inbox-arm.sh:65- The generated check embedsBOARDandANSWERSinside single quotes without shell escaping. A board/home path containing a single quote or newline can register a syntactically broken or injectable check, making the relay fail silently; generate shell-escaped assignments instead.🔧 Fix: Fix inbox relay safety and PR verification
2 warnings still open:
bin/fm-inbox-arm.sh:106- The generated check callslavish-axi polleven when the board is unserved. Current LavishpollcallsensureServer()first, so an idle watcher can start a Lavish server itself; because the shim does not setLAVISH_AXI_HOST, that server may be localhost-bound and laterfm-inbox-serve.shcan reuse it instead of serving on Tailscale. Gate polling on an already-running served session before invokingpoll.bin/fm-inbox-arm.sh:111- The answer-count grep does not match Lavish's prompt rows, which are serialized as rows likedecision,"DECISION ...", not lines beginning with two spaces and a quote. With zero matches,grep -cprints0and exits 1, so the fallback appends another0; real answer wakes can say0\n0 captain answer(s)even after writing an answer file. Count prompt rows correctly or emit a fixed single-line wake message.🔧 Fix: Gate inbox relay before polling
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bin/fm-test-run.sh --list --changed --base 2b7cd66ad09722fec4cf11acdbee4fd8046f06e7initially exposed the unmapped renderer; after the fix it succeeded and wrotechanged-selection-after-fix.txt.bin/fm-test-run.sh tests/fm-test-run.test.shbin/fm-test-run.sh tests/fm-inbox-view.test.sh tests/fm-inbox-arm.test.shGenerated a fixture board withPATH="$FAKEBIN:$PATH" FM_HOME="$EVIDENCE/fixture-home" bin/fm-inbox-view.sh "$EVIDENCE/inbox-board.html"and comparedstate/+data/manifests before/after.Served and armed the fixture board withLAVISH_AXI_PORT=4891 FM_HOME="$EVIDENCE/fixture-home" bin/fm-inbox-serve.sh --no-generate --link-host 127.0.0.1 "$EVIDENCE/inbox-board.html".Captured rendered UI withplaywright screenshotat desktop and 390px mobile widths.Rannode "$EVIDENCE/inbox-board-interaction.cjs"to clear a quick-pick, submit a free-text-onlyDECISION, submit aDISCUSSquestion, verify immediate send calls, and assert no horizontal overflow.Checked relay trust withfm_custom_check_registered "$STATE" inboxand verifiedstate/inbox.check.shmode700.Cleaned up the temporary Lavish server withLAVISH_AXI_PORT=4891 lavish-axi stopand confirmed the port no longer answered.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.