@twinsearth/w2m-dsh-plugin 1.2.6 → 1.3.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.
@@ -1,29 +1,30 @@
1
- {
2
- "name": "twinsearth-w2m",
3
- "owner": {
4
- "name": "TwinsEarth"
5
- },
6
- "metadata": {
7
- "description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
8
- "version": "1.2.6"
9
- },
10
- "plugins": [
11
- {
12
- "name": "w2m-fleet",
13
- "source": "./",
14
- "description": "Read fleet state and dispatch commands across the machines registered to one account. Read-only MCP surface: the five exposed tools cannot change anything.",
15
- "version": "1.2.6",
16
- "author": {
17
- "name": "TwinsEarth"
18
- },
19
- "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
20
- "repository": "https://github.com/TwinsEarth/dsh-windows2macos",
21
- "license": "MIT",
22
- "keywords": [
23
- "fleet",
24
- "remote-execution",
25
- "mcp"
26
- ]
27
- }
28
- ]
29
- }
1
+ {
2
+ "name": "twinsearth-w2m",
3
+ "owner": {
4
+ "name": "TwinsEarth"
5
+ },
6
+ "metadata": {
7
+ "description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
8
+ "version": "1.3.0"
9
+ },
10
+ "plugins": [
11
+ {
12
+ "name": "w2m-fleet",
13
+ "source": "./",
14
+ "description": "Read fleet state and dispatch commands across the machines registered to one account. Read-only MCP surface: the five exposed tools cannot change anything.",
15
+ "version": "1.3.0",
16
+ "author": {
17
+ "name": "TwinsEarth"
18
+ },
19
+ "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
20
+ "repository": "https://github.com/TwinsEarth/dsh-windows2macos",
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "fleet",
24
+ "remote-execution",
25
+ "mcp"
26
+ ]
27
+ }
28
+ ],
29
+ "version": "1.3.0"
30
+ }
@@ -1,29 +1,29 @@
1
- {
2
- "name": "w2m-fleet",
3
- "description": "Drive the same project on online Windows, macOS and Linux machines from one account: read fleet state, dispatch commands and read results. The MCP surface is deliberately read-only.",
4
- "version": "1.2.6",
5
- "author": {
6
- "name": "TwinsEarth"
7
- },
8
- "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
9
- "repository": "https://github.com/TwinsEarth/dsh-windows2macos",
10
- "license": "MIT",
11
- "keywords": [
12
- "fleet",
13
- "remote-execution",
14
- "windows",
15
- "macos",
16
- "linux",
17
- "mcp",
18
- "claude-code",
19
- "codex"
20
- ],
21
- "mcpServers": {
22
- "w2m-fleet": {
23
- "command": "node",
24
- "args": [
25
- "${CLAUDE_PLUGIN_ROOT}/bin/w2m-mcp.mjs"
26
- ]
27
- }
28
- }
29
- }
1
+ {
2
+ "name": "w2m-fleet",
3
+ "description": "Drive the same project on online Windows, macOS and Linux machines from one account: read fleet state, dispatch commands and read results. The MCP surface is deliberately read-only.",
4
+ "version": "1.3.0",
5
+ "author": {
6
+ "name": "TwinsEarth"
7
+ },
8
+ "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
9
+ "repository": "https://github.com/TwinsEarth/dsh-windows2macos",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "fleet",
13
+ "remote-execution",
14
+ "windows",
15
+ "macos",
16
+ "linux",
17
+ "mcp",
18
+ "claude-code",
19
+ "codex"
20
+ ],
21
+ "mcpServers": {
22
+ "w2m-fleet": {
23
+ "command": "node",
24
+ "args": [
25
+ "${CLAUDE_PLUGIN_ROOT}/bin/w2m-mcp.mjs"
26
+ ]
27
+ }
28
+ }
29
+ }
package/CHANGELOG.md CHANGED
@@ -7,6 +7,203 @@
7
7
 
8
8
  All notable changes to this project are documented here.
9
9
 
10
+ ## [1.3.0] — 2026-10-21
11
+
12
+ The first stable release of the collaboration line. It consolidates the four betas that followed 1.2.7 and changes no protocol in a
13
+ way that a 1.2.7 peer cannot ignore; every addition is additive and named.
14
+
15
+ ### Data plane
16
+
17
+ **A reduce phase you can actually run.** `w2m_reduce` dispatches a reduce task only from complete data: it refuses unless every
18
+ declared shard reported as a blob and the named reducer reported (`W2M_REDUCE_NOT_READY` with the missing indexes), never picks a
19
+ reducer, and refuses inline inputs that cannot be carried to another machine. The relay validates, stores, ledgers and offers
20
+ `reduce_of` + `reduce_inputs`; the agent fetches those content-addressed blobs before the reduce runs and exposes
21
+ `W2M_REDUCE_INPUTS_DIR` / `W2M_REDUCE_MANIFEST` / `W2M_REDUCE_OF`; a failed fetch refuses the whole task rather than reducing a
22
+ partial fleet.
23
+
24
+ **A suggestion that dispenses no action.** `w2m_wait` carries `reduce_suggestion`: ready only when every declared shard reported and
25
+ a reducer was named, the missing indexes otherwise, the candidate machines, and the literal next step. It is derived, never stored,
26
+ and it dispatches nothing.
27
+
28
+ **Message ids survive a restart, artifacts are one size everywhere.** Chat ids carry a random per-process relay epoch
29
+ (`msg-<epoch>-<seq>`) so a restart cannot recycle an id a client persisted; the blob transport frame is 16 MiB, matching the agent's
30
+ dataflow hard cap, instead of 1 MiB against it.
31
+
32
+ ### Collaboration
33
+
34
+ **The panel shows the scheduler's own buckets.** The fleet-chat panel renders 准备 / 进行 / 等待 / 阻塞 counts beside the relay's task
35
+ list, derived from the relay's `status` and `waiting_on`; read-only, no dispatch.
36
+
37
+ **Conflicts name their next step.** `w2m_memory append` returns `merge_hint` with a 409: `re-read-then-append`, `behind_by`, the
38
+ unseen count, the sample size and the literal instruction to re-read, compare and append against the current revision -- never an
39
+ automatic merge.
40
+
41
+ **Decisions leave a trail.** `w2m_council fleet-decide` reports `trail` (`appended` / `refused` / `error` / `not-written`), and the
42
+ test asserts that status instead of implying a record exists.
43
+
44
+ ### Verified
45
+
46
+ Gate GREEN twice, 15 of 15: 2710 tests / 2696 pass / 0 fail / 14 skip in 133 files. Two-machine live runs over the real relay cover
47
+ the turn state machine (ask/await over SSE with a refusal), dependency scheduling, and the missing-shard refusal, plus the panel and
48
+ proxy behaviour above.
49
+
50
+ ### Known limits, stated
51
+
52
+ - `pipeline` keeps `index_total` at 1; blob map results come from plain commands above the inline cap.
53
+ - The reduce suggestion is advisory: the caller dispatches the reduce (this release does not let the relay decide to run a command).
54
+ - The composer panel does not render the board buckets on a host without the Connection service; the tools still work.
55
+
56
+ ## [1.2.11-beta.1] — 2026-10-21
57
+
58
+ A beta by policy: GitHub prerelease only, never npm.
59
+
60
+ ### The task board in the composer panel
61
+
62
+ The fleet-chat panel already listed the relay's tasks; it now also shows the four buckets the scheduler uses — 准备 / 进行 / 等待 / 阻塞 — derived from the relay's own `status` and `waiting_on`, never from a classification of its own. Read-only: the panel dispatches nothing.
63
+
64
+ ### Merge hint on memory conflicts
65
+
66
+ `w2m_memory append` returns `merge_hint` with a 409: state `re-read-then-append`, `behind_by`, the number of unseen records, the sample size, and the literal next step (re-read, compare, append against the current revision — do not merge unseen records). Pure function, no automatic merge.
67
+
68
+ ### Council decisions leave a trail
69
+
70
+ `w2m_council fleet-decide` reports `trail` (`appended` / `refused` / `error` / `not-written`) instead of implying a record exists; the test now asserts that status.
71
+
72
+ Verified: gate GREEN twice, 2704 tests / 2690 pass / 0 fail / 14 skip in 132 files; client-bundle 25/25 after the board line; the merge-hint and trail tests are new.
73
+
74
+ ## [1.2.10-beta.1] — 2026-10-21
75
+
76
+ A beta by policy: GitHub prerelease only, never npm. This one makes the reduce phase executable.
77
+
78
+ ### Reduce execution (caller-dispatched)
79
+
80
+ `w2m_reduce` dispatches a reduce only from complete data: it refuses unless every declared shard reported as a blob and the named reducer reported (`W2M_REDUCE_NOT_READY`, with the missing indexes). It never picks a reducer and never invents inputs; inline results are refused (`W2M_REDUCE_INLINE_INPUT`) because they cannot be carried to another machine.
81
+
82
+ The relay validates and carries `reduce_of` + `reduce_inputs` (at most 16 artifacts, sha256 only) on the task, the ledger and the offer; the agent fetches those content-addressed blobs BEFORE running the reduce and exposes `W2M_REDUCE_INPUTS_DIR` / `W2M_REDUCE_MANIFEST` / `W2M_REDUCE_OF` to the command. A failed fetch refuses the task (`REDUCE_INPUT_FETCH_FAILED`) rather than reducing half the fleet.
83
+
84
+ Verified: gate GREEN twice, 2701 tests / 2687 pass / 0 fail / 14 skip in 131 files; new agent e2e proves the reduce reads the exact artifact bytes; the tool has a dispatch test and a refusal test.
85
+
86
+ ## [1.2.9-beta.1] — 2026-10-21
87
+
88
+ A beta by policy: **GitHub prerelease only, never npm.** The theme is the data plane and the collaboration plane: the design is
89
+ written down (`docs/DATA-PLANE-COLLABORATION.md`) and this release ships its first slice.
90
+
91
+ ### `reduce_suggestion` on `w2m_wait`
92
+
93
+ Every terminal wait now answers three questions without acting on them: is the data complete, who could reduce it, and what the
94
+ caller is supposed to do next (`dispatch the reduce yourself; this tool does not run it`). It is a pure function over the machine
95
+ facts the aggregate already publishes:
96
+
97
+ - **ready** only when every declared shard reported and a reducer was named;
98
+ - **missing indexes** when a shard never reported (an expired lease cannot claim one);
99
+ - **candidates** listing the machines that reported, with the reason;
100
+ - a named reducer that never reported is refused by name (`REDUCER_UNREPORTED`) rather than silently replaced -- changing the
101
+ machine changes what the reduce runs against;
102
+ - a caller that named no reducer gets `REDUCER_MISSING` and candidates, never an invented choice.
103
+
104
+ Nothing is stored and nothing is dispatched. Two readers of the same aggregate get the same suggestion, and reading does not
105
+ change relay state.
106
+
107
+ ### The design document states the open question
108
+
109
+ Caller-dispatched reduce (current direction) versus auto-reduce in the relay. The design leans caller-dispatched, because a relay
110
+ that decides to run a command is the boundary every other decision avoids; a beta that flipped that default would be the wrong
111
+ place to discover it. The document also names the next collaboration slices: conflict-merge UI, council decision trail to shared
112
+ memory, and the board in the composer panel.
113
+
114
+ ### Verified
115
+
116
+ Gate GREEN twice: 2699 tests / 2685 pass / 0 fail / 14 skip in 131 files. New: `suggestReduce` unit tests (7) covering complete,
117
+ partial-with-missing, no-reducer, unreported-reducer and mixed inline+blob inputs; `w2m_wait` carries the suggestion for every
118
+ aggregate.
119
+ ## [1.2.8-beta.1] — 2026-10-21
120
+
121
+ A beta, by policy: **not published to npm**. It is a GitHub prerelease, installable by a developer who asks for it.
122
+
123
+ ### Message ids are unique across a relay restart
124
+
125
+ The relay's chat sequence resets on restart, so a bare `msg-000001` named a different message after every deploy. Clients persist
126
+ those ids -- an open turn, a read watermark, `reply_to` -- and live testing caught exactly that: a Windows `ask` recorded a turn, the
127
+ relay restarted, a new message reused the id, and the wait settled the OLD turn as `answered_after_timeout` while the reply was
128
+ fresh. Message ids now carry a random per-process epoch (`msg-<epoch>-<seq>`), so a restart cannot recycle an id and old clients that
129
+ render ids verbatim keep working.
130
+
131
+ ### One size for "how large may one artifact be"
132
+
133
+ The blob transport frame was 1 MiB while the agent's dataflow hard cap is 16 MiB, so an artifact between the two was accepted by the
134
+ agent and then failed its upload (`BLOB_UPLOAD_FAILED`) on a relay that looked fine. `BLOB_FRAME_BYTES` is now 16 MiB, matching the
135
+ agent, while every JSON route keeps the 64 KiB frame limit.
136
+
137
+ ### Verified
138
+
139
+ Gate GREEN twice: 2691 tests / 2677 pass / 0 fail / 14 skip in 130 files. New tests: an id is unique across two store instances
140
+ (same sequence, different process), an explicit epoch is honoured, and a 2 MiB blob survives the transport intact.
141
+
142
+ ### Still open (unchanged from 1.2.7)
143
+
144
+ - `pipeline` keeps `index_total` at 1; blob map results come from plain commands above the inline cap.
145
+ - macmini runs 1.2.6 from its own checkout and needs a `git pull` + restart for the plain-command blob behaviour.
146
+ ## [1.2.7] — 2026-10-21
147
+
148
+ **The scheduler enforces the graph, a wait survives a restart and a dropped stream, and a partial fleet can no longer call itself
149
+ a complete reduce.**
150
+
151
+ ### Turns are a state machine on disk
152
+
153
+ A question written with `action:'ask'` becomes a persisted turn (`ask_id`, deadline). `await` attaches to that record, so a process
154
+ restart does not lose the question or turn a late answer into silence; a late answer is recorded as `answered_after_timeout`. The
155
+ wait prefers the relay's event stream, re-subscribes when the stream drops, and polls only when streaming is unavailable. A reply
156
+ now carries `reply_state: 'answered'|'refused'`, so a refusal is a state the waiter reads rather than wording it guesses.
157
+
158
+ ### Dependencies block dispatch, and release when their prerequisite settles
159
+
160
+ A task with `depends_on` is not offered: its leases are `waiting`, which redelivery skips and the sweeper ignores. When every
161
+ prerequisite is settled -- **by a result, an expiry or a refusal** -- `_releaseDependents` moves those leases to offers. The graph
162
+ edges and `assignee` are written into the task ledger, because live testing showed a restarted relay losing them and keeping a
163
+ waiting task waiting forever. `assignee` is enforced, not recorded: only that machine gets a lease, and an unknown assignee is
164
+ refused by name. `GET /v1/tasks` now publishes a derived `status` (`waiting`/`active`/`done`/`failed`/`cancelled`) and
165
+ `waiting_on`, so the board and the scheduler cannot disagree.
166
+
167
+ ### Dataflow: an expected shard count, and a reduce that refuses partial input
168
+
169
+ `buildReducePlan` takes `expectedIndexTotal` from the task (`index_total`) and refuses `MISSING_SHARD` (with the missing indexes),
170
+ `MAP_INDEX_MISSING` and `MAP_INDEX_OUT_OF_RANGE`; only machines that actually reported can claim a shard, so an expired lease no
171
+ longer makes a partial run look complete. Two production gaps were found by live testing and closed with it:
172
+
173
+ - **A large plain-command stdout was never a blob.** The blob path lived only in the pipeline loop, so a split/mapreduce map result
174
+ was always inline and a reduce plan could never see what it reduces. Any command output above the inline cap now uploads a blob
175
+ exactly as a pipeline stage does (best effort: a failed upload keeps the command successful and says why).
176
+ - **The relay's own frame limit made the blob channel unusable.** `MAX_FRAME_BYTES` (64 KiB) equals the inline threshold, so every
177
+ blob was by construction larger than the frame cap and a real upload failed with `FRAME_TOO_LARGE` while the fake relay in tests
178
+ accepted it. Blob PUTs now get a bounded 1 MiB frame (`BLOB_FRAME_BYTES`); every JSON route keeps the 64 KiB cap.
179
+ - The aggregate now publishes `blob_sha256`, `output_mode`, `stdout_head` and `stdout_bytes` per machine: `w2m_wait` used to read
180
+ `aggregate.detail.machines`, a shape the production `/v1/tasks` response never carries, so the reduce plan was unreachable
181
+ outside tests.
182
+
183
+ ### Verified
184
+
185
+ Gate GREEN twice, 15 of 15: 2687 tests / 2673 pass / 0 fail / 14 skip in 130 files.
186
+
187
+ Two-machine live runs on Windows (ToFuture) and jp-shared over the real relay:
188
+
189
+ - **await over SSE**: Windows asks, jp-shared replies `reply_state:'refused'`; the wait returns `state=refused, via=stream`, and the
190
+ turn is settled on disk.
191
+ - **dependency chain**: B with `depends_on: [A]` is `waiting`, A settles, B is released (observed `status=active` with offers).
192
+ - **missing shard**: a split task with `index_total: 5` whose map results are blobs is refused as
193
+ `MISSING_SHARD {missing:[1,3,4], expectedIndexTotal:5}` with `dataflow_degraded: true` -- the first live exercise of the blob
194
+ channel end to end.
195
+
196
+ Both live-testing defects above (release only on a result; graph edges missing from the ledger) were found by these runs and are
197
+ covered by tests now.
198
+
199
+ ### Limits, stated
200
+
201
+ - A pipeline task runs the whole chain per machine, so `index_total` stays 1 for it; blob map results come from plain commands above
202
+ the inline cap.
203
+ - The blob transport frame is 1 MiB while the agent's dataflow hard cap is 16 MiB: a single artifact above 1 MiB fails its upload
204
+ explicitly (`BLOB_UPLOAD_FAILED`) rather than being split across frames.
205
+ - The jp-shared agent was upgraded to this build; macmini runs 1.2.6 from its own checkout and needs a `git pull` + restart to get
206
+ the plain-command blob behaviour.
10
207
  ## [1.2.6] — 2026-10-20
11
208
 
12
209
  **Security surface: signatures, a permission matrix, and output that no longer carries secrets — plus the reliability the chat
package/README.md CHANGED
@@ -14,7 +14,7 @@ English | [中文](#中文说明)
14
14
 
15
15
  ## Install
16
16
 
17
- Five routes in. They all end with **15 tools registered** in your agent. Pick one.
17
+ Five routes in. They all end with **16 tools registered** in your agent. Pick one.
18
18
 
19
19
  ### 1. DSH plugin (recommended — zero configuration)
20
20
 
@@ -136,7 +136,7 @@ hand-maintained host list.
136
136
  ### Tools
137
137
 
138
138
  `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
139
- `w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks`
139
+ `w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks` · `w2m_reduce`
140
140
  - `w2m_intent` — map a structured intent (run-tests, node-version, git-status, git-head, fleet-facts, shard-probe) onto an allow-listed command; unknown intents are refused by name, never guessed.
141
141
 
142
142
  ### Environment a dispatched command receives (1.1.15+)
@@ -261,7 +261,7 @@ MIT.
261
261
 
262
262
  ### 安装
263
263
 
264
- 五种方式,最终都会在你的 agent 里注册 **15 tools registered**。任选其一。
264
+ 五种方式,最终都会在你的 agent 里注册 **16 tools registered**。任选其一。
265
265
 
266
266
  #### 1. DSH 插件(推荐,零配置)
267
267
 
@@ -12,6 +12,6 @@
12
12
 
13
13
  [mcp_servers.w2m-fleet]
14
14
  command = "npx"
15
- args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.2.6", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.3.0", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
@@ -0,0 +1,82 @@
1
+ # Data plane and collaboration: the design this fleet is building toward
2
+
3
+ > Status: **design + first slice (v1.2.9-beta.1)**. The slice is a pure function that turns an aggregate into a *reduce
4
+ > suggestion*; it dispatches nothing. Everything below the slice is the plan the slice is measured against, not a claim that it
5
+ > exists.
6
+
7
+ ## 1. What "data plane" means here
8
+
9
+ A task has three planes in this fleet:
10
+
11
+ | plane | what moves | owns |
12
+ |---|---|---|
13
+ | control | dispatch, leases, retries, cancellation | relay (`state.mjs`) |
14
+ | data | command output, stage artifacts, blobs | agent (`agent.mjs`) + relay blob store |
15
+ | collaboration | chat, turns, memory, council | plugin (`tools.mjs`, `ask-store.mjs`, `council-fleet.mjs`) |
16
+
17
+ The data plane is **not allowed to make a verdict**. It moves bytes; the aggregate decides. That rule is why the reduce plan is
18
+ computed by a reader (`w2m_wait`) from published facts, and why this release adds only a *suggestion*: a party that has the plan
19
+ must still be the one that says "run the reduce".
20
+
21
+ ## 2. The shard contract (implemented in v1.2.7)
22
+
23
+ 1. A task declares `index_total`. It is the completeness contract, not a hint.
24
+ 2. A result claims a shard with `index`; only machines that actually reported count.
25
+ 3. `buildReducePlan({ results, reducerMachineId, expectedIndexTotal })` refuses:
26
+ - `MISSING_SHARD` + `{ missing, expectedIndexTotal }`,
27
+ - `MAP_INDEX_MISSING`, `MAP_INDEX_OUT_OF_RANGE`,
28
+ - `BLOB_MISSING`, `MAP_MACHINE_MISSING`, `NO_MAP_RESULTS`.
29
+ 4. An artifact above the inline cap is a blob (`blob_sha256` + `output_mode: 'blob'`), uploaded by the agent, addressed by its own
30
+ hash, transport frame 16 MiB.
31
+ 5. **A partial fleet never reduces.** `dataflow_degraded: true` is the value; a caller that ignores it is ignoring a stated fact.
32
+
33
+ ## 3. What v1.2.9-beta.1 adds: `suggestReduce`
34
+
35
+ `w2m_wait` now returns a `reduce_suggestion` alongside the verdict:
36
+
37
+ ```json
38
+ {
39
+ "ready": true,
40
+ "plan": { "ok": true, "reducerMachineId": "…", "inputs": [ … ] },
41
+ "candidates": [{ "machine_id": "…", "reason": "reported-shard" }],
42
+ "next": "dispatch the reduce yourself; this tool does not run it"
43
+ }
44
+ ```
45
+
46
+ Rules it encodes:
47
+
48
+ - **Ready means all declared shards reported and a reducer is named.** Anything else is a value with the missing indexes.
49
+ - **The reducer is not chosen by the tool.** It lists candidates (machines that reported, preferring the declared
50
+ `reducer_machine_id` when present) and refuses to invent one: a reduce that runs on an arbitrary machine is a second dispatch
51
+ decision, and dispatch belongs to the caller.
52
+ - **The suggestion is derived, never stored.** Two readers of the same aggregate get the same suggestion, and no relay state
53
+ changes by reading it.
54
+
55
+ ## 4. The reduce execution question (open)
56
+
57
+ Two shapes are possible, and the fleet has not chosen one:
58
+
59
+ | shape | dispatch of reduce | trade |
60
+ |---|---|---|
61
+ | **caller-dispatched** (current direction) | the caller reads the plan and dispatches a task with `reduce_argv` to the named reducer | relay never decides to run a command; one extra round trip and the caller can ignore the plan |
62
+ | **auto-reduce** | the relay, on all-shards-reported, dispatches the reduce itself | fewer steps; the relay becomes an executor of a command it chose, which is the boundary every other design decision avoids |
63
+
64
+ The design leans caller-dispatched. A beta that flips that default would be the wrong place to discover it.
65
+
66
+ ## 5. Collaboration plane, next
67
+
68
+ The collaboration gaps that remain after v1.2.6/v1.2.7 (turns, DAG, membership, memory CAS) are:
69
+
70
+ 1. **Conflict merge UI** — `w2m_memory read` returns `behind_by`/`unread_records`; a reader can see a conflict but has no guided
71
+ merge. Next slice: a pure `renderMergeHint()` and a re-read flow that shows both sides of a conflicting record.
72
+ 2. **Council decision trail** — `council-fleet.mjs` decides and reports; the decision is not written to shared memory. Next slice:
73
+ an explicit, caller-invoked append of the decision as a memory record (same CAS as any other write).
74
+ 3. **DAG visibility in the client** — the board exists in `w2m_tasks`; the composer panel does not show it. Next slice: a read-only
75
+ board view from the same endpoint.
76
+
77
+ ## 6. Acceptance for this design
78
+
79
+ - The suggestion is a pure function with unit tests for: all shards present, missing shards, no reducer named, mixed
80
+ inline+blob inputs, and a duplicate machine index.
81
+ - `w2m_wait` carries it for every complete aggregate and never dispatches anything.
82
+ - Gate GREEN twice before the beta tag; beta artifacts are a GitHub prerelease, never npm.