@twinsearth/w2m-dsh-plugin 1.3.1 → 1.3.11

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +588 -0
  4. package/bin/w2m-maintenance.mjs +112 -0
  5. package/codex/mcp-servers.toml +1 -1
  6. package/deploy/maintenance/bootstrap.ps1 +30 -0
  7. package/deploy/maintenance/bootstrap.sh +59 -0
  8. package/deploy/server/w2m-maintenance.path +10 -0
  9. package/deploy/server/w2m-maintenance.service +20 -0
  10. package/deploy/windows/w2m-maintenance.xml +31 -0
  11. package/docs/COLLAB/COLLABORATION-MODEL.md +240 -0
  12. package/docs/COLLAB/EXECUTION-AUTH-MODEL.md +250 -0
  13. package/docs/COLLAB/M1-ACCEPTANCE-RUNBOOK.md +70 -0
  14. package/docs/COLLAB/M1-MAINTENANCE.md +313 -0
  15. package/docs/COLLAB/M2-TASK-GRANT.md +231 -0
  16. package/docs/COLLAB/M3-GIT-NATIVE-TASKS.md +291 -0
  17. package/docs/COLLAB/M4-PROJECT-ROOM.md +260 -0
  18. package/docs/COLLAB/M5-MULTI-CLIENT-PRIMITIVES.md +263 -0
  19. package/docs/COLLAB/M6-CROSS-NETWORK-RELIABILITY.md +231 -0
  20. package/docs/COLLAB/M7-METRICS-ACCEPTANCE.md +263 -0
  21. package/docs/COLLAB/TASK-MODEL.md +281 -0
  22. package/docs/MULTI-MACHINE-COLLABORATION-PLAN.md +186 -0
  23. package/docs/RELEASE-STABLE.md +193 -0
  24. package/docs/acceptance/1.3.10-beta.10/M1-LIVE-DIAG.json +27 -0
  25. package/docs/acceptance/1.3.10-beta.10/M1-LIVE-POSTUPDATE-DIAG.json +18 -0
  26. package/docs/acceptance/1.3.10-beta.10/M1-LIVE-RESTART.json +24 -0
  27. package/docs/acceptance/1.3.10-beta.10/M1-LIVE-UPDATE.json +24 -0
  28. package/docs/acceptance/1.3.11/M1-LIVE-DIAG.json +26 -0
  29. package/package.json +22 -4
  30. package/plugin.json +1 -1
  31. package/src/acceptance/evidence.mjs +79 -0
  32. package/src/agent/agent.mjs +64 -2
  33. package/src/agent/grant-gate.mjs +82 -0
  34. package/src/agent/maintenance-ferry.mjs +111 -0
  35. package/src/agent/outbox.mjs +138 -0
  36. package/src/agent/patch-artifact.mjs +53 -0
  37. package/src/agent/worktree.mjs +109 -0
  38. package/src/maintenance/actions.mjs +203 -0
  39. package/src/maintenance/grant.mjs +139 -0
  40. package/src/maintenance/operator-key.mjs +151 -0
  41. package/src/maintenance/protocol.mjs +204 -0
  42. package/src/maintenance/runner.mjs +189 -0
  43. package/src/plugin/tools.mjs +1 -1
  44. package/src/relay/blob-sessions.mjs +148 -0
  45. package/src/relay/merge-queue.mjs +137 -0
  46. package/src/relay/project-locks.mjs +179 -0
  47. package/src/relay/project-notifications.mjs +182 -0
  48. package/src/relay/project-reviews.mjs +161 -0
  49. package/src/relay/project-room.mjs +163 -0
  50. package/src/relay/reliability-metrics.mjs +180 -0
  51. package/src/relay/server.mjs +164 -1
  52. package/src/relay/state.mjs +128 -1
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
8
- "version": "1.3.1"
8
+ "version": "1.3.11"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "w2m-fleet",
13
13
  "source": "./",
14
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.1",
15
+ "version": "1.3.11",
16
16
  "author": {
17
17
  "name": "TwinsEarth"
18
18
  },
@@ -26,5 +26,5 @@
26
26
  ]
27
27
  }
28
28
  ],
29
- "version": "1.3.1"
29
+ "version": "1.3.11"
30
30
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "w2m-fleet",
3
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.1",
4
+ "version": "1.3.11",
5
5
  "author": {
6
6
  "name": "TwinsEarth"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -7,6 +7,594 @@
7
7
 
8
8
  All notable changes to this project are documented here.
9
9
 
10
+ ## [1.3.11] — 2026-10-22
11
+
12
+ **Stable release: the collaboration stack's first seven modules, with the gate that measures them.**
13
+
14
+ This is the first stable since 1.3.9 (docs) that ships runtime work, and the first beta line to end with its acceptance evidence
15
+ enforced by the pre-flight rather than stored beside it. What is in it, by module:
16
+
17
+ - **M1 maintenance channel** (live on the relay host): signed update/restart/diag requests, operator-key pinning, a root runner and
18
+ a bootstrap for Linux/Windows. Accepted on the real machine: diag (9 sections / 62 KB), relay restart with a new relay_id and
19
+ the result delivered after it, update to 1.3.10-beta.10 (download + sha256 + atomic swap), and a post-update diag. Evidence:
20
+ docs/acceptance/1.3.10-beta.10/.
21
+ - **M2 grants**: single-use, task-bound Ed25519 grants with argv hash, TTL, use ledger and named refusals; e2e covers an empty
22
+ allow-list succeeding under a grant, replay, argv tampering and the no-grant refusal.
23
+ - **M3 Git-native tasks**: isolated worktrees at the caller's base commit, a content-addressed patch artifact contract, a merge
24
+ queue with merge-slot serialisation, strict/replay policies, already_merged and a structured conflict artifact. Module-level,
25
+ 24 tests across the three slices plus two composed workflow tests.
26
+ - **M4 project room**: project objects, membership roles, a deny-by-default action matrix, snapshot + incremental events, task
27
+ attribution (implicit default project, explicit projects refused when unknown), and the /v1/projects routes.
28
+ - **M5 multi-client primitives**: branch/file locks (leases, renewal, owner force-release, merge preflight conflicts), reviews
29
+ (self-review guard, duplicate-verdict refusal, approval gate by patch sha) and notifications (dedupe, digest, per-reader
30
+ watermarks, retention), with the lock routes live and their events projected into the project stream.
31
+ - **M6 cross-network reliability**: chunked, resumable blob uploads with verified commits, Range downloads, and an agent result
32
+ outbox (atomic, persisted exponential backoff, cross-restart recovery, bounded).
33
+ - **M7 metrics and acceptance**: a 39-metric contract with a cardinality guard, live on /metrics; a validator for acceptance
34
+ evidence; and the pre-flight's 16th check, which fails a release whose evidence does not validate or whose release line has no
35
+ record.
36
+
37
+ Verified: pre-flight GREEN twice (16/16 checks, 2828 tests: 2814 pass, 0 fail, 14 skip); beta line beta.1 through beta.25 shipped on
38
+ GitHub prereleases only.
39
+
40
+ Known limitations, stated rather than implied: the merge queue and the review/notification routes are not wired into the task path
41
+ yet; a request that restarts the agent itself loses the ferry result (relay restarts are fine); M4 role checks cover the lock routes
42
+ and reads, not every future write; the relay runs beta.10 and is upgraded separately from this tag.
43
+
44
+ ## [1.3.10-beta.25] — 2026-10-22
45
+
46
+ **M7 final core slice: acceptance evidence is a gate, not a folder.**
47
+
48
+ The pre-flight grew its 16th check. `acceptance evidence (every committed record validates, and the release line has one)`
49
+ does three things a file list cannot:
50
+
51
+ 1. every JSON record under `docs/acceptance/` is validated with the M7 schema (`src/acceptance/evidence.mjs`) — a record that
52
+ does not say which machine ran it, when, by whom and under which task id fails the gate;
53
+ 2. the current release line must have at least one record: shipping a beta that claims an accepted capability while pointing at
54
+ no evidence from its own `1.3.x` line is RED;
55
+ 3. it reports what it read ("4 acceptance record(s) validated; 4 for the 1.3.10 line"), so a green row is a statement about a
56
+ specific set of records rather than about an empty directory.
57
+
58
+ The check is bound to `docs/acceptance` in `GATE_INVENTORY`, so deleting the directory or the check breaks the inventory's
59
+ both-directions rule; the evidence validator's accepted-orphan entry is removed now that the gate imports it.
60
+
61
+ Not claimed: this gate proves the records are well-formed and current, not that the described runs happened — that is what the
62
+ task id, the machine ids and the executor field are for, and they are now mandatory.
63
+
64
+ Verified: gate GREEN twice with 16/16 registered checks; the evidence check passes on the four M1 records.
65
+
66
+ ## [1.3.10-beta.24] — 2026-10-22
67
+
68
+ **M7 second slice: acceptance evidence has a schema, and the committed evidence is validated against it.**
69
+
70
+ - `src/acceptance/evidence.mjs` validates every required field of the M7 evidence record: version, uppercase case id, kind
71
+ (positive/negative/chaos), requirement, at least one machine, ISO `executed_at`, executor (lead/human/agent), a result with a
72
+ verdict, and — for a positive case — the real `task_id`. Optional fields are validated when present: argv arrays,
73
+ `[from, to]` ledger ranges, `{sha256, bytes}` artifacts and an assertions object. Every problem is returned at once, because a
74
+ release check should list what to fix rather than one field per run.
75
+ - `summarizeEvidence()` counts valid records by kind and version and names the invalid ones, which is what a release note or a
76
+ gate summary reads.
77
+ - The test suite validates the **actual committed files** under `docs/acceptance/` — a schema that rejected the project's own
78
+ evidence would be a schema written for a fictional project. It also pins that both the flat `result.outcome` and the nested
79
+ `result.task.outcome` (the live restart record) are accepted as the same fact.
80
+
81
+ Registered as an accepted library with its exit named (the preflight evidence gate).
82
+
83
+ Still to wire for M7: the preflight check that a release's evidence exists and validates, and the remaining instrumentation.
84
+
85
+ Verified: evidence 6/6; gate GREEN twice.
86
+
87
+ ## [1.3.10-beta.23] — 2026-10-22
88
+
89
+ **M7 wiring: the registry is live on /metrics.**
90
+
91
+ - Relay state owns the M7 registry. The project room now notifies a listener per event, and that callback counts
92
+ `wm_project_events_total{type}` once for both the room's own events (project created, member changes, task linked) and the
93
+ lock/review/notification events projected into it — a projection is appended, then counted through the room hook, so it cannot
94
+ be double-counted.
95
+ - `GET /metrics` appends the registry body under a `# reliability registry` marker. A relay that never records into the registry
96
+ renders exactly what it rendered before, which is why the existing exposition tests still pass unchanged.
97
+ - Lock routes count `wm_lock_acquired_total{kind}` and `wm_lock_conflict_total{kind}`.
98
+ - One route test: after creating a project and acquiring a branch lock, `/metrics` carries
99
+ `wm_project_events_total{type="project.created"}` and `wm_lock_acquired_total{kind="branch"}`.
100
+
101
+ Verified: relay 189/189 (one new test); the registry and room suites are unchanged; gate GREEN twice.
102
+
103
+ ## [1.3.10-beta.22] — 2026-10-22
104
+
105
+ **M7 first slice: one metrics contract for the whole collaboration stack.** A local module and eight tests; the relay /metrics
106
+ route and the call sites do not record into it yet.
107
+
108
+ - `src/relay/reliability-metrics.mjs` declares every M7 metric name, its type and the labels it may carry — 39 definitions across tasks,
109
+ authorization, data plane, collaboration and transport. An undeclared name, an undeclared label, or a high-cardinality label
110
+ (`task_id`, `dedupe_key`, ids) is refused at record time, so a metrics endpoint cannot silently become a memory leak.
111
+ - Counters refuse decrements; gauges hold the last value; histograms keep cumulative buckets plus sum/count. `renderPrometheus()`
112
+ emits `_bucket/_sum/_count` with sorted, escaped label values, and `snapshot()` gives the same data as plain JSON for the panel.
113
+ - Eight tests: label aggregation, undeclared metric/label refusal, wrong-type and decrement refusal, gauge semantics, cumulative
114
+ buckets, Prometheus text (including `+Inf`), label escaping, and the contract's own shape.
115
+
116
+ Registered as an accepted library with its exit named (the /metrics route and instrumentation). Note on the name: the exposition module was already called metrics.mjs, so after a naming collision during this work its file and test were restored, and this registry ships as reliability-metrics.mjs; both test files pass.
117
+
118
+ Still to wire for M7: the /metrics route, the instrumentation call sites, the acceptance-evidence validator and the preflight
119
+ evidence check.
120
+
121
+ Verified: metrics 8/8; gate GREEN twice.
122
+
123
+ ## [1.3.10-beta.21] — 2026-10-22
124
+
125
+ **M6 second slice: the agent result outbox.** A local module and eight tests; the agent delivery path still uses the existing
126
+ spool and has not switched to it yet.
127
+
128
+ - `src/agent/outbox.mjs` — a result is written to disk (temp + rename, so a crash leaves either the old state or the complete
129
+ entry) before any network attempt, keyed by `(task_id, attempt)` so a retried handler is one entry. Delivery removes the entry
130
+ only after the relay acknowledged it; the relay's own dedupe makes a re-delivery safe when that acknowledgement was lost.
131
+ - Retries use exponential backoff persisted with the entry (`1s, 2s, 4s …` capped at 60s), so closing the laptop for an hour is
132
+ a resume rather than a restart. Past `max_attempts` the entry moves to `dead/` and is reported as exhausted.
133
+ - Limits: 200 entries / 16 MiB; beyond that the oldest entries are dropped and the caller sees `outbox_full: true`
134
+ (`OUTBOX_FULL` in the changelog's words) rather than a silent disk fill. A single oversize result is refused.
135
+ - Eight tests: atomic persist + depth, duplicate enqueue, restart recovery with attempt counts, backoff growth and exhaustion,
136
+ ack-only removal, count-limit drop keeping the newest, corrupt-file quarantine, oversize refusal.
137
+
138
+ Registered as an accepted library with its exit named (the agent delivery switch).
139
+
140
+ Still to wire for M6: the agent's upload/download decision for large artifacts, and P2P as acceleration-only with metrics.
141
+
142
+ Verified: outbox 8/8; gate GREEN twice.
143
+
144
+ ## [1.3.10-beta.20] — 2026-10-22
145
+
146
+ **M6 wiring: chunk session and Range routes.** Large artifacts now travel over HTTP without restarting from zero.
147
+
148
+ - `POST /v1/fleet/blob-sessions` declares `{sha256, size, chunk_bytes?}`; `PUT …/:sid/chunks/:index` uploads one idempotent chunk
149
+ (octet-stream); `GET …/:sid` returns received/missing so an interrupted upload resumes; `POST …/:sid/commit` verifies the
150
+ assembled sha256 and, on success, stores the bytes through the existing content-addressed blob store. A missing chunk answers
151
+ `409 BLOB_INCOMPLETE` with the indices; a mismatch deletes the session and creates nothing.
152
+ - `GET /v1/fleet/blobs/:sha?range=start-end` answers `206` with `Content-Range`, so a client can fetch a large patch (or a conflict
153
+ artifact) in slices instead of one allocation.
154
+ - The blob-session module's responses are now uniformly `{ok, session: {...}}`, which the routes and the original tests share.
155
+
156
+ Two HTTP tests: a chunked upload that commits and is then read whole plus as a Range, and an incomplete commit naming `[0,1]`.
157
+
158
+ Still to wire for M6: the agent result outbox, and P2P as acceleration-only with its observable success rate.
159
+
160
+ Verified: blob-sessions 9/9, relay 188/188 (2 new route tests); gate GREEN twice.
161
+
162
+ ## [1.3.10-beta.19] — 2026-10-22
163
+
164
+ **M6 first slice: large artifacts can be uploaded in resumable chunks.** A local module and nine tests; the routes and the
165
+ agent upload path are the wiring slice after it.
166
+
167
+ - `src/relay/blob-sessions.mjs` — a client declares the final `sha256` and `size` once, then uploads chunks in any order
168
+ (each index idempotent), asks what is missing, and commits. Only a commit whose assembled bytes hash to the declared sha256
169
+ becomes a blob; a mismatch deletes the session and returns `BLOB_SHA_MISMATCH`, so nothing can be referenced that does not
170
+ exist.
171
+ - Limits follow the spec: default 1 MiB chunks, `≤8 MiB` per chunk, `≤256` chunks and `≤64 MiB` per blob, two open sessions and
172
+ 512 MiB unfinished bytes per machine, 24h session TTL. A resume after a disconnect is `status()` plus the missing indices,
173
+ which is exactly the "don't restart a 40 MiB patch" property.
174
+ - Nine tests: plan/status, out-of-order + idempotent chunks, verified commit through the store hook, incomplete commit naming
175
+ the missing chunks, sha mismatch deleting the session, chunk length/size refusals, quotas, TTL expiry, and blob/chunk ceilings.
176
+
177
+ Registered as an accepted library with its exit named (the blob-session routes + agent upload).
178
+
179
+ Still to wire for M6: the HTTP chunk routes and Range downloads, the agent outbox for offline results, and P2P as acceleration
180
+ only.
181
+
182
+ Verified: blob-sessions 9/9; gate GREEN twice.
183
+
184
+ ## [1.3.10-beta.18] — 2026-10-22
185
+
186
+ **M5 wiring, part 1: lock routes and one event stream for the primitives.**
187
+
188
+ - Relay state now owns the three M5 primitives (locks, reviews, notifications) and projects their events into the project room
189
+ stream with the module sequence attached, so a client can reconstruct "what happened" from one cursor instead of three.
190
+ - `/v1/projects/:id/locks`: `GET` list, `POST` acquire, `POST …/:lock_id/renew`, `DELETE …/:lock_id` (holder release), and
191
+ `DELETE …?force=1&reason=…` (owner only). A conflict is an answer, not a transport error: `409` with the holder and expiry, so a
192
+ client can show who is editing instead of parsing a failure. Role checks sit on M4 membership (default project treats a paired
193
+ machine as worker); a non-member receives `404` and learns nothing about the project.
194
+ - Two route tests: acquire/list/conflict/foreign-release/ordered-release, plus non-member `404` and `LOCK_FORCE_DENIED` for a
195
+ worker trying to force release.
196
+
197
+ Still to wire: review and notification routes, the merge/review gate, and the panel views.
198
+
199
+ Verified: relay 186/186 (2 new lock route tests); gate GREEN twice.
200
+
201
+ ## [1.3.10-beta.17] — 2026-10-22
202
+
203
+ **M5 third primitive: notifications.** A local module and eight tests; no routes or SSE projection use it yet.
204
+
205
+ - `src/relay/project-notifications.mjs` — notifications for a project addressed to a member or broadcast, each with kind,
206
+ subject, refs and `requires_action`. Every row is also a `notification.created` event for the live stream, and the list route
207
+ is the catch-up path.
208
+ - Semantics pinned by tests: the same (kind, primary ref, target) inside 60s is one row; different refs/kinds are distinct;
209
+ overflow beyond 120 per minute collapses into one digest row that carries the count; unread is counted after a per-reader
210
+ watermark; a watermark never moves backwards; paging is `since_ms` + limit; retention drops rows older than 7 days (or beyond
211
+ 2000 per project).
212
+ - The reader is a member identity (machine or operator), not a transport: an offline client reconnects and reads the same
213
+ watermark it left.
214
+
215
+ Registered as an accepted library with its exit named (the notification routes).
216
+
217
+ M5 primitives are now all designed-and-implemented at module level: locks (beta.15), reviews (beta.16), notifications (beta.17).
218
+ Still missing for M5: the routes, the merge/ref gates using them, and the panel views.
219
+
220
+ Verified: project-notifications 8/8; gate GREEN twice.
221
+
222
+ ## [1.3.10-beta.16] — 2026-10-22
223
+
224
+ **M5 second primitive: review tasks.** A local module and ten tests; the review routes and the merge gate do not call it yet.
225
+
226
+ - `src/relay/project-reviews.mjs` — a review points at a content-addressed `{kind, sha256, source_task_id, diffstat}` target and
227
+ collects decisions from listed reviewers. Verdicts are records, not a mutable flag: `approve`/`changes_requested`/`comment`
228
+ accumulate, approvals are counted per distinct reviewer, and reaching `required_approvals` moves the review to `approved`.
229
+ - Safety rules the tests pin: self-review is refused unless an owner overrides it (and the override is its own event); reviewers
230
+ must be maintainer/owner; only listed reviewers may decide; a reviewer cannot record the same verdict twice; a
231
+ `changes_requested` closes the round and refuses a later approve; a due date expires a pending review and late decisions are
232
+ refused; `approvedFor(project, sha256)` answers the merge gate for an exact patch.
233
+ - Every state change is emitted as an event (`review.requested/decided/approved/changes_requested/expired/self_review_override`).
234
+
235
+ Registered as an accepted library with its exit named (the review routes + merge-requires-review gate).
236
+
237
+ Not in this slice: notifications (§4), the third M5 primitive.
238
+
239
+ Verified: project-reviews 10/10; gate GREEN twice.
240
+
241
+ ## [1.3.10-beta.15] — 2026-10-22
242
+
243
+ **M5 first primitive: branch and file locks.** A local module and eight tests; no routes or merge preflight call it yet.
244
+
245
+ - `src/relay/project-locks.mjs` — one exclusive holder per `(project, branch)` or `(project, path)`, leases with renewal, lazy
246
+ expiry that frees the target and records `lock.expired`, owner-only force release with a mandatory reason recorded as
247
+ `lock.force_released`, per-project/per-holder limits (20/10), and a `conflicts()` preflight that answers which live locks cover a
248
+ branch or a set of paths.
249
+ - Every mutation is returned as an event (`lock.acquired/renewed/released/expired/force_released`) so the project stream can carry
250
+ it; the module holds no relay state and takes the clock as an injection.
251
+ - Eight tests: acquire/list/release, LOCK_HELD naming the holder, expired re-entry with both events, renew ownership and
252
+ non-revival, force-release permission and reason, limits, preflight conflicts for branch and paths, and malformed-input refusal.
253
+
254
+ Registered as an accepted library with its exit named (the lock routes and the merge preflight check).
255
+
256
+ Not in this slice: review tasks (§3) and notifications (§4), which are the next two M5 primitives.
257
+
258
+ Verified: project-locks 8/8; gate GREEN twice.
259
+
260
+ ## [1.3.10-beta.14] — 2026-10-22
261
+
262
+ **M4 third slice: the `/v1/projects` routes.**
263
+
264
+ - `GET /v1/projects` — the default project plus the projects this machine is a member of (device token).
265
+ - `GET /v1/projects/:id` — snapshot with `last_event_seq`; a machine that is not a member gets `NOT_FOUND`, so a project's
266
+ existence is not leaked. The default project stays readable for any paired machine because it is the implicit home of unnamed
267
+ tasks.
268
+ - `GET /v1/projects/:id/events?since_seq=N` — the incremental slice and `next_seq`, so a client can resume after a disconnect.
269
+ - `POST /v1/projects` and `POST /v1/projects/:id/members` — operator token only (the dispatch credential), because these change
270
+ who may act on shared state; the body's initial `members` array is seeded atomically with the project.
271
+ - The routes authenticate per method and therefore sit above the shared device guard: an operator token is not a device token,
272
+ and the earlier placement made every write answer `UNAUTHORIZED`.
273
+ - Three HTTP tests: list/create/snapshot/events with a member, a non-member seeing `NOT_FOUND`, the operator requirement, and an
274
+ unknown project answering `NOT_FOUND`.
275
+
276
+ Still missing: artifact/memory/decision mounts, the panel view, and multi-client e2e.
277
+
278
+ Verified: relay 184/184 (3 new route tests); gate GREEN twice.
279
+
280
+ ## [1.3.10-beta.13] — 2026-10-22
281
+
282
+ **M4 second slice: tasks belong to a project, and the room records it.** Relay state now owns a project room; no HTTP routes yet.
283
+
284
+ - `POST /v1/task` (internal `createTask`) accepts an optional `project_id`. Unnamed tasks land in the implicit `default`
285
+ project, which is created lazily on first use; an explicit unknown project is refused with `BAD_REQUEST` rather than being
286
+ created silently.
287
+ - The project room records a `task.linked` event for every task, so a snapshot's `last_event_seq` moves with the work and a
288
+ second client can apply it as an incremental event.
289
+ - State exposes `projectSnapshot(projectId)` and `projectEvents(projectId, {sinceSeq})`; the task summary carries
290
+ `project_id`; the replay path restores it from the ledger.
291
+ - Three attribution tests: implicit default + linked event consistency, explicit project visible on the summary, and an unknown
292
+ project refused without creating one.
293
+
294
+ Still missing, stated: the `/v1/projects` HTTP routes, artefact/memory/decision mounts, the panel view, and multi-client e2e.
295
+
296
+ Verified: project-attribution 3/3, project-room 8/8, relay 181/181; gate GREEN twice.
297
+
298
+ ## [1.3.10-beta.12] — 2026-10-22
299
+
300
+ **M4 first slice: the project room object.** A local module and eight tests; the relay routes and the panel do not call it yet.
301
+
302
+ - `src/relay/project-room.mjs` — a project aggregate (project id, name, origin, settings, members), the four roles
303
+ (owner/maintainer/worker/observer), a deny-by-default action matrix (`read/dispatch/artifact/lock/memory/decide/merge/
304
+ settings/members`), member add/remove/role-change as recorded events, a snapshot carrying `last_event_seq`, incremental
305
+ events since a sequence, and per-member project listing.
306
+ - Safety rules the tests pin: the last owner cannot be removed; a duplicate member is refused by name; an unknown project is
307
+ refused rather than creating an empty one; the permission check returns the required and current role rather than a bare
308
+ false; a role change records `{from,to}`.
309
+
310
+ Registered as an accepted library with its exit named (the `/v1/projects` routes and the room panel), because nothing reaches it
311
+ yet.
312
+
313
+ Also in this tree since beta.11: the M1 live-acceptance evidence files under `docs/acceptance/1.3.10-beta.10/`.
314
+
315
+ Verified: project-room 8/8; gate GREEN twice.
316
+
317
+ ## [1.3.10-beta.11] — 2026-10-22
318
+
319
+ **M1 update path fixed and the full acceptance set recorded.**
320
+
321
+ The live update acceptance exposed two defects that made `update` unusable even though diag and restart already worked:
322
+
323
+ 1. **EXDEV on swap** — staging unpacked under `/tmp` (often tmpfs) and the swap is a rename into `/opt/w2m/app`, so the kernel
324
+ refused with `EXDEV: cross-device link`. Staging now lives on the same filesystem as the app directory
325
+ (`/opt/w2m/.maintenance-staging`).
326
+ 2. **Ferry reported success for a failed action** — the ferry returning a result file only means the runner wrote one; the
327
+ runner's own `status` was not inspected, so a failed update was reported as `ok`. The agent now reports
328
+ `MAINTENANCE_FAILED` with the runner's code, and a test covers the shape.
329
+
330
+ Acceptance evidence now in the tree:
331
+ - `docs/acceptance/1.3.10-beta.10/M1-LIVE-DIAG.json` (9 sections, 62,238 bytes, runner ok)
332
+ - `M1-LIVE-RESTART.json` (relay restarted, new relay_id, result delivered)
333
+ - `M1-LIVE-UPDATE.json` (download + sha256 + swap to 1.3.10-beta.10, no auto-restart)
334
+ - `M1-LIVE-POSTUPDATE-DIAG.json` (after update + restart, a new diag still returns 59,581 bytes)
335
+
336
+ Known limitation unchanged and documented: a request restarting the agent itself kills the ferry before it reads the result
337
+ file, so the accepted restart target is the relay; delayed restart is the next fix.
338
+
339
+ Verified: gate GREEN twice; actions/ferry unit tests pass.
340
+
341
+ ## [1.3.10-beta.10] — 2026-10-22
342
+
343
+ **M1 live acceptance: the maintenance channel works end to end on a real machine.**
344
+
345
+ The relay host (jp-shared) was bootstrapped and two signed requests were executed by the root runner:
346
+
347
+ - **diag** — the runner produced 9 sections (version/services/journal/disk/ports), the agent returned the task as `ok` with
348
+ 62,238 bytes of result, and the runner's own result file confirms `status: ok`. Evidence:
349
+ `docs/acceptance/1.3.10-beta.10/M1-LIVE-DIAG.json`.
350
+ - **restart (rabbit)** — the relay was restarted by the runner, the agent survived it and delivered the task result, and the
351
+ relay came back with a new `relay_id` and a fresh uptime. Evidence:
352
+ `docs/acceptance/1.3.10-beta.10/M1-LIVE-RESTART.json`.
353
+
354
+ The run found four real defects, all fixed here:
355
+
356
+ 1. The agent's envelope builder hashes and sizes the output; the maintenance ferry passed an execution object without
357
+ `stdout_sha256/stdout_bytes/stderr_sha256/stderr_bytes`, so every maintenance task ended as `crashed` **after** the action had
358
+ already run. The ferry now fills them.
359
+ 2. `--operator-key` without `--operator-key-id` claimed an empty key id, which the pin store correctly refused as a mismatch;
360
+ an absent id now means "trust the key that arrives".
361
+ 3. `bootstrap.sh` made the queue root-only, so the unprivileged agent could never enqueue a request; the queue is now
362
+ `root:w2m 0770` (write but not execute) and `done` is `root:w2m 0750`.
363
+ 4. Known limitation, documented rather than hidden: a request that restarts **the agent itself** kills the process still waiting
364
+ for its result file, so that result is lost (the action still runs). Restarting the relay is unaffected, which is why the
365
+ accepted case uses that target; the delayed-restart fix is next.
366
+
367
+ Also verified: gate GREEN twice; the local agent suite still passes with the ferry envelope fix.
368
+
369
+ ## [1.3.10-beta.9] — 2026-10-22
370
+
371
+ **M3 conflict contract completed: MERGE_CONFLICT now carries the conflict itself.**
372
+
373
+ When `git apply --3way` cannot apply a patch, the queue previously reported the changed files and the git message. It now also
374
+ captures the state git left in the merge worktree (conflict markers included) as a `conflictArtifact`:
375
+ `{ sha256, bytes, text }`. That is the evidence a reviewer needs to decide whether to resubmit against the new base or resolve
376
+ by hand, and it is the same content-addressed shape the patch artifacts use.
377
+
378
+ The head still does not move on a conflict; the test asserts both the artifact (with conflict markers) and the unchanged head,
379
+ and the composed M3 workflow test still passes.
380
+
381
+ Verified: merge-queue 7/7 (conflict artifact assertion included), m3-workflow 2/2; gate GREEN twice.
382
+
383
+ ## [1.3.10-beta.8] — 2026-10-22
384
+
385
+ **M3 composed: worktree -> patch -> merge queue, proven together.**
386
+
387
+ Two integration tests drive the three modules the way a real task pair would: both tasks start from the same base in isolated
388
+ worktrees, each commits its change and produces a patch artifact, and the queue merges them one after the other. The disjoint
389
+ case ends with both files changed and the branch advanced to the second merge; the same-file case ends with a structured
390
+ MERGE_CONFLICT listing the file, the head still on the first approved commit, and the first task's change intact.
391
+
392
+ No runtime wiring yet: the agent's task handler does not create worktrees, and the relay does not run the queue. This release
393
+ records that the modules agree with each other before either becomes reachable, which is the point of composing them now.
394
+
395
+ M1 real-machine acceptance remains pending: the relay host agent is offline and root SSH authentication is refused, so the
396
+ privileged bootstrap has not run.
397
+
398
+ Verified: m3-workflow 2/2, merge-queue 7/7, worktree 5/5, patch-artifact 5/5; gate GREEN twice.
399
+
400
+ ## [1.3.10-beta.7] — 2026-10-22
401
+
402
+ **M3 third block: the merge queue.** A local module and seven real-git tests; the relay task path does not import it yet.
403
+
404
+ - `src/relay/merge-queue.mjs` — one queue per project+branch with a single merge slot (holder + TTL), items named by the
405
+ patch sha256, `strict` and `replay` base policies, and terminal verdicts: MERGED (branch head advances), `already_merged`
406
+ for a repeat submission, `BASE_MISMATCH` under strict, and a structured `MERGE_CONFLICT` with the changed files that never
407
+ advances the head.
408
+ - Application happens in a detached throwaway worktree, then the repository's branch and working tree are moved to the new
409
+ commit — a merge that committed only in the worktree would leave the next reviewer looking at a head that never moved.
410
+ - Seven tests cover merge-and-advance, idempotent re-submission, stale base, conflict (head unchanged), replay onto a moved
411
+ head, merge-slot serialisation, and patch naming.
412
+
413
+ Registered as an accepted library with its exit named (the relay merge executor dispatch), because nothing reaches it yet.
414
+
415
+ Still pending, unchanged: the M1 real-machine acceptance (relay runs beta.5 and carries the maintenance field live, but the
416
+ relay host's agent is offline and root SSH authentication has not been restored), and the M2/M3 live runs.
417
+
418
+ Verified: merge-queue 7/7; the rest of the suite is unchanged; gate GREEN twice.
419
+
420
+ ## [1.3.10-beta.6] — 2026-10-21
421
+
422
+ **M3 first two blocks: isolated worktrees and a patch artifact contract. Plus the grant replay e2e.**
423
+
424
+ - `src/agent/worktree.mjs` — one git worktree per task, created detached at the caller's `base_commit`; hooks disabled,
425
+ commit identity fixed, a porcelain snapshot before and after, commit-if-changed, `format-patch` relative to the base, and
426
+ a removal that reports failure instead of leaking a directory. Unknown base commit and missing repository are named refusals.
427
+ - `src/agent/patch-artifact.mjs` — the transportable result: sha256 over the patch bytes, byte count, base/head commits,
428
+ sorted changed files, inline/blob mode, and `no_change` as a successful outcome for an empty patch. Non-ASCII patches are
429
+ measured in UTF-8 bytes, not characters.
430
+ - Five real-git tests for the worktree lifecycle and five for the artifact contract.
431
+ - `test/agent.test.mjs` gains the grant **replay** e2e: the same signed grant is used twice, the first run executes, the second
432
+ is refused with `GRANT_REPLAY` — the use ledger is what makes "single-use" true across processes.
433
+
434
+ Not in this slice (stated, not implied): the merge queue itself (strict/replay bases, merge-slot serialization,
435
+ `MERGE_CONFLICT`), review tasks and locks (M5), and the project room (M4). The M1 real-machine acceptance is also still pending:
436
+ the relay was upgraded to beta.5 and the maintenance field is verified live, but the relay host's agent is offline and root SSH
437
+ authentication is refused, so the privileged bootstrap has not run.
438
+
439
+ Verified: agent 159/157 (replay e2e included), relay 181/181, grant 10/10, grant-gate 6/6, maintenance 17/17, ferry 4/4,
440
+ worktree 5/5, patch-artifact 5/5, layer-model 24/24, claims 47/47; gate GREEN twice.
441
+
442
+ ## [1.3.10-beta.5] — 2026-10-21
443
+
444
+ **M2 grant e2e: the explicit-authorisation path is proven end to end.**
445
+
446
+ Four fake-relay tests now drive the real agent process:
447
+
448
+ - an empty allow-list executes a command **only** because a valid grant authorises that exact argv (the envelope is `ok` and
449
+ the stdout hash matches);
450
+ - the same command with no grant is still `COMMAND_NOT_ALLOWED`;
451
+ - a grant that signed a different argv is refused with `GRANT_ARGV_MISMATCH`;
452
+ - a grant that names another machine is refused with `GRANT_WRONG_MACHINE`.
453
+
454
+ Two integration defects the e2e exposed, both fixed: the per-stage allow-list re-check ran again after step 5, so a grant that
455
+ had authorised the task was refused at the stage boundary; and the matcher's result carries `allowed` (not `ok`), so both the
456
+ gate result and the stage override had to use that shape or the refusal fell back to `COMMAND_NOT_ALLOWED`.
457
+
458
+ Verified: agent 158/156 (4 new e2e cases), relay 181/181, grant 10/10, grant-gate 6/6, maintenance 17/17, ferry 4/4,
459
+ layer-model 24/24, claims 47/47; gate GREEN twice.
460
+
461
+ ## [1.3.10-beta.4] — 2026-10-21
462
+
463
+ **M1 bootstrap tooling: one privileged step, then the channel is remote.** No runtime behaviour changes.
464
+
465
+ - `deploy/maintenance/bootstrap.sh` (Linux, run once as root) — installs the fixed-action path unit/service, creates the
466
+ queue/done/claimed directories with root ownership, optionally pins the operator public key, and enables the watcher. It
467
+ refuses to run without `--machine-id` and refuses a tree where `bin/w2m-maintenance.mjs` is absent.
468
+ - `deploy/maintenance/bootstrap.ps1` (Windows, run once as Administrator) — same shape via a SYSTEM scheduled task; it
469
+ substitutes the app directory and machine id into the task XML rather than editing the shipped file.
470
+ - `docs/COLLAB/M1-ACCEPTANCE-RUNBOOK.md` — the real-machine acceptance cases (diag/restart/update/rollback plus four named
471
+ refusals), what "green" means, and the limitations that must appear in the release note (privileged bootstrap, TOFU pinning,
472
+ no automatic recovery if a restarted machine cannot be reached).
473
+
474
+ Both scripts install **only** the fixed update/restart/diag runner; neither installs a shell, and neither touches the
475
+ allow-list. The design's security boundary is unchanged: the agent ferries a signed request, the root runner decides.
476
+
477
+ Honest gap, unchanged: the M1 real-machine acceptance run itself still needs a machine with a privileged bootstrap and a
478
+ relay carrying the maintenance fields; and the M2 grant path still lacks a fake-relay e2e case. Both are next.
479
+
480
+ Verified: gate GREEN twice; pack carries the new scripts and document.
481
+
482
+ ## [1.3.10-beta.3] — 2026-10-21
483
+
484
+ **M2 first slice: task-level grants become the explicit authorisation channel.** Design: `docs/COLLAB/M2-TASK-GRANT.md`.
485
+
486
+ - `src/maintenance/grant.mjs` — grant protocol: task binding, named machine ids, argv hash + expanded argv for the operator
487
+ to read, max uses, TTL capped at 10 minutes, nonce, and constraint fields that may only tighten the task (mode/cwd/write/
488
+ write_scope). Verification has a named refusal for every distinct failure: wrong machine, wrong task, argv mismatch,
489
+ expired, future, TTL too long, replay, uses exhausted, revoked key, unknown key, bad signature, constraint widening.
490
+ - `src/agent/grant-gate.mjs` — the agent-side gate: pins the operator key on first sight, verifies the grant against the
491
+ concrete argv, and persists use counts so a spent grant stays spent across restarts. A corrupt use ledger is never read as
492
+ "nothing used yet".
493
+ - The agent asks the grant gate **before** falling through to the allow-list refusal, and reports a grant refusal with its own
494
+ `GRANT_*` code instead of folding it into `COMMAND_NOT_ALLOWED`.
495
+ - The relay accepts an optional `grant` on `POST /v1/task` (8 KiB cap, `grant_id` + 64-hex `argv_hash` checked), carries it on
496
+ the offer, writes `grant_summary` into the ledger and exposes `grant_id` on the task summary.
497
+
498
+ Honest gap: the grant path has unit coverage (protocol 10, gate 6) and the existing agent/relay suites are unchanged-green,
499
+ but there is no fake-relay e2e yet for "empty allow-list executes under a grant". That case and the real two-machine run land
500
+ in the next slice together with the M2 acceptance evidence.
501
+
502
+ Verified: agent 154/152, relay 181/181, grant 10/10, grant-gate 6/6, maintenance 17/17, ferry 4/4, layer-model 24/24; gate
503
+ GREEN twice.
504
+
505
+ ## [1.3.10-beta.2] — 2026-10-21
506
+
507
+ **M1 maintenance channel: relay passthrough + agent ferry.** The remote loop now has both ends.
508
+
509
+ - The relay accepts an optional `maintenance` request and `operator_key` on `POST /v1/task` (operator token already required),
510
+ registers the key once (`operator.key.registered` in the ledger, replayed on restart), carries the request on the offer, and
511
+ exposes `maintenance_request_id` on the task summary.
512
+ - `src/agent/maintenance-ferry.mjs` — the agent pins the operator key on first sight (TOFU, rotation chain thereafter), verifies
513
+ the request against the pin, writes it atomically into the queue the root runner watches, waits for the runner's result file,
514
+ and reports it as the task result. A refusal is a named value (`MAINTENANCE_NO_PINNED_KEY`, `MAINTENANCE_BAD_SIGNATURE`,
515
+ `MAINTENANCE_RUNNER_TIMEOUT`, ...), never a thrown error, so the task result says why.
516
+ - The agent never gains the privilege to run update/restart/diag: the root runner (beta.1) remains the only executor.
517
+ - Layer model declares `maintenance` as a capability layer with an `agent -> maintenance` edge, so the new dependency is
518
+ explained rather than excused.
519
+
520
+ Known gap, stated: no fake-runner agent-e2e test for the ferry yet (the ferry's own four cases cover pin/verify/queue/refusal);
521
+ the end-to-end case lands with the real-machine acceptance run in beta.3.
522
+
523
+ Verified: gate GREEN twice with the bundled node on PATH: 2731 tests / 2717 pass / 0 fail / 14 skip in 135 files
524
+ (agent 154/152, relay 181/181, maintenance 17/17, ferry 4/4, layer-model 24/24, claims 44/44).
525
+
526
+ ## [1.3.10-beta.1] — 2026-10-21
527
+
528
+ **M1 maintenance channel, first implementation slice.** Design: `docs/COLLAB/M1-MAINTENANCE.md` (the design series names this slice v1.3.2-beta; it ships as 1.3.10-beta.1 because 1.3.9 was already published as a stable release and versions must stay ordered).
529
+
530
+ The maintenance channel lets an operator authorise exactly one of three fixed actions on one machine (update, restart, diag)
531
+ with an Ed25519-signed request, executed by a root/SYSTEM runner that reads a queue the unprivileged agent may write to. An
532
+ allow-list entry is not needed: the signature is the authorisation, and widening the allow-list is never the answer.
533
+
534
+ ### What this slice contains
535
+
536
+ - `src/maintenance/protocol.mjs` — request envelope, canonical signing bytes, parameter whitelists for the three actions,
537
+ and the complete refusal vocabulary (wrong machine, expired, future, TTL too long, replay, revoked key, unknown key,
538
+ bad signature, bad action, bad params, too large, missing signature). TTL is capped at 10 minutes and only the pinned
539
+ operator key can sign.
540
+ - `src/maintenance/operator-key.mjs` — Ed25519 key generation, relay registration payload, trust-on-first-use pinning with a
541
+ rotation chain (a new key must be signed by the pinned one), revocation, and a corrupt pin store that refuses instead of
542
+ trusting nothing.
543
+ - `src/maintenance/runner.mjs` — queue scanning, claim-before-verify, signature verification, persisted replay sets
544
+ (request id + nonce), idempotent re-runs, and a result file for every request it looks at, refusals included.
545
+ - `src/maintenance/actions.mjs` — the three fixed actions with no shell interpolation: diag (fixed read-only command set,
546
+ capped output), restart (fixed unit order and health waits), update (https download, sha256 verify, staged extraction,
547
+ version check inside the artifact, backup, atomic swap, health check, automatic rollback).
548
+ - `bin/w2m-maintenance.mjs` — the root entry point (once/watch, pin bootstrap, root guard).
549
+ - `deploy/server/w2m-maintenance.{service,path}` and `deploy/windows/w2m-maintenance.xml` — the runner as a systemd path
550
+ unit / SYSTEM scheduled task.
551
+
552
+ ### What this slice deliberately does not contain
553
+
554
+ The relay routes that distribute the operator public key and the agent-side ferry that carries a request from a task offer
555
+ to the queue (and the result back) are the next slice (1.3.10-beta.2). Until then the channel is exercised by placing a
556
+ signed request in the queue directory directly — which is also the offline/bootstrap path the design keeps.
557
+
558
+ Tests: 17 new cases cover the protocol, pinning/rotation/revocation and the runner's refusal and replay behaviour. The
559
+ destructive actions are not executed in tests; they are covered by the real-machine acceptance runbook (M7 evidence).
560
+
561
+ Verified: gate GREEN twice.
562
+
563
+ ## [1.3.9] — 2026-10-21
564
+
565
+ **Design-first release: the collaboration line is specified, not yet implemented.**
566
+
567
+ This release ships the design specification for the multi-machine / multi-client collaboration capability, and no behaviour change: the
568
+ plugin, relay and agent are byte-identical in function to 1.3.1. It exists so the plan is reviewable, versioned, and citable before any
569
+ of it is built.
570
+
571
+ ### What is in this release
572
+
573
+ - `docs/MULTI-MACHINE-COLLABORATION-PLAN.md` — capability audit (what exists today is dispatch + aggregation, not collaboration),
574
+ root causes (missing execution-authorization model, missing workspace model, missing shared-object model), and the M1–M7 module plan.
575
+ - `docs/COLLAB/M1-MAINTENANCE.md` — v1.3.2-beta design: signed maintenance requests executed by a root/SYSTEM unit through a queue
576
+ directory; remote update/restart/diag without SSH and without widening the allow-list.
577
+ - `docs/COLLAB/M2-TASK-GRANT.md` — v1.3.3-beta design: operator-signed per-execution grants (argv hash, machine ids, TTL, task binding),
578
+ agent-side verification, ledger audit, revocation; the allow-list stops being the only execution authority.
579
+ - `docs/COLLAB/M3-GIT-NATIVE-TASKS.md` — v1.3.4-beta design: per-task git worktrees, patch artifacts as the transportable result, a
580
+ serialized merge queue, and MERGE_CONFLICT as a first-class outcome.
581
+ - `docs/COLLAB/M4-PROJECT-ROOM.md` — v1.3.5-beta design: relay-side project objects (members, board, artifacts, memory, decisions,
582
+ refs), snapshot + incremental event protocol, role-based permissions.
583
+ - `docs/COLLAB/M5-MULTI-CLIENT-PRIMITIVES.md` — v1.3.6-beta design: branch/file locks, review tasks with findings and merge gating,
584
+ and notifications with delivery guarantees and read watermarks.
585
+ - `docs/COLLAB/M6-CROSS-NETWORK-RELIABILITY.md` — v1.3.7-beta design: relay as the authoritative channel, chunked/resumable blobs,
586
+ agent result outbox, and P2P demoted to opportunistic acceleration.
587
+ - `docs/COLLAB/M7-METRICS-ACCEPTANCE.md` — v1.3.8-beta design: every capability must be triggerable by a real task, negative tests are
588
+ mandatory, metrics are computed by the relay, and evidence is checked by pre-flight.
589
+
590
+ ### What is explicitly not in this release
591
+
592
+ No code for M1–M7. The three model-level designs (v1.4.0 execution-authorization, v1.4.1 task, v1.4.2 collaboration) are not written yet.
593
+ The next stable release that changes behaviour will be the one that implements M1 + M2 (the remote-operations loop), with real-machine
594
+ acceptance evidence recorded as described in M7.
595
+
596
+ Verified: gate GREEN twice on this tree; the package differs from 1.3.1 only in documentation and version metadata.
597
+
10
598
  ## [1.3.1] — 2026-10-21
11
599
 
12
600
  **A red Compat and a green local gate: the loader-entry test was the gap.**