@twinsearth/w2m-dsh-plugin 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/docs/TEST-LEDGER.md +1 -0
- package/docs/TEST-MATRIX.md +9 -4
- package/package.json +1 -1
- package/src/chatops/ilink-fleet-bridge.mjs +41 -0
- package/src/plugin/tools.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,96 @@ 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.48.0] — 2026-10-15
|
|
8
|
+
|
|
9
|
+
**`--dry-run` dispatched. Two dry runs sent a command to five machines, and one made a real task.**
|
|
10
|
+
|
|
11
|
+
### The defect, measured before it was described
|
|
12
|
+
|
|
13
|
+
The bridge guarded exactly one thing under `--dry-run`: the **reply**. The dispatch ran unconditionally.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
[15:13:57] dry run : true
|
|
17
|
+
[15:15:00] dispatched task 01M50231AYJYDD41WSPMS9PRM3
|
|
18
|
+
[15:15:50] verdict for …: partial
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**That is a real task record with five real leases, produced by a run whose flag promised nothing would
|
|
22
|
+
happen.** A rehearsal executed commands on production machines — and it was found by accident, while draining
|
|
23
|
+
a message queue, not by a test.
|
|
24
|
+
|
|
25
|
+
### Why the fix is "skip the dispatch" and not "rename the flag"
|
|
26
|
+
|
|
27
|
+
**This repository already fixes the meaning of the word.** `update-install`'s `dryRun` is asserted to **call no
|
|
28
|
+
package manager and write nothing**, and `auto-update`'s is asserted to **write nothing to the profile**. So a
|
|
29
|
+
second `dry-run` meaning "do the dangerous part anyway" is not a naming preference — **it is two flags with one
|
|
30
|
+
name and opposite meanings, and the bridge held the unsafe one.**
|
|
31
|
+
|
|
32
|
+
**Renaming would have left the flag. Aligning keeps the word and removes the hazard**, and a reader who knows
|
|
33
|
+
`dryRun` from anywhere else in this project is now already right about this one.
|
|
34
|
+
|
|
35
|
+
### Why the guard is a function in `lib`, not an `if` in the script
|
|
36
|
+
|
|
37
|
+
`scripts/` is **not in `package.json#files`**, so nothing there is covered by a test that ships. A safety
|
|
38
|
+
property expressed as one line inside an untested script is a property nobody re-checks. `dryRunPolicy` is
|
|
39
|
+
exported and asserted beside the wire shapes — **a script is not where a guard should live if the guard is the
|
|
40
|
+
point.**
|
|
41
|
+
|
|
42
|
+
The guard sits at the **top of the only function that reaches `fleet.run`**, not at its call site, because a
|
|
43
|
+
guard at the call site is one edit away from being bypassed by a second caller.
|
|
44
|
+
|
|
45
|
+
### A name collision that made my own test wrong
|
|
46
|
+
|
|
47
|
+
I asserted that `dryRun: 'true'` must not dispatch. **It does dispatch — the code was right and the test was
|
|
48
|
+
wrong**, and the cause is two different things sharing one word:
|
|
49
|
+
|
|
50
|
+
| | meaning |
|
|
51
|
+
|---|---|
|
|
52
|
+
| the CLI **flag** `--dry-run` | **present-means-true** (`argv.includes('--dry-run')`) |
|
|
53
|
+
| the **parameter** `dryRun` | **defaults to false**, which is what `update-install` means by it |
|
|
54
|
+
|
|
55
|
+
`dryRunPolicy` takes the parameter, and the script always hands it a boolean. The assertion is now the strict
|
|
56
|
+
one — **only `true` turns the dispatch off, so a future caller cannot open the path with a truthy value.**
|
|
57
|
+
|
|
58
|
+
**5 new tests, 0 failures.** One of them reads the script and asserts the guard appears **before** the only
|
|
59
|
+
call that can reach the fleet — because a test of the pure function alone would pass while the script still
|
|
60
|
+
dispatched, **which is precisely the shape of the original defect.**
|
|
61
|
+
|
|
62
|
+
## [0.47.0] — 2026-10-15
|
|
63
|
+
|
|
64
|
+
**Correcting my own counts: 0.46.0 said 54/32/22, the table says 61/53/8.**
|
|
65
|
+
|
|
66
|
+
0.46.0's entry claimed *"**54 rows** across twelve sections"*, *"**32 of 54** rows are executed; **22** are not"*.
|
|
67
|
+
**All three numbers were typed by hand, and all three were wrong.** Read from `docs/TEST-MATRIX.md`:
|
|
68
|
+
|
|
69
|
+
| | 0.46.0 said | the table says |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| rows | 54 | **61** |
|
|
72
|
+
| executed | 32 | **53** |
|
|
73
|
+
| not executed | 22 | **8 never run**, plus **11 blocked**, **1 skipped**, **2 refuted** |
|
|
74
|
+
|
|
75
|
+
### The cause is that I merged two words that are not the same
|
|
76
|
+
|
|
77
|
+
**`阻塞` means the command RAN and the environment stopped it. `待执行` means it never ran at all.** I collapsed
|
|
78
|
+
both into "not executed" — **and a count that merges two categories is wrong in both directions at once**: it
|
|
79
|
+
overstates what was never attempted and understates what was attempted and blocked.
|
|
80
|
+
|
|
81
|
+
**The matrix now marks `阻塞` as "the command executed" explicitly, and carries a note recording this mistake**,
|
|
82
|
+
because the distinction is the one a reader needs in order to tell "we have not tried" from "we tried and were
|
|
83
|
+
stopped".
|
|
84
|
+
|
|
85
|
+
### It is the same error the matrix itself documents, one level up
|
|
86
|
+
|
|
87
|
+
The matrix's own rule is that **a row's result may only be changed by re-running its `Instance` and reading the
|
|
88
|
+
output**, and that a count copied from an older plan becomes more true-looking each time it is copied. **I then
|
|
89
|
+
wrote the headline counts of the document from memory instead of reading the document.** The rule was in the
|
|
90
|
+
file I was writing.
|
|
91
|
+
|
|
92
|
+
**So the counts are no longer asserted anywhere by hand.** They are read from the table, and the reading is
|
|
93
|
+
recorded: `61 = 39 通过 + 8 待执行 + 11 阻塞 + 1 跳过 + 2 证伪`.
|
|
94
|
+
|
|
95
|
+
**No new tests.** What changed is three numbers and the reason they drifted.
|
|
96
|
+
|
|
7
97
|
## [0.46.0] — 2026-10-15
|
|
8
98
|
|
|
9
99
|
**The test matrix the plans asked for — with every row it could not earn left as `待执行`, and the reason named.**
|
package/docs/TEST-LEDGER.md
CHANGED
|
@@ -118,6 +118,7 @@ was not re-measured keeps its old date — **a stale date is information, and qu
|
|
|
118
118
|
| `GATE-002` | an accepted orphan that is no longer an orphan fails — the list cannot go stale | **PASS** | 2026-10-15 | `field-authority` was removed when 0.41.0 gave it a caller, **because this check went red** |
|
|
119
119
|
| `GATE-003` | `layer-check` prints `clean` **and exits 0** | **PASS** | 2026-10-15 | after the fix. **Before it printed `clean:` and exited `1`** — every reader trusting the status read a passing check as a failure; the pre-flight never noticed because its step reads the *printed text* |
|
|
120
120
|
| `HARNESS-001` | a hand-rolled test harness measures the harness | **PASS (as a warning)** | 2026-10-15 | **41 failures** running every suite in one process with `--experimental-test-isolation=none`, vs **1** via the project's own entry. The other 38 were `mdns-discover`'s fetch stub leaking into `outbound-only` |
|
|
121
|
+
| `HARNESS-002` | **a count typed by hand drifts from the table it describes** | **FAIL, then corrected** | 2026-10-15 | CHANGELOG 0.46.0 claimed *"54 rows / 32 executed / 22 not"*. Read from `TEST-MATRIX.md`: **61 rows / 53 executed / 8 never run / 11 blocked / 1 skipped / 2 refuted.** I had merged **`阻塞`** (the command ran and the environment stopped it) with **`待执行`** (never run) — so 61 became 54 and 8 became 22 |
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
package/docs/TEST-MATRIX.md
CHANGED
|
@@ -13,12 +13,16 @@
|
|
|
13
13
|
|---|---|
|
|
14
14
|
| **通过** | 已执行,且期望成立 |
|
|
15
15
|
| **失败** | 已执行,且期望不成立 |
|
|
16
|
-
| **阻塞** |
|
|
16
|
+
| **阻塞** | **命令执行了**,结果被环境挡住(**并写明被什么挡住**) |
|
|
17
17
|
| **跳过** | 平台不适用,套件自报原因 |
|
|
18
18
|
| **待执行** | **未执行**。必须写明为什么,否则这一行只是噪声 |
|
|
19
19
|
| **证伪** | 计划中的一条主张被测量推翻 |
|
|
20
20
|
|
|
21
21
|
> **一行"通过"必须能追到一个 `Instance`。追不到的,写"待执行"。** 这一条比表里任何数字都重要——它是本表唯一不能违反的规则。
|
|
22
|
+
>
|
|
23
|
+
> **"阻塞"与"待执行"不是同一件事,而我把它们混为一谈过一次。** 阻塞 = 命令**执行了**,被环境挡住;待执行 = **从未运行**。
|
|
24
|
+
> 在 CHANGELOG 0.46.0 里我手数成"54 行 / 22 未执行",把两者合并了——**而从本表读出来是 61 行 / 8 待执行 / 11 阻塞**。
|
|
25
|
+
> **手数的计数会漂移,所以本节的所有数字都是从表格里读出来的。**
|
|
22
26
|
|
|
23
27
|
---
|
|
24
28
|
|
|
@@ -159,9 +163,10 @@
|
|
|
159
163
|
| 热修复 | 对应失败项 + 相邻模块回归 | 修复后必须回归通过 |
|
|
160
164
|
| 季度/半年 | 全表重跑一次 | 防止环境漂移导致漏测 |
|
|
161
165
|
|
|
162
|
-
###
|
|
166
|
+
### 这张表当前未通过的分布,以及它们是不是同一件事
|
|
163
167
|
|
|
164
|
-
**
|
|
168
|
+
**61 项里 53 项已执行,8 项从未运行,11 项被执行但被环境挡住,1 项平台跳过。**
|
|
169
|
+
未通过的**不是一堆独立问题,而是 4 个阻塞源**:
|
|
165
170
|
|
|
166
171
|
| 阻塞源 | 卡住的项 | 解锁条件 |
|
|
167
172
|
|---|---|---|
|
|
@@ -170,7 +175,7 @@
|
|
|
170
175
|
| **没有可用配对码** | `TC-PAIR-003/004/005` | 一次可控的配对实验;会惊动编队,需你同意 |
|
|
171
176
|
| **需要真实中断或外部资源** | `TC-UPD-001/002/004/005/007` | 一次真实更新检查;`TC-SEC-007` 需要 macOS/Linux |
|
|
172
177
|
|
|
173
|
-
>
|
|
178
|
+
> **未通过的项 ≠ 未通过的项数那么多风险。** 它们集中在 4 处,其中**两处**(中继升级、唤醒 Windows 机器)是可以立刻解决的。
|
|
174
179
|
|
|
175
180
|
---
|
|
176
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
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",
|
|
@@ -25,6 +25,47 @@
|
|
|
25
25
|
|
|
26
26
|
import { parseInvocation, renderVerdict, renderDispatchFailure } from './run-command.mjs';
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* What a run with `--dry-run` set is allowed to do.
|
|
30
|
+
*
|
|
31
|
+
* ## The defect this exists to make unreachable
|
|
32
|
+
*
|
|
33
|
+
* `scripts/ilink-fleet-bridge.mjs` guarded exactly one thing under `--dry-run`: the **reply**. The
|
|
34
|
+
* dispatch itself ran unconditionally, so **two dry runs sent `node--version` to all five machines**
|
|
35
|
+
* — and one of them produced a real task record with five real leases. The flag's name promises that
|
|
36
|
+
* nothing happens, and the promise was false in the direction that matters: **a rehearsal executed
|
|
37
|
+
* commands on production machines.**
|
|
38
|
+
*
|
|
39
|
+
* ## Why "skip the dispatch" rather than "rename the flag"
|
|
40
|
+
*
|
|
41
|
+
* This repository already fixes the meaning of the word. `update-install`'s `dryRun` is asserted to
|
|
42
|
+
* **write nothing and call no package manager**, and `auto-update`'s is asserted to **write nothing to
|
|
43
|
+
* the profile**. So a second `dry-run` that means "do the dangerous part anyway" is not a naming
|
|
44
|
+
* preference — it is two flags with one name and opposite meanings, and the bridge held the unsafe one.
|
|
45
|
+
*
|
|
46
|
+
* **Renaming would have left the flag. Aligning keeps the word and removes the hazard**, and it means
|
|
47
|
+
* a reader who knows `dryRun` from anywhere else in this project is already right about this one.
|
|
48
|
+
*
|
|
49
|
+
* ## Why this is a function rather than an `if` in the script
|
|
50
|
+
*
|
|
51
|
+
* `scripts/` is not in `package.json#files`, so nothing there is covered by a test that ships. A safety
|
|
52
|
+
* property expressed as one line inside an untested script is a property nobody re-checks. As an
|
|
53
|
+
* exported function with a name, it is asserted in `chatops-ilink-fleet-bridge.test.mjs` beside the
|
|
54
|
+
* wire shapes — **a script is not where a guard should live if the guard is the point.**
|
|
55
|
+
*
|
|
56
|
+
* @param {{ dryRun?: boolean }} [options]
|
|
57
|
+
* @returns {{ dispatch: boolean, reply: boolean, reason: string|null }}
|
|
58
|
+
*/
|
|
59
|
+
export function dryRunPolicy(options = {}) {
|
|
60
|
+
if (options.dryRun !== true) return { dispatch: true, reply: true, reason: null };
|
|
61
|
+
return {
|
|
62
|
+
dispatch: false,
|
|
63
|
+
reply: false,
|
|
64
|
+
// Carried so the caller logs *why* nothing happened rather than printing a bare "[dry-run]".
|
|
65
|
+
reason: 'dry run: the fleet was not dispatched to, and no reply was sent',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
/** The header every authenticated call needs; undocumented and mandatory. */
|
|
29
70
|
export const AUTH_TYPE = 'ilink_bot_token';
|
|
30
71
|
export const APP_ID = 'bot';
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -208,7 +208,7 @@ const MAX_STAGES = 16;
|
|
|
208
208
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
209
209
|
* overrides it at runtime for tests and packagers.
|
|
210
210
|
*/
|
|
211
|
-
const PLUGIN_VERSION = '0.
|
|
211
|
+
const PLUGIN_VERSION = '0.48.0';
|
|
212
212
|
|
|
213
213
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
214
214
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|