@twinsearth/w2m-dsh-plugin 0.9.7 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,279 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.10.2] — 2026-10-15
8
+
9
+ **Tiered storage — temporary, compressed, permanent, and the two kinds of knowledge that persist forever.**
10
+
11
+ ### Why tiers rather than one directory
12
+
13
+ The Linux host reached 100% filesystem on 2026-10-14 from a single uncapped writer at 14.7 GB/day, and the
14
+ recovery cost real time. The lesson was not "delete more": it was that **data with different lifetimes was
15
+ stored in one place under one policy**, so the only available policy was "never delete" until it became
16
+ "delete under pressure" — which is the worst possible moment to be deciding what matters.
17
+
18
+ So a file's tier is written down when it is stored, and the tier alone decides what may happen to it:
19
+
20
+ | Tier | Lifetime | May it be deleted? |
21
+ |---|---|---|
22
+ | `temp` | a stated TTL | yes, automatically, once expired |
23
+ | `compressed` | until the archive is unwanted | only whole, never partially |
24
+ | `permanent` | forever | **no — refused, not warned about** |
25
+ | `skill` | forever | **no** |
26
+ | `law` | forever | **no — amended instead** |
27
+
28
+ ### Why `permanent` refuses rather than warns
29
+
30
+ A warning is a thing a caller ignores, and the caller under pressure is exactly who this exists for.
31
+ `memory.jsonl` is the **only** durable copy of what a command printed — the relay keeps `stdout_sha256` and
32
+ never the text — so deleting it deletes the fleet's ability to answer what it already tried, and nothing
33
+ else in the system notices. `device.key` is the identity peers know a machine by; losing it makes every
34
+ peer holding the old one refuse that machine with no visible cause. A forced removal remains possible,
35
+ because a rule with no escape hatch gets bypassed rather than followed.
36
+
37
+ `law` entries carry an **amendment chain** naming the hash each amendment supersedes, because "what does
38
+ the rule say now" and "what did it used to say" are both questions a reader has, and only the second
39
+ explains why something was done the old way.
40
+
41
+ ### A test that asserted nothing, found while writing it
42
+
43
+ The compressed-removal case ended with `existsSync(join(..., 'nope'))` — a path that never exists, so the
44
+ assertion could never be false. It now checks the real property: removal leaves no orphan on disk, because
45
+ an index that forgets a file still on disk is how a store grows without anything being able to name what
46
+ is in it.
47
+
48
+ **24 tests, 0 failures.**
49
+
50
+ ## [0.10.1] — 2026-10-15
51
+
52
+ **A service-health agent: measure, classify, recommend — never act.**
53
+
54
+ ### Why it only advises
55
+
56
+ A component that both decides a server is unhealthy *and* changes that server is one bug away from
57
+ deleting the thing it was protecting. The 2026-10-14 disk incident is the concrete example: the recovery
58
+ was `truncate`, **not** `rm`, because rsyslog still held the file descriptor open, so removing the file
59
+ would have freed nothing while appearing to succeed. A cleaner that had decided on its own to delete "the
60
+ big log file" would have looked successful and changed nothing.
61
+
62
+ ### Where the thresholds come from — measurement, not taste
63
+
64
+ - **Disk** — critical at 90% used *or* under 1 GB free; warn at 80%. The host reached 100% with 268 MB
65
+ free, which is the state this is for. The absolute floor matters independently: a huge filesystem can be
66
+ "only 85% used" and still have less headroom than a task needs.
67
+ - **Log growth** — 14.7 GB/day is the measured rate that caused the incident. Any single file over 1 GB is
68
+ reported **by name**, because "the disk is filling" is not actionable and "this file is filling it" is.
69
+ - **Load** — compared against CPU count, never an absolute number, because the consequence worth naming is
70
+ that a loaded machine starts expiring tasks at the ~50 s lease ceiling.
71
+ - **Memory** — set where a host starts swapping, not where it looks comfortable.
72
+
73
+ ### Two defects the suite found in its own work
74
+
75
+ - The critical load multiple was `loadPerCpuWarn * 2`, which **reads as a rule and is a coincidence** —
76
+ equal to 3.0 only while the warning threshold is 1.5, so changing one would silently move the other. Its
77
+ own test asserted the documented 1.5 / 3.0 pair and failed. It is now a named constant.
78
+ - A test asserted `critical` for 4.0 load over 2 CPUs. 4 / 2 is 2.0 per CPU, between warn and critical.
79
+ **The implementation was right and the assertion was not.**
80
+
81
+ ### Permanent storage is a classification, not a copy
82
+
83
+ `DATA_CLASSES` marks `memory.jsonl`, `device.json`, `device.key` and `fleet-memory.md` as
84
+ `IRREPLACEABLE`. It copies nothing: deciding where data may live is a policy this module has no standing
85
+ to make, and the honest half is saying which data cannot be regenerated.
86
+
87
+ ### A limitation measured rather than assumed
88
+
89
+ `df` exists on Linux and macOS and not on Windows, so a Windows sample carries `disks: []`. The important
90
+ part is that it is **empty** rather than filled with a healthy-looking default — an unmeasured disk must
91
+ never read as free space. A test asserts the empty case on whichever host runs the suite.
92
+
93
+ **20 tests, 0 failures.**
94
+
95
+ ## [0.10.0] — 2026-10-15
96
+
97
+ **The cross-session approval index, and a syncer that refuses to wedge a repository.**
98
+
99
+ ### The approval index (roadmap v4.9.0)
100
+
101
+ `approval-broker.mjs` answers approvals **per session**: a request belongs to one IM window and the
102
+ operator answers it there. That is right for the person in that conversation and wrong for a supervisor,
103
+ who by definition is not in it — the design's own words are that a supervisor reviews all agents' pending
104
+ approvals *from one queue*. Without this, a request raised in a session nobody is watching sits until its
105
+ deadline delegates it.
106
+
107
+ The index presents pending work across sessions, filtered by what the viewer may act on, and verifies
108
+ authority at the moment of decision. It decides nothing itself: `decide()` returns a verdict and the caller
109
+ settles the broker, because the broker already owns settlement and a second settler would be a second owner
110
+ of the same promise.
111
+
112
+ Two choices worth stating. The subject's authority level is recorded **at raise time**, because it is a
113
+ fact about who asked and must not be re-derived from a caller who might be the approver — an index that let
114
+ the approver supply it would let the approver lower it. And `listFor` returns work the viewer **cannot**
115
+ answer, with the reason, rather than filtering it out: a filtered list makes the fleet look idle at the
116
+ moment it is most blocked.
117
+
118
+ ### `scripts/fleet-sync.mjs` — broadcast, sync, merge, with a decision
119
+
120
+ Three defects were found by running it, two of them dangerous in production:
121
+
122
+ - **A failed rebase wedged the repository.** No committer identity meant `git rebase` stopped part-way,
123
+ leaving a detached HEAD and `.git/rebase-merge` for a person to resolve. An automatic tool that can
124
+ wedge a repository is worse than one that does nothing. The identity is now checked before starting, and
125
+ a failure **aborts** the rebase to restore the exact previous state.
126
+ - **A failed `rev-parse` was read as a ref name.** `git rev-parse <unknown>` **echoes the argument** to
127
+ stdout and then exits non-zero, so a failed lookup produced `{ok:false, out:'origin/main'}` — a plausible
128
+ ref name attached to a status saying it does not exist. `inspect` read `out` alone and reported a
129
+ repository with **no remote at all** as `ahead` with `ahead/behind: NaN/0`. Every ref lookup now uses
130
+ `--verify`, and an unparseable `rev-list` is `unreadable`/stop rather than falling through to a default.
131
+ - **The published/unpublished safety test was dead code.** `published = local_only < ahead` can never be
132
+ true: `ahead` counts the commits absent from the remote-tracking branch and `--not --remotes` selects
133
+ exactly those commits. The real question is narrower — would this rebase rewrite a commit some remote ref
134
+ already reaches — and it is now answered by testing each local commit for reachability from the remotes.
135
+
136
+ A finding that changed a test rather than the code: **on a single upstream, a "both sides published"
137
+ divergence cannot exist.** If a local commit is reachable from `origin/main` then HEAD is its ancestor, so
138
+ `behind` is 0 and there is no divergence. Three fixtures failed before that became clear. The reachable
139
+ version of the hazard is a local commit some *other* remote ref already has — a branch pushed as a feature
140
+ and then rewritten — and that is what the test now builds.
141
+
142
+ **30 tests across the two, 0 failures**, against real repositories and a real bare remote: a mocked git
143
+ would have been a test of the mock.
144
+
145
+ ## [0.9.9] — 2026-10-15
146
+
147
+ **Cognitive memory blocks (roadmap v4.8.0): field-level acceptance and lineage — and the first release whose suite was run on the fleet machine rather than on the development host.**
148
+
149
+ ### `src/agent/blocks.mjs`
150
+
151
+ The memory layer shipped in 0.9.6/0.9.7 accepts a peer's claim **whole or not at all**, which is a blunt
152
+ instrument with two costs the roadmap records as P1 and P2. A block is now seven fixed fields so the
153
+ decision can be made per field:
154
+
155
+ | Field | May a receiver choose it? |
156
+ |---|---|
157
+ | `subject`, `body`, `evidence` | yes |
158
+ | `confidence` | **no — refused whatever the chooser says** |
159
+ | `origin`, `observed_at`, `lineage` | **no — inherited, and setting them would be forging provenance** |
160
+
161
+ Lineage is by **content hash**, not by id, because an id is assigned by whoever creates the block and can
162
+ be copied. A block whose parent cannot be produced is **refused rather than re-rooted**: re-rooting turns
163
+ an unverifiable history into no history, which reads as a fresh, independent assertion, and that is how a
164
+ fabricated claim acquires a respectable past.
165
+
166
+ ### Three defects the suite found in its own module
167
+
168
+ - **`confidence` was only a default, not an invariant.** Supplying a custom `choose` *replaced* the
169
+ default rather than refining it, so the file's central conservative property — a receiver does not adopt
170
+ the sender's certainty about the sender's own assertion — was silently optional. "Silently optional" is
171
+ how a safety default stops being one, so the refusal is now a hard invariant that no chooser can open.
172
+ - **A cycle fixture that asserted nothing.** The first cycle test mutated `lineage` after hashing and keyed
173
+ the map by the recomputed digest, leaving the references pointing at hashes the map did not hold — the
174
+ walker reported `missing` and never met the cycle it was supposed to test. `linkFixtures` now makes the
175
+ reference and the key the same string by construction.
176
+ - **A test that measured the clock.** `makeBlock` stamps the current time when no `observed_at` is given
177
+ (correctly — that is what the field means), so two blocks built a millisecond apart differ. The failure
178
+ looked like a hashing defect and was a 1 ms difference in a field the test never mentioned.
179
+
180
+ ### Verified on MacMini, not only here
181
+
182
+ This is the first release whose suite was executed on the fleet machine:
183
+
184
+ ```
185
+ MacMini (MacMinideMac-mini.local)
186
+ HEAD : 642242446d3eb0ed1928f128d088b727284a6d43
187
+ expected : 642242446d3eb0ed1928f128d088b727284a6d43 MATCH
188
+ suiteOk : true
189
+ ```
190
+
191
+ `test/blocks.test.mjs` — **28 tests, 0 failures**, adversarial by design: most cases assert a refusal
192
+ rather than a happy path, because what makes a fleet memory worse than no memory is losing the ability to
193
+ tell a fact from an echo. `eslint` 0 errors.
194
+
195
+ ### Notes on how this got here, because the tooling cost more than the module
196
+
197
+ `run-relay.mjs` targets `root@<relay>` and runs **bash on the relay host**; `relay-run.mjs` runs a
198
+ **JavaScript file on a fleet machine**. Both are called "relay" and only one of them is the fleet — a
199
+ clone believed to be on MacMini was on the relay for a round. Also: `Set-Content -Encoding utf8` writes a
200
+ **BOM**, and a shebang behind a BOM is not a shebang. And a 43 KB script cannot travel as an argv element
201
+ under Windows' 32 KB command-line limit, which is why this work reaches MacMini as a **branch** it fetches
202
+ rather than as an inline command.
203
+
204
+ ### Environment, measured
205
+
206
+ MacMini has `git` 2.54.0, `curl`, `python3`, 70 GiB free, and **working** HTTPS to npm, the GitHub API,
207
+ release assets and codeload — including `git ls-remote`, which an earlier session recorded as failing.
208
+ It has no `npm`, `pnpm` or `brew`, so `node --test` directly is the suite runner.
209
+
210
+ ## [0.9.8] — 2026-10-14
211
+
212
+ **The numeric authority model — the layer four of the five roadmap layers depend on — plus a roadmap and a memory write that reports its own outcome.**
213
+
214
+ ### `ROADMAP.md`
215
+
216
+ The operator's architecture document specifies five layers across v4.7.0–v4.13.0. It lives in the
217
+ repository now, and it states the delta honestly before it states the plan: this repository has **one
218
+ relay and five machines**, against a target of 1,000+ agents. Every release row carries both a
219
+ deliverable and **the observation that would falsify it**, because a row without the second is a feature
220
+ list rather than a plan.
221
+
222
+ It also refuses three things on the record: it does not adopt a named protocol (InterMesh, A2A, NLIP,
223
+ AIX, MMP) before a line of its wire format exists; it does not build BFT consensus among five machines on
224
+ one relay, where it would add latency and failure modes without adding trust; and it does not specify
225
+ 1,000-agent behaviour, because the source document's own §6.4 reports 30–50 agents as the practical limit
226
+ and superlinear "coordination collapse" beyond 100 — **a design not measured at 50 cannot honestly
227
+ specify 1,000.**
228
+
229
+ ### `src/agent/authority.mjs` — v4.7.0
230
+
231
+ An integer `authority_level` on every actor, the four bands (0–49 ordinary, 50–69 captain, 70–89 org
232
+ admin, 90+ system admin), `matching_exempt_level` as the Unix-root-style exemption, and a decision
233
+ function returning `allow | needs_approval | deny` **with the rule that produced it**.
234
+
235
+ Why an integer rather than role names: authority appears in four of the five layers — permissions, the
236
+ approval flow, committee voting weights, and the defence hierarchy where a Coordinator outranks an
237
+ Analyst. An enumerated set of names would be re-partitioned by each layer, and every re-partition is a
238
+ place for two of them to disagree.
239
+
240
+ Three decisions are load-bearing and each is pinned by a test:
241
+
242
+ - **A rule with no `matching_exempt_level` exempts nobody.** The tempting default is "absent means
243
+ unconstrained", and it is wrong in the one direction that matters: a rule author who forgets the field
244
+ would silently create an unrestricted action.
245
+ - **An exemption bypasses membership, not the level floor.** The Unix analogy is exact — root bypasses a
246
+ file's permission bits, not the requirement that the file exists. A rule that says both is
247
+ self-contradictory, and the strict half wins.
248
+ - **A derived actor never exceeds its parent.** No rule in the source design grants delegation, and the
249
+ default an implementation reaches for — "a captain's delegate is a captain" — turns one compromised
250
+ actor into an escalation path. Where the source is silent, the narrow reading is the safe one.
251
+
252
+ ### `evaluateAll` — a design corrected by its own test
253
+
254
+ The first draft evaluated every rule and returned the strictest verdict. A test asserting that the rules
255
+ after a gate are not evaluated failed, and the failure was right. `decisive` must be **unique**, or a
256
+ list can hold several non-allow decisions and whichever the caller picks, the *stated* reason for a
257
+ refusal stops being the reason. And a gate is a gate whether it says `deny` or `needs_approval`: in both
258
+ cases the caller's next action is determined and the remaining rules are decoration. It now stops at the
259
+ first non-allow.
260
+
261
+ ### `envelope.memory` — a silent write that now reports
262
+
263
+ `appendMemory`'s result was deliberately unchecked, so a memory write could not fail a task. That remains
264
+ true — but leaving it entirely *silent* was the right call for the control flow and the wrong one for the
265
+ reader: **a machine whose memory is not being written is indistinguishable from one that has nothing to
266
+ remember**, which is precisely the class of invisible failure this layer exists to end. The envelope now
267
+ carries `memory: {written, file, rotated}` or `{written: false, reason_code}`. It is not a comparable
268
+ field, so no verdict moves and two machines cannot disagree over it.
269
+
270
+ ### Verified
271
+
272
+ - `test/authority.test.mjs` — **27 tests, 0 failures.** Every one is about a boundary, because the only
273
+ interesting thing an authority model can get wrong is being off by one: a level too permissive is an
274
+ escalation and one too strict is an outage, so the band edges and the exemption boundary are asserted
275
+ **from both sides** rather than by a middle value that would pass either way.
276
+ - Band partition is proven total and non-overlapping across 0..100 by walking every level, so a seam that
277
+ belongs to two bands — a bug that only appears once someone is assigned that level — cannot exist.
278
+ - `eslint .` — 0 errors on the new module and test.
279
+
7
280
  ## [0.9.7] — 2026-10-14
8
281
 
9
282
  **`w2m_memory` gains the collective layer and the one write a machine may not make for itself.**
package/ROADMAP.md ADDED
@@ -0,0 +1,135 @@
1
+ # Fleet roadmap — from a 5-machine fleet to a governed multi-agent system
2
+
3
+ Status: **plan, with one consumer already written.** This file exists so the roadmap lives in the
4
+ repository rather than in a conversation, and so each version's deliverable can be measured against
5
+ something rather than remembered.
6
+
7
+ Source: the operator's architecture document (2026-10-14), which specifies five layers — coordination,
8
+ memory, authority/scheduling, committee governance, and defence — across releases v4.7.0 to v4.13.0.
9
+
10
+ ---
11
+
12
+ ## 0. What this repository already is, stated honestly
13
+
14
+ Before the roadmap, the delta, because a plan that does not say where it starts is a wish.
15
+
16
+ | Layer | This repository today | The document's target |
17
+ |---|---|---|
18
+ | Networking | one relay, one fleet, five machines; P2P direct path when NAT allows | InterMesh/A2A/NATS-MQTT/libp2p/NLIP federation |
19
+ | Memory | **individual per-machine (shipped 0.9.6), collective curated document (0.9.7), conclusions (0.9.7)** | three-layer memory with field-level acceptance, lineage, consolidation and forgetting |
20
+ | Authority | one operator token; write scope; a global approval rate limiter | numeric `authority_level`, `matching_exempt_level`, a pending-approval index |
21
+ | Governance | none | AURA seven-phase council, weighted voting, recursive Sentinel/MetaSentinel |
22
+ | Scale | five machines, tasks in the hundreds | 1,000+ agents, sub-10 ms coordination, "coordination collapse" mitigation |
23
+ | Defence | hash-only ledger, HMAC signing, BFT-free consensus | three-tier sensor/analyst/coordinator with BFT |
24
+
25
+ **The gap is four to six orders of magnitude in agent count.** Nothing below claims otherwise, and the
26
+ roadmap is ordered so that what is built first is what the larger system would still need.
27
+
28
+ ---
29
+
30
+ ## 1. The one thing to build first, and why it is the only honest starting point
31
+
32
+ The document's own structure gives the answer: `authority_level` appears in **four of its five layers**.
33
+ It is in the permission model (§4.1), the approval flow (§4.2), the committee's weighted voting (§5.3,
34
+ weight includes authority) and the defence hierarchy (§2.3, where a Coordinator outranks an Analyst).
35
+ Nothing in the roadmap can be built without it, and building any of the rest first would mean inventing
36
+ authority implicitly — twice, differently.
37
+
38
+ **And it already has a consumer waiting.** §3.3's P1 is *field-level acceptance*: each agent decides
39
+ per field what to take from a peer, rather than accepting or rejecting a whole message. That is a
40
+ permission decision about a *field*, which is exactly what a numeric authority model expresses. The
41
+ memory layer shipped in 0.9.6/0.9.7 accepts everything or nothing today; `authority_level` is what turns
42
+ it into P1. So this is not a foundation poured in advance of a use — the use is already written.
43
+
44
+ ## 2. Release plan
45
+
46
+ Each row states its **deliverable** and its **falsifying test** — the observation that would show it is
47
+ not working. A row without the second is a feature list, not a plan.
48
+
49
+ ### v4.7.0 — authority model **shipped in 0.9.8**
50
+
51
+ **Deliverable.** `src/agent/authority.mjs`: an integer `authority_level` on every actor, the four bands
52
+ (0–49 ordinary, 50–69 captain, 70–89 org admin, 90+ system admin), a rule evaluator carrying
53
+ `matching_exempt_level`, and a decision function returning `allow | needs_approval | deny` with the rule
54
+ that produced it.
55
+
56
+ **Falsifying tests.**
57
+ - an actor at exactly `matching_exempt_level` bypasses the membership check, and one level below does not
58
+ (an off-by-one here is an escalation, so the boundary is tested from both sides);
59
+ - an actor never inherits another's level by acting on its behalf — delegation is not assumed, because
60
+ the document does not grant it and inventing it would be the worst possible default;
61
+ - a rule with no `matching_exempt_level` exempts nobody, rather than exempting everybody.
62
+
63
+ ### v4.8.0 — memory: field-level acceptance (MMP P1/P2) **shipped in 0.9.9, suite verified on MacMini**
64
+
65
+ **Deliverable.** Extend the shipped memory layer: a CAT7 seven-field cognitive block, per-field
66
+ acceptance driven by §2's authority model, and lineage — each accepted claim records the content hash of
67
+ its parent, so a returned claim is identifiable as the receiver's own earlier thought.
68
+
69
+ **Falsifying tests.** a field refused by policy is *absent* from the stored block rather than null; a
70
+ claim's ancestor chain survives a round trip; a claim whose parent is missing is refused rather than
71
+ silently re-rooted.
72
+
73
+ ### v4.9.0 — approval queue and scheduling
74
+
75
+ **Deliverable.** A cross-session pending-approval index (§4.2's "supervisor reviews every agent's pending
76
+ approvals from one queue"), with the already-shipped rate limiter as its front door. Scheduling stays
77
+ out of scope here — MARL is not buildable at five machines, and pretending otherwise would be the most
78
+ expensive kind of dishonesty in this roadmap.
79
+
80
+ ### v4.10.0 — committee governance
81
+
82
+ **Deliverable.** The AURA six-role council as a *single* decision engine with the seven phases, weighted
83
+ voting whose weights come from v4.7.0's authority plus reputation, and **dissent retention** — the losing
84
+ arguments are stored, because §5.1's "preserves dissenting agents' arguments" is the property that makes
85
+ a decision auditable later.
86
+
87
+ **Falsifying tests.** a phase cannot be skipped; a vote's weights are reproducible from stored inputs; a
88
+ decision with recorded dissent is retrievable *with* the dissent.
89
+
90
+ ### v4.11.0–v4.13.0 — scale, defence, integration
91
+
92
+ Named for completeness, not planned in detail, because planning them now would produce fiction. The
93
+ document's own §6.4 is the reason: it reports 30–50 agents as the practical limit of current frameworks
94
+ and describes superlinear "coordination collapse" beyond 100. **A design that has not been measured at 50
95
+ cannot honestly specify 1,000**, and the bitmask/sub-10 ms claims are targets to be tested, not facts to
96
+ be designed around.
97
+
98
+ ## 3. What this roadmap deliberately refuses
99
+
100
+ - **It does not adopt a protocol by name.** InterMesh, A2A, NLIP, AIX and MMP are cited in the source
101
+ document; none is implemented here yet, and listing one as "chosen" before a line of its wire format
102
+ exists would be a claim no test could falsify. What is implementable now is the *semantics* they share
103
+ — identity, authority, field-level acceptance, lineage — and that is what v4.7.0 and v4.8.0 build.
104
+ - **It does not build consensus where there is nothing to agree on.** BFT among five machines on one
105
+ relay adds latency and failure modes without adding trust. It becomes meaningful when there are
106
+ mutually-distrusting organisations, which is a v4.12.0 precondition, not a v4.7.0 one.
107
+ - **It does not put a governance layer above a fleet that has no disagreements yet.** A council that
108
+ decides nothing is indistinguishable from a delay. The council earns its place when two fleets want the
109
+ same machine — and that is measurable, so it can wait for the measurement.
110
+ - **It does not scale the agent count by declaring it scaled.** §6.4's collapse is a real reported
111
+ phenomenon; the response here is a benchmark before a design, not the reverse.
112
+
113
+ ## 4. Where the numbers in the source document are treated as claims, not facts
114
+
115
+ Several figures in the source (20–40% faster convergence, 45% storage reduction, 37% stability
116
+ improvement, 85–120 ms decision latency at 100+ agents, 35–42% cache hit rates, 35–78% token reduction)
117
+ come from cited literature on other systems. They are recorded as *expectations to test at this fleet's
118
+ scale*, and no roadmap item above is justified by them. The one number this repository has measured
119
+ itself — the ~50 s task ceiling, from `heartbeatIntervalMs × missedHeartbeats + leaseGraceMs` — is in
120
+ `fleet-memory.md` with its three measurements, and it is the reason every long job here is started
121
+ detached.
122
+
123
+ ## 5. Relationship to the memory layer already shipped
124
+
125
+ The shipped layer is not a detour from this roadmap; it is §3.1's L2 and part of L3, built early because
126
+ it was cheap and the gap it closed had been paid for repeatedly. Concretely:
127
+
128
+ - `memory.jsonl` per machine = **L2 individual memory**, persisted, owned by the machine.
129
+ - `fleet-memory.md` on the archive host = **L3 collective memory**, curated rather than generated.
130
+ - `w2m_memory` = the retrieval entry point for both, plus `record` for conclusions.
131
+ - **L1 conversation memory** and §3.2's *emergent* collective memory (environment traces) are **not**
132
+ built.
133
+
134
+ The honest summary: the memory layer is at the point where the next thing it needs is authority, which is
135
+ why v4.7.0 is the next thing this roadmap builds.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "0.9.7",
3
+ "version": "0.10.2",
4
4
  "description": "DSH: Windows2MacOS — drive the same project on online Windows and macOS machines from one DeepSeek account, across networks and regions. P2P hole punching is the default path (relay + shared server as rendezvous, ledger and fallback), with a bundled STUN responder, Tailscale/public-VPS/tunnel shapes, Rabbit relay + per-machine Localside + eight DSH tools (replicate / split), nine aggregate verdicts and signed requests.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -28,6 +28,7 @@
28
28
  "README.md",
29
29
  "CHANGELOG.md",
30
30
  "HANDOFF.md",
31
+ "ROADMAP.md",
31
32
  "LICENSE",
32
33
  "PROTOCOL.md",
33
34
  "PROTOCOL-v0.1.2.md",
@@ -111,13 +112,13 @@
111
112
  }
112
113
  },
113
114
  "scripts": {
114
- "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
115
+ "test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
115
116
  "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",
116
117
  "test:relay": "node --test --test-force-exit test/relay.test.mjs",
117
118
  "test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
118
119
  "test:tools": "node --test --test-force-exit test/tools.test.mjs",
119
120
  "test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
120
- "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
121
+ "test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
121
122
  "pack": "node scripts/pack.mjs",
122
123
  "verify:join": "node scripts/verify-join.mjs",
123
124
  "lint": "eslint .",
@@ -126,7 +127,13 @@
126
127
  "format:check": "prettier --check .",
127
128
  "verify": "node scripts/check-entrypoints.mjs && node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/tools.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/p2p-node.test.mjs test/p2p-agent.test.mjs test/client-bundle.test.mjs test/loader-entry.test.mjs",
128
129
  "test:p2p": "node --test --test-force-exit test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/stun-server.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs",
129
- "test:memory": "node --test --test-force-exit test/memory.test.mjs"
130
+ "test:memory": "node --test --test-force-exit test/memory.test.mjs",
131
+ "test:authority": "node --test --test-force-exit test/authority.test.mjs",
132
+ "test:blocks": "node --test --test-force-exit test/blocks.test.mjs",
133
+ "test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
134
+ "test:fleet-sync": "node --test --test-force-exit test/fleet-sync.test.mjs",
135
+ "test:health": "node --test --test-force-exit test/health.test.mjs",
136
+ "test:storage": "node --test --test-force-exit test/storage.test.mjs"
130
137
  },
131
138
  "keywords": [
132
139
  "dsh-bundle",