Skip to content

feat(capacity): publish persistent tailnet-only dashboard service - #8

Merged
purple-phoenix merged 24 commits into
mainfrom
fm/fm-publish-a-persistent-tailnet-only-fleet-25
Jul 28, 2026
Merged

purple-phoenix merged 24 commits into
mainfrom
fm/fm-publish-a-persistent-tailnet-only-fleet-25

Conversation

@purple-phoenix

@purple-phoenix purple-phoenix commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

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

  • Add a launchd-managed dashboard service and transactional Tailscale Serve installer that maintains a stable tailnet-only URL across reboots while protecting existing mappings and failing closed on Funnel exposure.
  • Extend the capacity dashboard with authenticated refresh and auto-rendering, rich work/decision/idea views, subscription usage, and validated one-click dispatch through a durable Firstmate inbox.
  • Harden dashboard accuracy with private identity refs, recursive blocker guidance, owner-qualified decisions, honest keyless worker questions, loud degraded-state reporting, documentation, and regression coverage.

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.

  • Evidence: Rendered captain capacity dashboard (local file: /var/folders/g0/z_4x96f92cgfpm7940cqvtt00000gn/T/no-mistakes-evidence/01KYMYRBAQ8B9B19EGRG79V21K/capacity-dashboard.png)
Evidence: Authenticated dashboard response
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="color-scheme" content="dark light">
  <title>Firstmate capacity dashboard</title>
  <style>
    :root{color-scheme:dark;--bg:#0d0d0d;--ink:#ffffff;--ink2:#c3c2b7;--muted:#898781;--hair:#2c2c2a;--line:rgba(255,255,255,.10);--blue:#3987e5;--good:#0ca30c;--warn:#fab219;--serious:#ec835a;--crit:#d03b3b;--gray:#52514e;font-family:system-ui,-apple-system,"Segoe UI",sans-serif}
    @media(prefers-color-scheme: light){:root{color-scheme:light;--bg:#f9f9f7;--ink:#0b0b0b;--ink2:#52514e;--muted:#66645f;--hair:#e1e0d9;--line:rgba(11,11,11,.10);--blue:#1b5fae;--good:#087708;--warn:#8a6200;--serious:#a54824;--crit:#ae2525;--gray:#c3c2b7}}
    .sev-critical{--sev:var(--crit)}.sev-serious{--sev:var(--serious)}.sev-info{--sev:var(--blue)}.sev-neutral{--sev:var(--muted)}.sev-good{--sev:var(--good)}
    *{box-sizing:border-box}html{background:var(--bg)}
    body{margin:0;color:var(--ink);background:var(--bg);line-height:1.45;overflow-wrap:anywhere}
    a{color:var(--blue);text-underline-offset:.18em}button,a{outline-offset:3px}button:focus-visible,a:focus-visible{outline:3px solid var(--blue)}
    .skip{position:absolute;left:-9999px}.skip:focus{left:1rem;top:1rem;z-index:10;background:var(--ink);color:var(--bg);padding:.7rem 1rem}
    h1,h2,h3,p,ul{margin:0}ul{padding:0;list-style:none}
    .sevbar{height:.7rem;background:var(--sev)}
    .band{padding:clamp(1.5rem,4vw,3.25rem) clamp(1rem,6vw,5rem)}
    .wrap{max-width:70rem;margin-left:auto;margin-right:auto}
    .band-alarm{background:color-mix(in srgb,var(--sev) 13%,var(--bg));border-bottom:1px solid var(--line)}
    .kicker{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;flex-wrap:wrap;color:var(--sev);font-weight:800;letter-spacing:.16em;text-transform:uppercase;font-size:.76rem}
    .kicker .stamp{color:var(--muted);letter-spacing:.02em;text-transform:none;font-weight:400;font-size:.76rem;text-align:right}
    .band .kicker{margin-bottom:.6rem}
    .headline{font-size:clamp(2.4rem,7vw,4.6rem);font-weight:800;letter-spacing:-.03em;line-height:.98}
    .headline::first-letter{text-transform:uppercase}
    .evidence{font-size:clamp(1.02rem,2vw,1.3rem);color:var(--ink2);max-width:75ch;margin-top:1rem}
    .action-id{border:1px solid var(--sev,var(--muted));color:var(--sev,var(--ink));padding:.1rem .55rem;font:700 .72rem ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;vertical-align:middle;margin-left:.5rem}
    .rollcall{margin-top:2.25rem}
    .rollcall h2{font-size:.76rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;display:flex;align-items:baseline;gap:.6rem}
    .rollcall h2 .n{font-size:2.1rem;letter-spacing:0;line-height:1}
    .needs-you h2{color:var(--serious)}.blocked-items h2{color:var(--crit)}
    .rollcall ul{margin-top:.6rem}
    .rollcall li{display:grid;grid-template-columns:5.2rem minmax(0,.45fr) minmax(0,1fr);gap:.4rem 1.1rem;align-items:baseline;border-top:1px solid color-mix(in srgb,var(--sev) 30%,var(--hair));padding:.55rem 0;font-size:1.02rem;min-width:0}
    .rollcall li.empty{display:block}
    .verb{font-weight:800;text-transform:uppercase;letter-spacing:.08em;font-size:.72rem}
    .verb-approve{color:var(--blue)}.verb-decide{color:var(--serious)}.verb-blocked{color:var(--crit)}
    .who{font-weight:650;min-width:0}.who .item-id{margin-right:.35rem}
    .why{color:var(--ink2);font-size:.92rem;min-width:0}
    .chain{display:block;color:var(--muted);font-size:.8rem;margin-top:.25rem}
    .cando{display:block;color:var(--ink);font-size:.8rem;font-weight:650;margin-top:.15rem}
    .item-id{font:700 .82rem ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--ink)}
    .next{margin-top:2rem;font-size:clamp(1.05rem,1.8vw,1.25rem)}
    .prompt{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.7rem;align-items:center;margin-top:.9rem;border:1px solid var(--line);padding:.65rem .8rem;background:color-mix(in srgb,var(--bg) 55%,transparent)}
    .prompt code{font-size:.78rem;white-space:normal;color:var(--ink2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
    .prompt button{border:1px solid var(--ink);background:var(--ink);color:var(--bg);font-weight:700;padding:.5rem .9rem;cursor:pointer;font-size:.82rem}
    .fine{color:var(--muted);font-size:.76rem;margin-top:.8rem;max-width:100ch}
    .band-meter .kicker{color:var(--muted)}
    .split{display:flex;align-items:baseline;gap:1rem 2.75rem;flex-wrap:wrap;margin-top:.5rem}
    .split .pair{display:flex;align-items:baseline;gap:.75rem}
    .split .num{font-size:clamp(3rem,8vw,5.5rem);font-weight:850;line-height:1;letter-spacing:-.03em}
    .num-working{color:var(--good)}
    .split .lbl{font-size:1rem;color:var(--ink2);text-transform:uppercase;letter-spacing:.14em;font-weight:700}
    .split .of{color:var(--muted);font-size:.9rem}
    .meterbar{display:flex;gap:3px;height:2.9rem;margin-top:1.4rem}
    .m-working{background:var(--good)}.m-waiting{background:var(--gray)}
    .whys{margin-top:1.6rem}
    .whys h3{font-size:.76rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
    .whys ul{display:flex;flex-wrap:wrap;gap:1.1rem 2.75rem;margin-top:.8rem}
    .whys li{display:flex;align-items:baseline;gap:.65rem;min-width:0}
    .why-n{font-size:1.9rem;font-weight:800;line-height:1}
    .why-decide{color:var(--serious)}.why-blocked{color:var(--crit)}.why-gates{color:var(--warn)}.why-ready{color:var(--blue)}.why-queued{color:var(--muted)}
    .why-l{font-size:.92rem;color:var(--ink2)}.why-l small{display:block;color:var(--muted);font-size:.76rem}
    .band-quiet{border-top:1px solid var(--line);font-size:.88rem}
    .qhead{font-size:.76rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--hair);padding-bottom:.45rem;margin-top:2.4rem}
    .band-quiet>.qhead:first-child{margin-top:0}
    .rec{border-bottom:1px solid var(--hair);padding:1rem 0;max-width:70rem}
    .rec-line strong{font-size:1rem}.rec-line strong::first-letter{text-transform:uppercase}
    .rec-line .action-id{margin-left:0;margin-right:.6rem;--sev:var(--muted)}
    .rec-evidence{color:var(--ink2);margin-top:.35rem;max-width:90ch}
    .rec-next{margin-top:.35rem;font-weight:650}
    .rec-fine{color:var(--muted);font-size:.76rem;margin-top:.4rem;max-width:100ch}
    .stage-group{margin-top:1.4rem}
    .stage-h{display:flex;align-items:baseline;gap:.6rem;font-size:.74rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
    .stage-n{font-size:1.35rem;letter-spacing:0;color:var(--ink)}
    .stage-n-alarm{color:var(--crit)}
    .mlist{margin-top:.4rem}
    .mrow{display:grid;grid-template-columns:minmax(11rem,.4fr) minmax(0,1fr) auto;gap:.3rem 1.25rem;border-top:1px solid var(--hair);padding:.45rem 0;align-items:baseline;min-width:0}
    .mid{min-width:0}.mowner{color:var(--muted);font-size:.78rem;margin-left:.5rem}
    .mreason{color:var(--ink2);min-width:0}
    .mmeta{color:var(--muted);font-size:.76rem;text-align:right}
    .lanes-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 3rem}
    .lanes-grid h3{font-size:.8rem;color:var(--ink2);margin-top:1.1rem;text-transform:uppercase;letter-spacing:.1em}
    .lane-row{display:flex;gap:.6rem;align-items:baseline;border-top:1px solid var(--hair);padding:.5rem 0;color:var(--ink2);min-width:0;margin-top:.4rem}
    .dot{flex:none;width:.6rem;height:.6rem;border-radius:50%;align-self:center}
    .dot-ok{background:var(--good)}.dot-bad{background:var(--crit)}.dot-warn{background:var(--warn)}
    .appendix{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 3rem}
    .appendix h3{font-size:.8rem;color:var(--ink2);margin-top:1.1rem;text-transform:uppercase;letter-spacing:.1em}
    .clean-list{margin-top:.4rem}
    .clean-list li{display:grid;grid-template-columns:minmax(8rem,.4fr) minmax(0,1fr);gap:.75rem;border-top:1px solid var(--hair);padding:.45rem 0;min-width:0}
    .clean-list li>*{min-width:0}.clean-list span{color:var(--ink2)}
    .artifact,.path{font:500 

... [32116 bytes truncated] ...

ueue
    // captain messages for firstmate.
    if (cfg.ideas.length) {
      const ideasSection = document.createElement("section");
      ideasSection.className = "band band-quiet";
      ideasSection.setAttribute("aria-label", "Ideas");
      const wrap = document.createElement("div");
      wrap.className = "wrap";
      const heading = document.createElement("h2");
      heading.className = "qhead";
      heading.textContent = "Ideas · awaiting your verdict";
      wrap.appendChild(heading);
      const list = document.createElement("ul");
      list.className = "mlist";
      cfg.ideas.forEach((idea) => {
        const row = document.createElement("li");
        row.className = "mrow";
        const chip = document.createElement("span");
        chip.className = "item-id fmdash-click";
        chip.textContent = idea.id;
        chip.setAttribute("role", "button");
        chip.setAttribute("tabindex", "0");
        chip.setAttribute("aria-label", "Open pitch for " + idea.id);
        const title = document.createElement("span");
        title.className = "mreason";
        title.textContent = idea.title;
        const open = () => openIdea(idea);
        chip.addEventListener("click", (event) => { event.stopPropagation(); open(); });
        chip.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); open(); } });
        row.addEventListener("click", open);
        row.style.cursor = "pointer";
        row.appendChild(chip);
        row.appendChild(title);
        list.appendChild(row);
      });
      wrap.appendChild(list);
      ideasSection.appendChild(wrap);
      const footer = document.querySelector("footer");
      const main = document.querySelector("main");
      if (main) main.appendChild(ideasSection);
      else if (footer) footer.before(ideasSection);
    }
    async function sendIdeaVerdict(id, verdict, suggestion, button) {
      button.disabled = true;
      const original = button.textContent;
      button.textContent = "Sending…";
      try {
        const res = await postJson({ idea: id, verdict, suggestion });
        const out = await res.json();
        if (out.status === "queued" || out.status === "replaced" || out.status === "already-queued") {
          button.textContent = verdict === "suggest" ? "Suggestions sent" : (verdict === "approve" ? "Approved · queued" : "Denied · queued");
          showPending(out.pending);
          return true;
        }
        button.textContent = "Refused";
        button.disabled = false;
      } catch { button.textContent = original; button.disabled = false; }
      return false;
    }
    async function openIdea(idea) {
      const overlay = document.createElement("div");
      overlay.className = "fmdash-overlay";
      const panel = document.createElement("div");
      panel.className = "fmdash-panel";
      panel.setAttribute("role", "dialog");
      panel.setAttribute("aria-modal", "true");
      panel.setAttribute("aria-label", idea.id + " pitch");
      overlay.appendChild(panel);
      const close = () => { overlay.remove(); document.removeEventListener("keydown", onKey); };
      const onKey = (event) => { if (event.key === "Escape") close(); };
      overlay.addEventListener("click", (event) => { if (event.target === overlay) close(); });
      document.addEventListener("keydown", onKey);
      const kicker = document.createElement("div");
      kicker.className = "fmdash-kicker";
      kicker.textContent = "Idea pitch";
      const closeButton = document.createElement("button");
      closeButton.type = "button";
      closeButton.className = "fmdash-close";
      closeButton.textContent = "Close";
      closeButton.addEventListener("click", close);
      kicker.appendChild(closeButton);
      panel.appendChild(kicker);
      panel.appendChild(textBlock("h2", idea.id + " — " + idea.title));
      panel.appendChild(textBlock("p", "Loading pitch…"));
      document.body.appendChild(overlay);
      closeButton.focus();
      let detail = null;
      try {
        const res = await fetch("/api/detail?idea=" + encodeURIComponent(idea.id));
        if (res.ok) detail = await res.json();
      } catch { /* handled below */ }
      panel.replaceChildren(kicker);
      panel.appendChild(textBlock("h2", idea.id + " — " + idea.title));
      const pitchText = detail && (detail.pitch || detail.description);
      panel.appendChild(textBlock("pre", pitchText || "No pitch or concept summary is on file for this idea."));
      if (cfg.readOnly) return;
      const controls = document.createElement("div");
      controls.className = "fmdash-option";
      const buttons = document.createElement("div");
      const approve = document.createElement("button");
      approve.type = "button";
      approve.className = "fmdash-send";
      approve.textContent = "Approve";
      approve.addEventListener("click", () => sendIdeaVerdict(idea.id, "approve", null, approve));
      const deny = document.createElement("button");
      deny.type = "button";
      deny.className = "fmdash-send";
      deny.textContent = "Deny";
      deny.style.marginLeft = ".6rem";
      deny.addEventListener("click", () => sendIdeaVerdict(idea.id, "deny", null, deny));
      const suggest = document.createElement("button");
      suggest.type = "button";
      suggest.className = "fmdash-send";
      suggest.textContent = "Add suggestions";
      suggest.style.marginLeft = ".6rem";
      buttons.appendChild(approve);
      buttons.appendChild(deny);
      buttons.appendChild(suggest);
      controls.appendChild(buttons);
      panel.appendChild(controls);
      panel.appendChild(textBlock("p", "Approving asks firstmate to create the work item(s) through the normal backlog lifecycle; nothing runs from this page."));
      suggest.addEventListener("click", () => {
        if (panel.querySelector("textarea")) return;
        const box = document.createElement("textarea");
        box.rows = 4;
        box.style.width = "100%";
        box.style.marginTop = ".6rem";
        box.setAttribute("aria-label", "Suggestions for " + idea.id);
        box.placeholder = "Your suggestions for this idea…";
        const send = document.createElement("button");
        send.type = "button";
        send.className = "fmdash-send";
        send.style.marginTop = ".5rem";
        send.textContent = "Send suggestions";
        send.addEventListener("click", async () => {
          if (!box.value.trim()) return;
          const ok = await sendIdeaVerdict(idea.id, "suggest", box.value.trim(), send);
          if (ok) box.disabled = true;
        });
        panel.appendChild(box);
        panel.appendChild(send);
        box.focus();
      });
    }
    if (cfg.readOnly) {
      copyButtons.forEach((copyButton) => copyButton.remove());
      return;
    }
    copyButtons.forEach((copyButton) => {
      const id = ((copyButton.dataset.copy || "").match(/CAP-\d{2}/) || [])[0];
      if (!id) { copyButton.remove(); return; }
      if (!cfg.dispatchable.includes(id)) {
        const note = document.createElement("span");
        note.className = "fmdash-chat";
        note.textContent = "Raise in captain chat";
        copyButton.replaceWith(note);
        return;
      }
      const send = document.createElement("button");
      send.type = "button";
      send.className = "fmdash-send";
      send.textContent = "Approve & send";
      send.setAttribute("aria-label", "Approve " + id + " and queue it for firstmate");
      send.addEventListener("click", async () => {
        send.disabled = true;
        send.textContent = "Sending…";
        try {
          const res = await postJson({ id });
          const out = await res.json();
          if (out.status === "queued" || out.status === "already-queued") {
            send.textContent = "Approved · queued";
            showPending(out.pending);
            return;
          }
          send.textContent = "Refused";
          send.disabled = false;
        } catch { send.textContent = "Failed"; send.disabled = false; }
      });
      copyButton.replaceWith(send);
    });
  })();
  </script></body>
</html>
Evidence: Dashboard end-to-end test transcript
ok - every route except healthz requires the configured captain identity
ok - authenticated browser posts require a matching same origin
ok - served page is the producer dashboard wearing the injected interactive layer
ok - subscription usage is bounded to Claude, Codex, and Grok
ok - operational data cannot break out of the inline configuration script
ok - clickable work items serve rich detail from briefs, metadata, and previews
ok - decision documents validate option picks and bounded custom answers
ok - equal decision keys remain distinct across origins and homes
ok - refs are usable only for their matching dashboard generation
ok - ideas render their pitches and verdicts flow through the durable inbox
ok - a click becomes exactly one durable captain command record
ok - dispatch refuses free text, non-allowlisted actions, and stale actions
ok - refresh reruns the capacity producer server-side and replaces the dashboard
ok - inbox claim delivers before archive and safely permits replay
ok - read-only mode serves the page and refuses every mutation
ok - watcher registration follows writable mode without deleting pending commands
ok - the launchd agent survives reboots and the installer is structurally funnel-free
ok - custom serve ports persist and old mappings are removed
ok - launchd env is pinned and a mostly-unknown render is loudly marked degraded
ok - every copy-prompt affordance on the served page is replaced by direct dispatch
ok - the service is documented and wired into the operating contract
fm-dash tests passed

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 keyless needs-decision event never become a captain decision. This guard skips key: &#34;default&#34; only when decision.id is 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 of id, 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 without waits_on or what_you_can_do (the captain-hold, structured-wait, and time-gate branches have the same omission), so blockedContext() renders nothing for them.
  • 🚨 bin/fm-capacity.mjs:702 - Criterion 8 also requires recursive resolution to the root cause. blocked_by can contain tasks-axi's comma-separated dependency list, but this code treats the entire value as one task ID; a,b therefore 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 marked RENDER 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 assumes blocked_by already aggregates all dependencies, but the real snapshot parser captures only the last repeated blocked-by: marker from tasks-axi markdown. Multi-blocker production rows therefore still lose all but one root before reaching this code; preserve every edge in fm-fleet-snapshot.sh and 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 &gt;= 3 suppresses RENDER DEGRADED when 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.” currentById retains 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 in done is described here as “queued and not started,” while done or failed current states fall through to “unblocks itself when … finishes.” Treat terminal blockers as stale dependency edges requiring reconciliation.
  • ⚠️ bin/fm-capacity.mjs:707 - The unbounded recursive flatMap enumerates 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 global scheduled guard 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—leaving waits_on and what_you_can_do empty. Detect cycles independently of the chosen parent tree.
  • 🚨 bin/fm-capacity.mjs:920 - Criteria 8 and 10 require truthful roots, including keyless worker questions. mateTaskEvidence includes only active_children, but secondmate parked/paused/blocked workers are projected under holds; 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 &gt;/dev/null || { echo &#34;tmux is required for e2e tests&#34; &gt;&amp;2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo &#34;== $t ==&#34;; bash &#34;$t&#34; || rc=1; done; exit &#34;$rc&#34;
  • Focused E2E: bash tests/fm-dash.test.sh | tee /var/folders/g0/z_4x96f92cgfpm7940cqvtt00000gn/T/no-mistakes-evidence/01KYMYRBAQ8B9B19EGRG79V21K/fm-dash-e2e.log
  • Captured the authenticated running-service GET / response using the dashboard fixture as capacity-dashboard-served.html
  • Rendered the captured service response in Chrome at 1440×1100, inspected its visible layout, and saved capacity-dashboard.png
  • Verified the rendered surface visibly contains tailnet-live status, refresh, direct Approve & send, recursive blocker explanations with What you can do guidance, and subscription usage
  • Verified git status --short is 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.

…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 purple-phoenix changed the title feat(capacity): publish persistent tailnet-only dashboard feat(capacity): publish persistent tailnet-only dashboard service Jul 28, 2026
@purple-phoenix
purple-phoenix merged commit d626855 into main Jul 28, 2026
6 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant