moshcode 0.76.0 → 0.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
4
4
  "type": "module",
5
5
  "description": "moshcode \u2014 a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",
6
6
  "repository": {
@@ -362,14 +362,32 @@ export async function readPid(path = pidfilePath()) {
362
362
  }
363
363
  }
364
364
 
365
- /** Is that pid actually ours and alive? A stale pidfile must not read as running. */
366
- export function isAlive(pid) {
365
+ /**
366
+ * Is that pid alive? A stale pidfile must not read as running.
367
+ *
368
+ * "Alive" and "ours" are different questions, and answering the first with the
369
+ * second cost a machine its resolver. `process.kill(pid, 0)` fails two ways:
370
+ * ESRCH for a pid that is gone, and EPERM for one that is there but belongs to
371
+ * another user. Catching both as "dead" was wrong in precisely the case this
372
+ * tool manufactures — `dns enable` escalates, so the bridge is root's while
373
+ * every later `status` asking after it is not.
374
+ *
375
+ * A live root-owned bridge therefore read as a stale pidfile for the rest of
376
+ * its life: `stop` deleted the file and reported it cleared while the daemon
377
+ * kept running, and `start` saw nothing there and put a second bridge on
378
+ * 127.0.0.1 underneath the working one on 0.0.0.0 — the shadowing outage
379
+ * `bridgePresence` describes, arrived at by believing our own liveness check.
380
+ *
381
+ * So only ESRCH is dead. EPERM is alive and someone else's, which the caller
382
+ * needs told rather than papered over.
383
+ */
384
+ export function isAlive(pid, kill = (target) => process.kill(target, 0)) {
367
385
  if (!pid) return false;
368
386
  try {
369
- process.kill(pid, 0);
387
+ kill(pid);
370
388
  return true;
371
- } catch {
372
- return false;
389
+ } catch (error) {
390
+ return error?.code === "EPERM";
373
391
  }
374
392
  }
375
393
 
package/src/rates.mjs CHANGED
@@ -139,7 +139,7 @@ export function parseRate(spec) {
139
139
  // A flat fee with no period stated is a project fee, not an hourly one: "$5000
140
140
  // for the project" is how it is written, and defaulting it to per-hour would
141
141
  // silently multiply the invoice by every hour tracked.
142
- if (!sawPeriod && rate.unit === "flat" && rate.cap === null) rate.per = "hour";
142
+ if (!sawPeriod && rate.unit === "flat" && rate.cap === null) rate.per = "project";
143
143
  if (rate.cap !== null && rate.unit === "flat") {
144
144
  throw new Error("upto: caps a unit, so say what it caps — $100/hour/agent/upto:4");
145
145
  }