Skip to content

fix: merge upstream firstmate main at 00679ae3 into house - #123

Merged
jazz127 merged 12 commits into
housefrom
fm/firstmate-house-upstream-merge-r8
Sep 29, 2026
Merged

jazz127 merged 12 commits into
housefrom
fm/firstmate-house-upstream-merge-r8

Conversation

@jazz127

@jazz127 jazz127 commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Merge upstream Firstmate main 00679ae3350ffa82d2d70a123484f8f9f16bbed6 into fork house 8f75cac4cd1a17304f9c36aa372e67736a1b6ecd with a real merge commit.
Both remote refs still matched the intake pins when fetched on 2026-09-29; upstream had not moved.
This imports the ten upstream changes since fa483673: accurate away-return host latch reporting, attended quiet-mode handling, sequential PR merging for one task, ahoy wording, quiet entry authority, Claude Calm supervision notes, manual Stop-hook argument refusal, idempotent procevent doorbells, Claude updater isolation in harness tests, and task watcher/journal teardown cleanup.
Nothing is published upstream.

House features touched by conflicts

  • Row-scoped reports and detailed wake/notification information: upstream does not cover house's exact-row binding or one-report-per-row requirement. At the pinned upstream commit, bin/fm-branch-report.sh validates only the turn's task set, writes a four-field receipt, and has no --row; the merged file retains --row, row_tasks, five-field receipts, and the host's missing-row check. Both versions retain the summary, wake reason, durable outcome store, and return notification. House's row-level detail remains on top of upstream's quiet-versus-away presence check.

  • Safe report receipt handling: upstream does not cover house's serialization of receipt deduplication and append. Its pinned report script has no RECEIPT_LOCK; the merged file retains the early fm-wake-lib.sh import and lock across the duplicate check, outcome append, and receipt write, beside upstream's new fm-afk-contract.sh import. Existing actor/turn and regular-file checks remain.

  • Routine outcome rules and hidden routine notes: upstream does not cover the complete house behavior. The diff against pinned upstream in bin/fm-branch-prompt.sh still contains direct answers as captain outcomes, unchanged blockers/worker completion as routine, and the hidden-routine rule; .pi/extensions/fm-branch-supervision.ts retains display: false and historical routine-note suppression. These auto-merged around the report conflict and remain intact.

  • Published evidence validation during inactive reconciliation: upstream does not cover house's bounded forge-backed PR-body validation. The conflict in docs/scripts.md included upstream's unchanged "without forge access" description beside house's description. Keep the house description for fm-inactive-reconcile.sh, whose existing house behavior is unchanged, and adopt upstream's new away-or-quiet description for fm-afk-contract.sh.

  • Claude host-backed daemon eligibility (semantic auto-merge): upstream now covers the old Claude-only daemon guard in the shared fm_afk_launch_host_primary/fm_afk_launch_daemon_allowed path: Claude is an eligible host primary, host opt-in still refuses an away daemon, and fm_afk_launch_requested_mode gives the posture record priority over a stale flag. House's duplicate Claude clause intercepted that shared path and caused five imported quiet-launch assertions to fail, while the complete launch suite passed on pinned upstream alone. Remove only that duplicate clause and retain the shared upstream implementation and house's remaining host-backed features.

No intended house behavior was dropped. The redundant Claude-only daemon guard is covered by upstream's shared guard.
House's declared-wait classification and host-backed supervision remain intact; only upstream's intended distinction between quiet and away changes posture decisions.

Validation

The initial targeted synthetic/offline built-CLI run exercised 20 test files: 16 passed and four failed. The imported quiet-host assertion was aligned with house's two row-scoped captain outcomes, and the quiet-launch regression was traced to the duplicate guard described above. After reconciliation, all three focused reruns passed: tests/fm-pi-watch-extension.test.sh, tests/fm-supervision-host.test.sh, and tests/fm-afk-launch.test.sh. The Pi suite passed on untouched house and on the merged code when run serially; the initial parallel-run failure is preserved as a timing-sensitive result, with no Pi production change in this merge. Final file-level results are 19 passed and the one inherited teardown fixture failure described below.

All five newly imported teardown watcher-marker/journal cases passed in a focused synthetic fixture replay, using the unchanged test functions after the inherited earlier failure had stopped the whole file. The task-temp focused-teardown.sh only selects those existing test functions and sources the repository's test helpers; it is not included in the deliverable. Changed-bin ShellCheck, the canonical bin/fm-lint.sh path (including workflow lint), the reconciliation lint, bin/fm-doc-audience-check.sh, scratch preflight, and whitespace checks passed. Results and exact commands are recorded in /tmp/fm-firstmate-house-upstream-merge-r8/final-validation.json, with the referenced logs alongside it.

The teardown mate-pr-ready fixture failure also reproduces on untouched house 8f75cac4cd1a17304f9c36aa372e67736a1b6ecd: fm-pr-check refuses the fixture's missing published file list and published intent validation fails. This pre-existing fixture failure is left unchanged. Baseline commands/results are in /tmp/fm-firstmate-house-upstream-merge-r8/baseline-results.json and baseline-teardown.log.
The full suite was not run. House CI runs its configured workflow, delivery, lint, and inventory checks; it does not run the full suite or the supervision-host test file.
No real-account harness drive is claimed by these fixture tests.

External ref evidence was read from the freshly fetched fork/upstream Git refs in this isolated worktree; the artifact records the remote URLs, exact SHAs, commit history, commands, and capture time.
evidence-artifact: /tmp/fm-firstmate-house-upstream-merge-r8/fetched-refs.json
evidence-command: git rev-parse origin/house upstream/main HEAD
evidence-captured: 2026-09-29T03:31:14Z

Pipeline

No-mistakes run 01M3NQN2DEP6SN7S3KYXFCWKFH completed review, targeted test validation, documentation, lint, push, and PR publication for e68d385bcbcf5d988c7dbabf94c24a9e55ae63c6. CI is monitoring the configured house checks.

The pipeline test agent additionally exercised the merged CLI in a disposable fixture home and private tmux socket. These were synthetic/offline built-CLI probes with a test-seam Claude harness pin; no real-account Claude session or real-account Herdr teardown was driven. Its targeted files passed except the inherited teardown fixture failure; its separate supervision-host rerun was unfinished when it submitted its report. The worker's completed serial supervision-host rerun passed and is recorded in final-validation.json. The analyzer's machine attestation below is retained verbatim; its live_validation field describes those synthetic fixture probes and does not establish live or real-account evidence.

karotkriss and others added 12 commits September 28, 2026 03:33
…ardown (kunchenguid#5997)

* WIP: retire task-keyed watcher markers and orphan journals at teardown

Re-applies old PR kunchenguid#5584 on current main: teardown retires the
turn-ended .seen-* signature and an orphaned Herdr presentation
journal whose workspace is already gone, and the wake-drain rotates
its own dead scratch files. Not yet validated through no-mistakes.

* no-mistakes(ci): Fixed the Greptile P1 finding in bin/backends/herdr.sh. fm_backend_herdr_projection_token_workspace_gone used `! ... jq -e ... 2>&1`, which swallowed a jq runtime error (thrown when a non-object workspace entry, e.g. a number before a live token-bearing workspace, hits `.label`) and flipped it to a "gone" verdict, causing teardown to delete a still-live v1 presentation journal. Invariant: a workspace-query error/ambiguity must never be read as token absence; only a cleanly-parsed list with no token-bearing label is "gone". Replaced the body with a single jq verdict (unknown/present/gone): a non-array list or any non-object/non-string-label entry yields "unknown", jq errors/empty output fall through `|| return 1` to unknown, and only "gone" returns 0. Sibling fm_backend_herdr_projection_endpoint_matches_journal already fails safe on jq error (empty match -> journal kept), so it needed no change, matching the author's scoping. Added test_teardown_retains_v1_journal_when_workspace_query_ambiguous driving real teardown with a malformed workspace-list entry, proving the journal is kept and no workspace close occurs. Verified the old logic returns GONE on that input (test fails before, passes after); full tests/fm-teardown.test.sh suite passes (exit 0) and shellcheck is clean. Marker-naming finding left untouched per explicit out-of-scope instruction
…unchenguid#6002)

* fix(tests): disable Claude Code's auto-updater during live harness runs

fm_live_gate let a live run proceed without ever setting
DISABLE_AUTOUPDATER, so a live Claude test could let the real updater
repoint ~/.local/bin/claude into a temporary directory and stop every
Claude process on the machine from starting. Export
DISABLE_AUTOUPDATER=1 on every path where the gate lets a live run
proceed, and assert the export in tests/fm-live-gate.test.sh, including
that it reaches a child process the same way a real harness pane would
inherit it.

* no-mistakes(ci): Greptile flagged that the PR's DISABLE_AUTOUPDATER inheritance test only checked a `bash -c` direct child, not the fm-spawn.sh launch path. The user chose to fix it with a regression on that path. In tests/fm-live-gate.test.sh I replaced the generic child test with test_disable_autoupdater_reaches_the_claude_pane_on_the_fm_spawn_launch_path: it drives the real fm-spawn claude launch through the spawn fixtures, captures the exact staged launch command, and runs it as a synthetic pane whose only `claude` is a stub recording the inherited DISABLE_AUTOUPDATER, asserting it saw 1. Switched the file to source fixtures.sh (pulls in lib.sh, guarded) for the spawn helpers. Verified it is a real guard: the stub records `1` when the ambient var is set and `unset` when absent, so it fails if fm-spawn ever scrubbed the variable (e.g. env -i or -u). This confirms fm-spawn's launch construction never references the name and passes it through via ordinary ambient inheritance with no allowlist. Full suite passes (12 tests ok), shellcheck clean. Note for the outer executor: I embedded the daemon caveat as a code comment in the test, but the finding also asks the PR body to state that a backend daemon already running before the gate exported the variable does not inherit it and fully covering that would need launcher support - that forge-side PR-body sentence is outside this CI phase's scope

* no-mistakes(ci): Fixed Greptile finding ci-1. Root cause: fm-spawn.sh handed its launch command to an already-running backend daemon that never inherited the test process's exported DISABLE_AUTOUPDATER, so ambient inheritance dropped it and Claude's auto-updater could still run. Fix (bin/fm-spawn.sh): when DISABLE_AUTOUPDATER is set in the spawn's own environment, embed `export DISABLE_AUTOUPDATER=<value>;` into the LAUNCH command text (same idiom as the adjacent COMPACT_ADVISER_DISABLE export), so it survives a daemon-built pane, the env -i allowlist path, and relaunch alike; gated on presence so ordinary spawns are unchanged. Added regression test test_disable_autoupdater_survives_a_daemon_pane_that_never_inherited_it in tests/fm-live-gate.test.sh: stages a real claude launch with DISABLE_AUTOUPDATER set in the spawn env, then runs that exact command in a synthetic pane with `env -u DISABLE_AUTOUPDATER` and asserts the claude stub still recorded autoupdater=1. Verified the test fails (autoupdater=unset) without the fix and passes with it; the round-1 ambient test stays green either way. Full suite passes (13 ok); test file and isolated snippet shellcheck-clean (full fm-spawn.sh shellcheck kept getting terminated by the memory-constrained host, not by findings). Forge-side note for the outer executor: the PR-body caveat that fully covering the daemon case would need launcher support no longer applies to the Claude launch path and should be corrected
…cevent record (kunchenguid#6010)

* fix(bin): ring the inbox doorbell only for a newly published procevent result

publish_result rewrote a worker's captured Lavish round idempotently on
every reconcile, unconditionally moved an already-acknowledged inbox
record back out of handled/, and rang the doorbell every time - so an
already-processed round rang the owning worker on every cycle. Snapshot
the existing active and handled records before the idempotent write and
ring, or move anything, only when the write actually created a fresh
record; re-delivery of a still-open round is left to the inbox's own
re-ring ladder.

* no-mistakes(document): docs: reflect worker-board doorbell rings only on fresh inbox record

* no-mistakes(ci): Fixed Greptile finding ci-1 in tests/fm-procevent.test.sh (test-only change). The redelivery regression previously moved the delivered note into handled/ before any repeated reconciles, so it only proved an acknowledged note stays quiet and would still pass if an unchanged active note rang every cycle. Per the user's instruction, I inserted (before the mv into handled/) five repeated `pe reconcile` runs with the note still in the active inbox and asserted the ring log holds exactly one line and 001.msg remains active; the existing acknowledged-note assertion after the move is kept unchanged. No product code changed. bash -n confirms syntax is valid; the block mirrors the already-passing post-move reconcile/ring-count assertion directly below it
…unchenguid#6032)

* fix(bin): make the Claude Stop auto-arm refuse arguments before arming

A model running bin/fm-claude-stop-autoarm.sh --help mid-turn armed a real
supervision-host park owned by its short-lived tool process, leaving
supervision down once that process exited. The Stop hook passes no
arguments, so -h/--help now prints usage and any other argument is refused
before anything is sourced, read, or armed.

* no-mistakes(document): Clarify Claude Stop hook documentation for manual invocations

* no-mistakes(ci): Updated the argument-run regression test to compare checksums of state files as well as entry names. The Stop auto-arm test suite passes, and git diff --check is clean

* docs: restore the bin/ toolbelt intro's manual-use clause

The document step dropped "interactive entrypoints work by hand too" from
docs/scripts.md, which still holds for most bin/ scripts.

* no-mistakes(document): Clarify Claude auto-arm manual-use guidance
…uid#6039)

* feat(calm): show supervision sailboat and anchor notes on Claude Code

The Calm mod follows a bounded display tail copy of the outcome store,
which bin/fm-branch-outcome.sh append now refreshes, and the supervision
host's latch, and appends one dim transcript line per visible routine
outcome, captain outcome, and latch change, replaying unread and
unprocessed outcomes at session start. It shows them whenever the mod is
active, regardless of config/calm, and never marks anything read.

* fix(calm): show each supervision note once per session on Claude Code

Claude Code 2.1.283 stores ui.log lines in the session and restores them
on --continue, so the mod records how far each session has followed the
outcome store and a resume replays only newer outcomes. It also checks
file existence before reads so absent files do not log debug errors.
The live guard gains the supervision-notes scenario and the dated
2.1.283 record documents the observed behavior.

* docs: name the Claude supervision note row as the engine draws it

* no-mistakes(review): Seed outcome tail on present and anchor first tail on markers

* no-mistakes(review): Seed outcome tail at session start; replay against start markers

* no-mistakes(review): Bound outcome tail by bytes; reread recently changed files

* no-mistakes(review): Skip store validation when outcome tail already exists

* no-mistakes(document): Clarify bounded Claude supervision note replay

* no-mistakes(ci): Fixed seed-tail to validate only a bounded suffix of complete store rows and write it through the existing byte- and row-limited tail writer. Added a regression test with malformed history outside that window and updated the script header. Outcome tests and shellcheck passed; the full session-start suite timed out after 240 seconds
…enguid#6033)

* fix(bin): read a quiet-mode record as a present captain, never hold-for-return

Daemon-backed quiet mode writes the away-posture record marked mode: quiet,
but the entry announcement, read-back, and session-start digest rendered it
as "hold-for-return only", and the spend cap and PR merge gate treated it as
away. A present captain's requested actions could then be held for a return
that was not coming.

bin/fm-afk-contract.sh now owns which posture a record is (the mode
subcommand, fm_afk_contract_mode, fm_afk_contract_away_present). A quiet
record announces, reads back, and appears in the digest as a present captain
holding nothing; merges under it stay attended and it binds no spend cap. An
away record is unchanged, an /afk entry over quiet mode rewrites the record
as away, and a quiet entry never turns a standing away record quiet.

* no-mistakes(document): Clarify quiet-mode authority and remove stale away guidance

* no-mistakes(ci): The CI failure came from a race in the supervision-host test: its restart fixture could observe a watcher left by the preceding cycle. The test now retires that watcher and waits for the fixture arm to report its own started cycle. The focused test passed three times; the full suite was attempted but stopped at a separate intermittent test failure

* no-mistakes(ci): Fixed daemon refresh mode selection so an unset-mode refresh follows the posture record: /afk over a running quiet daemon now changes state/.afk to away, while a plain quiet refresh stays quiet. Added script-level regression coverage for start and start-native and corrected a quiet-refresh fixture. The launch test suite, syntax checks, and diff check passed

* no-mistakes(ci): Herdr was blocked before tests ran by a GitHub HTTP 500 downloading pinned Treehouse; no code change was warranted for that check. Fixed the Lint 1 ShellCheck warning in tests/fm-afk-launch.test.sh by annotating the intentional background PID capture. The focused test suite, ShellCheck, syntax check, and diff check passed
…merge (kunchenguid#6053)

* fix(bin): accept a task's next PR once fm-pr-merge confirms the bound one merged

require_recorded_pr_identity now checks fm_pr_poll_merge_already_notified for
the recorded pr= before refusing a different URL, so a task's later PR is
accepted once its earlier PR's merge is confirmed, while it keeps refusing
while the bound PR is still unmerged.

* no-mistakes(document): docs(fm-pr-merge): note next-PR accepted after bound PR merges
…6064)

* fix(bin): read a live quiet record as a present captain at the host and watcher

A quiet record left without its daemon (a quiet start that never ran or was
interrupted) was read as away by the supervision host, so it parked a present
captain's main and held captain outcomes for a return that never comes, and
the watcher and daemon silenced captain-held rechecks on record presence.

The host's posture checks, the watcher's and daemon's captain-held silencing,
and the host's outcome path (branch report, drain BRANCH OUTCOMES, relocated
branch authority, the owners' away wake note, and the Codex checkpoint bound)
now ask the record owner's away-or-quiet reading, so only an away record is
away. A live away record keeps today's behavior.

* no-mistakes(document): Correct quiet-record documentation and supervision guidance

* no-mistakes(document): Clarify quiet-record posture and captain-held rechecks

* no-mistakes(document): Clarify quiet-record posture in documentation
…kunchenguid#6043)

* fix(bin): name an in-window engine latch in the return brief and drop the false handling GAP line

The away return brief said nothing had failed after the supervision host
latched on engine errors during the window, and printed a GAP: watcher
downtime line whenever a wake was merely being handled or queued at return.

The failures section now reads the host ledger and latch record and names
the latch time, the window's engine-error count, and whether the session
is still paused or recovered. An open recovery episode is reported as
information, and as a gap only when a queued episode outlived the return
grace or the marker cannot be read.

* no-mistakes(review): Fix latch trip time, drop marker-age grace, bound error count

* no-mistakes(review): Report paused latch without ledger trip row; bound errors

* no-mistakes(review): Never report a failed probe's latch row as trip time

* no-mistakes(review): Only a retained trip row marks a pre-window latch

* no-mistakes(document): Clarify return-brief latch and watcher-gap documentation

* no-mistakes(ci): Fixed Lint 1 by marking the shared cooldown constant as used by sourcing scripts. The repository lint command and diff check pass; the return test run was stopped by a 180-second timeout after its completed cases passed

* no-mistakes(ci): Fixed the return brief so the trip time and error count come from the same initial latch row, and ledger rows before the current session’s lock boundary cannot affect its latch report. Added real-script regressions for both findings. The return test suite, repository lint, and diff check pass

* no-mistakes(ci): Fixed the return brief’s restart cutoff so it retains in-window failures, prints one line per initial-trip row, and omits zero-error count wording. Added real-script restart regressions. The return test suite, ShellCheck, and diff check pass

* no-mistakes(ci): Fixed the return brief so a recorded trip followed by recovery stays recovered, while a later pause with a lost trip append gets a separate “trip time unavailable” line. Added a real-script regression that failed before the fix. The return test suite, ShellCheck, syntax checks, and diff check pass

* no-mistakes(ci): Fixed the false second latch during recovery. A real-script regression failed before the fix and passes now; the lost-second-trip test still passes. The return test suite, ShellCheck, syntax checks, and diff check pass
Keep house row-scoped reports, receipt locking, routine outcome rules and published evidence validation beside upstream quiet-mode posture checks. Align the imported quiet-host fixture with one captain outcome per house wake row.
@jazz127
jazz127 merged commit 8bb9ab2 into house Sep 29, 2026
1 check passed
@jazz127
jazz127 deleted the fm/firstmate-house-upstream-merge-r8 branch September 29, 2026 05:28
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.

3 participants