Repository navigation
Conversation
|
🔍 Automated Cursor review is underway. Please hold off on merging until the review comment lands here (usually 10–20 minutes). |
|
Review complete: HighThe non-default Herdr session guard does not constrain which server is used. MediumCleanup is an emptying close with no focus protection, and a create result can adopt the parent pane. Each entry is a new workspace ( A lost close cannot be finished once the native entry leaves the projection. Binding host identity is rewritten, and A lost bind is retried instead of adopted by readback. One invalid projection entry blocks heartbeats for the whole home. A successful attach is marked ready without the argv check the lost-reply path requires. LowA crashed stale-lock reclaim bricks the home. If the process dies after Tests
|
Adds an explicit native ShuvBro entrypoint for new managed Shuvcode homes. Shuvcode owns durable work and execution; ShuvBro supplies orchestration policy and places exact, persistent Session views in Herdr.
Requires companion Shuvcode #440 and Herdr #16. The integration contract and joint evidence describe the three-repository boundary.
Behavior
fm-native.mjsinitializes new homes with the tracked ShuvBro profile, runs the native supervisor, forwards typed work commands, and reconciles Herdr views. Existing homes are refused.watchmaintains status leases. Display close, restart, cleanup and reopen do not start or cancel execution. Cleanup requires positive settlement and closes only the recorded pane; reopen creates a new binding generation for the same Session.Validation
checks-passedat089d2b7: 12 targeted public-CLI tests, an isolated CLI evaluation using a fake native service and fake Herdr socket, and fresh full CI passed. Coverage includes two workers, lost replies, authority conflicts, independent restarts, stale locks and guarded cleanup. These checks do not access live/default services.089d2b7, SHA-256e63cfe03b39a15f6382e146df952b50ee6c3a42306a0d72682161a2b3d3e0c05: cleanup and reopen work whilewatchstays active. The Session remains unchanged, the new binding receives later reports, neighboring panes from both generations and parent focus survive, and model requests remain 4→4.Initial scope is new managed homes. Existing-home migration, real two-host networking, SSH disconnect behavior and production load remain unqualified. Production services and installed binaries were not changed.
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-native.mjs:246-syncreconciles entries one after another and stops at the first error, so one uncertain or conflicting entry blocks every entry after it. Example: the lead'sworkspace.createreply is lost, leaving entry-0 increate_pending. Every latersync,uporwatchpass then throws at line 264 before it reaches the workers. Their views are never created, and existingreadyentries are never re-reported, so their 5 s leases expire to unknown for good. The test at tests/fm-native-supervisor.test.mjs:128 (creates === 1) confirms that the two worker views are never created. A foreign binding on one entry (line 239) has the same effect. Fix: catch errors per entry inside thesyncloop, keep reconciling and reporting the rest, then throw a combined error so the exit code stays non-zero.bin/fm-native.mjs:256- The secondsyncloop sends an unknown report for journal entries that have left the projection but are stillready, and it does not catchpane_not_found/not_foundthe way thereadybranch ofreconciledoes (line 315). Example: a native entry drops out of the projection, then its pane is closed. From then on everysync/upexits 1 andwatchlogs an error every 2 s. Cleanup is also impossible, becausefactis undefined and fails the settlement check. Fix: apply the same not_found →closedtransition here.bin/fm-native.mjs:268- The entry is saved ascreate_pendingbefore the label is computed withfact.title.slice(0, 100), andpresentation()never checks thattitleis a string. If a ship or scout fact has no title, this throws a TypeError beforeworkspace.createis ever sent. The entry is then stuck increate_pending('Uncertain creation'), even though nothing was created, and there is no CLI way to recover it. Fix: validatetitleinpresentation()and compute the label before savingcreate_pending.bin/fm-native.mjs:129-watchholds the home lock for its entire life. The docs say to keepwatchrunning and also to use explicitcleanupandreopen, but both fail with 'Native display adapter is already running' whilewatchholds the lock. To clean up, the operator has to stopwatch, which lets every lease expire to unknown. Fix: take the lock for each reconcile pass and release it between passes, or document thatwatchmust be stopped first.bin/fm-native.mjs:264- Nothing in the CLI resolves an entry stuck increate_pendingorlaunch_pending.cleanupandreopenboth refuse it, and the docs only say to 'inspect the journal and Herdr'. In practice that means editing native-display.json by hand. The intent ('remains visibly pending without blind duplicate allocation') is met, so the open question is only whether an explicit resolve or abandon operation is wanted. Adding one would extend the scope.bin/fm-native.mjs:231- The--host-idoption and theattachment()rewrite replace the native attachment'shost_id: "local"withhostname()or a user-chosen string before binding. The intent requires binding the 'exact existing native Session/home/host/location identity'. A host field that differs from the native value, plus an option to override it, goes beyond what the intent requires. Remedy: remove the--host-idoption and bind the native attachment unchanged, unless Herdr explicitly needs a concrete host name.bin/fm-native.mjs:226- Attach-argv validation accepts three prefix shapes: a bare executable, interpreter plus script, and--no-env-file --preload X script. Nothing in the intent requires the five-item preload/dev-runner form. If the managed Shuvcode only emits one shape, accept just that shape.🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:
bin/fm-native.mjs:169- The fix round changedwatchto take the lock on every pass and made waiters retry every 100 ms, so lock handoff now happens constantly. Two parts of the new lock code are not atomic. (1) Release callsrm(directory, {recursive:true}), which deletespidfirst and then the directory. On Linux,rename()of a directory onto an existing empty directory succeeds. So a waiter (for examplecleanuppolling whilewatchreleases) can rename its staging dir into place in that gap. The releaser's rmdir then hits ENOTEMPTY. Depending on Node's rm implementation, it either deletes the waiter's fresh lock (the waiter now runs with no lock held) or throws fromfinally, failing a pass that otherwise succeeded. (2) Stale reclaim (line 176) does check-then-remove. Stoppingwatchwith Ctrl-C during a pass leaves a dead-pid lock. If a restartedwatchand acleanupboth try to recover it, B'srm(force)can remove A's just-acquired lock, and both run passes at once. Either way two processes hold the lock together, and a stalewatchjournal can overwritecleanup/reopenresults, which is the overwrite F4 was meant to prevent. Fix: make release atomic by renaming the lock dir to a unique tombstone and then removing it, or use an O_EXCL lock file (writeFile(..., {flag:'wx'})) released withunlink. Reclaim a stale lock by renaming it to a unique tombstone and re-checking ownership, not by removing it in place.tests/fm-native-supervisor.test.mjs:77-start()registers its kill hook witht.afterafter the fixture's hook. node:test runs after-hooks in registration order (FIFO, checked locally). So in the watch+cleanup test, the fixture closes the fake Herdr server and runsrm(directory, {recursive:true})while thewatchchild is still running. That child can still be appendingnative-calls.jsonl, writing journal temp files, or creating lock staging dirs in that directory, so cleanup can race and the test can flake. Fix: kill the watch child and awaitclosedinside the test body before it returns, or make the fixture's teardown run last.bin/fm-native.mjs:235- As the user directed for F3, title validation now happens at the projection boundary, so a single worker without a title fails the wholepresentation(). The lead and the other workers are then neither created nor reported, and the new test assertscreates === 0. This is consistent with how other contract fields are already validated, but it is a deliberate exception to the F1 rule of independent per-entry reconciliation. Noted only so the tradeoff is visible.🔧 Fix applied.
1 info still open:
bin/fm-native.mjs:175- If a process dies afterclaim(guard)but beforeunlink(guard)(a window of a few microseconds during stale reclaim),.native-display-lock.reclaimis left owned by a dead pid. From then on, everysync,cleanupandreopencall, and everywatchpass, fails with 'Ambiguous native display lock recovery' until someone deletes it by hand.docs/native-supervisor.mddoes not name this file. This fails closed, which matches the intent's 'retain for inspection' semantics. Noted only so the manual recovery step is known.✅ **Test** - passed
✅ No issues found.
control -- task steerforwarded with --home;control -- attachexit 1bin/fm-test-run.sh --exclude-family live-harness-optin --exclude-family real-herdr-gated tests/fm-native-supervisor.test.sh(12/12 node:test cases pass)bin/fm-test-run.sh --check-coverage(new test script is covered as unclassified; guard ok)node drive-native-cli.mjs bin/fm-native.mjs main: --help, init, up, control task steer, control attach refusal, idempotent sync, foreign-binding conflict, done-but-unsettled cleanup refusal, lost pane.close then retried cleanup, user-closed view during work, explicit reopen, retired reopen refusalnode drive-native-cli.mjs bin/fm-native.mjs guards: default session, credential argv, old Herdr without runtime methods, re-init and legacy home refusal, sync on non-native homenode drive-native-cli.mjs bin/fm-native.mjs lost: lost workspace.create reply for lead while two workers proceednode drive-native-cli.mjs bin/fm-native.mjs restart: watch started, stopped and restarted independently; status while watch runs✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.