instar 1.3.48 → 1.3.50

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,99 @@
1
+ # Side-Effects Review — mentee receiver wiring fires on lifeline-forward path
2
+
3
+ **Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Recipient side.
4
+ Fast-follow to PR #462 (mentee receiver wiring) — closes the dual-path
5
+ ingress gap surfaced by dogfood.
6
+
7
+ **Change:** Three production files + three test files.
8
+ - `src/messaging/TelegramAdapter.ts` (+ ~55 LoC: `dispatchAgentMessageHook`
9
+ public method; polling text-dispatch now calls it instead of inlining the
10
+ hook logic — pure extraction, no behavior change on the polling path)
11
+ - `src/server/routes.ts` (+ ~20 LoC: destructure `senderIsBot` /
12
+ `senderChatId` / `senderBotId` from forward body; invoke
13
+ `dispatchAgentMessageHook` before `onTopicMessage`; short-circuit on
14
+ `handled:true`)
15
+ - `src/lifeline/TelegramLifeline.ts` (+ ~15 LoC: derive + include
16
+ `senderIsBot`/`senderChatId`/`senderBotId` in the forward body)
17
+ - `tests/unit/TelegramAdapter-dispatchAgentMessageHook.test.ts` (new, 8)
18
+ - `tests/integration/telegram-forward-a2a-dispatch.test.ts` (new, 3)
19
+
20
+ ## What changed
21
+
22
+ 1. **`dispatchAgentMessageHook` public method** on `TelegramAdapter`:
23
+ wraps the existing hook invocation with the FAIL-OPEN try/catch (a
24
+ broken hook never freezes the dispatch loop) and the senderBotId
25
+ derivation logic (sender_chat wins; rawFromId iff senderIsBot;
26
+ undefined for real users). Polling path now calls the dispatcher
27
+ instead of inlining the same code — pure extraction.
28
+ 2. **`/internal/telegram-forward` handler** destructures the new spoof-
29
+ defense fields from the request body. Before invoking `onTopicMessage`,
30
+ calls `dispatchAgentMessageHook`. On `handled:true`, returns
31
+ `{ ok: true, forwarded: true, agentMessage: true }` (NEW response shape
32
+ addition — additive, doesn't change the existing `{ ok, forwarded }`
33
+ contract for non-a2a forwards). On hook throw, logs + falls through
34
+ (same fail-open semantics as the polling path).
35
+ 3. **`TelegramLifeline.forwardToServer`** derives `senderIsBot` from
36
+ `rawMsg.from.is_bot === true`, `senderChatId` from
37
+ `rawMsg.sender_chat?.id`, `senderBotId` from `senderChatId ?? (isBot ?
38
+ rawMsg.from.id : undefined)`. Includes them in the buildBody output
39
+ only when they have values (omitted fields → server treats as falsy →
40
+ marker-bearing forwards drop closed for spoof defense).
41
+
42
+ ## The seven questions
43
+
44
+ 1. **Over-block.** N/A — additive. The forward route now invokes the hook
45
+ before `onTopicMessage`; if no hook is installed (or no adapter has
46
+ `dispatchAgentMessageHook`), the route falls through to the existing
47
+ behavior. Backward-compatible test (`preserves existing behavior when
48
+ the adapter has NO dispatchAgentMessageHook`) covers this.
49
+ 2. **Under-block.** Spoof defense is end-to-end on the forwarded path:
50
+ missing `senderIsBot` from older lifelines → server treats as falsy →
51
+ any marker drops as `agent-marker-spoofed-by-user`. This is the
52
+ fail-CLOSED side, matching spec invariant.
53
+ 3. **Level-of-abstraction fit.** Extraction-then-reuse: the hook
54
+ invocation logic moves from inlined to a method on the adapter, called
55
+ from both polling and forward paths. No new abstraction; just exposes
56
+ what was already there.
57
+ 4. **Signal vs authority.** The dispatcher returns a clean boolean to the
58
+ caller; the caller decides whether to short-circuit. Authority lives in
59
+ the caller (route or polling loop), not in the dispatcher. Same as the
60
+ prior inline version.
61
+ 5. **Interactions.** Reuses `agentMessageHook` set by `setAgentMessageHook`
62
+ — no new state. The forward route reads three new optional fields from
63
+ the body; older clients (lifeline pre-this-PR) omit them and get the
64
+ fail-CLOSED spoof defense automatically.
65
+ 6. **External surfaces.** The `/internal/telegram-forward` response gains
66
+ one additive flag (`agentMessage: true`) when the hook claims the
67
+ message; the existing `{ ok, forwarded }` contract is preserved
68
+ otherwise. No new routes. No new config.
69
+ 7. **Rollback cost.** Trivial — revert restores the inline hook call in
70
+ the polling path, drops the dispatcher and the forward-route call,
71
+ drops the lifeline body additions. The new `agentMessage:true` flag is
72
+ an additive response field, so any consumer relying on it would need a
73
+ one-line fix back to checking `forwarded:true` alone.
74
+
75
+ ## Testing
76
+
77
+ 11 new tests, all green (`tsc --noEmit` clean):
78
+
79
+ - **Tier 1 unit (8):** `TelegramAdapter-dispatchAgentMessageHook` covers
80
+ no-op safe (no hook), handled:true short-circuit, handled:false
81
+ fall-through, FAIL-OPEN on hook throw, three senderBotId derivation
82
+ modes (sender_chat / rawFromId+isBot / undefined for users), explicit
83
+ override.
84
+ - **Tier 2 integration (3):** `telegram-forward-a2a-dispatch` boots
85
+ `createRoutes` with a mock adapter through the real Express pipeline;
86
+ asserts short-circuit when hook claims, fall-through when not,
87
+ backward-compat when adapter lacks the method.
88
+ - **Tier 3 E2E:** the existing `mentee-receiver-lifecycle.test.ts` from
89
+ PR #462 (which installs the hook end-to-end on server boot) plus the
90
+ live dogfood loop that motivated this PR are the lifecycle proof.
91
+
92
+ ## Migration parity
93
+
94
+ No config changes; no new config keys. The lifeline-side change is
95
+ deployed via the same npm release as the server-side change, so the
96
+ mixed-version state is only momentary (server upgrades alongside its
97
+ lifeline via the existing AutoUpdater + LifelineDriftPromoter pair).
98
+ Mixed-version safety is preserved either direction (server new / lifeline
99
+ old: spoof-fail-closed; server old / lifeline new: unknown fields ignored).