instar 1.3.987 → 1.3.989

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.
Files changed (42) hide show
  1. package/dist/core/MachineSshEndpoint.d.ts.map +1 -1
  2. package/dist/core/MachineSshEndpoint.js +5 -1
  3. package/dist/core/MachineSshEndpoint.js.map +1 -1
  4. package/dist/core/MutualSshVerifier.d.ts.map +1 -1
  5. package/dist/core/MutualSshVerifier.js +3 -1
  6. package/dist/core/MutualSshVerifier.js.map +1 -1
  7. package/dist/core/PeerAuthorizedKeys.d.ts.map +1 -1
  8. package/dist/core/PeerAuthorizedKeys.js +5 -1
  9. package/dist/core/PeerAuthorizedKeys.js.map +1 -1
  10. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  11. package/dist/core/PostUpdateMigrator.js +9 -0
  12. package/dist/core/PostUpdateMigrator.js.map +1 -1
  13. package/dist/core/SshBootstrapAdvert.d.ts.map +1 -1
  14. package/dist/core/SshBootstrapAdvert.js +3 -1
  15. package/dist/core/SshBootstrapAdvert.js.map +1 -1
  16. package/dist/core/StandingSshVerifier.d.ts.map +1 -1
  17. package/dist/core/StandingSshVerifier.js +3 -1
  18. package/dist/core/StandingSshVerifier.js.map +1 -1
  19. package/dist/core/channelRegistry.d.ts +104 -0
  20. package/dist/core/channelRegistry.d.ts.map +1 -0
  21. package/dist/core/channelRegistry.js +86 -0
  22. package/dist/core/channelRegistry.js.map +1 -0
  23. package/dist/core/instarChannels.d.ts +32 -0
  24. package/dist/core/instarChannels.d.ts.map +1 -0
  25. package/dist/core/instarChannels.js +91 -0
  26. package/dist/core/instarChannels.js.map +1 -0
  27. package/dist/scaffold/templates.d.ts.map +1 -1
  28. package/dist/scaffold/templates.js +1 -0
  29. package/dist/scaffold/templates.js.map +1 -1
  30. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  31. package/dist/server/CapabilityIndex.js +6 -0
  32. package/dist/server/CapabilityIndex.js.map +1 -1
  33. package/dist/server/routes.d.ts.map +1 -1
  34. package/dist/server/routes.js +40 -0
  35. package/dist/server/routes.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/data/builtin-manifest.json +63 -63
  38. package/src/scaffold/templates.ts +1 -0
  39. package/upgrades/1.3.988.md +55 -0
  40. package/upgrades/1.3.989.md +55 -0
  41. package/upgrades/side-effects/channel-registry.md +199 -0
  42. package/upgrades/side-effects/ssh2-cjs-named-imports.md +183 -0
@@ -0,0 +1,55 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ New `GET /channels`: every peer-to-peer channel this agent has, each with purpose, when-preferred,
9
+ cost, a live verdict and the evidence for it. Adds `src/core/channelRegistry.ts` (pure resolver) and
10
+ `src/core/instarChannels.ts` (the four channel definitions with injected probes).
11
+
12
+ The design property is not the list — a hand-built list was wrong three times in one hour. It is that
13
+ **the channel set is code-defined**, so a channel that failed to construct still gets a row saying so;
14
+ and that the verdict vocabulary (`working | broken | half-built | reachable-no-credential |
15
+ not-configured | unknown`) is wide enough to describe what was actually observed. `unknown` carries a
16
+ reason and is counted separately from broken — "could not tell" is not "is down".
17
+
18
+ Registry First awareness added to the CLAUDE.md template (new agents) and via `PostUpdateMigrator`
19
+ (existing agents).
20
+
21
+ ## What to Tell Your User
22
+
23
+ If you run more than one agent, you can now ask which ways of reaching the others actually work, and
24
+ get an honest answer in one place. Each entry says what the channel is for, when to prefer it, what it
25
+ costs, and whether it is usable right now — with the reason.
26
+
27
+ The useful part is what it does when things are wrong. A channel that failed to start still appears,
28
+ saying it failed, rather than quietly vanishing from the list. A channel that cannot work out its own
29
+ state says so instead of reporting that it is fine. And states like "this can receive but cannot send"
30
+ or "reachable, but I hold no key for it" are reported as themselves rather than squashed into working
31
+ or broken.
32
+
33
+ It does not repair anything. It makes the situation legible so a sensible choice is possible.
34
+
35
+ ## Summary of New Capabilities
36
+
37
+ - Ask which peer channels exist and which are usable right now, with the evidence for each verdict.
38
+
39
+ ## Evidence
40
+
41
+ Three refusals, restored to 25/25 green and `tsc` exit 0:
42
+
43
+ - Drop channels whose probe failed → 5 unit tests fail (`expected […] to have a length of 2 but got 1`).
44
+ - Treat an undetermined probe as healthy → 5 unit tests fail.
45
+ - Empty the ROUTE's registry → 4 integration tests fail (`expected [] to deeply equal ['a2a-telegram', …]`).
46
+
47
+ The third was run before the integration test existed: the route served zero channels and all 19 unit
48
+ tests passed. The module was guarded; the wiring was not.
49
+
50
+ ## Known limits
51
+
52
+ Machine-local (no pool-scope merge). One entry (`a2a-telegram` = half-built) is asserted rather than
53
+ probed because it is a build-time fact, guarded by a source scan scoped to `src/`. `mutual-ssh`
54
+ reports construction, explicitly NOT a completed round-trip. No dashboard rendering. Fixes no
55
+ channel. <!-- tracked: CMT-1044 -->
@@ -0,0 +1,199 @@
1
+ # Side-Effects Review — a channel registry whose defining property is that nothing can vanish from it
2
+
3
+ **Version / slug:** `channel-registry`
4
+ **Date:** `2026-07-26`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `see Phase 5`
7
+
8
+ ## Summary of the change
9
+
10
+ Operator request (topic 29723): inter-agent channel choice "feels arbitrary"; there is no registry of
11
+ what channels exist and when each is appropriate. Evidence: when the relay died I stopped and reported
12
+ that I could not reach the peer. I did not evaluate alternatives — I had no way to ask what they were
13
+ or which were alive.
14
+
15
+ Adds `src/core/channelRegistry.ts` (pure resolver + state vocabulary), `src/core/instarChannels.ts`
16
+ (this agent's four peer channels with injected probes), and `GET /channels`.
17
+
18
+ **The design is NOT the list.** A hand-built list, tried first, was wrong three times in one hour —
19
+ each time by classifying something by its label rather than its consumer. The load-bearing properties:
20
+
21
+ 1. **Absence is impossible.** The channel set is code-defined, never derived from what constructed
22
+ successfully. Row count == definition count, whatever probes do.
23
+ 2. **A channel that cannot determine liveness says so.** `unknown` carries a reason; it is never a
24
+ synonym for healthy and is counted separately from `unusable`.
25
+ 3. **The vocabulary matches reality:** `working | broken | half-built | reachable-no-credential |
26
+ not-configured | unknown`. Every value was observed on a real channel; none is speculative.
27
+
28
+ ## Refusal evidence (constraint 2)
29
+
30
+ ```
31
+ REFUSAL 1 — drop channels whose probe failed (the incident's exact shape)
32
+ × REGRESSION: a channel whose probe THROWS still gets a row
33
+ → expected [ { id: 'healthy', …(7) } ] to have a length of 2 but got 1
34
+ Tests 5 failed | 7 passed (12)
35
+
36
+ REFUSAL 2 — treat an undetermined probe as healthy (the mirror error)
37
+ × UNKNOWN is never counted as unusable — "could not tell" is not "broken"
38
+ × discriminates — it is not stuck on one verdict, and it reports the healthy case
39
+ Tests 5 failed | 7 passed (12)
40
+
41
+ REFUSAL 3 — empty the ROUTE's registry
42
+ × REGRESSION: the route serves EVERY code-defined channel — an emptied registry fails here
43
+ → expected [] to deeply equal [ 'a2a-telegram', 'mutual-ssh', …(2) ]
44
+ Tests 4 failed | 2 passed (6)
45
+ ```
46
+
47
+ Restored: **25 passed (25)**, `tsc --noEmit` exit 0.
48
+
49
+ **Refusal 3 is the finding.** I ran it BEFORE writing the integration test: the route served zero
50
+ channels and **all 19 unit tests passed.** The module was thoroughly guarded; the wiring was not. That
51
+ is this feature's own defect one layer up — a surface reporting nothing wrong because nothing asked it
52
+ anything — and without the mandatory refusal step I would have shipped it as covered.
53
+
54
+ ## Decision-point inventory
55
+
56
+ | point | classification | note |
57
+ |---|---|---|
58
+ | per-channel probe → state | `invariant` per channel | Deterministic reads of runtime state. No model call. |
59
+ | probe failure → `unknown` | `invariant` | Fails toward "undetermined", never toward healthy. |
60
+ | malformed probe result → `unknown` | `invariant` | An unrecognised shape is a failed probe, not a healthy channel. |
61
+ | 3s probe timeout | `invariant` | One wedged channel must not make the surface unanswerable. |
62
+ | `unknown` excluded from `unusable` | `invariant` | Deliberate: "could not tell" ≠ "broken". |
63
+ | a2a-telegram = `half-built` | **asserted, guarded** | Build-time fact; see §2. |
64
+
65
+ No judgment points. No LLM. Nothing gates or blocks.
66
+
67
+ ## 1. Over-block
68
+
69
+ Nothing is blocked — this is a read surface with no authority over any send. The available harm is
70
+ **misinforming a reader**, and the direction that matters is a channel reported healthier than it is.
71
+ Every failure path resolves to `unknown` or a specific unusable state; no path resolves to `working`
72
+ without a probe explicitly saying so (asserted by the garbage/nullish/throw/hang tests).
73
+
74
+ The opposite over-block — reporting a working channel as unusable — would cost me a usable path
75
+ mid-outage. Guarded by the discrimination test asserting all four verdict classes occur and by the
76
+ route test asserting a connected relay reports `working`.
77
+
78
+ ## 2. Under-block
79
+
80
+ **One entry is asserted, not measured.** `a2a-telegram` is reported `half-built / receive-only`
81
+ because its send function has no executing caller — a build-time property no runtime probe can see.
82
+ Mitigated by a source-scan test that fails the moment a caller appears, forcing the entry to be
83
+ corrected. Honest limit: it detects a caller in `src/`, not one added in scripts or templates.
84
+
85
+ **Liveness is construction, not round-trip.** `mutual-ssh` reports `working` when its runtime
86
+ constructed. That is NOT proof a peer was reached, and the detail string says so verbatim (asserted by
87
+ test). Real round-trip probing is a bigger change and is deliberately not bundled. <!-- tracked: CMT-1044 -->
88
+
89
+ **`peer-http` is honest but inert here.** No peer HTTP endpoint is configured, so it reports exactly
90
+ that rather than probing. On an agent that configures one, this needs a real probe.
91
+
92
+ **Not wired into the operator dashboard.** The data is available at `GET /channels`; nothing renders it.
93
+ Deliberate — the same separation I applied in #1656, and stated here rather than left for a reader to
94
+ notice. <!-- tracked: CMT-1044 -->
95
+
96
+ **It does not fix a single channel.** Two of three peer channels remain unusable. This makes their state
97
+ visible, which is a precondition for choosing between them, not a repair.
98
+
99
+ ## 3. Level-of-abstraction fit
100
+
101
+ The resolver is pure (`fs`-free, network-free, clock injected) and testable without a server; the
102
+ definitions own the runtime coupling; the route owns transport. Probes are injected, which is what
103
+ made the incident state reproducible in tests.
104
+
105
+ **A smarter thing already exists and is deliberately NOT duplicated.** `/capability-registry`
106
+ (`scanState: "never-observed"`) and `/capabilities` (`autoDispatch: false` rather than an omitted key)
107
+ and a discovery adapter's breaker-aware `isAvailable()` all already implement honest-absence. This is
108
+ the same idiom applied to channels — universality, not invention. A future consolidation into
109
+ `capability-registry` as host is reasonable; it is not attempted here because that surface has its own
110
+ projection/scan lifecycle this data does not share.
111
+
112
+ ## 4. Signal vs authority compliance
113
+
114
+ Pure signal, marked `advisory: true` in the response. It cannot refuse a send, select a channel, or
115
+ influence routing; nothing consumes it programmatically. `docs/signal-vs-authority.md` is satisfied
116
+ trivially. The one risk an observer carries — taking down what it watches — is closed by the probe
117
+ timeout and by the route returning a self-describing error object rather than a 500.
118
+
119
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
120
+
121
+ None introduced. Every branch is a deterministic read.
122
+
123
+ ## 5. Interactions
124
+
125
+ - **`/threadline/status`** — reads the same `ctx.threadlineRelayClient.connectionState`; unchanged, not
126
+ wrapped. Two surfaces over one source, deliberately: that one is threadline-specific, this one is
127
+ cross-channel.
128
+ - **App-level auth** — `/channels` carries no per-handler auth check, matching `/capability-registry`.
129
+ I asserted 401 first and got 200; on checking, auth is app-level middleware absent from the test
130
+ harness by construction. Recorded in the test rather than deleted (§6b).
131
+ - **`globalThis.__instarMutualSshRuntime`** — read only. Set by `server.ts` after successful construction.
132
+ - **Excluded by design:** the upstream dispatcher and the reputation/discovery client. Both were on my
133
+ first list; both are asserted absent by test so the category error cannot be re-introduced quietly.
134
+
135
+ ## 6. External surfaces
136
+
137
+ One new authenticated read route. No config key, no persisted state, no message to any user, nothing
138
+ installed into an agent home. Response contains channel ids, purposes, verdicts and evidence strings —
139
+ no credentials, no peer identifiers, no message content.
140
+
141
+ ## 6b. Operator-surface quality
142
+
143
+ Each row carries purpose / when-preferred / cost / verdict / evidence — the operator's four questions
144
+ plus the reason. Asserted non-empty for every row by test.
145
+
146
+ Wording carries two deliberate hedges earned tonight: `mutual-ssh` says construction "is not a
147
+ completed round-trip", and `not-configured` reads as a decision rather than a fault, so switched-off
148
+ infrastructure does not manufacture alarms.
149
+
150
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
151
+
152
+ **Machine-local BY DESIGN, and this is a real limitation rather than a neutral choice.** Every probe
153
+ reads this process's own state; a channel healthy here may be dead on a peer machine. No replication,
154
+ no lease interaction, no generated URL. A pool-scope merge (`?scope=pool`, dark-peer tolerant) is the
155
+ obvious extension and is not attempted here. <!-- tracked: CMT-1044 -->
156
+
157
+ ## 8. Rollback cost
158
+
159
+ Low. Two new modules, one route, three test files. No migration, no persisted state, no config default.
160
+ Reverting removes a read surface and nothing else; no caller depends on it.
161
+
162
+ ## Phase 5 — Second-pass review
163
+
164
+ Not a gate, sentinel, guard or watchdog; holds no block/allow authority; touches no session lifecycle
165
+ or trust level. The high-risk trigger list is not engaged. Author-applied lenses, disclosed:
166
+
167
+ **Adversarial — "how would I make this useless?"** Two ways, both closed and asserted: let a failed
168
+ probe delete its channel (refusal 1), or let it report healthy (refusal 2). A third — let the route
169
+ stop asking — was open until refusal 3 found it.
170
+
171
+ **"Would it have caught the incident?"** It would have shown, in one read: relay `broken`, a2a
172
+ `half-built/receive-only`, mutual-ssh `broken`, peer-http `reachable-no-credential`. Summary: zero
173
+ working. That is the answer I needed and could not get — and notably it would ALSO have stopped me
174
+ telling the operator a fallback existed, because the row says it cannot send.
175
+
176
+ **"Symptom or cause?"** Neither: it makes the state legible. The channels are still broken.
177
+
178
+ **Weakest point:** the asserted `half-built` entry. It is guarded, but a guard scoped to `src/` is
179
+ narrower than the claim it protects, and the entry is the single place where this registry could
180
+ become the confident-but-stale label it exists to prevent.
181
+
182
+ ## Post-CI addendum — three awareness registries I did not know existed
183
+
184
+ I updated `templates.ts` + `PostUpdateMigrator` by hand and believed the Agent Awareness Standard was
185
+ satisfied. CI disagreed, three times:
186
+
187
+ 1. **`feature-delivery-completeness`** — the CLAUDE.md section must be listed in `featureSections`, or
188
+ the template↔migrator parity assertion cannot see it at all.
189
+ 2. **`capabilities-discoverability`** — a new route prefix must be classified in `CapabilityIndex`:
190
+ surfaced in `/capabilities` or explicitly `INTERNAL_PREFIXES`. Its message is exactly right —
191
+ *"The lint refuses to assume; the author makes the call."* Surfaced, since agents need it.
192
+ 3. **`migrateFrameworkShadowCapabilities`** — without a marker, a Codex/Gemini agent never learns the
193
+ capability and "will improvise a weaker workaround". That is this feature's own failure mode
194
+ reproduced one layer out, and I would have shipped it.
195
+
196
+ **This is the Structure-over-Willpower case restated by accident.** I was deliberately doing the
197
+ awareness work and still missed three of five required registries. No amount of care would have closed
198
+ that gap; only the gates did. Recorded here rather than quietly fixed, because the ratio (2 found by
199
+ intent, 3 by machinery) is the useful number.
@@ -0,0 +1,183 @@
1
+ # Side-Effects Review — five modules that threw at load, and a guard that could not see it
2
+
3
+ **Version / slug:** `ssh2-cjs-named-imports`
4
+ **Date:** `2026-07-26`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `see Phase 5`
7
+
8
+ ## Summary of the change
9
+
10
+ `ssh2` is CommonJS. Five files took named VALUE imports from it
11
+ (`import { Server, utils } from 'ssh2'`). Node's ESM loader cannot statically detect
12
+ CJS named exports, so each of those modules threw at load. Observed at every server
13
+ boot, warning-level, surfaced nowhere:
14
+
15
+ ```
16
+ [mutual-ssh] initialization blocked: The requested module 'ssh2' does not provide
17
+ an export named 'Server' (server.ts:21596)
18
+ [peer-execution] disabled-grant cleanup blocked: Named export 'utils' not found.
19
+ (server.ts:21491)
20
+ ```
21
+
22
+ Fix: default-import the namespace, destructure at runtime, keep the type import
23
+ type-only (types are erased, so `import type { Connection }` is safe and unchanged).
24
+
25
+ Verified against the built output under Node's real loader — all five previously
26
+ threw, all five now load:
27
+
28
+ ```
29
+ $ npm run build && node --input-type=module -e "…await import('./dist/core/'+m+'.js')…"
30
+ LOADS PeerAuthorizedKeys / MachineSshEndpoint / MutualSshVerifier
31
+ LOADS StandingSshVerifier / SshBootstrapAdvert
32
+ ```
33
+
34
+ ## The guard is a source scan, and that was not the first attempt
35
+
36
+ The obvious regression guard — import each module, assert no throw — was written,
37
+ passed, and was **wrong**. Reverting one file to the broken form left it fully green:
38
+
39
+ ```
40
+ $ npx vitest run tests/unit/ssh2-modules-load-under-esm.test.ts # BROKEN source
41
+ ✓ (8 tests) — Tests 8 passed (8)
42
+ $ npx tsc --noEmit # BROKEN source
43
+ tsc exit=0
44
+ $ node --input-type=module -e "import { Server } from 'ssh2';"
45
+ SyntaxError: Named export 'Server' not found…
46
+ ```
47
+
48
+ Vitest transforms through Vite, which rewrites CJS interop; production runs the
49
+ compiled output under Node's loader, which does not. An in-process import assertion
50
+ is **structurally incapable** of observing this class — it does not merely miss it.
51
+ `tsc` is blind for a different reason: the types are genuinely correct.
52
+
53
+ The replacement scans source text for named value imports from a CJS allowlist. It
54
+ refuses correctly:
55
+
56
+ ```
57
+ $ npx vitest run … # after reverting src/core/MutualSshVerifier.ts
58
+ × REGRESSION: no source file takes a named value import from a CJS-only package
59
+ → core/MutualSshVerifier.ts: import { Client, utils } from 'ssh2'
60
+ Tests 1 failed | 5 passed (6)
61
+ ```
62
+
63
+ ## Decision-point inventory
64
+
65
+ | point | classification | note |
66
+ |---|---|---|
67
+ | import form per file | `invariant` | Mechanical; no runtime branch introduced. |
68
+ | scan allowlist (`CJS_ONLY_PACKAGES`) | `invariant` | Explicit list, currently `['ssh2']`. Deliberately not auto-derived — see §2. |
69
+ | comment/string stripping before match | `invariant` | Asserted by a test using this file's own prose. |
70
+
71
+ No judgment points. No model call. Nothing gates or blocks at runtime.
72
+
73
+ ## 1. Over-block
74
+
75
+ The scan is the only thing that can refuse, and it refuses at test time, never at
76
+ runtime. Its over-block risk is flagging an innocent file whose *prose* contains the
77
+ forbidden shape — the known weakness of text checks, and one this codebase has been
78
+ bitten by three times. Closed by stripping comments and string literals first, with a
79
+ test that feeds it this very file's description of the bug and asserts zero matches.
80
+ Both safe forms (`import ssh2 from 'ssh2'`, `import type { … }`) are asserted to pass.
81
+
82
+ ## 2. Under-block
83
+
84
+ **The allowlist is manual.** A named value import from some *other* CJS package is
85
+ not caught. Auto-deriving it (reading every dependency's `package.json` type field)
86
+ was considered and rejected for this change: it turns a two-line list into a
87
+ resolution problem with its own failure modes, and would have shipped untested. The
88
+ list is the honest, visible limit. <!-- tracked: CMT-1044 -->
89
+
90
+ **It scans `src/` only.** Scripts, hooks, and templates are not covered.
91
+
92
+ **Loading is not working.** This proves five modules load. Whether `mutualSshRuntime.start()`
93
+ then binds, finds keys, and reaches a peer is untested here and is NOT claimed. The
94
+ precise claim: the channel could not have worked before, and one specific blocker is gone.
95
+
96
+ **The guard cannot see the compiled reality.** A source scan infers the runtime
97
+ failure from the source shape. The direct check — importing built output under Node —
98
+ runs in this review but is not wired into CI, because it requires a build step the
99
+ unit shard does not have.
100
+
101
+ ## 3. Level-of-abstraction fit
102
+
103
+ The fix is at the only possible layer: the import statements themselves. The guard sits
104
+ in the unit shard, which is where a cheap always-runs check belongs. A lint rule
105
+ (`eslint-plugin-import`) would be the more conventional home; it is not adopted here
106
+ because the repo has no such plugin configured and adding one is a larger change than
107
+ the bug warrants.
108
+
109
+ ## 4. Signal vs authority compliance
110
+
111
+ No runtime authority is introduced or moved. The change removes a load-time crash; it
112
+ adds no gate, no branch, no decision. The test-time scan holds authority over CI only,
113
+ which is the appropriate place for a brittle text check per `docs/signal-vs-authority.md`
114
+ — brittleness is acceptable when the blast radius is a red build, not a blocked action.
115
+
116
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
117
+
118
+ None introduced.
119
+
120
+ ## 5. Interactions
121
+
122
+ - **`MutualSshRuntime` / mesh transport** — unchanged. This only lets its dependencies load.
123
+ - **`guardRegistry`** — see §6b. Not modified here.
124
+ - **Type imports** — `import type { Connection, Server as SshServerType }` retained in
125
+ `MachineSshEndpoint.ts`; `Server` needed splitting into a value binding and a type
126
+ alias because it is used as both. Caught by `tsc`, not by me.
127
+
128
+ ## 6. External surfaces
129
+
130
+ None. No route, no config key, no log line, no user-visible message, no persisted state.
131
+
132
+ ## 6b. Operator-surface quality — the finding this change does NOT fix
133
+
134
+ `multiMachine.mutualSsh.enabled` registers itself with `guardRegistry` **inside** the
135
+ try block that threw. So the failure removed itself from the inventory built to catch
136
+ exactly this. Confirmed against the live pre-fix server:
137
+
138
+ ```
139
+ total guard rows: 88
140
+ mutualSsh/peerExecution rows: 1 → multiMachine.peerExecution.enabled
141
+ ```
142
+
143
+ `mutualSsh` has no row at all — not "off", not "errored", absent. This is the same
144
+ shape as tonight's relay defect (a dropped connection left "connected" as the only
145
+ record): **the defect deletes its own evidence.** Registering guards before the
146
+ fallible construction, so a crashed subsystem still appears as `errored`, is a real
147
+ change to a shared registry and is deliberately not bundled here. <!-- tracked: CMT-1044 -->
148
+
149
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
150
+
151
+ Machine-local by design — a module either loads in this process or does not. No
152
+ replication, no lease interaction, no shared state, no generated URL. The *feature*
153
+ being repaired is cross-machine, but this change to it is not.
154
+
155
+ ## 8. Rollback cost
156
+
157
+ Low. Five import statements and one test file. No migration, no persisted state, no
158
+ config default, nothing installed into an agent home. Reverting restores the previous
159
+ behaviour exactly — a warning at boot and a dead channel — and the guard would fail
160
+ loudly on the way, which is the intended announcement.
161
+
162
+ ## Phase 5 — Second-pass review
163
+
164
+ Touches no block/allow decision, no session lifecycle, no trust level, no gate or
165
+ sentinel, so the high-risk trigger list is not engaged. Author-applied lenses, disclosed:
166
+
167
+ **Adversarial — "how would I make this useless?"** By writing a guard that cannot
168
+ observe the failure. I did exactly that on the first attempt, and only caught it
169
+ because the refusal step is mandatory rather than optional. Recorded in §2 as the
170
+ central lesson rather than quietly replaced.
171
+
172
+ **"Did I fix the symptom or the cause?"** The cause, for the load failure. Not for the
173
+ invisibility — §6b is the cause of *why nobody noticed for so long*, and it is left
174
+ open and tracked rather than half-done.
175
+
176
+ **"Would it have caught the incident?"** The guard would have failed the build the day
177
+ the named import was introduced. It would not have surfaced the boot warning; nothing
178
+ here changes observability.
179
+
180
+ **Weakest point:** §2's last item. The scan infers a runtime property from source
181
+ text. The direct check exists and passes, but runs by hand in this review rather than
182
+ in CI — so the thing CI actually enforces is one inference removed from the thing that
183
+ broke.