Skip to content

fix(omp): make task-inbox doorbell handshake truthful - #141

Merged
dnth merged 17 commits into
mainfrom
fm/fm-omp-doorbell-handshake-unwritten
Sep 14, 2026
Merged

fix(omp): make task-inbox doorbell handshake truthful#141
dnth merged 17 commits into
mainfrom
fm/fm-omp-doorbell-handshake-unwritten

Conversation

@dnth

@dnth dnth commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Intent

Make the OMP task-inbox doorbell handshake observable and truthful: today a partially failed activate() leaves state/.omp-ready present with no state/.omp-doorbell-ready marker and no diagnostic, so every fm-send reports 'omp-native-refused session-pid=unreadable' and owned redelivery can never succeed. Required change: (1) touch .omp-ready only after activate() succeeds; (2) on activate failure, journal the reason durably (state/.omp-doorbell-failed with the error text) and keep retiring cleanly; (3) make fm-send's refusal message name the concrete missing marker / failure journal so firstmate sees WHY instead of 'unreadable'; (4) spawn-time verification: fm-spawn.sh waits (bounded) for .omp-doorbell-ready after .omp-ready and reports a clear spawn failure if it never appears. Tests: extension unit tests for activate success/failure marker behaviour; bash tests for spawn-time wait and the improved refusal text. Accepted Firstmate requirements: deterministic default failure-journal derivation from the ready marker (caller-neutral .omp-doorbell-failed. fallback when no marker configured); the bounded doorbell wait applies to every launch emitting the generated extension including secondmate OMP launches, while raw OMP launches without the generated template remain exempt; drain-time send failures (synchronous and asynchronous sendMessage rejections) journal 'drain' and retire the marker, and initial async sends are included in activation success so a rejection cannot leave .omp-ready published; existing PR #136 doorbell/turn-proof tests are preserved including test_requester_window_tracks_turn_grace_and_acked_suppresses and uncorrelated turn_start expectations. Firstmate-Validation-Generation: e9d3c7781595d3a306320cb18dca0177

What Changed

  • Updated the generated OMP extension handshake so .omp-ready is published only after successful activation; activation and drain failures are durably journaled, owned markers retire cleanly, and initial async sends participate in activation success.
  • Added a bounded .omp-doorbell-ready wait and clear failure reporting to template-based OMP spawns, including secondmate launches, while leaving raw OMP launches exempt.
  • Improved native OMP refusal diagnostics to identify the missing, unreadable, or unproven marker or its failure journal, with expanded coverage for activation, redelivery, relaunch, dispatch, and secondmate paths.

Risk Assessment

⚠️ Medium: The changes satisfy the stated handshake, journaling, refusal-diagnostics, bounded-wait, and async-drain requirements; no concrete source defect was substantiated, though the concurrency and lifecycle logic is materially complex.

Testing

The focused executable tests covering activation success/failure journaling, refusal diagnostics, bounded OMP spawn waits, and secondmate extension handling passed. The required live OMP worker check was attempted but blocked by the no-mistakes gate-worktree fleet guard, so live scenarios remain untested.

  • Live validation: ⚠️ inconclusive - 0 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Start a generated OMP worker and observe truthful readiness: .omp-ready appears only after successful doorbell activation and .omp-doorbell-ready is present. ⏸️ untested no The real OMP worker harness could not run because fm-send.sh refused fleet lifecycle from this no-mistakes gate worktree. Provide a normal checkout/worktree (or equivalent fleet-driving authority) a…
Force OMP activation failure and verify no false ready marker, durable .omp-doorbell-failed journal, and clean retirement. ⏸️ untested no The real worker could not be launched in this gate worktree due the fleet lifecycle guard. Re-run outside the gate worktree with fleet-driving authority.
Send to an OMP session with missing/failed doorbell binding and verify fm-send names the concrete marker or failure journal instead of 'session-pid=unreadable'. ⏸️ untested no A real running session was unavailable because the live harness was refused by the no-mistakes gate-worktree guard. Provide a normal runtime environment and rerun the live test.
Spawn generated-extension OMP workers (including secondmates) and verify bounded wait for .omp-doorbell-ready; raw OMP launches remain exempt. ⏸️ untested no Fleet spawn lifecycle is blocked from this gate worktree. Run the live spawn harness from a regular checkout/worktree with permission to create endpoints and workers.
Evidence: Doorbell targeted tests
ok - OMP extension drains canonical counted requests and safely retires signal readiness
ok - OMP extension requires turn proof and re-drives a deferred triggerTurn through the user channel
ok - OMP doorbell driven by external turn notifications proves turns and unlatches on turn_end
ok - OMP extension activation reports failures, journals their reasons, and retires cleanly
ok - doorbell routing keeps OMP on its native adapter and preserves the non-OMP composer branch
ok - OMP pending retries revalidate identity while ambiguous claims suppress resend
ok - fm-send and both tmux/Herdr adapters preserve task-bound OMP programmatic doorbells
ok - fm-send: an OMP steer is received natively over tmux and Herdr with no composer transport
ok - fm-send: refused and unacknowledged OMP steers stay bounded, durable and non-resend-inviting
ok - fm-send: an unproven OMP session binding is refused, never redirected to the terminal
ok - requester ack window tracks the turn grace and an acked receipt suppresses the re-ring
Evidence: Spawn doorbell targeted tests
ok - no --model/--effort records defaults and types the claude launch instructions
ok - relative home overrides ignore CDPATH and become absolute before spawn launch construction
ok - FM_HOME defaults resolve relative paths and preserve absolute spellings
ok - generated OMP worker extension gates .omp-ready on doorbell activation and journals the failure
ok - OMP spawn bounded-waits on .omp-doorbell-ready and fails loudly on absence or journaled failure
ok - OMP secondmates trust exact primary and fleet extensions while inspecting staged code
- Outcome: ⚠️ 2 warnings across 1 run (6m5s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (7) ✅
  • 🚨 .omp/extensions/lib/fm-task-inbox-doorbell.ts:390 - The activation wait snapshots activationSends once at line 389. A request arriving while that snapshot is pending can start a second asynchronous sendMessage; that promise is added after the snapshot and is not awaited. If the first send resolves, activate() returns true and the generated extension touches .omp-ready; when the late send rejects, its handler only journals and retires the doorbell marker, leaving .omp-ready behind. This recreates the false-ready invariant during concurrent initial drain activity. Keep activation from resolving until all sends initiated during activation have settled, or otherwise propagate any later rejection before readiness is published.

🔧 Fix applied.
1 warning still open:

  • ⚠️ bin/fm-task-inbox-lib.sh:252 - fm_task_inbox_omp_doorbell_state() reports doorbell-binding-unproven=<marker> whenever the marker is valid but the native request fails for another reason. For example, if <marker>.requests is missing during teardown/race, fm_omp_task_doorbell_request() returns 1, the refusal path reaches this helper, and fm-send claims the session binding is unproven even though the binding is valid and the request directory is the concrete failure. Add a state check for the request directory (and any similarly distinguishable request-write failure) so the refusal names the actual missing artifact instead of a false cause.

🔧 Fix applied.
1 error still open:

  • 🚨 .omp/extensions/lib/fm-task-inbox-doorbell.ts:329 - A late rejection from an older doorbell generation can poison a newer healthy launch. Generation A starts an async sendMessage, is retired, and generation B reuses the same task marker and activates successfully (clearing the old journal). If A's promise then rejects, the handler at line 329 writes the shared failure journal even though A is no longer active; fm_task_inbox_omp_doorbell_state() prioritizes that journal over the live marker, so fm-send refuses delivery to generation B. Only journal/retire an async rejection while that doorbell instance still owns an active marker, or otherwise guard the journal write by generation ownership.

🔧 Fix applied.
1 error still open:

  • 🚨 .omp/extensions/lib/fm-task-inbox-doorbell.ts:341 - An asynchronous drain rejection can lose the durable request when omp.on is unavailable. In drain(), the request is renamed from .ambiguous to .delivered immediately after sendMessage() returns a Promise (line 341), before that Promise settles. If it later rejects, the handler journals and retires the doorbell (lines 328-332) but never reverts the .delivered record to a retryable state. The watcher therefore treats the failed delivery as complete and owned redelivery cannot occur. Keep the request pending/failed until the async send succeeds, or move it back to a retryable state in the rejection path.

🔧 Fix applied.
1 error still open:

  • 🚨 .omp/extensions/lib/fm-task-inbox-doorbell.ts:359 - A synchronous drain-time sendMessage() throw strands the durable request as .pending.ambiguous: invoked is set true before the call, so the catch at line 359 skips the only rename, then journals and retires the doorbell. On the next activation, reconciliation handles .processing.* and .awaiting-turn but not .ambiguous, so the request is never redelivered. Restore the request to .pending (or another retryable state) in this catch, matching the asynchronous rejection path.

🔧 Fix applied.
1 error still open:

  • 🚨 .omp/extensions/lib/fm-task-inbox-doorbell.ts:329 - The async rejection handler exits immediately when active is false (line 329), which can strand other requests from the same drain after one send failure retires the doorbell. For a runtime without omp.on, each send is renamed to .delivered immediately (lines 343-345); if the first Promise rejects, it retires the doorbell and sets active=false, then any later in-flight send rejection skips the retry-state rename and journaling, leaving its .delivered record unrecoverable on the next activation. Keep all sends initiated by the draining generation retryable (or otherwise reconcile them) even after the first failure retires the marker.

🔧 Fix applied.
1 error still open:

  • 🚨 bin/fm-spawn.sh:1045 - The required criterion says activation failures must be journaled durably at state/<id>.omp-doorbell-failed. The spawn failure cleanup path now deletes that journal (bin/fm-spawn.sh:1045) after the generated extension records the failure (:4741-4746), whenever the owned worktree is clean and returned. A failed launch therefore exits with no .omp-doorbell-failed artifact left for durable inspection; please clarify whether cleanup is intentionally allowed to remove this required diagnostic, or preserve it (or otherwise provide an explicitly durable equivalent).

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Test** - 2 warnings
  • ⚠️ Real-worker live validation was blocked because fleet lifecycle commands refuse execution from a no-mistakes gate worktree. Re-run the live harness from a normal checkout/worktree with fleet-driving authority.
  • ⚠️ live validation verdict: inconclusive (0 of 4 scenarios were driven live against the product); untested: Start a generated OMP worker and observe truthful readiness: .omp-ready appears only after successful doorbell activation and .omp-doorbell-ready is present., Force OMP activation failure and verify no false ready marker, durable .omp-doorbell-failed journal, and clean retirement., Send to an OMP session with missing/failed doorbell binding and verify fm-send names the concrete marker or failure journal instead of 'session-pid=unreadable'., Spawn generated-extension OMP workers (including secondmates) and verify bounded wait for .omp-doorbell-ready; raw OMP launches remain exempt.
  • Live validation: ⚠️ inconclusive - 0 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Start a generated OMP worker and observe truthful readiness: .omp-ready appears only after successful doorbell activation and .omp-doorbell-ready is present. ⏸️ untested no The real OMP worker harness could not run because fm-send.sh refused fleet lifecycle from this no-mistakes gate worktree. Provide a normal checkout/worktree (or equivalent fleet-driving authority) a…
Force OMP activation failure and verify no false ready marker, durable .omp-doorbell-failed journal, and clean retirement. ⏸️ untested no The real worker could not be launched in this gate worktree due the fleet lifecycle guard. Re-run outside the gate worktree with fleet-driving authority.
Send to an OMP session with missing/failed doorbell binding and verify fm-send names the concrete marker or failure journal instead of 'session-pid=unreadable'. ⏸️ untested no A real running session was unavailable because the live harness was refused by the no-mistakes gate-worktree guard. Provide a normal runtime environment and rerun the live test.
Spawn generated-extension OMP workers (including secondmates) and verify bounded wait for .omp-doorbell-ready; raw OMP launches remain exempt. ⏸️ untested no Fleet spawn lifecycle is blocked from this gate worktree. Run the live spawn harness from a regular checkout/worktree with permission to create endpoints and workers.
  • bash tests/fm-omp-task-inbox-doorbell.test.sh
  • Targeted fm-spawn-dispatch-profile checks for generated-extension readiness gating, bounded handshake failure, and secondmate extension inspection
  • bash tests/fm-remote-secondmate-lifecycle-e2e.test.sh (remote lifecycle portion reached successful remote spawn checks)
  • FM_SEND_INBOX_LIVE_E2E=1 FM_SEND_INBOX_LIVE_HARNESSES=omp FM_SEND_INBOX_LIVE_TIMEOUT=90 bash tests/fm-send-inbox-doorbell-live-e2e.test.sh (blocked by gate-worktree refusal)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A partially failed doorbell activation left .omp-ready present with no
.omp-doorbell-ready marker and no diagnostic, so every fm-send reported
omp-native-refused session-pid=unreadable and owned redelivery could never
succeed.

- activate() now returns whether the doorbell is live; the generated worker
  extension touches .omp-ready only after it succeeds.
- Activation and drain failures journal their reason durably to
  <id>.omp-doorbell-failed (staged write, mode 0600) and still retire cleanly;
  a successful activation clears a stale journal.
- fm-send refusals name the concrete artifact via FM_TASK_INBOX_RING_OMP_DOORBELL:
  doorbell-failure=<journal>, doorbell-marker-missing=<marker>,
  doorbell-marker-unreadable=<marker>, or doorbell-binding-unproven=<marker>.
- fm-spawn bounded-waits for .omp-doorbell-ready after launch and fails the
  spawn loudly, naming the marker and the journaled reason when present.
- teardown and the spawn abort/relaunch artifact lists cover the new journal.
…he failing secondmate fixtures to publish `.omp-doorbell-ready` alongside their OMP acknowledgement markers, and relaxed outdated preservation-text assertions to match the new bounded-wait failure path. `tests/fm-omp-secondmate.test.sh` passes; shell syntax and diff checks pass
@dnth
dnth force-pushed the fm/fm-omp-doorbell-handshake-unwritten branch from 68cfe86 to acd5544 Compare September 13, 2026 14:34
…ll-escaped OMP doorbell paths before extraction, handling wrapped remote launches. bash -n, shellcheck, diff checks, and a fixture integration reproduction pass. The full lifecycle test reaches its pre-existing unrelated markerless-reconciliation failure before the OMP scenario; fm-lint.sh was started but terminated after an extended run without diagnostics
@dnth
dnth merged commit e6d97d5 into main Sep 14, 2026
15 checks passed
@dnth
dnth deleted the fm/fm-omp-doorbell-handshake-unwritten branch September 14, 2026 00:20
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