@twinsearth/w2m-dsh-plugin 0.8.0 → 0.8.3
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 +207 -0
- package/HANDOFF.md +173 -0
- package/bin/w2m-localside.mjs +325 -308
- package/deploy/mac/mac-self-update.mjs +257 -0
- package/package.json +4 -3
- package/src/agent/agent.mjs +24 -2
- package/src/chatops/account.mjs +400 -360
- package/src/chatops/fleet-join.mjs +164 -0
- package/src/chatops/fleet-wiring.mjs +63 -4
- package/src/chatops/ilink-fleet-bridge.mjs +197 -0
- package/src/chatops/index.mjs +440 -429
- package/src/plugin/tools.mjs +4691 -4622
- package/src/relay/state.mjs +49 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,213 @@ 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.8.3] — 2026-10-14
|
|
8
|
+
|
|
9
|
+
**The last wire, and the reason it was never there.**
|
|
10
|
+
|
|
11
|
+
### `autoStartAgent` was dead code
|
|
12
|
+
|
|
13
|
+
The option existed, was documented, and **threw on its first call**. `startLocalStack` built its
|
|
14
|
+
`createAgent` arguments as `projectDir` (the option is `project`), omitted `identity` (which is
|
|
15
|
+
required), and passed a `pairingCode` that `createAgent` does not accept.
|
|
16
|
+
|
|
17
|
+
Verified rather than inferred — calling `createAgent` with that exact shape:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
createAgent THREW: createAgent: project is required
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**So the code path that was supposed to bring an agent up had never run.** That is why the
|
|
24
|
+
fleet-join wire appeared to be missing: the place it was meant to live could not be reached at all.
|
|
25
|
+
An option that exists, is documented, and cannot execute is worse than one that is absent — it
|
|
26
|
+
reads as a feature.
|
|
27
|
+
|
|
28
|
+
### Two more things wrong with the shape
|
|
29
|
+
|
|
30
|
+
* **It created a local relay unconditionally.** For a machine joining a shared fleet that is the
|
|
31
|
+
opposite of what is wanted: the machine must connect to the relay that already exists, not mint
|
|
32
|
+
another one — and with `rabbitUrl` pointing at a remote host it would have tried to bind a relay
|
|
33
|
+
on that hostname. It now stands up a local relay **only when the configured URL actually is
|
|
34
|
+
local**, and otherwise logs that it is joining the relay at the given address.
|
|
35
|
+
* **Nothing paired the agent.** Even a started agent could fetch no offers, which looks identical to
|
|
36
|
+
a fleet with no work.
|
|
37
|
+
|
|
38
|
+
### Now
|
|
39
|
+
|
|
40
|
+
`startLocalStack` passes the option names `createAgent` actually documents, loads the identity from
|
|
41
|
+
the **same file the CLI uses** — so one machine has one identity whether the agent runs in-process
|
|
42
|
+
or as its own process, rather than each minting a keypair and overwriting the other's announcement
|
|
43
|
+
under a shared `machine_id` (the v0.6.2 defect, in a new place) — and **pairs by the account fleet
|
|
44
|
+
key**, falling back to a pairing code.
|
|
45
|
+
|
|
46
|
+
Pairing is attempted **before** `agent.start()`, and a failure is **named at warn level** rather than
|
|
47
|
+
swallowed: an agent that failed to pair runs and does nothing, which is indistinguishable from a
|
|
48
|
+
quiet fleet.
|
|
49
|
+
|
|
50
|
+
### Config
|
|
51
|
+
|
|
52
|
+
`fleetKey` is read from the config layer or `W2M_FLEET_KEY`. The key must be **handed down** rather
|
|
53
|
+
than computed where it is used, because a standalone agent cannot read the signed-in account — which
|
|
54
|
+
is why it comes from the host.
|
|
55
|
+
|
|
56
|
+
### Verified
|
|
57
|
+
|
|
58
|
+
**132 cases** across the tools, apply and fleet-join suites. `tools.mjs` loads. The 12 agent-suite
|
|
59
|
+
failures remain pre-existing and environmental, measured against an untouched clone of the same
|
|
60
|
+
commit.
|
|
61
|
+
|
|
62
|
+
### ⚠️ Not yet proven on real hardware
|
|
63
|
+
|
|
64
|
+
The wiring is fixed and the mechanism was verified end to end in v0.8.2, but **no machine has yet
|
|
65
|
+
joined a fleet through this path in a live run**. That is the next check, and it needs a machine
|
|
66
|
+
that is not already paired.
|
|
67
|
+
|
|
68
|
+
## [0.8.2] — 2026-10-14
|
|
69
|
+
|
|
70
|
+
**The client half of the auto-join.** v0.8.1 landed the credential and the relay's acceptance of
|
|
71
|
+
it; this is the wiring that makes a real machine use them.
|
|
72
|
+
|
|
73
|
+
### What is now connected
|
|
74
|
+
|
|
75
|
+
| piece | what it does |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `account.mjs` | derives the fleet key on **every** resolve, including the stored-SN path |
|
|
78
|
+
| `index.mjs` | exposes it as `fleetKey` — the key, never the account id |
|
|
79
|
+
| `agent.mjs` | `pair(code, userId, fleetKey)`, sending only the discriminator that was given |
|
|
80
|
+
| `w2m-localside` | `--fleet-key`, a peer of `--pair` |
|
|
81
|
+
| relay | derives the fleet from the key; never believes a claimed one |
|
|
82
|
+
|
|
83
|
+
**Verified end to end:** an agent given **only** `--fleet-key`, against a relay with **no fleet of
|
|
84
|
+
its own**, pairs and lands in the account-derived fleet (`d5bb1b9ddf3faad4`, `source=fleet_key`).
|
|
85
|
+
|
|
86
|
+
### The bug that would have made it work exactly once
|
|
87
|
+
|
|
88
|
+
`resolve()` returned early when an SN was already stored and never read the account at all. That is
|
|
89
|
+
right for the SN — it must not be regenerated, or every restart looks like a new device — and
|
|
90
|
+
**wrong for the key**: a machine has a stored SN from its *second* start onward, so it would have
|
|
91
|
+
derived a fleet key once and never again.
|
|
92
|
+
|
|
93
|
+
**The auto-join would have worked on a machine's first boot and silently stopped afterwards** — the
|
|
94
|
+
hardest kind of bug to see, because it works when you test it. Found by asking what the stored-SN
|
|
95
|
+
path actually does, rather than by reading the happy path.
|
|
96
|
+
|
|
97
|
+
### The bug in this release's own patch
|
|
98
|
+
|
|
99
|
+
The fleet-key handling was first inserted **inside `if (flags.pair)`**, so the key path could only
|
|
100
|
+
run when a pairing code was also supplied — precisely the case it exists to avoid. **It worked
|
|
101
|
+
whenever anyone tested it with a code, and did nothing in the case that mattered.** The end-to-end
|
|
102
|
+
test is what caught it, not review.
|
|
103
|
+
|
|
104
|
+
### A provenance bug, also caught by testing
|
|
105
|
+
|
|
106
|
+
A device that paired by key was recorded as `fleet_source: 'pairing_code'`, because the relay builds
|
|
107
|
+
a synthetic code record so the binding reads from one place. That matters: `fleet_source` is what an
|
|
108
|
+
operator reads in `devices.json` to answer *"how did this machine get into my fleet?"*, and
|
|
109
|
+
answering "a pairing code" when there was none sends them looking for a code never issued.
|
|
110
|
+
|
|
111
|
+
### Also
|
|
112
|
+
|
|
113
|
+
`HANDOFF.md` records the state, the single reachable path to the Mac, and the traps this codebase
|
|
114
|
+
has already fallen into.
|
|
115
|
+
|
|
116
|
+
### Verified
|
|
117
|
+
|
|
118
|
+
**585 chatops cases pass.** The 12 agent-suite failures are **pre-existing and environmental** —
|
|
119
|
+
measured against an untouched clone of the same commit, both give **147 tests / 133 pass / 12 fail /
|
|
120
|
+
2 skipped**, identical, so this change is not the cause.
|
|
121
|
+
|
|
122
|
+
### ⚠️ Still not user-facing
|
|
123
|
+
|
|
124
|
+
**The plugin does not yet spawn the agent or hand it the key**, so with the default configuration a
|
|
125
|
+
new PC still will not auto-join. The mechanism is complete and verified; that last wire is what
|
|
126
|
+
makes it something a user experiences rather than something a test proves.
|
|
127
|
+
|
|
128
|
+
## [0.8.1] — 2026-10-14
|
|
129
|
+
|
|
130
|
+
**A machine on the same account joins the same fleet with no human step** — the requirement that
|
|
131
|
+
had no implementation behind it, and the bug that made it visible.
|
|
132
|
+
|
|
133
|
+
### The reported bug, and what it actually was
|
|
134
|
+
|
|
135
|
+
A new PC signed into the same DeepSeek account did **not** join the same fleet. Traced rather than
|
|
136
|
+
guessed, the cause was four gaps stacked:
|
|
137
|
+
|
|
138
|
+
* the plugin computed its SN, derived `fleet` from the account code, **logged it, and never used it
|
|
139
|
+
for anything**;
|
|
140
|
+
* nothing called `/v1/pair`;
|
|
141
|
+
* `autoStartAgent` defaults to **false**, so no agent started;
|
|
142
|
+
* and even with an agent, membership needed a **human-typed one-time pairing code**.
|
|
143
|
+
|
|
144
|
+
So "same account joins the same fleet automatically" was a stated requirement with no code path.
|
|
145
|
+
|
|
146
|
+
### Why the obvious fix is the wrong one
|
|
147
|
+
|
|
148
|
+
The obvious fix is to use `accountCode` as the credential. `account.mjs` documents the trade
|
|
149
|
+
honestly — *"the cost of 32 bits, stated rather than hidden"* — and for **grouping** 32 bits is
|
|
150
|
+
fine. As a **secret** it is not: 2^32 is brute-forceable, so authorising a join with it would let
|
|
151
|
+
anyone enumerate their way into any fleet.
|
|
152
|
+
|
|
153
|
+
**The bug was a conflation: a grouping identifier was doing duty as a secret.**
|
|
154
|
+
|
|
155
|
+
### The fix
|
|
156
|
+
|
|
157
|
+
The machines of one account share something an outsider cannot obtain — the ability to read
|
|
158
|
+
`userId` — so the fleet secret is derived from it:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
fleetKey = HMAC-SHA256(userId, "w2m-fleet-v1") 256 bits, same-account machines only
|
|
162
|
+
fleetId = SHA-256(fleetKey) what the relay groups by
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Presenting the key is the proof.** The relay *computes* a fleet id from the key it is given
|
|
166
|
+
rather than believing a claimed one, so a device can only land in the fleet whose key it holds.
|
|
167
|
+
No trust-on-first-use, no operator configuration, no account lookup, nothing to forge — and a
|
|
168
|
+
device without the key forms a different, empty fleet instead of joining someone else's.
|
|
169
|
+
|
|
170
|
+
`deriveAccountFleetKey` **refuses anything shorter than 16 characters**, and the refusal is asserted
|
|
171
|
+
in a test rather than left to a comment: passing the 8-hex account code by mistake would otherwise
|
|
172
|
+
produce a key that *looks* strong and is not — the worst kind of credential failure, because it is
|
|
173
|
+
silent.
|
|
174
|
+
|
|
175
|
+
### The relay side
|
|
176
|
+
|
|
177
|
+
`deviceFleetFields` gains a third source, ordered by authority:
|
|
178
|
+
|
|
179
|
+
| source | who decided |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `pairing_code` | the operator, by issuing the code |
|
|
182
|
+
| **`fleet_key`** | **the account, by derivation** — the auto-join path |
|
|
183
|
+
| `request` | the relay operator, via `--fleet` |
|
|
184
|
+
|
|
185
|
+
A **malformed key does not fall through** to the relay's own fleet. Falling through would be the
|
|
186
|
+
silent wrong answer: a device that meant to join its account would be placed in an unrelated fleet
|
|
187
|
+
and told nothing. It is refused, with the reason recorded on the device record.
|
|
188
|
+
|
|
189
|
+
This also removes the deployment problem recorded in v0.7.9: `--fleet` no longer needs an operator
|
|
190
|
+
to compute and hand-carry a 32-bit code, because the relay needs to know **nothing** about any
|
|
191
|
+
account.
|
|
192
|
+
|
|
193
|
+
### Verified
|
|
194
|
+
|
|
195
|
+
* **21 cases** for the derivation: same account agrees, different accounts differ, the 32-bit code
|
|
196
|
+
is refused, the wire form round-trips, a client and a relay independently derive the same id, and
|
|
197
|
+
comparison is constant-time.
|
|
198
|
+
* The derived bytes are **pinned**, not self-compared — a regression would silently split every
|
|
199
|
+
existing fleet into two halves that cannot see each other, with no error anywhere.
|
|
200
|
+
* **223 cases** across the relay, wiring, scoping and join suites, unchanged.
|
|
201
|
+
|
|
202
|
+
### ⚠️ What is still not wired
|
|
203
|
+
|
|
204
|
+
The **derivation and the relay's acceptance** are done. The pieces that make a real machine use
|
|
205
|
+
them are not:
|
|
206
|
+
|
|
207
|
+
* `index.mjs` does not yet derive or expose the fleet key;
|
|
208
|
+
* the agent does not yet present it at pairing;
|
|
209
|
+
* `autoStartAgent` still defaults to false.
|
|
210
|
+
|
|
211
|
+
**Until those land, a new PC still will not auto-join.** The credential exists and is accepted; the
|
|
212
|
+
client does not yet offer it.
|
|
213
|
+
|
|
7
214
|
## [0.8.0] — 2026-10-14
|
|
8
215
|
|
|
9
216
|
**The first release on npm since 0.6.3.** Eight versions of work — account identity, fleet
|
package/HANDOFF.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Handoff — where this stands, and what to do next
|
|
2
|
+
|
|
3
|
+
Written at the point of switching development from the Windows machine to the Mac. Everything here
|
|
4
|
+
is **committed and pushed**; nothing depends on the machine that wrote it.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The Mac is reachable by exactly one path
|
|
9
|
+
|
|
10
|
+
Discovered the hard way — four blind attempts failed first, each diagnosed by encoding state into
|
|
11
|
+
an **exit code**, because `unverifiable` tasks do not return their stdout and that is the only
|
|
12
|
+
readable channel from that machine:
|
|
13
|
+
|
|
14
|
+
| target | from the Mac |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `raw.githubusercontent.com` | ❌ |
|
|
17
|
+
| `github.com` release tarball | ❌ |
|
|
18
|
+
| `http://202.182.123.154:8899/pub` | ❌ |
|
|
19
|
+
| `http://202.182.123.154:8787` | ❌ |
|
|
20
|
+
| **`http://geefish.cn/pub/...`** | ✅ **the only one** |
|
|
21
|
+
|
|
22
|
+
Port 80, behind the nginx that already fronts the relay. A `location ^~ /pub/` was added to that
|
|
23
|
+
same server block, so releases travel the one path already proven to work. The nginx change is
|
|
24
|
+
tested with `nginx -t` **before** loading and restores its backup on any failure, because a broken
|
|
25
|
+
config there takes the whole fleet down, not just a download.
|
|
26
|
+
|
|
27
|
+
## Updating the Mac
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
curl -fsSL http://geefish.cn/pub/mac-self-update.mjs -o ~/w2m-self-update.mjs
|
|
31
|
+
curl -fsSL http://geefish.cn/pub/w2m-0.8.2.tgz -o ~/w2m-0.8.2.tgz
|
|
32
|
+
node ~/w2m-self-update.mjs --tarball ~/w2m-0.8.2.tgz \
|
|
33
|
+
--sha256 8ff593845612a4699c11c03ab22bbd81afa02f93b865d2e444badc8b62cfb60a
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`--sha256` is not optional in spirit: the supervisor **refuses** a tarball whose checksum differs,
|
|
37
|
+
before it touches anything.
|
|
38
|
+
|
|
39
|
+
The supervisor is **detached** on purpose — it has to survive the DSH it restarts. Seven steps,
|
|
40
|
+
ordered so the previous state is restorable **without the network**: verify the checksum, back up in
|
|
41
|
+
place, check the staged tree for its entry point, swap, restart, self-check, and **roll back
|
|
42
|
+
automatically** on failure. Every step is appended to `~/w2m-self-update-report.json` as it happens,
|
|
43
|
+
so a process killed mid-way still says where it stopped.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Current version, everywhere
|
|
48
|
+
|
|
49
|
+
| machine | version |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Windows | 0.8.1 |
|
|
52
|
+
| **Linux relay host** | **0.8.2** |
|
|
53
|
+
| **Mac** | **0.8.0 — needs the update above** |
|
|
54
|
+
| npm | 0.8.0 |
|
|
55
|
+
| GitHub | v0.8.0 released; 0.8.1 and 0.8.2 committed, **no release tag yet** |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## What was fixed, and the bug that is now closed
|
|
60
|
+
|
|
61
|
+
**The reported bug:** a new PC signed into the same DeepSeek account did not join the same fleet.
|
|
62
|
+
|
|
63
|
+
**The cause, traced rather than guessed** — four gaps stacked:
|
|
64
|
+
|
|
65
|
+
1. the plugin computed its SN, derived `fleet` from the account code, **logged it, and never used
|
|
66
|
+
it for anything**;
|
|
67
|
+
2. nothing called `/v1/pair`;
|
|
68
|
+
3. `autoStartAgent` defaults to **false**;
|
|
69
|
+
4. and even with an agent, membership needed a **human-typed one-time pairing code**.
|
|
70
|
+
|
|
71
|
+
So *"same account joins the same fleet automatically"* was a requirement with **no code path**.
|
|
72
|
+
|
|
73
|
+
**Why the obvious fix would have been wrong:** using `accountCode` as the credential. It is
|
|
74
|
+
`sha256(userId)` truncated to **32 bits** — documented as fine for *grouping*, brute-forceable as a
|
|
75
|
+
*secret*. The bug was a **conflation: a grouping identifier doing duty as a secret.**
|
|
76
|
+
|
|
77
|
+
**The fix** derives the secret from what same-account machines share and outsiders cannot obtain —
|
|
78
|
+
the ability to read `userId`:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
fleetKey = HMAC-SHA256(userId, "w2m-fleet-v1") 256 bits
|
|
82
|
+
fleetId = SHA-256(fleetKey) what the relay groups by
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Presenting the key is the proof.** The relay *computes* a fleet id from the key rather than
|
|
86
|
+
believing a claimed one, so a device can only land in the fleet whose key it holds. No
|
|
87
|
+
trust-on-first-use, no operator configuration, no account lookup, nothing to forge — and a device
|
|
88
|
+
without the key forms a different, empty fleet rather than joining someone else's.
|
|
89
|
+
|
|
90
|
+
This also removes the deployment problem recorded in v0.7.9: **`--fleet` no longer needs an operator
|
|
91
|
+
to compute and hand-carry a 32-bit code**, because the relay needs to know *nothing* about any
|
|
92
|
+
account.
|
|
93
|
+
|
|
94
|
+
**Verified end to end:** an agent given **only** `--fleet-key`, against a relay with **no fleet of
|
|
95
|
+
its own**, pairs and lands in the account-derived fleet (`d5bb1b9ddf3faad4`, `source=fleet_key`).
|
|
96
|
+
|
|
97
|
+
### The bug that would have made it work exactly once
|
|
98
|
+
|
|
99
|
+
`resolve()` returned early when an SN was already stored and never read the account at all. Right
|
|
100
|
+
for the SN — it must not be regenerated, or every restart looks like a new device — and **wrong for
|
|
101
|
+
the key**: a machine has a stored SN from its *second* start onward, so it would have derived a key
|
|
102
|
+
once and never again.
|
|
103
|
+
|
|
104
|
+
**The auto-join would have worked on a machine's first boot and silently stopped afterwards** — the
|
|
105
|
+
hardest kind of bug to see, because it works when you test it. Found by asking what the stored-SN
|
|
106
|
+
path actually does, not by reading the happy path.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## What is wired, and the one thing that is not
|
|
111
|
+
|
|
112
|
+
| piece | state |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `account.mjs` derives the key on **every** resolve, both paths | ✅ |
|
|
115
|
+
| `index.mjs` exposes it as `fleetKey` (the key, never the account id) | ✅ |
|
|
116
|
+
| `agent.mjs` `pair(code, userId, fleetKey)` | ✅ |
|
|
117
|
+
| `w2m-localside --fleet-key` | ✅ |
|
|
118
|
+
| relay derives the fleet from the key | ✅ |
|
|
119
|
+
| **the plugin spawns the agent and passes its key** | ❌ **not wired** |
|
|
120
|
+
|
|
121
|
+
**So with the default configuration a new PC still will not auto-join.** The mechanism is complete
|
|
122
|
+
and verified; the plugin does not yet start the agent or hand it the key. Until that lands, either:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
w2m-localside --rabbit http://geefish.cn --fleet-key <KEY> --project <dir> --name <name> --state <dir>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
or set `autoStartAgent` and wire the key through.
|
|
129
|
+
|
|
130
|
+
**This is the next task.** It is also the last one: after it, installing the plugin and signing into
|
|
131
|
+
the same account is the whole of the user-facing procedure.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Traps this codebase has already fallen into — worth knowing before editing
|
|
136
|
+
|
|
137
|
+
Each of these cost a debugging round, and most were **silent**:
|
|
138
|
+
|
|
139
|
+
* **A presence check reads `undefined` as "provided".** `createILinkChannel` passed
|
|
140
|
+
`{ fetch: undefined }` and threw on mount (v0.7.0). The same shape appears in the pairing body:
|
|
141
|
+
an absent code must be an **absent key**, not a `null` one.
|
|
142
|
+
* **An anchor in a `patch` file is an assertion, not a rename.** A mismatched `name:` makes the
|
|
143
|
+
Loader skip the whole patch *silently*.
|
|
144
|
+
* **`statSync` on an injected path defeats the injection.** The secrets guard called it
|
|
145
|
+
unconditionally, so an injected reader was never reached and the scan reported **zero findings
|
|
146
|
+
across zero files** — a guard that is silently off.
|
|
147
|
+
* **`createHmac('sha256', Buffer.alloc(0))` is not `createHash('sha256')`.** A signature over the
|
|
148
|
+
wrong bytes verifies nowhere and says nothing about why.
|
|
149
|
+
* **A test that hard-codes a rule name measures the name, not the behaviour** — renaming broke it.
|
|
150
|
+
* **`login` confirms as `confirmed`, not `scaned`.** Waiting on `scaned` misses the token entirely.
|
|
151
|
+
* **The relay needs two credentials.** The operator token dispatches; the **device token** reads the
|
|
152
|
+
verdict. Supplying one gives a *confident wrong answer* — `判定:unknown` — rather than an error.
|
|
153
|
+
|
|
154
|
+
## Reading the Mac without stdout
|
|
155
|
+
|
|
156
|
+
Exit codes are the only channel. Encode state into them and probe:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// on the Mac, via node -e
|
|
160
|
+
process.exit(tgzSize < 700000 ? 10 : sup.done ? (sup.ok ? 0 : 13) : 12);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Ground rules that have held
|
|
164
|
+
|
|
165
|
+
* `deploy/server/` holds the units **as the live host runs them**; the deploy script's step order
|
|
166
|
+
*is* the safety argument.
|
|
167
|
+
* `/var/lib/w2m/rabbit/` — `operator-token.txt` and the ledger — **must never be committed**.
|
|
168
|
+
`.gitignore` refuses the paths; `scripts/scan-for-secrets.mjs` reads the **content** of every
|
|
169
|
+
tracked file, because a `.gitignore` refuses a path while the ways a credential escapes are
|
|
170
|
+
pasting one into a source file, a fixture, or an example.
|
|
171
|
+
* **A deploy cannot be an outage.** The relay runs **unscoped**, so its four devices keep working.
|
|
172
|
+
Scoping it requires an account code and a re-pair of every device.
|
|
173
|
+
* **Never publish to npm without the token**, and rotate it: it has been pasted into a chat log.
|