feat(capacity): publish persistent tailnet-only dashboard service - #8
Merged
purple-phoenix merged 24 commits intoJul 28, 2026
Merged
Conversation
…mmand service Add bin/fm-dash-serve.mjs, a loopback service published tailnet-only through tailscale serve (never Funnel) that wears the producer dashboard unchanged and injects an interactive layer for the captain-authenticated tailnet identity: one-click CAP action approval, decision option approval, idea verdicts over data/ideas/, server-side and interval capacity refresh, and de-anonymized clickable work-item and decision detail views assembled from briefs, recorded metadata, the backlog, status tails, and scout reports. Clicked commands never execute in the web process: each becomes one durable fm-dash-command.v1 record in state/dash-inbox/, surfaced to the running firstmate through the registered fm-dash watcher check and claimed exactly once with bin/fm-dash-inbox.sh under the capacity skill's authority limits. bin/fm-dash-install.sh owns launchd persistence (RunAtLoad + KeepAlive + kickstart), the never-Funnel serve mapping with post-install verification, config/dash.json, and a --read-only install shape for serving ahead of command wiring. fm-capacity.mjs gains the opt-in --refs identity sidecar it owns so the authenticated service can enrich the page while the on-disk dashboard stays identity-opaque, plus a fix for the rollcall empty-state wrapping. AGENTS.md, docs/configuration.md, docs/dashboard-service.md, docs/scripts.md, and the capacity skill carry the contract; tests/fm-dash.test.sh covers identity fail-closed behavior, dispatch validation, detail assembly, idea verdicts, read-only mode, inbox claim, the watcher check, and the installer's funnel-free structure.
…ns, blocker chains, zero copy Two live-use defects and two dropped ordered requirements, found on the deployed read-only service: - The launchd agent ran the capacity generator with launchd's minimal PATH, so every state-reader tool (tmux, no-mistakes, tasks-axi, gh) was unresolvable and all worker states degraded silently to unknown. The installer now pins the installing shell's PATH into the agent, and the service marks a mostly-unknown render RENDER DEGRADED loudly on the page and in its log instead of presenting degraded data as truth. - A keyless needs-decision status event was fabricated into a captain decision literally keyed 'default' with a dead-end detail view. Keyless fold entries now surface honestly as a worker question firstmate is handling in chat: no decision identity, no Decide row, no dead-end modal. - Blocked rows now carry their plain-language blocker chain resolved recursively to the root cause (blocking task state, time gate, or captain decision) plus an explicit What-you-can-do line, kept privacy-safe in the offline artifact with opaque references that de-anonymize at serve time. - The served page now has zero copy-prompt affordances: producer copy buttons are replaced by direct dispatch, or removed in read-only mode, while the offline file keeps them for file:// use. Regression coverage in tests/fm-capacity.test.sh (keyless questions and chains stay privacy-safe) and tests/fm-dash.test.sh (plist PATH pinning, degraded banner, copy-affordance replacement).
purple-phoenix
added a commit
that referenced
this pull request
Jul 29, 2026
Second sibling PR to land on main while this sync was in flight. Integrated as a MERGE for the same reason as #8: rebasing would flatten the upstream merge commit whose two parents are the record of this sync. No conflicts - #9 touches the capacity surface and its own new bin/fm-wait-progress.mjs, which this branch does not modify. Verified the consumers that the earlier blocker-model union touched are still green: fm-capacity 24/24, fm-fleet-snapshot-view 15/15, fm-documentation-audiences 5/5, and the new script is already classified in the toolbelt and audience inventories that #9 updated.
purple-phoenix
added a commit
that referenced
this pull request
Jul 29, 2026
* fix(pi): distinguish stale locks when arming watcher (kunchenguid#681) * fix(pi): distinguish stale locks when arming watcher * no-mistakes(test): Stabilize watcher extension async waits * no-mistakes(document): Document Pi lock recovery * fix: accept secondmate house vocabulary (kunchenguid#685) * fix: accept secondmate as house vocabulary * no-mistakes(test): Update captain vocabulary contract test * no-mistakes(document): Align secondmate documentation vocabulary * fix(bin): parse handoff homes after registry parentheticals (kunchenguid#686) * fix: parse secondmate home after pre-field parentheses Registry summaries often include parentheticals before the structured (home: ...) field. Match that field with a greedy prefix so handoff no longer reports "has no home" for those entries. * no-mistakes(document): Refresh handoff test comments * feat: add native session-start nudges (kunchenguid#687) * feat: add native session-start nudges * no-mistakes(document): Document nudge script inventory * docs: call built-in defaults the firstmate repo, not template (kunchenguid#688) Relabel absent-captain and related domain defaults wording so it names the firstmate repo rather than treating "template" as this domain's identity label. Keep the design-tenet "shared template" statements and unrelated launch/PR-poll template uses unchanged. * fix(bin): repair fm-brief.sh parse error and harden set -u array expansion (kunchenguid#205) * fix(bin): use set -u-safe empty-array expansion in pr-merge and spawn Expanding "${arr[@]}" on an empty array under set -u fails on bash < 4.4 (notably macOS bash 3.2). Quote the portable "${arr[@]+"${arr[@]}"}" idiom in fm-pr-merge and fm-spawn batch dispatch so empty arrays expand to nothing. Co-authored-by: Cursor <cursoragent@cursor.com> * test(brief): harden fm-brief regression coverage for parse and scaffolds Tighten bash -n checking, pin literal backtick rendering in the no-mistakes DOD wording assertion, and keep a scout/secondmate scaffold smoke test so the Co-authored-by: Cursor <cursoragent@cursor.com> kunchenguid#166 apostrophe regression cannot return unnoticed. --------- Co-authored-by: Cursor <cursoragent@cursor.com> * fix(bin): keep watcher supervision continuous across child cycles (kunchenguid#693) * fix: make watcher supervision continuous * no-mistakes(review): Bound watcher retries and log attached signals * no-mistakes(review): Add bounded successor-recovery wake fallbacks * no-mistakes(review): Prevent overlapping successor-arm retries * no-mistakes(review): Resume supervision after late arm closes * no-mistakes(review): Bind OpenCode recovery to attempted arm * no-mistakes(test): Synchronize peer beacon regression fixture * no-mistakes(test): Synchronize Pi and OpenCode late-close lifecycle fixtures * no-mistakes(document): Captain: document watcher successor protocol behavior * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * fix: fetch current PR head for review diffs (kunchenguid#722) * fix: always fetch PR head for review diffs Prefer a freshly fetched refs/pull/<n>/head over a reachable recorded pr_head= so reviewers never hold a merge over a "missing" fix that already landed on the remote PR. Recorded SHA is offline fallback only; local branch is last resort with a warning. Store the tip under refs/fm-review/ so a later base-branch fetch cannot clobber the compare tip via FETCH_HEAD. * no-mistakes(test): Isolate session-start nudge tests from gate state * no-mistakes(document): Correct review-diff documentation * docs: resolve five contract contradictions across AGENTS.md, README, and skills (kunchenguid#736) * docs: resolve five contract contradictions * no-mistakes(test): align owner-pointer assertions with reworded docs; skip absent shellcheck * docs(harness): correct Grok exit guidance (kunchenguid#742) * docs(harness): reverify grok exit command * no-mistakes(test): Correct Grok exit resume attribution * fix(watcher): bound stale wakes for parked crew (kunchenguid#743) * fix(watcher): bound stale wakes for exited paused crew * no-mistakes(review): Gate pause suppression on confirmed agent death * no-mistakes(test): Fixed stale pause cadence * no-mistakes(document): Document dead-agent hold cadence * fix(supervision): distinguish ordinary wakes from recovery (kunchenguid#744) * fix(supervision): distinguish ordinary wakes from repair * no-mistakes(review): Make passive guard follow-ups recovery-only * no-mistakes(document): Clarify recovery-only turn-end guard documentation * fix(x-mode): dedupe pending mention wakes (kunchenguid#745) * fix(x-mode): dedupe pending mention wakes * no-mistakes(review): fix x-poll claim error deduplication * no-mistakes(review): separate claim diagnostics from relay recovery * no-mistakes(document): Document X-mode once-only mention wakes * feat(wake): enrich drained signals with bounded status context (kunchenguid#747) * feat(wake): enrich drained signal context * no-mistakes(review): Bound wake enrichment reads * no-mistakes(document): Document wake-drain annotations * docs(wake): explain at-least-once drain boundary * no-mistakes(review): Prevent symlink races in wake annotations * no-mistakes(review): Exercise wake symlink race regression * test: document intentional AFK marker subprocesses * fix(wake): isolate annotation marker state * feat(herdr): add optional presentation spaces (kunchenguid#784) * feat(herdr): add optional presentation spaces * no-mistakes(review): Harden Herdr projection creation and spawn serialization * no-mistakes(review): Captain, disarm Herdr cleanup before launch submission * no-mistakes(test): Correct stale Orca metadata failure fixture * no-mistakes(document): Document Herdr presentation projection accurately * fix(send): treat opencode busy-queued composer state as submitted (kunchenguid#775) * fix(send): treat opencode busy-queued composer state as submitted When fm-send sends a message to a BUSY opencode crewmate on the tmux backend, opencode accepts the Enter and queues the message for the next turn, but leaves the typed text visible in the composer row. The submit-verification loop sees a pending composer, exhausts retries, and reports a false "Enter swallowed" failure while the message is actually delivered. Fix: after Enter retries are exhausted and the composer still shows pending, check fm_pane_is_busy. If the pane is busy (agent mid-turn, footer shows "esc interrupt"), the harness queued the message, so return "empty" (accepted). On an idle pane, keep returning "pending" (genuine swallow detection preserved). Regression tests cover four scenarios: - busy pane + pending composer -> empty (message queued) - idle pane + pending composer -> pending (genuine swallow) - busy pane + composer clears on first Enter -> empty - idle pane + composer clears on first Enter -> empty (existing path) * docs: document busy-queued Enter exception across backend docs and skills Add explanatory comments and backend documentation for the busy-queued Enter fix (opencode 1.18.4 accepts Enter mid-turn but keeps typed text in composer until the turn ends): - bin/fm-tmux-lib.sh: document the busy-aware fallback in the file header and above fm_tmux_submit_enter_core - .agents/skills/afk/SKILL.md: daemon-facing policy note - .agents/skills/harness-adapters/SKILL.md: harness-specific fact - docs/tmux-backend.md: submit-acknowledgement section with the busy-queue exception - docs/herdr-backend.md: record the known gap - docs/architecture.md: cross-reference in the daemon section * test(tmux): fix SC2181 and make busy-submit test executable * fix(spawn): require two stable reads before accepting worktree path (kunchenguid#765) * fix(spawn): require two stable reads before accepting worktree path The treehouse-get worktree-detection loop in fm-spawn.sh accepted the first pane_current_path read that differed from the project path, but on some tmux/WSL setups a brand-new window transiently reports a stale-but-real path before the pane actually settles into the worktree. Since that stale path is itself a real, distinct git checkout, it also passes validate_spawn_worktree's isolation check, so the loop silently recorded the wrong worktree in state/<id>.meta (and, for claude harness spawns, installed the turn-end hook there too). Require two consecutive polls to agree on the same non-project path before accepting it, using the existing inter-poll sleep as the confirmation gap so an already-settled pane isn't slowed down by an extra cycle. * fix(tests): drop unused CASE_DIR read in worktree-settle test ShellCheck SC2034: CASE_DIR is split out of the case record but never referenced; discard it with _ instead. --------- Co-authored-by: Freudator86 <tim@allesknut.de> * fix(bin): make watcher process identity immune to Linux wall-clock changes (kunchenguid#752) * fix(watcher): stabilize Linux process identity * no-mistakes(document): document FM_PROC_ROOT_OVERRIDE and Linux starttime identity rationale * fix: prevent AFK idle stalls and stale run attribution (kunchenguid#758) * fix(supervision): verb-aware captain relevance, AFK wedge, head-bound state Stop free-text tokens like "merged" from promoting nonterminal working: lines to captain-relevant, so AFK no longer permanently suppresses idle recovery. Defend wedge aging independently for nonterminal progress verbs, bind no-mistakes current-state attribution to code identity (not branch alone), and mark setup-complete as nonterminal in the ship brief scaffold. * no-mistakes(review): Enforce nonterminal suppression and head-bound run attribution * no-mistakes(document): Document current-code-bound run attribution * no-mistakes(test): Wait for stable Herdr shell readiness * no-mistakes(test): Make Herdr and watcher readiness tests deterministic * no-mistakes(test): Make tmux capture and watcher lifecycle deterministic * no-mistakes(document): Document corrected supervision contracts * fix(bin): allow safe teardown during watcher recovery (kunchenguid#750) * fix: allow safe teardown during watcher recovery * no-mistakes(review): Distinguish unsafe-teardown deny guidance via policy reason code * no-mistakes(document): Sync continuity-gate docs to allow teardown recovery * test: mark dynamic teardown fixture literal * no-mistakes(document): docs: add teardown to continuity gate allow list * feat(herdr): order presentation spaces while preserving focus (kunchenguid#790) * feat(herdr): order presentation worker spaces * fix(herdr): preserve focus during projected cleanup * no-mistakes(review): Serialize Herdr cleanup and protect active seeded tabs * no-mistakes(review): Serialize Herdr aborts with guarded focus regressions * no-mistakes(review): Fall back flat when Herdr serialization is unavailable * no-mistakes(test): Stabilize watcher startup and AFK handoff tests * no-mistakes(document): Correct Herdr ordering and focus documentation * fix(bin): send literal config reread nudges after pushes (kunchenguid#809) * Send literal config reread after inherited config push When declared inherited config changes under an already-running secondmate, build a per-home instruction from validated destination post-write bytes and deliver it on the routed secondmate path. Unchanged config sends nothing; ABSENT represents removal; captain-shared is never inlined. Covers mid-session config-push and the locked bootstrap convergence path without hardening spawn against deliberate runtime choice. * no-mistakes(review): Fix config reread framing, partial propagation, and respawn order * no-mistakes(review): Send config rereads via durable single-line pointers * no-mistakes(review): Make failed config rereads retryable * no-mistakes(review): Make config reread retries generation-safe * no-mistakes(review): Make config rereads durable and ordered * no-mistakes(review): Drain retries, bound history, preserve detect-only read-only mode * no-mistakes(review): Retain write retries and quarantine stale respawn generations * no-mistakes(review): Preserve exact config reread retries and delivery order * no-mistakes(review): Preserve exact retry bytes and bounded quarantine pruning * no-mistakes(document): Consolidated config-reread documentation * feat(watch): follow GitLab merge requests to merge (kunchenguid#797) * feat(watch): follow GitLab merge requests to merge The merge watch only understood GitHub pull requests, so a task whose deliverable is a GitLab merge request was never followed to merge. Generalize the stored poll identity from owner/repository to a provider-tagged provider/url/host/path/number record. GitLab runs mostly on self-hosted instances and its projects nest under groups at no fixed depth, so the host and the full project path are data in the record rather than constants, and every consumer rebuilds the URL from those parts and refuses any record that does not reconstruct it exactly. The GitLab state is read with plain glab, matching the GitHub path's use of plain gh, so an upstream checkout needs no extra tooling. Two things about glab were established by running it rather than assumed, because a wrong invocation here fails silently into a permanent "not merged": - glab has no field selector, and its JSON would need a JSON processor that firstmate does not require, so the state is read from glab's own field output. Only an exact "merged" wakes firstmate, so a changed format stays silent instead of reporting a merge. - glab cannot take a merge request URL the way gh can, because that form resolves through the current git repository and the watcher has none. It is addressed by project URL and merge request number instead. An absent glab produces no wake rather than a false merge, and arming refuses with a clear message since that is the one point where a missing CLI can still be reported. A GitLab task records no pr_head, which both consumers already treat as optional. The merge path still addresses GitHub only and refuses a merge request URL rather than sending it to the wrong forge. The record version moves to v2, and the existing non-executing migration rebuilds an already-armed watch from its recorded URL, so no watch is lost by upgrading. docs/gitlab-merge-watch.md records the evidence, taken against the public fixture project https://gitlab.com/KarotKris/gitlab-merge-watch-fixture. * no-mistakes(review): Reject github.com host in GitLab MR URL/sidecar validation * no-mistakes(document): Note GitLab MR URLs are explicitly refused, not just malformed ones, in fm-pr-merge.sh docs * fix(herdr): group projected children beneath owning parents (kunchenguid#821) * feat(herdr): correct all-home child presentation topology Inherit the presentation opt-in to secondmate homes, label new projected spaces with the approved corner format, insert each child under its owning parent under one session-scoped lock, and keep flat non-destructive fallback. * no-mistakes(review): Exclude secondmates from Herdr presentation projection * no-mistakes(review): Harden shared Herdr locks and ambiguous child ordering * no-mistakes(review): Use adjacency-only Herdr child ownership * no-mistakes(review): Reject foreign legacy projections safely * no-mistakes(review): Validate Herdr session sockets before projection * no-mistakes(test): Fix Herdr teardown fixture session socket metadata * fix(herdr): canonicalize presentation lock socket paths Always resolve the session socket parent directory so symlink parents such as /tmp -> /private/tmp cannot split the shared cross-home lock identity. Refuse relative socket paths. Clarify lock-unavailable warnings. * no-mistakes(test): Fix Bash-compatible GitLab merge request URL parsing * no-mistakes(document): Document all-home Herdr child topology * no-mistakes(lint): Quote fallback provenance string for ShellCheck * fix: keep local no-mistakes tests intent-targeted (kunchenguid#823) * fix(no-mistakes): drop full-suite local Test override Local no-mistakes Test is intent-targeted; CI Behavior keeps the broad tests/*.test.sh suite. Keep commands.lint on bin/fm-lint.sh and add a focused contract test so the override cannot silently return. * no-mistakes(lint): Make CI contract assertion ShellCheck-clean * feat: add canonical timed test runner (kunchenguid#825) * feat(test): add canonical timed suite runner and honest CI timeout Introduce bin/fm-test-run.sh as the single serial owner for selecting one script, a family, a conservative changed-file set, or the explicit complete suite, with per-script timing markers and a JSON artifact. Wire CI Behavior through the runner, raise the hang-tripwire timeout to 25 minutes, and document entry points without restoring a full-suite local no-mistakes Test command. * no-mistakes(review): Captain: fix changed selection and empty summaries * no-mistakes(review): Captain: fail closed on unmapped changed sources * no-mistakes(document): Document canonical timed test entry points * fix: surface main inventory gaps in Bearings (kunchenguid#830) * fix: disclose main-home orphan and unstructured inventory gaps Main Bearings could report an empty fleet while structured in-flight rows lacked meta or current backlog rows were free-form. Emit main_inventory from the fleet snapshot, map it into Bearings omitted surfaces and a Charted Next gate, and keep meta as the only live Underway source. * no-mistakes(document): Document Bearings inventory-integrity projection * no-mistakes: apply CI fixes * feat: add bounded concurrent test isolation proof (kunchenguid#832) * feat: add concurrent test isolation proof for Phase 2 Prove an audited portable candidate set passes under concurrent workers with private mode-0700 temp roots, without enabling production CI sharding or fm-test-run --jobs. * no-mistakes(review): Pin isolation proof to audited candidate manifest * feat: guard against missed secondmate reports (kunchenguid#834) * feat(secondmate): parent-owned guards for missed status reports Marked parent-to-secondmate requests now create a durable pending-reply expectation with a privacy-safe correlation id before delivery. Transport success never resolves it; only a correlated parent status or document pointer does. After a completed turn with no report, the parent sends one recovery repost and escalates once if that turn is also missed, without scraping the secondmate conversation or looping. * no-mistakes(review): Deduplicate wrong-home pending-reply sightings * no-mistakes(review): Harden pending-reply recovery and escalation guards * no-mistakes(review): Bound pending-reply backend polling * no-mistakes(review): Cache pending-reply status scans * no-mistakes(review): Protect undelivered pending-reply records from scans * no-mistakes(review): Close pending-reply delivery durability gaps * no-mistakes(review): Separate pending-reply transport outcomes * no-mistakes(review): Escalate stalled pending-reply deliveries once * no-mistakes(review): Resolve attempted deliveries from correlated reports * no-mistakes(review): Resolve late reports after delivery escalation * no-mistakes(document): Document pending-reply grace and ownership * no-mistakes(lint): Silence intentional pending-reply test fixture lint warnings * feat: require pinned real-Herdr CI coverage (kunchenguid#838) * feat: add required pinned Herdr CI lane Install exact Herdr 0.7.4 and Treehouse 2.0.1 with official assets and SHA-256 pins, run the real-herdr-gated family serially through fm-test-run with hard-fail on herdr-not-found, and keep portable Behavior free of claimed Herdr coverage. * no-mistakes(document): Consolidate real-Herdr CI documentation ownership * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * feat: shard portable tests and add bounded local parallelism (kunchenguid#841) * feat: shard portable CI tests after isolation proof Balance the Phase 2 proven-isolated set into two LPT portable parallel lanes from Phase 1 timing evidence, keep stateful work in a required portable serial lane, exclude real Herdr to its dedicated required lane, and prove complete inventory coverage with a deterministic guard. Add bounded local --jobs only for the proven set, per-lane timing plus aggregate artifacts, and reduce the interim portable hang tripwire now that the serial remainder owns the long wall-clock path. * no-mistakes(review): Captain, fix CI contracts and completion-order worker scheduling * no-mistakes(review): Captain, preserve stderr gate-skip detection in parallel tests * no-mistakes(document): Document portable sharding and timing aggregation * no-mistakes: apply CI fixes * fix: block primary-session delegation outside the fleet (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#873) * fix: preserve trustworthy Bearings data in partial snapshots (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#898) * fix: execute every PR body compliance event * no-mistakes(document): Document independent PR compliance events * fix: exclude operational injections from ahoy boundaries (kunchenguid#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 (kunchenguid#909) * fix: type canonical operational inputs * no-mistakes(document): Correct canonical operational-input documentation ownership * fix: avoid generic secondmate start acknowledgements (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#945) * fix: escalate ask-user contract expansion * no-mistakes(document): Point project management to authority owner * docs: prefer direct operational paths (kunchenguid#946) * fix(pi): hide operational user rows in Calm mode (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 (kunchenguid#968) * Require shipshape routine acknowledgement (kunchenguid#969) * docs: separate current guidance from verification evidence (kunchenguid#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 (kunchenguid#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 (kunchenguid#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 * fix: recover Claude supervision without watcher-status gate (kunchenguid#1001) * fix: recover Claude supervision at session start * fix: remove Claude watcher-status command gate * no-mistakes(document): docs: remove stale continuity gate references * fix: make quota-aware profile selection agent-owned (kunchenguid#1018) * Replace quota dispatch selector instructions * no-mistakes(review): Align bootstrap docs with agent-owned dispatch selection * fix(bin): remove vestigial dispatch selector (kunchenguid#1026) * remove vestigial dispatch selector * no-mistakes(review): Synchronize isolation proof and portable shard evidence * no-mistakes(review): Correct shard history and proof archive date * no-mistakes(review): Remove reintroduced selector documentation reference * no-mistakes(document): Remove stale dispatch strategy documentation * docs(agents): drop superseded interim quota-window rule (kunchenguid#1039) quota-axi 0.1.13 emits schemaVersion 2 with a quotaSemantics object per provider, so the successor named in the interim rule has landed and the rule's own removal condition is satisfied. Keep the ownership clause so quota-axi remains the single owner of how model or product windows relate to bounding account windows, and drop the interim weakest-headroom instruction. The unknown-semantics case is already covered by the existing requirement to stop and report a candidate whose applicable quota data or interpretation cannot be established. Drop the matching assertion phrase from tests/fm-instruction-owners.test.sh; the retained ownership phrase still asserts. * fix(tmux): scope busy detection and recognize current Claude turns (kunchenguid#1049) * fix(tmux): scope Claude busy detection by harness * no-mistakes(review): Separate verified and fallback busy signatures * no-mistakes(test): Scope busy signatures to supplied harnesses * no-mistakes(document): Document harness-scoped busy detection * feat: add verified Kimi crewmate adapter (kunchenguid#1047) * Add verified Kimi crewmate harness adapter * no-mistakes(review): Scope Kimi moon detection to spinner lines * no-mistakes(review): Match only complete Kimi spinner rows * no-mistakes(review): Resolve Kimi binary portably before pane creation * no-mistakes(document): Align Kimi adapter documentation * no-mistakes(lint): Suppress false-positive ShellCheck warning for sourced watcher override * Fix Kimi busy spinner detection * no-mistakes(review): Recognize Kimi session-lock ancestry and holders * no-mistakes(review): Scope pending-reply Kimi busy detection by harness * no-mistakes(document): Correct Kimi spinner capture documentation * no-mistakes(document): Clarify optional Kimi spinner whitespace * no-mistakes(lint): Silence intentional pending-reply test stub warnings * test: align rebased Kimi busy fixtures * no-mistakes: apply CI fixes * Reconcile Kimi busy detection after per-harness scoping * no-mistakes(review): Clarify observed Kimi spinner whitespace contract * no-mistakes(document): Clarify Kimi harness documentation * fix: harden Kimi submission and spinner matching (kunchenguid#1058) * fix kimi pointer submission and spinner conformance * no-mistakes(review): Preserve Kimi submit target ownership guard * feat(bin): add guarded Kimi turn-end wake (kunchenguid#1059) * Add guarded Kimi turn-end hook * no-mistakes(review): Require jq before installing Kimi turn-end hook * no-mistakes(review): Expose jq inside isolated Kimi test fixtures * no-mistakes(review): Preserve Kimi config boundaries during hook removal * no-mistakes(review): Document Kimi removal newline safeguard * no-mistakes(document): Document Kimi shared-home preservation * fix(tmux): classify bordered composers across all rows (kunchenguid#1066) * Fix structural tmux composer reading * Verify Calm compatibility with Pi 0.82 * no-mistakes(review): Harden structural composer classification boundaries * no-mistakes(review): Refresh composer and Kimi regression fixtures * no-mistakes(review): Fail closed on unbounded composer edges * no-mistakes(review): Enforce aligned composer geometry safely * no-mistakes(review): Make composer ambiguity locale-safe * no-mistakes(review): Preserve ambiguity through composer submission * no-mistakes(review): Carry composer proof through retries * no-mistakes(document): Document structural tmux composer delivery guarantees * no-mistakes: apply CI fixes * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix(brief): keep Definition-of-done heredocs parseable under stock Bash 3.2 Move each delivery mode's Definition-of-done text into its own function body instead of an inline DOD=$(cat <<EOF ... EOF) assignment. Stock macOS Bash 3.2 tracks quote state through a heredoc body while it scans for the closing paren of a command substitution, so a single apostrophe in that body makes the entire script unparseable - fm-brief.sh could not generate any brief on macOS. A heredoc in a plain function body is parsed normally, so this removes the bug class rather than avoiding apostrophes in the prose, and the upstream wording that tests/fm-ask-user-authority.test.sh pins is restored verbatim. * fix(spawn): fail closed when task metadata cannot be recorded The metadata write used a bare redirect, and fm-spawn.sh runs under set -u without set -e, so a failed write was ignored: the script went on to print 'spawned <id> ...' and exit 0 with no state/<id>.meta on disk. That durable record is what makes a worker supervisable - the watcher, recovery, and teardown all locate a task through it - so a successful-looking spawn left a live worker that nothing was tracking. Stop instead; the existing abort cleanup still owns backend teardown. tests/fm-backend-orca.test.sh already asserted this and was failing. * fix(tests): settle merged-tree invariants and a Bash 3.2 concurrency fixture Three merge-integration fixes and one inherited upstream defect. Merge integration: - Upstream's source-graph boundary (kunchenguid#939) pins which tests may carry production shellcheck context. This fork's two task-id tests sourced bin/fm-pr-lib.sh with a followed directive purely for validator access; both already disabled SC1091, so the directive only widened the graph. Switched to the non-following form and said why inline. - Upstream's documentation-audience inventory did not classify this fork's capacity and user-journey-audit skills, which upstream never had. Both are agent-runtime, matching every other .agents/skills entry. Inherited defect: - tests/fm-session-start.test.sh read $BASHPID in each racer subshell. BASHPID arrived in Bash 4.0, so under stock macOS Bash 3.2 with set -u every racer aborted and the exactly-one-winner assertion always saw zero. The fallback execs sh in the substitution subshell so its PPID is that racer's own pid; $$ would report the shared parent and defeat the race the test exists to prove. * fix(watcher): reap the arm's child by blocking wait, not a fixed poll The signal path polled for up to 2s before escalating to SIGKILL, on top of the ledger-lock wait and the successor check. The HUP regression has an 8s budget, so under a loaded parallel suite the arm could miss it and the test saw a timeout instead of exit 129. Block on the owned child instead: it returns the moment the child dies, and SIGKILL stays as the backstop for a child that ignores TERM outright. This keeps the property the poll was added for - the exact child is fully reaped before any successor check, so a still-fresh dying child cannot read as a healthy successor and suppress the arm-death alarm - while removing the fixed delay. Verified with six concurrent runs of tests/fm-watcher-lock.test.sh, the load that exposed it: 29/29 green in every run. * fix(watcher): bound the TERM grace so a deferred trap cannot stall arm exit Measured HUP-to-exit latency was 5-6s against the regression's 8s budget, because the blocking wait sat through a whole FM_POLL interval: Bash defers the watcher's TERM trap until its poll sleep returns. Give TERM a 1s grace, then SIGKILL, then block to reap. The blocking wait still guarantees the exact owned child is fully reaped before any successor check, so a still-fresh dying child cannot read as a healthy successor and suppress the arm-death alarm; the bound just stops a deferred trap from dominating shutdown. Measured after the change: 1-2s across five runs, a 4-8x margin on the budget. This replaces the previous commit's unbounded wait, which traded the original fixed 2s poll for a stall of up to one poll interval - slower, not faster. * docs(audiences): classify the dashboard-service doc PR #8 added docs/dashboard-service.md, which upstream's documentation-audience inventory did not know about, so the merged tree failed its own completeness check. The doc states that it owns the architecture narrative, trust design, and verification evidence while pointing at other owners for mechanics, which is the maintainer-architecture audience rather than operator-current. * no-mistakes(review): Publish spawn metadata atomically and correct cleanup scope * no-mistakes(test): Captain: restore spawn metadata publication invariants * no-mistakes(document): Refresh merged toolbelt and harness documentation * no-mistakes: apply CI fixes --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Johans Ballestar <47162770+Ballestar@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Israel Wilson <31997700+ICGNU3@users.noreply.github.com> Co-authored-by: Kostadin Pop-Kochev <kostadin.popkocev@gmail.com> Co-authored-by: Freudator86 <94322668+Freudator86@users.noreply.github.com> Co-authored-by: Freudator86 <tim@allesknut.de> Co-authored-by: vvizlan <steve.rule.wilson@gmail.com> Co-authored-by: jjames27th-eng <jjames27th@gmail.com> Co-authored-by: nithingm <nithingm31@gmail.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: ItsFlow <florian.eberhart.z@gmail.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
Publish the fleet capacity dashboard as a persistent, always-reachable tailnet-only web service the captain can use to command firstmate, wearing the merged fm-capacity command-briefing redesign's look. Captain's hard requirements: (1) one-click dispatch of CAP-* actions delivered to the running firstmate through a safe durable inbound channel (state/dash-inbox records surfaced by the registered fm-dash watcher check), never executing anything in the web process; (2) a refresh-capacity button rerunning the generator server-side plus interval auto-render; (3) a persistent prod link on Tailscale surviving reboots via launchd, tailnet-only, never Funnel; (4) clickable work items with rich detail views (description, PR link, tailnet preview links, test plan from briefs, metadata, backlog, reports); (5) open decisions as rich options documents (data//decisions/.md, owner-qualified identity, wired into fm-decision-hold.sh filing) with per-option approve buttons and a bounded custom answer; (6) an Ideas section over data/ideas/idea-backlog.md with approve/deny/add-suggestions verdicts; (7) zero copy-prompt affordances on the served page - direct dispatch replaces copy everywhere; (8) every blocked row shows its plain-language blocker chain resolved recursively to the root cause plus an explicit What-you-can-do line; (9) a subscription usage panel from quota-axi --json for Claude, Codex, and Grok only with local-timezone resets; (10) prod-bug fixes from live use: the launchd agent must pin the installing shell's PATH so state readers resolve (states degraded silently to unknown under launchd's minimal env), a mostly-unknown render is marked RENDER DEGRADED loudly rather than presented as truth, and a keyless needs-decision status event must never be fabricated into a captain decision (keyed 'default' with a dead-end modal) - it renders honestly as a worker question firstmate handles in chat, with no decision identity and no Decide framing. Deliberate boundaries decided by the captain during review: the on-disk dashboard stays identity-opaque and privacy-bounded (no hold reasons, event summaries, or state details are copied into the artifact - concreteness comes from the authenticated service's refs-based de-anonymization and detail views); tailnet identity plus same-origin enforcement make dashboard input genuinely the captain; the only free text anywhere is the bounded captain-authored idea suggestion and decision custom answer, delivered as data, never interpreted; inbox delivery is at-least-once with idempotent handling (deliver-then-archive so a crash never loses a command); destructive or irreversible consequences still re-confirm in chat; secondmate-owned items keep a limited v1 note by accepted decision; the never-Funnel verification fails closed on unreadable tailscale status; installs are transactional, restore only observed prior mapping state, and refuse or skip ports whose mappings belong to other services. The final commit e7cbd85 on this branch applies exactly the item-10 prod fixes plus blocker chains and zero-copy replacement with regression tests; everything before it was validated by the prior run of this same intent through checks-passed.
What Changed
Risk Assessment
✅ Low: Captain, the follow-up cleanly addresses sibling cycles, secondmate held-worker evidence, and multiple simultaneous decision roots without introducing a material new risk.
Testing
The already-passing full baseline was supplemented with the focused dashboard E2E suite and manual Chrome rendering of the authenticated served page; dispatch/inbox durability, refresh, decisions, ideas, quota filtering, blocker roots, degraded rendering, zero-copy controls, launchd PATH/persistence structure, and transactional Tailscale behavior all passed, with HTML, screenshot, and transcript evidence captured.
/var/folders/g0/z_4x96f92cgfpm7940cqvtt00000gn/T/no-mistakes-evidence/01KYMYRBAQ8B9B19EGRG79V21K/capacity-dashboard.png)Evidence: Authenticated dashboard response
Evidence: Dashboard end-to-end test transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (4) ✅
bin/fm-capacity.mjs:813- Criterion 10 requires that a keylessneeds-decisionevent never become a captain decision. This guard skipskey: "default"only whendecision.idis also absent, but secondmate summaries attach the worker ID, so it fabricates an owner-qualified decision from that ID. Skip every missing/default key regardless ofid, matching the main-home path.bin/fm-capacity.mjs:880- Criterion 8 requires every blocked row to show its recursive blocker chain and an explicit What-you-can-do line. Secondmate blocked records are pushed as plain cards withoutwaits_onorwhat_you_can_do(the captain-hold, structured-wait, and time-gate branches have the same omission), soblockedContext()renders nothing for them.bin/fm-capacity.mjs:702- Criterion 8 also requires recursive resolution to the root cause.blocked_bycan contain tasks-axi's comma-separated dependency list, but this code treats the entire value as one task ID;a,btherefore cannot match either backlog record and is reported as unavailable instead of resolving both roots.bin/fm-dash-serve.mjs:1169- Criterion 10 requires a mostly-unknown worker-state render to be markedRENDER DEGRADED. The denominator counts every manifest row, including queued, ready, landed, and blocked backlog cards that have no worker-state reading. A dashboard where all active workers are unknown can therefore escape the warning when enough non-worker rows exist; compare unknown states against authoritative worker-state rows instead.🔧 Fix: Fix captain decision, blocker, and degraded-state handling
3 errors still open:
bin/fm-capacity.mjs:676- Criterion 8 requires “every blocked row” to resolve every root. The new comma split assumesblocked_byalready aggregates all dependencies, but the real snapshot parser captures only the last repeatedblocked-by:marker from tasks-axi markdown. Multi-blocker production rows therefore still lose all but one root before reaching this code; preserve every edge infm-fleet-snapshot.shand pass the complete list here.bin/fm-capacity.mjs:682- Criterion 8 requires chains to be “resolved recursively to the root cause,” but this new depth limit stops after five blockers even when a valid deeper root exists. Cycle detection already bounds cyclic paths, so traverse the bounded snapshot until a root, cycle, or missing record is reached.bin/fm-dash-serve.mjs:1170- Criterion 10 and the ordered fix require that “all-workers-unknown always trips the banner.” Although the denominator is now correct,unknownStates >= 3suppressesRENDER DEGRADEDwhen a fleet has only one or two workers and every state is unknown. Remove the minimum-count gate or explicitly handle the all-unknown case.🔧 Fix: Preserve captain blocker roots and tiny-fleet degradation
3 issues (2 errors, 1 warning) still open:
bin/fm-capacity.mjs:681- Criterion 8 requires every blocked row’s chain to be “resolved recursively to the root cause.”currentByIdretains only a worker’s state, so a dependency on a blocked worker with a keyed decision or keyless chat question ends at “currently blocked” instead of identifying that actual root. Preserve the task evidence and apply the same keyed/keyless/paused resolution used for the worker’s own card.bin/fm-capacity.mjs:694- Criterion 8 also requires a truthful plain-language root. A blocker already indoneis described here as “queued and not started,” whiledoneorfailedcurrent states fall through to “unblocks itself when … finishes.” Treat terminal blockers as stale dependency edges requiring reconciliation.bin/fm-capacity.mjs:707- The unbounded recursiveflatMapenumerates every dependency path without memoization. Because the main backlog is not capped, a long chain can overflow the call stack and a branching DAG can produce exponential output. Use iterative or memoized traversal and emit unique root chains.🔧 Fix: Resolve captain blocker roots with bounded traversal
3 errors still open:
bin/fm-capacity.mjs:741- Criterion 8 requires every blocked row to show its resolved root and explicit action. The globalscheduledguard can hide cycles crossing sibling branches: with initial blockers A and B where A→B and B→A, both targets are already scheduled, neither appears as an ancestor, and no root is emitted—leavingwaits_onandwhat_you_can_doempty. Detect cycles independently of the chosen parent tree.bin/fm-capacity.mjs:920- Criteria 8 and 10 require truthful roots, including keyless worker questions.mateTaskEvidenceincludes onlyactive_children, but secondmate parked/paused/blocked workers are projected underholds; their state and decision hints are therefore unavailable. Their own rows remain generic structured waits, and dependents describe them as queued rather than resolving paused, keyed-decision, or chat-question roots. Preserve and resolve child-state hold evidence too.bin/fm-capacity.mjs:593- Criterion 8 requires every root to be described, but.find()retains only the first keyed decision and the keyless-question branch takes precedence over all keyed decisions. A worker with multiple open decisions—or both a keyless question and a keyed decision—therefore produces an incomplete blocker chain even though all decisions are surfaced elsewhere. Return and combine every applicable root context.🔧 Fix: Complete captain blocker roots across cycles and holds
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Pre-supplied baseline:command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Focused E2E:bash tests/fm-dash.test.sh | tee /var/folders/g0/z_4x96f92cgfpm7940cqvtt00000gn/T/no-mistakes-evidence/01KYMYRBAQ8B9B19EGRG79V21K/fm-dash-e2e.logCaptured the authenticated running-serviceGET /response using the dashboard fixture ascapacity-dashboard-served.htmlRendered the captured service response in Chrome at 1440×1100, inspected its visible layout, and savedcapacity-dashboard.pngVerified the rendered surface visibly contains tailnet-live status, refresh, direct Approve & send, recursive blocker explanations with What you can do guidance, and subscription usageVerifiedgit status --shortis clean after removing transient capture helpers and browser-profile data🔧 **Document** - 1 issue found → auto-fixed ✅
bin/fm-dash-inbox.sh:117- Captain, the claim summary still tells Firstmate to treat every record as an action-ID approval, which is wrong for decision and idea records; correcting this runtime output is outside the documentation-only scope.🔧 Fix: Correct dashboard claim handling reminder
✅ Re-checked - no issues remain.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.