feat: /dispatch as the default setup route, and a service-unit fix for npm deployments (issue #96) - #97
Merged
Merged
Conversation
…ssed repo root (issue #96) service.mjs derived a repo root from its own location ("../.."). Right in a checkout; WRONG under npm install, where src/ lives at node_modules/@edgehero/pi-dispatch/src and "../.." is the @edgehero SCOPE dir. Every rendered unit pointed at files that do not exist: worker ExecStart, receiver ExecStart, EnvironmentFile, the plist wrapper path, the nssm paths. The wizard's service step has been installing those broken units. The tests never caught it because they injected a correct repoRoot -- no npm-layout fixture existed. Three anchors replace the guess, each correct in BOTH layouts: - deployDir (the cwd service runs from) owns everything host-side: WorkingDirectory, <deployDir>/.env, and <deployDir>/logs (created at install time -- launchd and nssm create log files, never their directory). Never the package dir, which npm may replace wholesale on update. - cliPath = join(moduleDir, "cli.mjs") -- cli.mjs sits beside service.mjs in src/ in both layouts, so the worker ExecStart needs no repo root at all. - receiverStart = import.meta.resolve("@edgehero/pi-dispatch-receiver/start") -- the receiver package's own exported entry, wherever npm or the workspace symlink put it. null (not installed) makes render AND install refuse loudly with the install command, instead of writing a unit that crash-loops at boot. The wrapper stops guessing too: no cd, cwd IS the deployment folder (plist WorkingDirectory / nssm AppDirectory guarantee it), it refuses empty argv and a missing ./.env naming $PWD, then runs "$@" verbatim -- the render substitutes the exec argv. The trap/double-wait SIGTERM forwarding and the exit-2 -> 0 conversion are preserved and still asserted against the real shipped wrapper. worker/deploy also gains docker-compose.yml, so an npm deployment can run the receiver compose profile; the exact-list mirror sync test grows with it. Tests: 37 in the service suite (was 30) -- 3 real npm-layout fixtures asserting rendered targets EXIST and never take the scope shape, the missing-receiver refusal, and 3 live-sh wrapper tests under the new argv contract. CONST-RETRY-INFRA-ONLY unchanged, checked. Signed-off-by: Rob Boerman <robboerman@live.nl>
As the default setup route, the console must not scare or block anyone on a newer pi, and a breaking pi release must fail CI before a newcomer finds it. - runtime advisory: the extension now reads pi's exported VERSION (a real runtime export; the bundle keeps pi external, so it is the HOST's pi). When it differs from the tested pin and the capability probe still passes, the first /dispatch surfaces ONE info line naming both versions. Never a refusal -- the all-or-nothing capability probe stays the only hard gate. Computed at factory time into a memoized module slot; nothing prints at factory time, which keeps load.test's exactly-one-stderr-line wall intact. - peer range: the exact pi peer pin widens to "*", which is what pi's own packages doc prescribes for host-provided packages (pi never installs them, and an exact pin ERESOLVEs plain-npm consumers). The devDependency keeps the exact pin -- it is the tested marker, and load.test still ties SUPPORTED_PI_VERSION to it. - canary: a weekly job installs latest pi into a SCRATCH dir (never the repo root -- the pinned assertions must keep asserting the pin) and asserts every type needle, every USED_API member, the VERSION export, and that the built bundle loads and registers /dispatch against it. admin/** joins both path filters. An anti-drift test parses the pinned test's needle list so a stale canary list fails the suite in either direction. Verified against pi 0.83.0 (current latest): the canary passes, so the advisory is what real operators on newer pi will see today. Signed-off-by: Rob Boerman <robboerman@live.nl>
- direct entry: bare /dispatch with nothing configured no longer asks a yes/no before showing the wizard's own opening select -- that select (Guided setup / Open the panel anyway / Cancel) IS the consent, and one keypress no longer costs two detections (the branch hands its detection to the wizard). A configured deployment with a down queue still keeps the unreachable banner and never enters the wizard. - docker pre-check (new step 3): a probe distinguishing not-on-PATH from daemon-down, then a Re-check / Continue anyway / Stop loop with per-OS vendor pointers and never an install command to pipe. Bounded at 5 rounds. The probe carries a 10s timeout: it is a synchronous exec on a dialog path, so a wedged docker socket would otherwise freeze pi's render loop; a timeout reads as daemon-down, which is the right verdict. `pi-dispatch up` keeps its own hard refusal, so this is a better message earlier, not the enforcement. - the trigger edge (new step 10): receiver as a user-level service (consented pinned install of @edgehero/pi-dispatch-receiver, post-install version assertion, then `service install --receiver` -- correct since the unit-anchoring fix earlier in this branch), or the compose profile (the compose file now ships in the runtime package; copied create-only), or the polling command printed, or skip. A receiver version mismatch skips the unit and continues: the receiver is optional, unlike the runtime install, which stops. - runtime skew notice: when the pointer names a deployment whose installed runtime is older than this console's pin, one notice per process points at /dispatch setup, which is already the upgrade path. Absent or unreadable version stays silent (an operator may run the worker from a clone). - RECEIVER_VERSION joins RUNTIME_VERSION with its own anti-drift test, and both npm argv wrappers keep the path-free exact-argv pin. Tests: 360 admin (was 341) -- 49 in the wizard suite, incl. every FIFO answer array renumbered for the two inserted steps, all four edge answers asserted by exact spawns, create-only compose copy, and the skew latch. Signed-off-by: Rob Boerman <robboerman@live.nl>
- README quickstart splits in two: "From pi (the default route)" leads with `pi install npm:@edgehero/pi-dispatch-admin` then `/dispatch`, and the npx block moves to "Servers and headless" unchanged. The admin section drops the word "offers" for what the branch now does, states the outage rule (setup appears when there is nothing, never over a down queue), and mentions the skew notice. The service section documents the new unit anchoring, and the trigger-edge list notes the wizard walks the same choice. - admin/README: the same flip, plus the docker check and trigger-edge steps. - docs/launch-kit.md gains the install line it never had, in both the elevator pitch and the Show HN body, scoped form only per its own naming rule. - Specs: DES-FIRST-RUN-SETUP-WIZARD amended (direct entry, the two new steps, the skew notice, RECEIVER_VERSION, and the outage rule restated as load-bearing); REQ-ADMIN-VIA-PI-EXTENSION acceptance amended (lands in the select, Cancel spawns and writes nothing; an untested pi version is an advisory, never a refusal; the skew notice). Revision History rows in both files record the service-unit fix and the pi-compatibility strategy. CONST-BUDGET-BEFORE-TOKENS, CONST-RETRY-INFRA-ONLY, REQ-DEPLOYMENT-BOOTSTRAP unchanged, checked. No dashes in the README prose, per the house style. Signed-off-by: Rob Boerman <robboerman@live.nl>
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.
Closes #96. Makes
/dispatchthe default way to set up pi-dispatch, and fixes a shipped bug found while verifying it.Fixed:
service installwas broken for every npm deploymentservice.mjsderived a "repo root" two directories above its own module. Right in a checkout; undernpm installthat isnode_modules/@edgehero, the scope directory, so every rendered unit pointed at files that do not exist: the worker ExecStart, the receiver ExecStart,EnvironmentFile, the plist wrapper path, the nssm paths. The wizard's service step has been installing those units since it shipped. Tests never caught it because they injected a correctrepoRoot; no npm-layout fixture existed.Three anchors replace the guess, each correct in both layouts:
deployDir(the cwdserviceruns from) owns everything host-side (WorkingDirectory,.env, andlogs/, created at install time since launchd and nssm create log files but never their directory),cliPathiscli.mjsbeside the service module, andreceiverStartcomes fromimport.meta.resolve("@edgehero/pi-dispatch-receiver/start"), with a loud refusal naming the install command when the package is absent instead of writing a unit that crash-loops at boot. The wrapper stops guessing too: cwd is the deployment folder, it refuses empty argv and a missing.envnaming$PWD, then runs"$@"verbatim. The exit-2 conversion and SIGTERM forwarding are preserved and still asserted against the real shipped wrapper.worker/deployalso gainsdocker-compose.yml, so npm deployments can run the receiver profile.Existing service installs from 0.1.1 should re-run
pi-dispatch service install --forcefrom their deployment folder.pi compatibility becomes strategy, not luck
As the advertised front door, a pi release must not break onboarding silently. The extension now reads pi's exported
VERSIONand, when it differs from the tested pin while the capability probe still passes, surfaces one info line on the first/dispatch. Never a refusal: the all-or-nothing capability probe stays the only hard gate. The peer pin widens to the"*"range pi's own packages doc prescribes for host-provided packages (the exact devDep stays the tested marker). And a weekly canary installs latest pi into a scratch dir (never the repo root, so the pinned assertions keep asserting the pin) and fails CI if any used API member or type needle disappears. Verified against pi 0.83.0, today's latest: the canary passes, so the advisory is what real operators on a newer pi will see.The default route
/dispatchwith nothing configured no longer asks a yes/no before showing the wizard's own opening select. That select (Guided setup / Open the panel anyway / Cancel) is the consent, and one keypress no longer costs two detections. A configured deployment with a down queue still keeps the unreachable banner and never enters the wizard.service install --receiver, correct after the fix above), or the compose profile (compose file copied create-only), or the polling command printed, or skip. A receiver version mismatch skips the unit and continues; the receiver is optional, unlike the runtime install, which stops./dispatch setup, which is already the upgrade path.Docs
README quickstart leads with the extension route; the npx block moves to "Servers and headless" unchanged; the clone note stays for custom images.
admin/READMEfollows.docs/launch-kit.mdgains the install line it never had. No dashes in README prose, per the house style.Specs
DES-FIRST-RUN-SETUP-WIZARDamended (direct entry, both new steps, the skew notice,RECEIVER_VERSION, the outage rule restated as load-bearing);REQ-ADMIN-VIA-PI-EXTENSIONacceptance amended (lands in the select, Cancel spawns and writes nothing; an untested-but-complete pi is an advisory; the skew notice). Revision rows indesign.mdandrequirements.mdrecord the service-unit fix and the compatibility strategy.CONST-BUDGET-BEFORE-TOKENS,CONST-RETRY-INFRA-ONLY,REQ-DEPLOYMENT-BOOTSTRAPunchanged, checked.Tests
Full suite: 1871 tests, 1855 pass, 0 fail, 16 pre-existing skips (from 1843). Worker service suite 30 → 37 (real npm-layout fixtures asserting rendered targets exist and never take the scope shape, the missing-receiver refusal, live-shell wrapper tests under the new argv contract); admin 341 → 360. Workflow YAML validated;
sh -n,xmllint, andplutil -lintpass on the wrapper and a live render; CI greps and control-byte scans clean.