instar 1.3.103 → 1.3.105

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.
@@ -0,0 +1,72 @@
1
+ # Side-effects review — /mesh/rpc bearer-auth exemption + transfer-by-nickname activation
2
+
3
+ ## What was happening (real-hardware, 2026-05-29)
4
+
5
+ After the HTTP presence transport (B, v1.3.102) deployed to the laptop, the Mac
6
+ mini STILL showed offline in `GET /pool`. Root cause, found by probing the mini's
7
+ `/mesh/rpc` over its tunnel: **401 "Missing or invalid Authorization header"**.
8
+
9
+ `/mesh/rpc` — the §L0 machine-to-machine command transport — was sitting behind
10
+ the general API bearer-token middleware, but `MeshRpcClient.send` sends only the
11
+ signed envelope (no `Authorization` header), and it *can't* send a useful one:
12
+ each machine install holds its own `authToken`, so the sender has no token the
13
+ receiver would accept. So EVERY cross-machine call — the new presence pull AND
14
+ `deliverMessage` / `place` / `claim` / `transfer` — was rejected 401 before the
15
+ dispatcher's envelope check ever ran. The pool passed all in-process dispatcher
16
+ tests yet was completely dead over a real tunnel.
17
+
18
+ Separately, the headline "move this to <nickname>" trigger was unbuilt: the
19
+ recognizer + planner existed as pure tested units but had no caller, nothing
20
+ persisted the pin, and `SessionRouter.route()` was called with no `topicMetadata`
21
+ — so placement always fell back to least-loaded and a relocation command did
22
+ nothing.
23
+
24
+ ## The fix
25
+
26
+ 1. **`src/server/middleware.ts`** — exempt `/mesh/rpc` from the bearer gate, next
27
+ to the other signature/HMAC-authed m2m endpoints (`/a2a/inbox`, relay). The
28
+ route is authed SOLELY by its Ed25519 signed, recipient-bound envelope
29
+ (verify → RBAC → nonce, in the dispatcher). This single change is what makes
30
+ the cross-machine pool function over the wire at all.
31
+
32
+ 2. **`src/core/TopicPlacementPinStore.ts`** (new) — durable `{topicId → {preferredMachine,
33
+ pinned, updatedAt}}`. `asTopicMetadata()` shapes the pin for
34
+ `PlacementExecutor.decide`; `lastUpdatedAtMs()` feeds the rate-limit guard.
35
+
36
+ 3. **`src/commands/server.ts`** — wire the §L4 transfer-by-nickname activation
37
+ (gated on stage ≠ 'dark', BEFORE the route() interception): recognize the
38
+ command → plan → on transfer/noop set the pin AND release local ownership so
39
+ the topic's next message re-places onto the pinned machine via the
40
+ already-wired placeAndClaim → spawnOnMachine → owner-side `onAccepted` resume
41
+ path; confirm-required/reject reply to the user; the command message is
42
+ consumed. And pass `topicMetadata: pinStore.asTopicMetadata(sessionKey)` into
43
+ `route()` so placement honors the pin.
44
+
45
+ ## Blast radius
46
+
47
+ - **The auth exemption is the only change to a live, always-on code path.** It
48
+ REMOVES a gate from `/mesh/rpc`; the dispatcher's envelope verification (which
49
+ already ran on every accepted call) becomes the sole auth — strictly stronger
50
+ than a shared bearer for cross-machine (per-peer Ed25519 keys, recipient
51
+ binding, nonce replay protection). A non-mesh / single-machine agent never
52
+ receives a `/mesh/rpc` POST, so the exemption is inert for it.
53
+ - **The activation is dark-gated.** The recognizer/pin/route-metadata wiring only
54
+ runs when `_sessionPoolStage() !== 'dark'` (default dark), so it ships inert and
55
+ is exercised only in the controlled rollout/proof. The pin store is a new file
56
+ under `stateDir/session-pool/`; nothing reads it unless the activation runs.
57
+ - **No new config / route / schema / hook / skill → no migration.** Server +
58
+ middleware + one new core module; existing agents pick it up on the next
59
+ release. Both machines need it (the receiver's `/mesh/rpc` must accept the
60
+ envelope; the router needs the recognizer + pin).
61
+
62
+ ## Tests
63
+
64
+ - `tests/unit/mesh-rpc-auth-exemption.test.ts` — `/mesh/rpc` reaches its handler
65
+ with NO Authorization header (and even with a wrong bearer — the envelope is
66
+ the auth); a normal protected path still 401s without a token.
67
+ - `tests/unit/topic-placement-pin-store.test.ts` — set/get/asTopicMetadata/
68
+ lastUpdatedAtMs/clear, durable across instances, corrupt-file tolerance.
69
+ - `tests/unit/transfer-activation-wiring.test.ts` — server boot constructs the
70
+ pin store + recognizer/planner imports, recognizes on inbound before route(),
71
+ applies a transfer (sets pin + releases ownership), returns when handled, passes
72
+ the pin as `topicMetadata`, and is dark-gated.
@@ -0,0 +1,54 @@
1
+ # Side-effects review — mesh signing-key filename fix (the SEND-side break)
2
+
3
+ ## What was happening (real-hardware, 2026-05-30)
4
+
5
+ After the /mesh/rpc auth exemption (v1.3.104) deployed to BOTH machines, the Mac
6
+ mini STILL showed offline on the laptop (the router). The mini→laptop presence
7
+ pull worked (the mini's log: `recorded 1 peer(s) online: m_cc2ec651`), but the
8
+ laptop→mini direction never fired and the mini never even RECEIVED the laptop's
9
+ `session-status` call.
10
+
11
+ Root cause: the server-boot loader for `localSigningKeyPem` — the private key the
12
+ `MeshRpcClient` signs every outbound machine-to-machine command with — hard-coded
13
+ the filename `signing-private.pem`. But `MachineIdentity` writes the signing key
14
+ as `signing-key.pem` (its `SIGNING_KEY_FILE`). So a normally-created install had
15
+ `signing-private.pem` ABSENT → `localSigningKeyPem` stayed `''` → the
16
+ `MeshRpcClient` signed with an empty key → the send threw → the machine could NOT
17
+ pull presence from / deliver to / transfer to any peer. It still RECEIVED fine,
18
+ because the receive path verifies the OTHER machine's key (which it has), not its
19
+ own. That produced the exact asymmetry observed: the mini (propagated with the
20
+ non-canonical `signing-private.pem`) could send; the laptop (canonical
21
+ `signing-key.pem`) could not — two filename bugs lining up.
22
+
23
+ Confirmed by giving the laptop a `signing-private.pem` copy of its own key +
24
+ restarting: the laptop immediately began recording the mini online over HTTP, and
25
+ the pool showed both machines online.
26
+
27
+ ## The fix
28
+
29
+ - **`src/commands/server.ts`** — the loader now reads the CANONICAL
30
+ `signing-key.pem` first, falling back to the legacy/propagated
31
+ `signing-private.pem`, so BOTH layouts load a key. (The lease transport already
32
+ used `idMgr.loadSigningKey()`, which reads the canonical name — only this
33
+ MeshRpcClient loader was on the wrong filename.)
34
+
35
+ ## Blast radius
36
+
37
+ - **Pure fix to a boot-time read on the multi-machine path.** A single-machine
38
+ agent never constructs a MeshRpcClient, so it is unaffected. A machine that
39
+ already had `signing-private.pem` (propagated) keeps working via the fallback.
40
+ A machine with the canonical `signing-key.pem` (the common case) now loads its
41
+ key and can SEND cross-machine commands — the bug fix.
42
+ - **No new config / route / schema / hook / skill → no migration.** Server code;
43
+ existing agents pick it up on the next release. This is the SEND-side companion
44
+ to the /mesh/rpc auth exemption (the receive-side fix) — both were needed for
45
+ the cross-machine pool to function over the wire.
46
+ - **Separately, the propagation path should write the canonical `signing-key.pem`**
47
+ (it currently writes `signing-private.pem`); that is a propagation-side cleanup
48
+ tracked separately. This fix makes the loader tolerant of both regardless.
49
+
50
+ ## Tests
51
+
52
+ - `tests/unit/mesh-signing-key-resolution.test.ts` — the loader reads the
53
+ canonical `signing-key.pem`, tries it BEFORE the legacy `signing-private.pem`
54
+ (fallback order), and the canonical name matches `MachineIdentity.SIGNING_KEY_FILE`.