@junghanacs/entwurf 0.30.1 → 0.32.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.
Files changed (76) hide show
  1. package/AGENTS.md +4 -2
  2. package/CHANGELOG.md +194 -0
  3. package/DELIVERY.md +1 -0
  4. package/README.md +64 -14
  5. package/VERIFY.md +36 -1
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/setup-clean-host.md +121 -4
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +7 -7
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +7 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +28 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +236 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +746 -0
  19. package/mcp/entwurf-bridge/src/index.ts +7 -7
  20. package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
  21. package/package.json +19 -19
  22. package/pi/entwurf-capabilities.json +1 -0
  23. package/pi/pi-durable/bootstrap.mjs +404 -0
  24. package/pi/pi-durable/carrier/LICENSE +21 -0
  25. package/pi/pi-durable/carrier/harness-setup.js +123 -0
  26. package/pi/pi-durable/carrier/prompt.js +74 -0
  27. package/pi/pi-durable/carrier/runtime.js +402 -0
  28. package/pi/pi-durable/carrier/sessions.js +56 -0
  29. package/pi/pi-durable/carrier/subagent.js +55 -0
  30. package/pi/pi-durable/carrier/tui.js +655 -0
  31. package/pi/pi-durable/carrier-relocation.mjs +160 -0
  32. package/pi/pi-durable/carrier-resolver.mjs +38 -0
  33. package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
  34. package/pi/pi-durable/overlay/upstream-pin.json +108 -0
  35. package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
  36. package/pi-extensions/lib/fresh-call-composition.ts +42 -7
  37. package/pi-extensions/lib/meta-sender-identity.ts +7 -1
  38. package/pi-extensions/lib/meta-session.ts +28 -2
  39. package/pi-extensions/lib/mux-fresh-call.ts +32 -2
  40. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
  41. package/pi-extensions/lib/pi-durable-runtime.ts +318 -0
  42. package/pi-extensions/meta-bridge-pi-durable.ts +1024 -0
  43. package/run.sh +624 -184
  44. package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
  45. package/scripts/check-fresh-call-dispatch.ts +18 -0
  46. package/scripts/check-gate-qualification.ts +87 -34
  47. package/scripts/check-herdr-activation.ts +5 -4
  48. package/scripts/check-pi-durable-contact.ts +767 -0
  49. package/scripts/check-pi-durable-receive.ts +1079 -0
  50. package/scripts/check-pi-durable-send.ts +1821 -0
  51. package/scripts/emit-pi-durable-carrier.ts +355 -0
  52. package/scripts/lib/mutation-qualify.ts +21 -45
  53. package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
  54. package/scripts/lib/pi-durable-operator-guard.ts +89 -0
  55. package/scripts/lib/pi-durable-r1.ts +100 -0
  56. package/scripts/lib/pi-durable-reap.ts +27 -0
  57. package/scripts/lib/pi-durable-receive.ts +137 -0
  58. package/scripts/lib/qualification-receipt.ts +7 -11
  59. package/scripts/mutants/control-send-receipt.json +1 -1
  60. package/scripts/mutants/copilot-birth.json +2 -2
  61. package/scripts/mutants/gate-qualification.json +13 -0
  62. package/scripts/mutants/meta-hook-session-switch.json +4 -2
  63. package/scripts/mutants/mux-fresh-call.json +69 -3
  64. package/scripts/mutants/omp-birth.json +2 -2
  65. package/scripts/mutants/omp-fresh.json +6 -4
  66. package/scripts/mutants/pack-install.json +3 -3
  67. package/scripts/mutants/pi-durable.json +723 -0
  68. package/scripts/mutants/pi-floor.json +24 -2
  69. package/scripts/pi-durable-contact-host.mjs +105 -0
  70. package/scripts/pi-durable-receive-host.mjs +127 -0
  71. package/scripts/pi-durable-send-host.mjs +284 -0
  72. package/scripts/smoke-herdr-raw-install-live.sh +1 -1
  73. package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
  74. package/scripts/smoke-setup-verdict.sh +22 -3
  75. package/scripts/smoke-user-scope-citizen.sh +1 -1
  76. package/scripts/tsconfig.json +1 -0
@@ -1,10 +1,12 @@
1
1
  // check-entwurf-bridge-pi-free (0.12.1 A-gate, static half)
2
2
  //
3
- // INVARIANT: the entwurf-bridge MCP server must boot WITHOUT any pi package
4
- // (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is one
5
- // optional adapter lane, not a boot dependency. A plain `npm install
6
- // @junghanacs/entwurf` (no pi peers) must still stand `entwurf-bridge` up and
7
- // answer MCP `tools/list`.
3
+ // INVARIANT: the entwurf-bridge MCP server must boot WITHOUT importing any pi
4
+ // package (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is
5
+ // one adapter lane, not a boot dependency. A plain `npm install @junghanacs/entwurf`
6
+ // must still stand `entwurf-bridge` up and answer MCP `tools/list` without touching
7
+ // pi. Since #130 that install carries the pi SDK as a production dependency (the
8
+ // pi-durable carrier's), so the runtime half proves this under a deny hook in
9
+ // check-pack-install rather than by the SDK's absence.
8
10
  //
9
11
  // This gate is the STATIC half of that contract: it walks the EAGER static
10
12
  // value-import closure of the bridge entry (`mcp/entwurf-bridge/src/index.ts`)
@@ -138,6 +138,24 @@ async function main(): Promise<void> {
138
138
  !herdrBranch.includes("codexCallerFreshPreflight"),
139
139
  );
140
140
 
141
+ // #129: pi-durable joined the tmux fresh set, NOT the herdr pilot set (GLG, 2026-10-06: no herdr
142
+ // pi-durable support in 0.31). Same axis as the codex cell: refused by the herdr reason, nothing
143
+ // spawned, no tmux fallback, and its installed-carrier preflight is never asked on this branch.
144
+ const durable = recordingSpawn();
145
+ const durableRefused = await dispatchFreshCall(
146
+ { ...REQUEST, backend: "pi-durable", model: "loopback/scripted" },
147
+ HERDR_ENV,
148
+ durable.spawn,
149
+ );
150
+ ok(
151
+ "[QK:FCD-HERDR-PI-DURABLE-UNSUPPORTED] inside herdr pi-durable is not a pilot backend: it refuses by the herdr reason with nothing spawned, never falls back to a tmux window, and its runtime preflight is never consulted there",
152
+ durableRefused.rail === "herdr" &&
153
+ !durableRefused.result.ok &&
154
+ durableRefused.result.reason === "herdr-backend-unsupported" &&
155
+ durable.calls.length === 0 &&
156
+ !herdrBranch.includes("piDurable"),
157
+ );
158
+
141
159
  // ── the tmux rail keeps its pre-existing ordering ────────────────────────────────────
142
160
  // The import line names the preflight too, so the body is what gets counted: the claim is
143
161
  // "called once, on one path", not "the word appears once".
@@ -66,9 +66,9 @@ import {
66
66
  runGateBounded,
67
67
  signatureAttributedToFailure,
68
68
  signatureOnFailureLine,
69
- sweepStaleSnapshots,
70
69
  validateManifest,
71
70
  validateManifestSet,
71
+ withOwnSnapshot,
72
72
  } from "./lib/mutation-qualify.ts";
73
73
  import { ACCEPTANCE_SCHEMA, checkAcceptanceRecord, RECORD_CONSISTENT } from "./lib/qualification-acceptance.ts";
74
74
  import {
@@ -637,6 +637,32 @@ function fixtureSpec(patch: Partial<MutantSpec>): MutantSpec {
637
637
  };
638
638
  }
639
639
 
640
+ /**
641
+ * A PRIOR run's snapshot root inside a self-test's private `tmpRoot`, in the exact shape the
642
+ * retired ambient sweep deleted (snapshot prefix, a `runner.json` naming a dead pid). It is
643
+ * that run's evidence: no qualification run may list it, let alone remove it. `state()`
644
+ * names every entry with its bytes, so a removal or a rewrite both read as a change.
645
+ */
646
+ const PRIOR_RUN_ROOT = "entwurf-qualify-prior1";
647
+ function seedPriorRunRoot(tmpRoot: string): { before: string; state: () => string } {
648
+ const dir = path.join(tmpRoot, PRIOR_RUN_ROOT);
649
+ fs.mkdirSync(dir);
650
+ fs.writeFileSync(path.join(dir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
651
+ fs.writeFileSync(path.join(dir, "evidence.log"), "a prior run's red evidence\n");
652
+ const state = (): string =>
653
+ fs.existsSync(dir)
654
+ ? fs
655
+ .readdirSync(dir)
656
+ .sort()
657
+ .map((name) => `${name}\0${fs.readFileSync(path.join(dir, name), "utf8")}`)
658
+ .join("\n")
659
+ : "absent";
660
+ return { before: state(), state };
661
+ }
662
+
663
+ /** Each prior-root observation, asserted ONCE under its QK by Phase 1c-s (the token is exactly-once). */
664
+ const priorRunKept: Record<string, boolean> = {};
665
+
640
666
  const quiet = (): void => {};
641
667
 
642
668
  {
@@ -897,21 +923,6 @@ const quiet = (): void => {};
897
923
  ok(`pgroup: the GRANDCHILD (pid ${grandchild}) is dead after the group SIGKILL`, dead);
898
924
  fs.rmSync(invocationDir, { recursive: true, force: true });
899
925
  }
900
-
901
- // Stale-snapshot sweep: dead-pid residue is reclaimed, a live runner's dir survives.
902
- {
903
- const tmpRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-sweep-")));
904
- const deadDir = path.join(tmpRoot, "entwurf-qualify-dead1");
905
- fs.mkdirSync(deadDir);
906
- fs.writeFileSync(path.join(deadDir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
907
- const liveDir = path.join(tmpRoot, "entwurf-qualify-live1");
908
- fs.mkdirSync(liveDir);
909
- fs.writeFileSync(path.join(liveDir, "runner.json"), JSON.stringify({ pid: process.pid, startedAt: Date.now() }));
910
- const swept = sweepStaleSnapshots(tmpRoot);
911
- ok("sweep: a dead-pid snapshot dir is reclaimed", swept.includes(deadDir) && !fs.existsSync(deadDir));
912
- ok("sweep: a live runner's snapshot dir survives", fs.existsSync(liveDir));
913
- fs.rmSync(tmpRoot, { recursive: true, force: true });
914
- }
915
926
  } finally {
916
927
  fs.rmSync(fixtureOrigin, { recursive: true, force: true });
917
928
  fs.rmSync(path.dirname(externalTarget), { recursive: true, force: true });
@@ -1262,8 +1273,11 @@ function buildDeclaredOutputFixture(): string {
1262
1273
  logError: quiet,
1263
1274
  tmpRoot,
1264
1275
  });
1265
- const snapshotsLeft = (): string[] => fs.readdirSync(tmpRoot).filter((f) => f.startsWith("entwurf-qualify-"));
1276
+ // This run's own snapshots only: the seeded prior root is not this run's to reclaim.
1277
+ const snapshotsLeft = (): string[] =>
1278
+ fs.readdirSync(tmpRoot).filter((f) => f.startsWith("entwurf-qualify-") && f !== PRIOR_RUN_ROOT);
1266
1279
  const readReceipt = (name: string) => JSON.parse(fs.readFileSync(out(name), "utf8"));
1280
+ const prior = seedPriorRunRoot(tmpRoot);
1267
1281
 
1268
1282
  const green = await runFx(["bash", "gates/kill.sh"], "green.json");
1269
1283
  const gr = readReceipt("green.json");
@@ -1279,6 +1293,7 @@ function buildDeclaredOutputFixture(): string {
1279
1293
  gr.receiptSha256 === green.receiptSha256,
1280
1294
  );
1281
1295
  ok("group run: the snapshot is reclaimed", snapshotsLeft().length === 0);
1296
+ priorRunKept["group green"] = prior.state() === prior.before;
1282
1297
 
1283
1298
  const red = await runFx(["bash", "gates/survive.sh"], "red.json");
1284
1299
  const rr = readReceipt("red.json");
@@ -1286,6 +1301,7 @@ function buildDeclaredOutputFixture(): string {
1286
1301
  "group run: a SURVIVED mutant is still written — red receipt, passed false",
1287
1302
  red.passed === false && rr.result.passed === false && rr.result.mutants[0].verdict === "SURVIVED",
1288
1303
  );
1304
+ priorRunKept["group red"] = prior.state() === prior.before && snapshotsLeft().length === 0;
1289
1305
 
1290
1306
  let refused = "";
1291
1307
  try {
@@ -1405,6 +1421,7 @@ const refuseArgs = (argv: string[], naming: string): void =>
1405
1421
  const receiptOf = (dir: string, file: string | null) =>
1406
1422
  JSON.parse(fs.readFileSync(path.join(tmpRoot, dir, file ?? ""), "utf8"));
1407
1423
 
1424
+ const prior = seedPriorRunRoot(tmpRoot);
1408
1425
  const abc = await collect("abc", [sA, sB, sC]);
1409
1426
  ok(
1410
1427
  "[QK:COLLECT-RED-DOES-NOT-STOP] collect: green / red / green — the red middle does not stop the tail; collection COMPLETE and intact",
@@ -1430,8 +1447,9 @@ const refuseArgs = (argv: string[], naming: string): void =>
1430
1447
  ) &&
1431
1448
  rB.result.control === "ok" &&
1432
1449
  rB.result.mutants[0].verdict === "SURVIVED" &&
1433
- fs.readdirSync(tmpRoot).every((f) => !f.startsWith("entwurf-qualify-")),
1450
+ fs.readdirSync(tmpRoot).every((f) => f === PRIOR_RUN_ROOT || !f.startsWith("entwurf-qualify-")),
1434
1451
  );
1452
+ priorRunKept["collect green/red/green"] = prior.state() === prior.before;
1435
1453
  ok(
1436
1454
  "collect: runId/runAttempt recorded exactly as the environment gave them; requested order kept",
1437
1455
  abc.runId === "123" &&
@@ -1586,6 +1604,47 @@ const refuseArgs = (argv: string[], naming: string): void =>
1586
1604
  }
1587
1605
  }
1588
1606
 
1607
+ // ═══ Phase 1c-s — every real run owns exactly its own snapshot (#130 r3) ═════
1608
+ //
1609
+ // The group (1c-g) and collection (1c-m) fixtures above seeded a prior run's root and
1610
+ // recorded whether it survived their green and red runs. That holds for real runs only while
1611
+ // every real run goes THROUGH the seam, so the production callers are read here too: the full
1612
+ // body and the group receipt each make their snapshot only via withOwnSnapshot, the collection
1613
+ // only via the group receipt, and the library exports no sweep and lists no tmp root. The
1614
+ // retired ambient sweep removed a protected prior snapshot during a release-gate run; the
1615
+ // fixtures cannot see a caller that bypasses the seam, this can. All four observations are
1616
+ // asserted once, under the claim's single token.
1617
+
1618
+ {
1619
+ const read = (rel: string): string => fs.readFileSync(path.join(REPO_DIR, rel), "utf8");
1620
+ const qualifier = read("scripts/check-gate-qualification.ts");
1621
+ // The LAST occurrence is the real header: this block's own needle sits above it.
1622
+ const bodyAt = qualifier.lastIndexOf("// ═══ Phase 2m — several exact-argv groups");
1623
+ const body = qualifier.slice(bodyAt);
1624
+ const receipt = read("scripts/lib/qualification-receipt.ts");
1625
+ const collection = read("scripts/lib/qualification-collection.ts");
1626
+ const lib = read("scripts/lib/mutation-qualify.ts");
1627
+ const libExports = Object.keys(await import("./lib/mutation-qualify.ts"));
1628
+ priorRunKept.wiring =
1629
+ bodyAt > qualifier.indexOf("// ═══ Phase 1c-s") &&
1630
+ countOccurrences(body, "withOwnSnapshot(REPO_DIR, undefined,") === 1 &&
1631
+ countOccurrences(body, "createRepoSnapshot(") === 0 &&
1632
+ countOccurrences(receipt, "withOwnSnapshot(repoDir, opts.tmpRoot,") === 1 &&
1633
+ countOccurrences(receipt, "createRepoSnapshot(") === 0 &&
1634
+ countOccurrences(collection, "runGroupReceipt(") >= 1 &&
1635
+ countOccurrences(collection, "createRepoSnapshot(") === 0 &&
1636
+ countOccurrences(collection, "withOwnSnapshot(") === 0 &&
1637
+ countOccurrences(lib, "createRepoSnapshot(") === 2 &&
1638
+ countOccurrences(lib, "const snap = createRepoSnapshot(originDir, tmpRoot);") === 1 &&
1639
+ countOccurrences(lib, "readdirSync(tmpRoot") === 0 &&
1640
+ !libExports.some((name) => /sweep/i.test(name));
1641
+ const failed = Object.keys(priorRunKept).filter((k) => !priorRunKept[k]);
1642
+ ok(
1643
+ `[QK:QUALIFY-PRIOR-RUN-EVIDENCE-KEPT] a prior run's dead-pid snapshot root keeps every name and byte through a green and a red group run and a green/red/green collection, each run's own snapshot is reclaimed, and the full body and group receipt snapshot only through withOwnSnapshot — failed: ${failed.join(", ") || "none"}`,
1644
+ Object.keys(priorRunKept).length === 4 && failed.length === 0,
1645
+ );
1646
+ }
1647
+
1589
1648
  // ═══ Phase 1c-l — the composite ledger over REAL fixture receipts (#124 P2) ═
1590
1649
  //
1591
1650
  // A late failure, end to end: receipts are written by runGroupReceipt on a fixture
@@ -3342,15 +3401,16 @@ let manifestCount: number;
3342
3401
  "copilot-birth": 19,
3343
3402
  "copilot-launch": 14,
3344
3403
  "pi-launch": 9,
3345
- "pi-floor": 1,
3404
+ "pi-floor": 3,
3346
3405
  "pi-bridge-sender": 4,
3347
3406
  "pi-mcp-bridge": 2,
3348
3407
  "pi-mcp-register": 3,
3408
+ "pi-durable": 47,
3349
3409
  "copilot-receive": 20,
3350
3410
  "entwurf-peers": 1,
3351
3411
  "fresh-call-dispatch": 11,
3352
3412
  "fresh-cut": 3,
3353
- "gate-qualification": 7,
3413
+ "gate-qualification": 8,
3354
3414
  "herdr-placement": 13,
3355
3415
  "herdr-plugin": 25,
3356
3416
  "herdr-plugin-profile": 14,
@@ -3365,7 +3425,7 @@ let manifestCount: number;
3365
3425
  "meta-identity": 4,
3366
3426
  "meta-retire": 4,
3367
3427
  "mux-boundary": 16,
3368
- "mux-fresh-call": 59,
3428
+ "mux-fresh-call": 65,
3369
3429
  "mux-launcher-fence": 8,
3370
3430
  "mux-parent-artifact": 3,
3371
3431
  "pack-install": 3,
@@ -3502,7 +3562,6 @@ if (ARGS.mode === "compose") {
3502
3562
  if (ARGS.mode === "groups") {
3503
3563
  const requested = ARGS.groups === "all" ? allGroupArgv(selected) : ARGS.groups;
3504
3564
  checkRequestedGroups(requested, selected);
3505
- sweepStaleSnapshots(os.tmpdir());
3506
3565
  const c = await collectGroups({
3507
3566
  repoDir: REPO_DIR,
3508
3567
  selected,
@@ -3531,7 +3590,6 @@ if (ARGS.mode === "groups") {
3531
3590
  // prints `[gate-qualification] ok` and never claims the full set.
3532
3591
 
3533
3592
  if (ARGS.mode === "group") {
3534
- sweepStaleSnapshots(os.tmpdir());
3535
3593
  const { passed: groupPassed } = await runGroupReceipt({
3536
3594
  repoDir: REPO_DIR,
3537
3595
  selected,
@@ -3549,17 +3607,12 @@ if (ARGS.mode === "group") {
3549
3607
  const headBefore = originHead(REPO_DIR);
3550
3608
  const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
3551
3609
 
3552
- sweepStaleSnapshots(os.tmpdir());
3553
- const snap = createRepoSnapshot(REPO_DIR);
3554
- console.log(
3555
- `[gate-qualification] snapshot: ${snap.repoDir} (${snap.fileCount} files, origin HEAD ${headBefore.slice(0, 12)})`,
3556
- );
3557
- let report: Awaited<ReturnType<typeof qualifyMutants>>;
3558
- try {
3559
- report = await qualifyMutants(snap, selected, (line) => console.log(line));
3560
- } finally {
3561
- fs.rmSync(snap.baseDir, { recursive: true, force: true });
3562
- }
3610
+ const report = await withOwnSnapshot(REPO_DIR, undefined, (snap) => {
3611
+ console.log(
3612
+ `[gate-qualification] snapshot: ${snap.repoDir} (${snap.fileCount} files, origin HEAD ${headBefore.slice(0, 12)})`,
3613
+ );
3614
+ return qualifyMutants(snap, selected, (line) => console.log(line));
3615
+ });
3563
3616
 
3564
3617
  // Tripwire, not recovery: the runner never writes the origin, so any drift here is
3565
3618
  // an outside writer racing the qualification — evidence cannot be attributed.
@@ -205,12 +205,13 @@ const {
205
205
  COMPONENT_STATES,
206
206
  } = mod;
207
207
 
208
- /** The pi pin package.json declares — the SSOT `run.sh pi_supported_range` derives its range from. */
208
+ /** The pi pin package.json declares (an exact production dependency since #130) — the SSOT
209
+ * `run.sh pi_supported_range` derives its range from. */
209
210
  const PI_PIN = (
210
211
  JSON.parse(fs.readFileSync(path.join(REPO, "package.json"), "utf8")) as {
211
- devDependencies: Record<string, string>;
212
+ dependencies: Record<string, string>;
212
213
  }
213
- ).devDependencies["@earendil-works/pi-coding-agent"];
214
+ ).dependencies["@earendil-works/pi-coding-agent"];
214
215
 
215
216
  /** A `pi` stand-in that reports `version` and nothing else — enough for a `--version` floor probe. */
216
217
  function fakePi(dir: string, name: string, version: string): string {
@@ -364,7 +365,7 @@ function ledgerWith(
364
365
  {
365
366
  const env = world("pi-floor");
366
367
  const bin = path.join(env.HOME as string, "bin");
367
- // The supported range is DERIVED from the package.json devDep pin (run.sh
368
+ // The supported range is DERIVED from the package.json production dependency pin (run.sh
368
369
  // `pi_supported_range`), so the fixtures are derived from it too — a hardcoded "0.85.1" here
369
370
  // would silently stop being out-of-range the day the pin moves past it.
370
371
  const [maj, min] = PI_PIN.split(".").map(Number);