@twinsearth/w2m-dsh-plugin 1.0.11 → 1.0.13

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
@@ -2,6 +2,104 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## [1.0.13] — 2026-10-17
6
+
7
+ **Concurrent publishing is verified safe on one relay, and the reason it is safe is stated so a future change can
8
+ falsify it.**
9
+
10
+ ### What v1.0.12 recorded as unverified, and is now verified
11
+
12
+ v1.0.12 closed the end-to-end gap **one machine at a time** and wrote concurrency down as open, with a specific
13
+ worry: the blob store keeps its ownership index as a read-modify-write, and a lost update there would leave **a stored
14
+ blob with no owner** -- readable by nobody, including the fleet that wrote it.
15
+
16
+ ### Why it turned out to be safe, stated as a property rather than a hope
17
+
18
+ `RabbitState.putFleetBlob` and `Persistence.putBlob` **do no awaiting**: every filesystem call in that path is the
19
+ synchronous `fs` API. The request handler awaits the body first, and after that the whole read-mutate-write runs to
20
+ completion inside one Node job. **On a single-threaded event loop that makes each publish atomic with respect to
21
+ every other request on the same relay process.**
22
+
23
+ **That reasoning is the thing the tests falsify rather than assume.** If any part of that path becomes asynchronous,
24
+ they fail with a lost index entry -- which is much better than finding it in a fleet.
25
+
26
+ ### What is verified
27
+
28
+ Four tests in `test/fleet-concurrency.test.mjs`:
29
+
30
+ * **ten publishes fired together** leave **ten** ownership entries, and every blob still readable and still owned -- a
31
+ lost update would show up as a 404;
32
+ * the **latest pointer names a document that was actually written** (not a torn value) and that document resolves;
33
+ * **two fleets publishing at the same moment stay separate** -- each fleet's pointer names its own document and the
34
+ index attributes each blob to the right fleet;
35
+ * the pointer is **stable once the writers stop** -- reading it twice gives the same hash and timestamp.
36
+
37
+ Full suite **2378 tests in 92 files, 2364 pass, 0 fail, 14 skip**. Pre-flight **GREEN, 15 of 15 checks**.
38
+
39
+ ### The limit of this claim, which matters more than the claim
40
+
41
+ **One relay process.** The design is one relay per fleet. **Two processes sharing one state directory would need real
42
+ file locking**, and nothing here provides it. "Concurrency is tested" must not be read as "any topology is safe".
43
+
44
+ Also still unverified: **two machines merging while a third publishes.** The merge itself is a pure read of local bytes
45
+ plus one fetched blob, so it has no shared state to corrupt -- but that is reasoning, not a test, and it is recorded
46
+ as such.
47
+
48
+ ## [1.0.12] — 2026-10-17
49
+
50
+ **The five fleet-memory actions are verified end to end, against a real relay, a real profile and a real
51
+ `fleet-memory.md` -- and the first attempt found a real defect.**
52
+
53
+ ### What was unverified until now
54
+
55
+ `publish`, `pull`, `latest`, `diff` and `merge` had each been shipped marked **UNVERIFIED**. The merge *rule* was
56
+ proven exhaustively as a pure function, and the relay *routes* were proven against a real relay -- **but nothing had
57
+ ever executed the tool surface itself.** A caller with no execution is indistinguishable from a working one, which is
58
+ the same gap this project keeps restating in a different shape.
59
+
60
+ ### The defect the end-to-end test found on its first run
61
+
62
+ `latest` reported `published_by: null` for a publication whose author the relay knew perfectly well.
63
+
64
+ `RabbitState.putFleetBlob` accepted a `publishedBy` argument that **nothing ever supplied**: the pointer store took
65
+ it, defaulted it to `null`, and the field looked implemented. Fixed by filling it from **the authenticated device's
66
+ `machine_id`, never from the request body** -- for the same reason the P2P announce route reads `machine_id` off the
67
+ token: a caller that could name itself in the body could attribute a publication to a machine that never made it.
68
+
69
+ **No unit test could have caught this**, because the unit under test was the pointer store, and it was behaving
70
+ exactly as written. The bug lived in the seam between two correct components, which is what an end-to-end test is for.
71
+
72
+ ### What the test exercises, in the order a machine would
73
+
74
+ A real relay on an ephemeral port and a real profile, then:
75
+
76
+ 1. **`publish`** — returns a 64-hex hash and the byte count.
77
+ 2. **`latest`** — discovers that hash **without being told it**, and reports the publishing machine.
78
+ 3. **`pull latest`** — fetches the bytes, re-hashes them, and they round-trip **exactly**.
79
+ 4. **`diff`** — on an unchanged document: `same: true`, no difference, **and the local file untouched**.
80
+ 5. the fleet's document is replaced out of band through the relay's own route;
81
+ 6. **`diff`** — now reports the divergence, and the local file is **still untouched**;
82
+ 7. **`merge`** — applies the declared rule (neither side declares a repair, so the **default layer** applies and the
83
+ newer publication wins), records the decision with a reason, carries the winning value into the merged text, and
84
+ **writes nothing to the local document.**
85
+
86
+ **`wrote_anything: false` is asserted three times**, because "these actions never edit the file they read" is the
87
+ property that makes it safe to let a network round trip participate at all.
88
+
89
+ ### Verified
90
+
91
+ - the end-to-end suite: 1 test, passing, covering all five actions;
92
+ - `test/fleet-merge.test.mjs`: 13 tests over the rule itself;
93
+ - full suite: **2374 tests in 91 files, 2360 pass, 0 fail, 14 skip**;
94
+ - pre-flight **GREEN, 15 of 15 checks**.
95
+
96
+ ### NOT verified, even now
97
+
98
+ **Concurrency.** Everything here is one machine at a time. Two machines publishing and merging simultaneously is
99
+ untested, and the pointer has no compare-and-swap, so the last writer to the pointer wins -- which is **the same rule
100
+ as the default layer**, but that is a coincidence rather than a guarantee. Recorded so the next person does not read
101
+ "end to end" as "under contention".
102
+
5
103
  ## [1.0.11] — 2026-10-17
6
104
 
7
105
  **`w2m_memory {"action":"merge"}`: merge this machine's collective memory with the fleet's current blob, under the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -117,13 +117,13 @@
117
117
  }
118
118
  },
119
119
  "scripts": {
120
- "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.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/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.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/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
120
+ "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.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/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.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/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
121
121
  "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",
122
122
  "test:relay": "node --test --test-force-exit test/relay.test.mjs",
123
123
  "test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
124
124
  "test:tools": "node --test --test-force-exit test/tools.test.mjs",
125
125
  "test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
126
- "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.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/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.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/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
126
+ "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.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/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.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/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
127
127
  "pack": "node scripts/pack.mjs",
128
128
  "verify:join": "node scripts/verify-join.mjs",
129
129
  "lint": "eslint .",
@@ -138,6 +138,8 @@
138
138
  "test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
139
139
  "test:fleet-sync": "node --test --test-force-exit test/fleet-sync.test.mjs",
140
140
  "test:fleet-merge": "node --test --test-force-exit test/fleet-merge.test.mjs",
141
+ "test:fleet-memory-e2e": "node --test --test-force-exit test/fleet-memory-e2e.test.mjs",
142
+ "test:fleet-concurrency": "node --test --test-force-exit test/fleet-concurrency.test.mjs",
141
143
  "test:health": "node --test --test-force-exit test/health.test.mjs",
142
144
  "test:storage": "node --test --test-force-exit test/storage.test.mjs",
143
145
  "test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "twinsearth-w2m-dsh",
4
- "version": "1.0.11",
4
+ "version": "1.0.13",
5
5
  "description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -211,7 +211,7 @@ const MAX_STAGES = 16;
211
211
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
212
212
  * overrides it at runtime for tests and packagers.
213
213
  */
214
- const PLUGIN_VERSION = '1.0.11';
214
+ const PLUGIN_VERSION = '1.0.13';
215
215
 
216
216
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
217
217
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -560,7 +560,20 @@ export class RabbitState {
560
560
  }
561
561
  const scope = this.scopeBlobFleet(device);
562
562
  if (scope.ok !== true) return { ok: false, reason: scope.reason, detail: scope.text };
563
- return this._persist.putBlob(bytes, { fleetId: scope.fleetId });
563
+ /**
564
+ * v1.0.12: `publishedBy` comes from **the authenticated device, never from the request body.**
565
+ *
566
+ * The relay knows which device is writing; the request does not get a say, for the same reason `machine_id` is
567
+ * read off the token in the P2P announce route -- a caller that could name itself in the body could attribute a
568
+ * publication to a machine that never made it.
569
+ *
570
+ * An end-to-end test caught this field being `null`: the pointer store accepted a `publishedBy` argument that
571
+ * nothing supplied, so `latest` reported `published_by: null` for a publication whose author was right there.
572
+ */
573
+ return this._persist.putBlob(bytes, {
574
+ fleetId: scope.fleetId,
575
+ publishedBy: typeof device?.machine_id === 'string' ? device.machine_id : null,
576
+ });
564
577
  }
565
578
 
566
579
  /**