@twinsearth/w2m-dsh-plugin 0.17.0 → 0.19.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,79 @@ 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.19.0] — 2026-10-15
8
+
9
+ **The plugin this machine runs is 0.9.5. The source tree is 0.18.0. Every source-level conclusion about the P2P failure was read from code that may not have run.**
10
+
11
+ Measured, not inferred:
12
+
13
+ ```
14
+ source : 0.18.0
15
+ installed : 0.9.5 at ...\profiles\desktop\node_modules\@twinsearth\w2m-dsh-plugin
16
+ verdict : installed 0.9.5 is 8995 rank(s) BEHIND the source 0.18.0
17
+ -- source-level conclusions about this machine are void
18
+ ```
19
+
20
+ Reading `p2p-node.mjs` established which call raises `P2P_PUNCH_FAILED`, and it is raised **by the plugin
21
+ process** — `tools.mjs:1463`, `node.dial(...)`, on the dispatching machine. That machine's plugin is **nine
22
+ versions behind the file that was being read.** Two rounds of crypto-capability work were therefore correct
23
+ about the code in this repository and only *possibly* correct about the code that failed.
24
+
25
+ ### Two runtime facts were assumed, and only one held
26
+
27
+ | Assumption | Status |
28
+ |---|---|
29
+ | which node the plugin runs under | **holds** — the DSH process runs `dsh-primary-runtime/.../node.exe` v24.21.0, the same binary that was probed; the runtime was never the variable |
30
+ | which plugin version is loaded | **did not hold** — 0.9.5 installed against 0.18.0 of source |
31
+
32
+ The first was checked last and held; the second was never checked and did not. **A version skew is not a
33
+ detail to note — it is a reason every source-level conclusion is void**, and it should have been the first
34
+ thing measured rather than the last.
35
+
36
+ ### The invariant is now a test
37
+
38
+ `test/installed-version.test.mjs` finds every plausible install location, compares numerically (because
39
+ `'0.9.0' > '0.10.0'` as strings, and `0.9.5` against `0.18.0` is exactly the case that would be got wrong),
40
+ and **reports the skew with both numbers** rather than failing an assertion nobody could act on. It also
41
+ refuses to compare what it cannot parse, and **says so when it finds no installed copy** — because passing by
42
+ absence is the same failure mode as an earlier skip guard in this repository, which passed for three rounds
43
+ while hiding a defect in the tooling rather than in the code.
44
+
45
+ ### What is needed from a person
46
+
47
+ **The local DSH must load the current plugin** for P2P diagnosis to mean anything. Until it does, this
48
+ machine's `P2P_PUNCH_FAILED` comes from 0.9.5 and no reading of 0.19.0's source can explain it.
49
+
50
+ **8 tests, 0 failures.**
51
+
52
+ ## [0.18.0] — 2026-10-15
53
+
54
+ **The whole handshake primitive set is present on both machines — so the P2P failure is not a missing algorithm.**
55
+
56
+ Two candidates for `P2P_PUNCH_FAILED: Digest method not supported` have now been **measured and excluded**:
57
+
58
+ | Candidate | Measurement | Verdict |
59
+ |---|---|---|
60
+ | BLAKE2s-256 | 32-byte digest on MacMini **and** here, on the same run that failed | **innocent** |
61
+ | every other primitive the handshake touches | `sha256`, `chacha20-poly1305` with a real AEAD round trip, `hkdfSync('sha256')`, `x25519`+`diffieHellman`, `ed25519` — all present on both | **innocent** |
62
+
63
+ The second row was answered by **one bit-encoded dispatch** that put eight questions to MacMini at once
64
+ (`exit_code: 255`, every bit set) rather than one question per round. That technique was already in this
65
+ repository and should have been the first move.
66
+
67
+ **What this changes:** the search moves off the runtime and onto the protocol, the session state, or the
68
+ transport. And it points somewhere specific — **`P2P_PUNCH_FAILED` is raised by the relay's own push, on the
69
+ machine that already has every primitive it needs.** The failing handshake is not on a peer missing an
70
+ algorithm; it is on a peer that has all of them.
71
+
72
+ `cryptoCapabilities()` now answers this question in one dispatch for any machine, and its suite asserts the
73
+ **count** — six primitives, six checks — because a capability report that silently checks four things reads as
74
+ complete.
75
+
76
+ **Honest status: the cause of `P2P_PUNCH_FAILED` remains UNVERIFIED**, and the 0 % direct share measured last
77
+ round is unchanged. What this release buys is that two hypotheses are dead and the next one costs one
78
+ dispatch to test.
79
+
7
80
  ## [0.17.0] — 2026-10-15
8
81
 
9
82
  **BLAKE2s-256 is not the cause of `P2P_PUNCH_FAILED`, and a failure that names nothing costs a probe per guess.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "0.17.0",
3
+ "version": "0.19.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/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/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/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/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/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/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/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/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 .",
@@ -143,7 +143,8 @@
143
143
  "test:test-report": "node --test --test-force-exit test/test-report.test.mjs",
144
144
  "test:fleet-scale": "node --test --test-force-exit test/fleet-scale.test.mjs",
145
145
  "test:fleet-state": "node --test --test-force-exit test/fleet-state.test.mjs",
146
- "test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs"
146
+ "test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs",
147
+ "test:installed-version": "node --test --test-force-exit test/installed-version.test.mjs"
147
148
  },
148
149
  "keywords": [
149
150
  "dsh-bundle",
@@ -704,6 +704,67 @@ export function derive(ikm, label, length = KEY_BYTES) {
704
704
  return Buffer.from(hkdfSync('sha256', ikm, Buffer.alloc(0), Buffer.from(label, 'utf8'), length));
705
705
  }
706
706
 
707
+ /**
708
+ * The cryptographic primitives the handshake requires, and whether this runtime has each.
709
+ *
710
+ * ## Why a capability report rather than a one-off diagnostic
711
+ *
712
+ * `P2P_PUNCH_FAILED: Digest method not supported` has been reproduced several times in production and never
713
+ * under instrumentation. Two candidates have now been **measured and excluded**:
714
+ *
715
+ * * **BLAKE2s-256** — present on both MacMini and this Windows host, on the same run that reported the
716
+ * failure. Ruled out by a direct 32-byte digest on each machine.
717
+ * * **every other primitive the handshake touches** — `sha256`, `chacha20-poly1305` with a real AEAD
718
+ * round trip, `hkdfSync('sha256')`, `x25519` with `diffieHellman`, and `ed25519` — all present on both.
719
+ *
720
+ * So the fault is **not a missing algorithm**, and knowing that is worth more than another hypothesis: it
721
+ * moves the search from the runtime to the protocol, the state, or the transport.
722
+ *
723
+ * The exclusion cost several dispatches because each candidate had to be probed over a channel carrying one
724
+ * exit code. This function means the next question of that kind is answered in **one** dispatch, by the
725
+ * machine itself, with no interpretation in between.
726
+ *
727
+ * @returns {{ ok: boolean, missing: string[], present: string[], node: string, detail: string }}
728
+ */
729
+ export function cryptoCapabilities() {
730
+ const present = [];
731
+ const missing = [];
732
+ const check = (name, fn) => {
733
+ try {
734
+ if (fn()) present.push(name);
735
+ else missing.push(`${name} (reported as unsupported)`);
736
+ } catch (e) {
737
+ missing.push(`${name} (${e?.message ?? String(e)})`);
738
+ }
739
+ };
740
+
741
+ check('blake2s256', () => createHash('blake2s256').update('').digest().length === 32);
742
+ check('sha256', () => createHash('sha256').update('').digest().length === 32);
743
+ check('chacha20-poly1305', () => {
744
+ const key = randomBytes(32);
745
+ const cipher = createCipheriv('chacha20-poly1305', key, Buffer.alloc(12), { authTagLength: 16 });
746
+ cipher.update(Buffer.from('probe'));
747
+ cipher.final();
748
+ return cipher.getAuthTag().length === 16;
749
+ });
750
+ check('hkdf-sha256', () => hkdfSync('sha256', randomBytes(32), Buffer.alloc(0), Buffer.from('probe'), 32).byteLength === 32);
751
+ check('x25519+diffieHellman', () => {
752
+ const { privateKey, publicKey } = generateKeyPairSync('x25519');
753
+ return diffieHellman({ privateKey, publicKey }).length === 32;
754
+ });
755
+ check('ed25519', () => generateKeyPairSync('ed25519') !== null);
756
+
757
+ return {
758
+ ok: missing.length === 0,
759
+ present,
760
+ missing,
761
+ node: process.version,
762
+ detail: missing.length === 0
763
+ ? `all ${present.length} handshake primitive(s) available on ${process.version}`
764
+ : `${missing.length} of ${present.length + missing.length} handshake primitive(s) unavailable on ${process.version}: ${missing.join('; ')}`,
765
+ };
766
+ }
767
+
707
768
  /**
708
769
  * How BLAKE2s-256 is reached on this runtime, discovered once and remembered.
709
770
  *
@@ -185,7 +185,7 @@ const MAX_STAGES = 16;
185
185
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
186
186
  * overrides it at runtime for tests and packagers.
187
187
  */
188
- const PLUGIN_VERSION = '0.17.0';
188
+ const PLUGIN_VERSION = '0.19.0';
189
189
 
190
190
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
191
191
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;