Repository navigation
Merge upstream firstmate into the fork - #35
Merged
Merged
Conversation
…6307) * fix(bin): name the accepted verdict actors in fm-contributions help and refusal * fix(ci): Updated tests/fm-contributions.test.sh to assert exactly captain, fleet, maintainer, and nobody in command-emitted help and refusal output. Three focused regressions passed; all three extra-actor mutations were rejected. ShellCheck, syntax, and diff checks passed. Production code remains unchanged
…chenguid#6306) * fix(bin): recognise a clone root git names with different path spelling fm-fleet-sync compared git's --show-toplevel with pwd -P as strings, so a clone root that git recorded with different casing (case-insensitive volume) was skipped as not a clone root and never refreshed. Compare filesystem identity instead, which also covers symlink spelling. * fix(document): Remove stale clone-root comparison comment
) * test(calm): pin Pi's regular TUI mode where pane assertions read scrollback Pi 1.0.0 defaults its TUI to a fullscreen alternate-screen mode whose scrollable transcript is application-owned, so rows that leave the viewport never enter terminal scrollback and tmux capture-pane -S can no longer see them. The Pi Calm e2e launches now pass --tui-mode regular wherever the flag exists so the transcript assertions keep reading real scrollback on both the Pi 1.0.0 line and earlier Pi lines, which have no such flag and render regular-only anyway. * no-mistakes(document): Correct Pi TUI documentation and scrollback rationale
…ries (kunchenguid#6331) * fix(bin): encode captain-hold reasons and reject self-inventory in complete hold now stores a reason with parentheses, line breaks, or percent signs through a reversible percent encoding that every reader decodes, instead of refusing it. hold --origin records the origin on the held task, and complete refuses the origin as its own inventory entry and an entry held for a different origin; holds with no recorded origin are accepted and flagged. * fix(review): Decode marked hold reasons consistently across readers * fix(review): Remove unnecessary lifecycle test dispatch * fix(review): Correct hold origin identity and inventory recovery * fix(review): Record origins before placing backend holds * fix(document): Clarify captain-hold validation and reason reader documentation * fix(ci): Fixed both findings: failed backend holds restore the previous origin, and invalid base64/UTF-8 reasons remain verbatim. Added regressions and documented valid-literal ambiguity. Both failures were reproduced before fixes. Verification: 54 lifecycle tests and 9 wrapper tests passed; 7 Beads-specific cases skipped because tasks-axi is markdown-only. Focused lint and diff checks passed. No pipeline or publication actions performed
* fix(bin): take over the watcher cycle a main-only pass-through leaves An attended main-only pass-through leaves a successor watcher cycle running through main's handling turn. The session's next park attached to that cycle instead of owning it, so the successor's arm, orphaned by its host's exit, kept owning the watcher while the new park's arm polled it twice a second until the next close or the park boundary, hours later in a quiet second mate. A second-mate restart hit this every time, since its persist request is a main-only close. The host now records the successor it leaves for main, and the next host's first cycle runs bin/fm-watch-arm.sh --take-over on it: when that arm still owns the healthy watcher, the new arm stops it, reports a reason the cycle delivered first, and otherwise owns a fresh cycle. The stop's own downtime publication is undone over an acknowledged episode when no wake was appended in between, so the handover wakes nobody. * no-mistakes(review): Keep left-arm record until the orphaned arm is gone * no-mistakes(review): Relinquish successor arm only after durably recording it * no-mistakes(review): Relinquish successor only after its record reads back * no-mistakes(document): Clarify successor takeover guarantees and authoritative documentation * no-mistakes(ci): Fixed ci-1: acknowledgement restore now requires the exact taken-over arm/watcher ledger row with signal=TERM, awaited within a short bound. Otherwise takeover proceeds without erasing downtime. Added a self-exit regression confirmed failing before the fix and passing afterward; ordinary takeover tests and the full watcher-arm suite pass. Updated Generation reuse documentation. Syntax, diff checks, and ShellCheck pass with existing SC1091/SC2034 warnings excluded. ci-2 remains unchanged * no-mistakes(document): Clarify watcher take-over recovery and restart limits
…cessor already closed (kunchenguid#6355) * fix(bin): restore supervision host hand-back continuity * no-mistakes(review): Scope host hand-back failure fallback to lost pending:handling * no-mistakes(review): Remove stray scratch test copy tests/.tmp-rest.test.sh * no-mistakes(test): Initialise successor globals so early hand-back survives set -u * no-mistakes(document): Document host hand-back downtime failure and Claude lost-handback notice * no-mistakes(ci): I fixed the Greptile finding. The rule that must hold: when the supervision host hands back an actionable wake, its rewake is refused, and no watcher is healthy, the hand-back still has to reach main as a delivered notice. That must be true whether the recovery marker is `pending:handling` or `announced:handling`. Only one place applies this check: the lost hand-back fallback in `bin/fm-claude-stop-autoarm.sh`. **Fix:** that check now accepts both `pending:handling:*` and `announced:handling:*` tokens (a one-line change). Nothing else in the fallback changed: - Refusals on any other marker, such as an already acknowledged one, still exit 0 silently and open no failure episode. - The notice is still sent once per episode, and repeats are recorded as `failed-suppressed`. **Tests:** - `tests/fm-claude-stop-autoarm.test.sh`: the lost hand-back test now runs as a shared helper with two variants, one writing a `pending:handling` marker and a new one writing `announced:handling` (`test_host_lost_announced_handback_notifies_once_per_episode`). - `tests/fm-supervision-host.test.sh`: the end-to-end test where downtime restoration fails is now a shared helper too, with a new `announced` variant (`test_claude_stop_hook_notifies_when_closed_announced_successor_downtime_restore_fails`). It moves the handling episode to `announced` before the host hands back. Without the fix the hook would exit 0 here; the test requires exit 2, `outcome=failed` and a delivered failure notice. **Verification (all under nice -n 10):** - The full `tests/fm-claude-stop-autoarm.test.sh` suite passed (rc=0), including both lost hand-back variants and the benign-refusal test. - In `tests/fm-supervision-host.test.sh` I ran only the four hand-back test functions, all passing (rc=0). The suite can't run single functions, so I used a temporary copy with a trimmed test list and deleted it afterwards; `git status` shows only the 3 intended files changed. - shellcheck is clean on all three changed files. I did not run `bin/fm-lint.sh`. - I did not run the new tests against the unfixed code; the claim that they fail without the fix comes from reading the old check
* perf: cut remote-job idle process creation in the three hot loops Post-update host measurement still attributes most idle churn to three per-sample loops: result-consumer state reads and date calls, the delta reader's capture/hash pass on every poll, and the lane preemption scan's per-field pipelines. This drops each to its minimum without touching the contracts around them. * fm_remote_job_read_state gains an optional result-variable form backed by fm_remote_job_read_line, a builtin-only bounded record read (regular non-symlink file, byte bound, one newline-terminated line, tolerated unterminated tail, no carriage returns). fm_remote_job_wait samples state and the SECONDS clock with no per-sample children; one date call converts the epoch deadline once. * fm-remote-delta-read stats the log each poll and re-runs the bounded capture and hashing only when size, mtime, ctime, inode, or device change. The snapshot's own stat writes the comparison key, so a log that moves between the gate and the capture is never read as stable. * worker_preempting_waiter_exists reads state, home, and the staged argv head with builtins only. The now-unused worker_job_command goes away. The bounded reads use -d '' -n, which behaves identically on the macOS stock bash 3.2 and current bash; -N does not exist on 3.2. Tests cover the malformed-record corpus, delta identity gating, fork-free lane scanning through counting PATH shims, and same-home versus cross-home preemption. No signal traps or sleep contracts change. * no-mistakes(review): Restore subsecond delta keys and byte-bounded builtin record reads * no-mistakes(document): Clarify delta snapshot caching and coarse-timestamp fallback * no-mistakes(lint): Scope UTF-8 regression locales to individual function calls * no-mistakes(ci): Fixed both lint failures by applying the documented production-library analysis boundary at the two affected test imports. Runtime behavior is unchanged; the library remains independently linted. Canonical full-analysis lint passed for the library and both suites, as did bash syntax checks and git diff --check
…ommands (kunchenguid#5963) * fix(composer): read a titled Claude top rule as the composer's edge A named Claude Code session draws its title into the composer's top rule. The strict separator predicate rejected that row, so the closing rule read as a lower unmatched separator and an idle, empty composer classified unknown on every cursorless backend, refusing fm-send, exit, and relaunch. Spare a bare agent-glyph row sandwiched between a width-proven titled rule and the screen's only unmatched separator directly below it. The strict separator predicate, dead-shell rule, and blank-row posture are unchanged. Fixes kunchenguid#5601 Fixes kunchenguid#5558 * no-mistakes(test): Keep Claude's grey slash command in Herdr payload proof * no-mistakes(test): Make missing-herdr version check hermetic to installed herdr
…#6387) * fix: pre-approve Pi trust for seeded secondmate homes Unattended first launches of Firstmate-seeded Pi secondmate homes stalled on "Trust project folder?" until Enter. Probe --approve like --tui-mode and pass it only for --secondmate when help advertises it (.fm-secondmate-home signal), leaving ordinary workers and older Pi unchanged. * no-mistakes(document): Consolidate Pi seeded-home trust documentation ownership * no-mistakes(ci): Fixed Lint 1’s unused polling counter. Diagnosed Behavior portable serial 4 as a pre-existing delta-reader test clock race; replaced timing-dependent rewrite and deletion with deterministic executable-boundary synchronization. ShellCheck, Bash syntax checks, and git diff --check passed. Delta-reader tests passed three consecutive runs; all three live Pi trust cases passed. Production behavior unchanged
…--external-sources (kunchenguid#6443) * fix(lint): retry memory-bound roots without external sources * no-mistakes(review): Make fallback tests portable and correct source-following telemetry * no-mistakes(review): Remove committed parity fixtures and use disposable test roots * no-mistakes(document): Document ShellCheck memory fallback and telemetry * no-mistakes(review): Cover bounded and unbounded fallback RSS behavior * no-mistakes(document): Correct stale lint fallback documentation * no-mistakes(document): Correct stale lint test documentation * no-mistakes(ci): The memory fallback (the retry without --external-sources) now gets only the time left in its root's original deadline, so it can no longer outlast the CI job. Invariant: one root's first attempt plus its fallback must fit inside a single FM_LINT_ROOT_SECONDS deadline, plus the cleanup grace. Only one site started a new deadline: the fallback call in fm_lint_run_root. The deadline is the only budget involved, because the memory limit already applies to each process separately. Changes in bin/fm-lint.sh: - fm_lint_exec_root now takes a <seconds> argument instead of always reading FM_LINT_INTERNAL_ROOT_SECS. - The first attempt passes the full deadline. - The fallback passes floor((start + deadline - now) / 1000) seconds. - When bounds are enforced and less than 1 second is left, no retry starts. fm_exec_timed rejects 0 seconds, so the retry cannot run with no time. The root keeps reason=memory, and the shard output says "no time left in its Ns deadline to retry without it". - Unbounded local runs have no deadline and behave as before. - The header comment now describes the shared deadline. Changes in tests/fm-lint.test.sh: a new test, test_memory_fallback_spends_only_the_remaining_root_deadline, runs only on hosts that can enforce bounds. It uses a 6 s deadline and 1 s grace. - Case 1: the first attempt runs 3 s and then fails with memory status 251. The test asserts one fallback ran, reported reason=timeout, and the root's recorded duration is under 7000 ms. - Case 2: the first attempt runs 5.2 s. The test asserts no fallback starts, the skip is explained, and the sidecar records memory with source-following 1. Verification: - Full `nice -n 10 bash tests/fm-lint.test.sh` passed, including the new test, in about 5 minutes. - Case 1 run against the HEAD script: the root took 9168 ms, so the under-7000 ms check fails before the fix. - `bin/fm-lint.sh bin/fm-lint.sh tests/fm-lint.test.sh` reported no findings. - The CI workflow is unchanged, so the Test step still runs only tests/fm-lint.test.sh with nice -n 10 and the 12 GiB ShellCheck limit
# Conflicts: # tests/fm-supervision-host.test.sh
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
firstmate-maint take 1 and 2, after that, create pr to merge update from upstream.
With PRs 31 and 33 merged, start the merge-from-upstream PR as planned (merge commit only, upstream changes only).
The plan: a fresh PR to cloud-practitioner/firstmate that brings kunchenguid/firstmate main into the fork, following the same rules as the earlier upstream-merge PR 29: it lands as a merge commit only, never squashed or rebased; its net change is exactly the upstream merge (upstream changes only); no pipeline auto-fix content is added; title "Merge upstream firstmate into the fork". If upstream has nothing new, report that instead of opening an empty PR.
What Changed
mainthroughd719ef3d, preserving hold reasons, validating completion inventory origins, and repairing watcher takeover and supervision hand-back recovery.Risk Assessment
🚨 High: Merge provenance is consistent with the upstream-only intent, but an interrupted hold reassociation can incorrectly pass completion and cleanup gates, requiring explicit human approval.
Testing
Git merge checks, focused regressions, and isolated live CLI, watcher, remote-worker, Pi, and Claude scenarios passed after correcting fixture setup; terminal images and product transcripts were captured, native supervision hand-back remained blocked by the gate-root scope guard, lint stayed deferred to its owning phase, and all worktree scratch was removed without changing the submitted merge.
Evidence: Validation results, setup retries, limitations, and reproduction notes
Source: Validation results, setup retries, limitations, and reproduction notes
Evidence: Upstream-only merge proof
Source: Upstream-only merge proof
Evidence: Hold reasons and completion guards
Source: Hold reasons and completion guards
Evidence: Aliased clone sync and non-clone refusal boundary
Source: Aliased clone sync and non-clone refusal boundary
Evidence: Live delta-reader protocol responses
Source: Live delta-reader protocol responses
Evidence: Remote worker preemption and home isolation
Source: Remote worker preemption and home isolation
Evidence: Contribution verdict actor behavior
Source: Contribution verdict actor behavior
Evidence: Live watcher takeover and subsequent wake
Source: Live watcher takeover and subsequent wake
Evidence: Real seeded Pi launch
Source: Real seeded Pi launch
Evidence: Real Claude composer classification and delivery
Source: Real Claude composer classification and delivery
Evidence: Native primary supervision attempt and scope blocker
Source: Native primary supervision attempt and scope blocker
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-captain-hold.sh:986- An interrupted reassociation can still certify the wrong investigation. Start with task X released from origin A and carrying A's recorded answer, then runhold X --origin B. Line 986 persists B before the backend hold succeeds. Termination before that hold, or failure of the rollback, leaves X associated with B but carrying only A's resolution.complete B Xaccepts the old resolution and new association, records completion, and permits B's scout cleanup despite no successful B hold. The shared verifier also permits concurrent completion during this window. Related sites: bin/fm-captain-hold.sh:989 (dated hold), :992 (undated hold), :999 (fallible rollback), :882 and :885–893 (durability and origin checks), :1765 (complete), and :1832 (verify). Reject incomplete reassociations at the shared verification boundary, including after interruption. A crash-safe remedy needs durable transition evidence; that remedy—not the defect—requires authorization because it adds state and this PR must remain upstream-only with no pipeline auto-fix content. Explicitly accept the residual risk or authorize an upstream/follow-up correction.🔧 **Test** - 2 issues found → no changes applied ✅
git -C ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C statusandgit -C ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C diff). Respond with fix to validate it, or abort.🔧 No changes applied.
✅ Re-checked - no issues remain.
git merge-tree --write-tree HEAD^1 HEAD^2with parent, history, tree-equivalence, and sole conflict-resolution checks.bash .test-phase-tmp/live-cli.sh— real hold/completion CLI, fleet snapshot, aliased-clone sync, and non-clone boundary.python3 .test-phase-tmp/live-delta.py— complete-line delivery, silent timeout, same-second rewrite, truncation, and traversal rejection.bash .test-phase-tmp/live-remote.sh— real worker, same-home poll preemption, short-job completion, and independent-home isolation.bash .test-phase-tmp/live-contributions.sh— accepted verdict actors and mutation-free invalid-actor refusal.bash .test-phase-tmp/live-watcher.sh— real watcher takeover, preserved acknowledgement, queued wake, drain, and acknowledgement.bash .test-phase-tmp/live-herdr.sh— guarded named Herdr lab, real Claude titled composer, draft protection, slash submission, exit confirmation, and teardown.bash .test-phase-tmp/live-pi-spawn.sh— real seeder/spawner and Pi startup with actual extensions, disposable trust store, and private 120x40 terminal.python3 .test-phase-tmp/live-pi.py— unseeded Pi trust-dialog negative control on a sized and drained pty.bash .test-phase-tmp/live-primary.sh— attempted native Claude Stop-hook supervision from the required gate-root lab.bash tests/.test-phase-fm-supervision-host.test.sh— seven selected upstream hand-back, failure-notice, takeover, interruption, and successor-record cases.bash tests/.test-phase-fm-watch-arm.test.sh— two selected takeover cases.FM_BACKEND=tmux bash .test-phase-tmp/pi-product/tests/.test-phase-fm-spawn-dispatch-profile.test.sh— selected approval, ordinary-worker omission, and unsupported-flag cases after correcting fixture placement and inherited backend selection.node .test-phase-tmp/render-terminals.cjsandconvert— rendered actual terminal captures to reviewer-visible HTML, SVG, and PNG; inspected Claude and Pi images.Removed both rounds' disposable fixtures, tools, caches, private sockets, copied product trees, and scratch selectors; verified emptygit status --porcelainand unchanged HEAD.docs/supervision-host.md:230- The new failure-direction paragraph contains two inaccurate claims. exit_to_main republishes the current recovery marker, not necessarily the recorded successor generation; publication after acknowledgement can mint a fresh generation. The Claude fallback also handles announced:handling, not only pending:handling, contradicting “every other refused rewake stays silent.” The announced case is explicitly covered by tests/fm-claude-stop-autoarm.test.sh. Reference watcher-continuity.md#generation-reuse for generation rules and describe both handling states. Left unchanged because pipeline-generated corrections would violate the required upstream-only merge; resolve through an upstream or separately authorized follow-up.tests/fm-remote-delta-read.test.sh:55- The new test comment says an unchanged log “never runs the snapshot path,” but the implementation and subsequent assertion require one initial snapshot before skipping unchanged samples. Correct this comment in an upstream follow-up; no pipeline fix content was added.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.