instar 1.3.988 → 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.
@@ -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.