@twinsearth/w2m-dsh-plugin 0.37.0 → 0.39.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/CHANGELOG.md CHANGED
@@ -4,6 +4,105 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.39.0] — 2026-10-15
8
+
9
+ **The orphan gate: a module nothing reaches now fails the pre-flight unless a reason is written down.**
10
+
11
+ ### What checking the remaining loops found — and it corrects a claim I made
12
+
13
+ 0.36.0's classifier reported 86 closed loops and **could not say which were defects**, so it printed and
14
+ exited 0. This sorts them, and the result is uncomfortable and precise:
15
+
16
+ **Eight orphan modules, 72 exports, every one of them written in this span** — and **two were described as
17
+ delivered** while nothing ran them.
18
+
19
+ `approval-gate.mjs` **is** wired: `tools.mjs` imports it, constructs it, and calls `mayRaise` on the raise path.
20
+ **`field-authority.mjs` is not.** Reading the tree rather than my own changelog:
21
+
22
+ ```
23
+ grep 'field-authority' src/ → comments and documentation only. Nothing imports it.
24
+ ```
25
+
26
+ **0.29.0 said "the connection now exists". It exists inside that module and nowhere else.** That is now
27
+ recorded as a correction in `scripts/accepted-orphans.mjs`, in the entry that keeps it on the list.
28
+
29
+ ### A dead branch, found by the test that could not be written
30
+
31
+ I added **second-order reachability** so that a module reached *through* another could be called a library
32
+ rather than an orphan. The test for it failed, and the reason is arithmetic:
33
+
34
+ > a row is `closed-loop` **only when no source file imports anything from it at all** — which is exactly what
35
+ > the `sibling-imported` class already excludes. **So there is never a first-order importer for a second-order
36
+ > walk to start from**, and the library case **cannot arise**.
37
+
38
+ Measured: **72 orphan, 0 library, 14 cli** — and **the zero was a tautology, not a finding.** The library
39
+ bucket is removed rather than printed as a permanent zero, and `LOOP_KIND.LIBRARY_UNREACHABLE` records *why*
40
+ so the next reader does not re-derive the idea. **Being a library is a claim about intent, and the import
41
+ graph cannot carry it — so the claim is made where it can be checked.**
42
+
43
+ ### `scripts/accepted-orphans.mjs`, and why a list rather than a rule
44
+
45
+ Each accepted orphan names a **reason**, and **what would remove it**. `library` is a design choice;
46
+ `interface-unrouted` **is the defect**, kept only because removing it needs a decision about *where* it should
47
+ be reached from.
48
+
49
+ The gate is `checkOrphans` in the pre-flight: **a closed loop is red unless its file is on that list.** The
50
+ eight known ones are decisions somebody made; **a ninth fails the run and forces the same conversation.**
51
+ A stale entry — one that names a file no longer an orphan — also fails, so the list cannot quietly claim a
52
+ problem that is gone.
53
+
54
+ Four of the eight are marked `interface-unrouted` and said so plainly: `health` (the sensor the user asked for
55
+ by name), `log-bounds` (the answer to the 40× log growth, shipped in 0.12.0 and called by nothing), `storage`,
56
+ and `field-authority`.
57
+
58
+ ### My own error, the same one as two rounds ago
59
+
60
+ `await` in a **non-async callback**, caught by `node --check` — **the second time in this span.** The fix is
61
+ the same both times: import it up front rather than reaching for it where it is not allowed.
62
+
63
+ **13 new tests, 0 failures.**
64
+
65
+ ## [0.38.0] — 2026-10-15
66
+
67
+ **A seven-phase council run on MacMini — real-machine evidence for the tool that had never touched one.**
68
+
69
+ 0.37.0's `w2m_council` was the only substantial addition in this span that had **never run anywhere but this
70
+ laptop.** It now has a measurement, and the exit code came from the machine:
71
+
72
+ ```
73
+ OK phases=7 outcome=ship dissent=1 reproduced=true exit 0
74
+ ```
75
+
76
+ Driven through `node -e` on MacMini against its own checkout at 0.37.0: the council was convened, roles
77
+ assigned, two arguments recorded, two weighted votes cast, the outcome fixed, the decision sealed, and the
78
+ sealed decision **read back after the council stopped being advanceable** — with its dissent, its reason, and a
79
+ `recount` that reproduced the outcome from the stored ballots. **Every one of those was asserted before the
80
+ process was allowed to exit `0`**, so this is not a "did it load" smoke test.
81
+
82
+ ### The same run answered a question that had only been a hypothesis for four rounds
83
+
84
+ **v4.10.0's council works on MacMini because it reaches for nothing that machine lacks.** `council.mjs` imports
85
+ `node:crypto` and `./authority.mjs`, and neither needs `node_modules`. The eight suites that failed there in
86
+ 0.32.0 therefore failed on **what their fixtures reach for**, not on the modules under test — **a measurement
87
+ now, where four earlier releases recorded a guess as a guess.**
88
+
89
+ It also sharpens the shape of that failure: a suite whose *subject* has no dependencies still fails if the
90
+ *suite* imports one, and **from an exit code alone the two are indistinguishable.**
91
+
92
+ ### The sixth time I exceeded the ceiling
93
+
94
+ The update dispatch — download a release tarball and extract it — **expired**. Measured again, and the number
95
+ has not moved: the agent's lease survives roughly **50 seconds** (`heartbeatIntervalMs 10s × missedHeartbeats 2
96
+ + leaseGraceMs 30s`), and an expired lease carries **no exit code at all**, so the work was lost rather than
97
+ reported as failed.
98
+
99
+ **What matters is what happened next:** rather than retrying the download, a **three-line probe** established
100
+ that the extraction had in fact completed — **the partial run had succeeded and only the report was lost.** A
101
+ retry would have spent another dispatch redoing finished work, and could have raced the `tar` it had left
102
+ half-written.
103
+
104
+ **No new tests.** What changed is a measurement, and a hypothesis that stopped being one.
105
+
7
106
  ## [0.37.0] — 2026-10-15
8
107
 
9
108
  **`council.mjs` is wired. v4.10.0's committee governance is reachable, and its dissent arrives with its decision.**
@@ -68,16 +68,24 @@ declared its own `export const AUTHORITY_MAX = 100` instead of importing the bou
68
68
  **Two copies of a limit that nothing can keep in step, failing silently** — raise the real maximum and this
69
69
  module keeps refusing `101` while naming the wrong number as authoritative. It now imports it.
70
70
 
71
- ### `v4.10.0`'s council is built and not routed — found here, not claimed before
71
+ ### `v4.10.0`'s council was BUILT AND NOT ROUTED — **fixed in 0.37.0**
72
72
 
73
73
  `reachability-classify` reported `src/agent/council.mjs` as a closed loop, and reading it to check that
74
74
  confirmed the finding: **275 lines implementing committee governance, imported by nothing but its own suite.**
75
75
  `v4.10.0` is marked *shipped in 0.11.0* in `ROADMAP.md`, and **0.30.0 verified its three falsifying tests are
76
76
  asserted by name** — which they are. **What was never checked is whether anything routes a question to it.**
77
77
 
78
- **This is the same defect as `field-authority` and `approval-gate`, in a third module**, and it is the one the
79
- reachability work was built to find. It is recorded here as **BUILT, NOT ROUTED** rather than described as
80
- delivered, and routing it needs a tool surface and a session — not a line of code.
78
+ **This was the same defect as `field-authority` and `approval-gate`, in a third module** — the one the
79
+ reachability work was built to find.
80
+
81
+ **`w2m_council` now routes it.** The state lives in `createCouncilSessions` because a council walks **seven
82
+ phases across many calls** and one held inside a single invocation **could never finish**. Every read of a
83
+ decision returns the **dissent with its reasons** and a **`recount`**, so v4.10.0's third falsifying test —
84
+ *"a decision with recorded dissent is retrievable with the dissent"* — holds **after the council that produced
85
+ it is gone**, which is a property of the session rather than of the decision. Both are tested.
86
+
87
+ **A tie is still refused, not broken**, and the tool does not paper over it: a split council is the one case
88
+ where the council exists to escalate to a person.
81
89
 
82
90
  ---
83
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "DSH: Windows2MacOS — drive the same project on online Windows and macOS machines from one DeepSeek account, across networks and regions. P2P hole punching is the default path (relay + shared server as rendezvous, ledger and fallback), with a bundled STUN responder, Tailscale/public-VPS/tunnel shapes, Rabbit relay + per-machine Localside + eight DSH tools (replicate / split), nine aggregate verdicts and signed requests.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -113,13 +113,13 @@
113
113
  }
114
114
  },
115
115
  "scripts": {
116
- "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
116
+ "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
117
117
  "test:e2e": "node --test --test-force-exit test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/p2p-agent.test.mjs",
118
118
  "test:relay": "node --test --test-force-exit test/relay.test.mjs",
119
119
  "test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
120
120
  "test:tools": "node --test --test-force-exit test/tools.test.mjs",
121
121
  "test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
122
- "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
122
+ "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
123
123
  "pack": "node scripts/pack.mjs",
124
124
  "verify:join": "node scripts/verify-join.mjs",
125
125
  "lint": "eslint .",
@@ -153,7 +153,8 @@
153
153
  "test:reachability": "node --test --test-force-exit test/reachability-check.test.mjs",
154
154
  "reachability": "node scripts/reachability-check.mjs",
155
155
  "reachability:classify": "node scripts/reachability-classify.mjs",
156
- "test:council-session": "node --test --test-force-exit test/council-session.test.mjs"
156
+ "test:council-session": "node --test --test-force-exit test/council-session.test.mjs",
157
+ "orphans": "node --test --test-force-exit test/accepted-orphans.test.mjs"
157
158
  },
158
159
  "keywords": [
159
160
  "dsh-bundle",
@@ -202,7 +202,7 @@ const MAX_STAGES = 16;
202
202
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
203
203
  * overrides it at runtime for tests and packagers.
204
204
  */
205
- const PLUGIN_VERSION = '0.37.0';
205
+ const PLUGIN_VERSION = '0.39.0';
206
206
 
207
207
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
208
208
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;