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.
- package/dist/core/MachineSshEndpoint.d.ts.map +1 -1
- package/dist/core/MachineSshEndpoint.js +5 -1
- package/dist/core/MachineSshEndpoint.js.map +1 -1
- package/dist/core/MutualSshVerifier.d.ts.map +1 -1
- package/dist/core/MutualSshVerifier.js +3 -1
- package/dist/core/MutualSshVerifier.js.map +1 -1
- package/dist/core/PeerAuthorizedKeys.d.ts.map +1 -1
- package/dist/core/PeerAuthorizedKeys.js +5 -1
- package/dist/core/PeerAuthorizedKeys.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +9 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SshBootstrapAdvert.d.ts.map +1 -1
- package/dist/core/SshBootstrapAdvert.js +3 -1
- package/dist/core/SshBootstrapAdvert.js.map +1 -1
- package/dist/core/StandingSshVerifier.d.ts.map +1 -1
- package/dist/core/StandingSshVerifier.js +3 -1
- package/dist/core/StandingSshVerifier.js.map +1 -1
- package/dist/core/channelRegistry.d.ts +104 -0
- package/dist/core/channelRegistry.d.ts.map +1 -0
- package/dist/core/channelRegistry.js +86 -0
- package/dist/core/channelRegistry.js.map +1 -0
- package/dist/core/instarChannels.d.ts +32 -0
- package/dist/core/instarChannels.d.ts.map +1 -0
- package/dist/core/instarChannels.js +91 -0
- package/dist/core/instarChannels.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +6 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +40 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.988.md +55 -0
- package/upgrades/1.3.989.md +55 -0
- package/upgrades/side-effects/channel-registry.md +199 -0
- 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.
|