@twinsearth/w2m-dsh-plugin 0.26.0 → 0.27.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 CHANGED
@@ -4,6 +4,51 @@ 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.27.0] — 2026-10-15
8
+
9
+ **A blocked fleet reports itself. The approval count is in `w2m_status`, and it arrives as a note.**
10
+
11
+ A queue visible only to someone who remembers to look at it is a queue that stays full, and **a fleet blocked
12
+ on approval looks exactly like an idle fleet.** `w2m_status` now carries an `approvals` block and, when
13
+ anything is waiting, a note placed **first** — ordering being the only emphasis a list of notes has:
14
+
15
+ > *"3 dispatch(es) are WAITING FOR AN APPROVER and will not run until one answers; read them with
16
+ > `w2m_approvals` (1 past their deadline); the oldest has been waiting 240s"*
17
+
18
+ ### `constructed: false` is not `pending: 0`
19
+
20
+ A machine that has never tripped the permission gate should not be described as having an empty queue **as
21
+ though that were a measurement.** The block says which it is, and the note is emitted only for real pending
22
+ work.
23
+
24
+ ### The whole chain, across three tools, asserted end to end
25
+
26
+ An ordinary actor (49) attempts a write → refused with `W2M_PERMISSION_NEEDS_APPROVAL` **carrying the queued
27
+ id** → `w2m_status` reports `pending: 1` and the blocking note **without being asked about approvals** → a
28
+ captain settles it → the signal clears.
29
+
30
+ ### The default approver is the requester, and the index says so
31
+
32
+ The test discovered this rather than assuming it: a request records its subject as the machine's name, and an
33
+ approver who **defaults to that same name is the subject** — so `may_act` is false with
34
+ `APPROVAL_SELF_APPROVAL`. That is correct, and it is a real consequence of the defaults rather than a bug. The
35
+ test now names a distinct approver and **asserts the refusal first**, so the self-approval rule is pinned
36
+ rather than sidestepped.
37
+
38
+ ### A payload silently dropped, caught by a test that found nothing
39
+
40
+ `W2MError` accepts only `code`, `message`, `hint`, `detail`, `cause` and `reason_code`. The first version
41
+ passed `approval_id` and `request` as **top-level options**, so they were **discarded without complaint** and
42
+ the refusal told a caller to await approval **with no way to name what they were waiting for.** They now
43
+ travel in `detail`, and the constructor's accepted keys are named in a comment beside the call.
44
+
45
+ Two of my own errors in this round were the same shape as the ones before it: a block hoisted into the **wrong
46
+ scope** (module level instead of inside `execute`, which `node --check` accepted because the name existed
47
+ *somewhere*), and a syntactic fix that put a statement **inside an object literal**. Both were caught by
48
+ running the suite, not by reading.
49
+
50
+ **1 new cross-tool test, 0 failures.**
51
+
7
52
  ## [0.26.0] — 2026-10-15
8
53
 
9
54
  **`w2m_approvals`: the approval chain is reachable by an operator, not only by the suite.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "0.26.0",
3
+ "version": "0.27.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",
@@ -193,7 +193,7 @@ const MAX_STAGES = 16;
193
193
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
194
194
  * overrides it at runtime for tests and packagers.
195
195
  */
196
- const PLUGIN_VERSION = '0.26.0';
196
+ const PLUGIN_VERSION = '0.27.0';
197
197
 
198
198
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
199
199
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -1118,6 +1118,7 @@ function safeFilePart(value) {
1118
1118
  */
1119
1119
  function canonicalJson(value) {
1120
1120
  if (value === undefined) return 'null';
1121
+
1121
1122
  if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';
1122
1123
  if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
1123
1124
  const keys = Object.keys(value).sort();
@@ -3087,9 +3088,26 @@ function readAggregate(body) {
3087
3088
  * @param {object} p2p The block from {@link readP2PStatus}.
3088
3089
  * @returns {string[]}
3089
3090
  */
3090
- function statusNotes(relay, rtt, cfg, p2p) {
3091
+ function statusNotes(relay, rtt, cfg, p2p, approvalsNote = null) {
3091
3092
  const notes = [];
3092
3093
 
3094
+ /**
3095
+ * v0.26.0: blocked work announces itself.
3096
+ *
3097
+ * The whole reason the count is in `w2m_status` at all. A note is what a caller reads without asking a
3098
+ * further question, and "three dispatches are waiting for an approver" is the single most actionable
3099
+ * sentence this tool can emit: the fleet is not idle, it is stopped, and it is stopped on a person.
3100
+ *
3101
+ * It is placed first because ordering is the only emphasis a list of notes has.
3102
+ */
3103
+ if (approvalsNote !== null && Number.isFinite(approvalsNote.pending) && approvalsNote.pending > 0) {
3104
+ notes.push(
3105
+ `${approvalsNote.pending} dispatch(es) are WAITING FOR AN APPROVER and will not run until one answers; ` +
3106
+ `read them with \`w2m_approvals\`${approvalsNote.overdue > 0 ? ` (${approvalsNote.overdue} past their deadline)` : ''}` +
3107
+ `${Number.isFinite(approvalsNote.oldest_waiting_ms) ? `; the oldest has been waiting ${Math.round(approvalsNote.oldest_waiting_ms / 1000)}s` : ''}`,
3108
+ );
3109
+ }
3110
+
3093
3111
  /**
3094
3112
  * §7's one-line note, and the reason it exists at all: the shared server is a *default*, and a
3095
3113
  * default that an operator cannot see is a machine quietly talking to somebody else's host. The
@@ -3777,8 +3795,12 @@ export async function apply(ctx, config = {}) {
3777
3795
  'W2M_PERMISSION_NEEDS_APPROVAL: ' + gate.decision.detail,
3778
3796
  {
3779
3797
  hint: 'this dispatch is queued awaiting an approver; approve or reject it with w2m_approvals, then dispatch again',
3780
- approval_id: raised.approval_id,
3781
- request,
3798
+ // Carried in `detail`, not as top-level options: `W2MError` accepts only code, message, hint,
3799
+ // detail, cause and reason_code, so anything else passed here is DROPPED SILENTLY. The first
3800
+ // version of this passed `approval_id` at the top level and the test caught it by finding
3801
+ // nothing -- which is exactly the kind of omission that would otherwise have reached a user as
3802
+ // "await approval" with no way to name what they are waiting for.
3803
+ detail: { approval_id: raised.approval_id, request },
3782
3804
  },
3783
3805
  );
3784
3806
  }
@@ -4327,6 +4349,45 @@ export async function apply(ctx, config = {}) {
4327
4349
  await p2p.ensureStarted();
4328
4350
  const p2pStatus = await readP2PStatus(cfg, p2p, Number.isInteger(relay.peers_announced) ? relay.peers_announced : null);
4329
4351
 
4352
+ /**
4353
+ * v0.26.0: how much work is waiting for an approver.
4354
+ *
4355
+ * ## Why it belongs in `w2m_status` rather than only in `w2m_approvals`
4356
+ *
4357
+ * A queue that is only visible to someone who remembers to look at it is a queue that stays full,
4358
+ * and a fleet that is blocked on approval looks exactly like a fleet that is idle. This makes the
4359
+ * blocked state **report itself**, which is the difference between a tool an operator has and a
4360
+ * signal an operator receives.
4361
+ *
4362
+ * `constructed` distinguishes "nothing is waiting" from "nothing has ever asked". They are the same
4363
+ * number and very different facts, and a machine that never tripped the permission gate should not
4364
+ * be described as having an empty queue as though that were a measurement.
4365
+ */
4366
+ const approvalsBlock = (() => {
4367
+ if (approvals === null) {
4368
+ return {
4369
+ constructed: false,
4370
+ pending: null,
4371
+ detail: 'no approval has ever been requested on this machine, so no queue exists yet; this is not the same as an empty queue',
4372
+ };
4373
+ }
4374
+ const pendingRows = approvals.all();
4375
+ const overdue = pendingRows.filter((r) => r.overdue === true).length;
4376
+ return {
4377
+ constructed: true,
4378
+ pending: pendingRows.length,
4379
+ overdue,
4380
+ // The oldest request is the one whose loss costs most, so it is reported rather than left for a
4381
+ // reader to sort out of the rows.
4382
+ oldest_waiting_ms: pendingRows.length === 0
4383
+ ? null
4384
+ : Math.max(...pendingRows.map((r) => r.waiting_ms ?? 0)),
4385
+ detail: pendingRows.length === 0
4386
+ ? 'the queue exists and nothing is waiting'
4387
+ : `${pendingRows.length} dispatch(es) waiting for an approver${overdue > 0 ? `, ${overdue} overdue` : ''}`,
4388
+ };
4389
+ })();
4390
+
4330
4391
  return JSON.stringify(
4331
4392
  {
4332
4393
  ok: true,
@@ -4416,7 +4477,8 @@ export async function apply(ctx, config = {}) {
4416
4477
  : targetRoutes.reason,
4417
4478
  sessions_with_a_target: defaultSelectionStore.list().length,
4418
4479
  },
4419
- notes: statusNotes(relay, rtt, cfg, p2pStatus),
4480
+ approvals: approvalsBlock,
4481
+ notes: statusNotes(relay, rtt, cfg, p2pStatus, approvalsBlock),
4420
4482
  },
4421
4483
  null,
4422
4484
  2,