Skip to content

feat(bin): wake instantly on glasses file events - #97

Merged
Amplify-Logic merged 6 commits into
mainfrom
fm/fm-instant-glasses-wake-i1
Aug 23, 2026
Merged

feat(bin): wake instantly on glasses file events#97
Amplify-Logic merged 6 commits into
mainfrom
fm/fm-instant-glasses-wake-i1

Conversation

@Amplify-Logic

Copy link
Copy Markdown
Owner

Intent

Instant firstmate wake on glasses events: a file-event nudger on the mailbox DB and bridge inbox must interrupt the watcher wait instead of waiting for the next poll. When data/glasses-voice-runtime/mailbox.db (including WAL/SHM siblings) or data/bridge-inbox/ changes, the watcher's terminal wait must return immediately and expire the slow-check timer so the next authenticated check sweep, including the home-local glasses pending/photo check, runs now rather than sitting until FM_CHECK_INTERVAL. Homes without those paths must keep today's sleep POLL behavior. Herdr blocked-wake must still work when glasses paths are also watched; a file event must not count as a herdr connect failure or disable the herdr fast path. The poll loop remains the permanent fail-closed backstop. A missing or unusable file-wait falls back to sleep. This is firstmate shared tracked material.

What Changed

  • Add portable file-event watching for glasses mailbox database/WAL/SHM changes and bridge inbox updates.
  • Interrupt the watcher’s terminal wait, expire the slow-check timer, and safely race Herdr events while preserving polling and sleep fallbacks.
  • Add regression coverage for file events, fractional poll intervals, Herdr races, and fallback behavior, with accompanying architecture and operations documentation.

Risk Assessment

✅ Low: Captain, the follow-up cleanly addresses all three prior race defects while preserving the required herdr failure accounting, file-wake behavior, and polling fallback.

Testing

No pre-supplied baseline commands were available; focused and broader watcher tests passed, including mailbox/WAL/directory events, unusable-wait fallback, Herdr blocked-wake coexistence, race stderr suppression, and fast-path failure handling, while a timed manual transcript demonstrated a real bridge-inbox event returning in 0.381 seconds against a 4-second poll and homes without glasses paths retaining POLL sleep behavior.

Evidence: Timed instant-wake CLI transcript
/var/folders/1g/hctp3vpn27b1zrlsn4nsfg680000gn/T/no-mistakes-evidence/01M0Q1JRHK9GNRG2NZMN4K80YP/manual-run/home/data/bridge-inbox
CASE instant bridge-inbox wake
configured_poll_seconds=4
observed_return_seconds=0.381
last_check_exists_after_event=no
triage=[2026-08-23T12:20:02+0200] glasses file event; next cycle runs checks immediately

CASE home without glasses paths
configured_poll_seconds=0.4
observed_return_seconds=0.588
behavior=sleep POLL backstop
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (4m29s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • 🚨 bin/fm-watch.sh:976 - Intent contradiction: the requirement says “a file event must not count as a herdr connect failure or disable the herdr fast path” and file changes “must interrupt the watcher wait.” If herdr returns 2 first, this branch kills the concurrent file waiter and apply_push_wait_result performs a blind sleep "$POLL"; a glasses event during that sleep cannot interrupt it. Preserve the failure count, but continue the remaining wait with the file-event waiter.
  • 🚨 bin/fm-watch.sh:1035 - The race watchdog assumes POLL is an integer in arithmetic expansion. Existing supported usage sets FM_POLL=0.2; when glasses paths and herdr are both active, $((POLL * 20 + 40)) raises a shell arithmetic error and can terminate the watcher. Compute the deadline without integer shell arithmetic or normalize fractional values safely.
  • ⚠️ bin/fm-watch.sh:1049 - The post-race catch-up signature uses second-resolution mtime and size. If herdr wins while SQLite updates a mailbox file within the same second without changing its size, the killed file waiter’s event is missed and .last-check remains unexpired. Use nanosecond metadata or preserve the file waiter’s event result before terminating it.

🔧 Fix: Preserve glasses wake race across herdr failures
✅ Re-checked - no issues remain.

🔧 **Test** - 1 issue found → auto-fixed ✅
  • ⚠️ bin/fm-watch.sh:1024 - When the file and Herdr waiters finish concurrently, the losing noclobber write can emit cannot overwrite existing file to watcher stderr. The focused test reproduced this diagnostic even though the race completed correctly. Suppress the shell-level redirection error so ordinary wake races do not pollute watcher logs.
  • Inspected git diff 361861c0e30c76bd5e541d3866fc53084ccc3a79..dd6d275bc7c4c0ba97c04e40fd5e3bdedb42341a against every authoritative acceptance constraint.
  • bin/fm-test-run.sh tests/fm-file-eventwait.test.sh tests/fm-supervision-events.test.sh
  • Manual Bash verification using the real event_wait_or_sleep: configured an 8-second poll, created a pending bridge-inbox photo after 350ms, measured the wait returning in 415ms, and verified .last-check was removed.
  • git status --short confirmed testing left the worktree unchanged.

🔧 Fix: Suppress noclobber noise in watcher wake races
✅ Re-checked - no issues remain.

  • bash tests/fm-file-eventwait.test.sh
  • bash tests/fm-supervision-events.test.sh && bash tests/fm-watch-triage.test.sh
  • Manual watcher-level check using the real event_wait_or_sleep and fm-file-eventwait.py: mutated data/bridge-inbox/ during a 4-second wait, measured return latency, verified .last-check removal and triage logging, then removed glasses paths and measured the sleep/POLL fallback.
  • git status --short after testing to confirm no working-tree artifacts remained.
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Captain: assert fractional poll propagation in file-wait race
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@Amplify-Logic
Amplify-Logic merged commit 9f158d3 into main Aug 23, 2026
13 checks passed
Amplify-Logic added a commit that referenced this pull request Aug 24, 2026
* fix(fork-surface): repair glasses capability commit to the landed squash merge

The glasses-file-event-wake entry still named pre-squash branch commit
74d836d, which no longer resolves anywhere in this clone or on origin,
so the fork-surface check failed for every session. PR #97 landed as
9f158d3; record the same post-merge repair the bridge capability above
documents.

Upstream: none (fork-manifest hygiene, not a port)

* fix(bin): correct session lock and attached watcher supervision (kunchenguid#1545)

Port upstream 88b2a94 onto this fork's diverged supervision code, adapting
rather than overwriting.

Fault 1, session-lock identity and ownership. Upstream rewrote its
bin/fm-session-lock-lib.sh; this fork never had that file, so the same
semantics land in this fork's owners instead. bin/fm-primary-scope-lib.sh
gains whole-path-component and argv[0] harness evidence
(fm_harness_path_name, fm_harness_process_matches), a contiguous
verified-harness ancestry walk (fm_harness_ancestry_pids), membership
ownership (fm_harness_ancestry_contains), the outermost-of-run resolver
(fm_harness_ancestry_pid), and a relation whose ancestry answer is
membership rather than one chosen pid. bin/fm-lock.sh resolves
harness_pid through that shared owner and now recognizes a live lock held
inside this session's own ancestry as its own earlier acquisition,
keeping the recorded owner instead of refusing or rewriting it.

Fault 2, attached arms reporting delivered cycles as failures. The
watcher publishes each printed reason to state/.watch-deliveries.log
bound to its PID and process identity before releasing the singleton
(bin/fm-watch.sh wake + bin/fm-wake-lib.sh, which owns the ledger once
for watcher and arm), fm_watcher_healthy returns the matched identity,
and bin/fm-watch-arm.sh tracks cycle identity and resolves an
unobservable close against the delivery record: a matching record
reports the delivered wake and exits zero, only a cycle that delivered
nothing stays the typed nonzero failure. Covers started-child and
attached closes on every harness protocol.

Fork-preserving adaptations:
- FM_HARNESS_RE keeps this fork's verified set (no pi-signed); loose
  holder-liveness survives as a boundary-checked argv-token tier so
  profile launchers named codex-primary still read as live holders while
  ordinary ~/.claude/hooks paths do not.
- The ancestry walk keeps the fork's eight-parent budget, preserving
  documented parity with Pi's lockOwnership().
- docs/verification/supervision.md hunks skipped: this fork has no such
  file.
- Upstream's e2e drove bin/fm-claude-stop-autoarm.sh, absent here; the
  end-to-end layer targets this fork's real bin/fm-lock.sh in orphaned
  version-named, daemon-parented, and combined process trees instead.
- Hermeticity hardening for every supervision suite (incident-driven):
  tests/lib.sh drops inherited operational-home variables and pins a
  temp hermetic FM_HOME, teardown reaps only path-scoped children, and
  new tests/fm-supervision-test-isolation.test.sh proves a test watcher
  or --restart can never resolve or stop a real home.
- tests/fm-test-run.sh registers the three suites in watcher-wake-lock
  and serial shard 2; fixture homes pinned explicitly in the affected
  suites per the hermeticity contract.

Full bin/fm-test-run.sh --all passes at this tree (140 scripts, 0
failed); shellcheck clean via bin/fm-lint.sh on every touched script.

Upstream: 88b2a94

* fix(bin): harden supervision health against fresh leftover beacons (kunchenguid#1495)

Port the transferable core of upstream 33a4287: every supervision
consumer now decides alarm state with the same identity-matched
live-watcher and fresh-beacon predicate, so a dead or reused watcher pid
with a leftover beacon can no longer read as healthy.

bin/fm-guard.sh drops its beacon-fresh-only field and calls
fm_watcher_healthy (bin/fm-wake-lib.sh) - the predicate the arm wrapper
and turn-end guard already use. bin/fm-supervision-lib.sh's docstring
records that split: banner text from the status fields, decisions from
the shared predicate.

Skipped as inapplicable to this fork, which has no Claude Stop auto-arm
and no --claude guard mode:
- bin/fm-claude-stop-autoarm.sh failure episodes, bounded retries,
  one-time attended fail-open, and post-alarm suppression;
- lock role records (fm_lock_set_role/fm_lock_role) and
  fm_failure_episode_reset, whose only consumers are that subsystem;
- the Claude repair-line rewording, X-mode-as-need source, and
  procevent sources, which would misdocument this fork's different
  continuity model.

Regression coverage ports upstream's: a fresh beacon without a live
watcher stays an alarm; healthy-recovery cases record genuinely live
identity-matched holders instead of touching only the beacon; drain
liveness asserts the same; secondmate config-push fixtures pin live
watchers where they assert silent stderr. fork-surface.conf claims the
ported surface as the supervision-recovery-port capability.

Upstream: 33a4287

* fix(bin): name the true watcher-down reason and key episodes on it (kunchenguid#1661)

Port the transferable core of upstream d0461e4: the pull guard's health
verdict becomes model-aware and its banner names the real failing
condition instead of always blaming the beacon.

bin/fm-wake-lib.sh gains fm_supervision_model and
fm_watcher_supervision_verdict. Under a between-turns arm-owner model
(FM_SUPERVISION_MODEL=autoarm) a fresh beacon with no live watcher is
healthy mid-turn and only a stale beacon alarms; under persistent models
a live identity-matched watcher with a fresh beacon is still required.
This fork maps every detected harness - Claude included - to the
persistent model, because its Claude continuity runs tracked background
arms rather than a Stop-hook auto-arm; the override keeps the model
branch exercised and upstream-shaped.

bin/fm-guard.sh consumes the verdict: the once-per-episode dedup keys on
the failing condition instead of the beacon mtime (which any beacon
touch used to churn into a fresh episode), and the banner now says
whether the home lacks a live watcher process or a genuinely fresh
beacon. The turn-end guard keeps the PID-strict predicate unchanged,
as does the arm layer.

Skipped as inapplicable: upstream's spawn-time FM_SUPERVISION_MODEL
injection for secondmates (this fork launches every harness under the
persistent default), the autoarm/claude-protocol doc rewrites, and the
procevent/X-mode need sources this fork's guard has never had.
docs/scripts.md rows for the guard and drain reflect the supervision-
health wording.

Upstream: d0461e4

* no-mistakes(review): Fix path-scoped test process cleanup

* no-mistakes(document): Document supervision recovery contracts

* no-mistakes: apply CI fixes
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