instar 1.2.44 → 1.2.46

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,77 @@
1
+ # Side-effects review — Telegram consent buttons (PR 6 of tunnel-failure-resilience chain)
2
+
3
+ **Scope (PR 6 of the chain):** Wire the consent state machine (PR 5)
4
+ to a real Telegram inline-button UX. When Tier-1 is exhausted and a
5
+ Tier-2 relay is available, the owner gets a DM with two buttons —
6
+ "Yes, use a backup" / "No, keep waiting" — and tapping one drives
7
+ `grantConsent` / `declineConsent` on the manager. Spec
8
+ `specs/dev-infrastructure/tunnel-failure-resilience.md` Part 3.
9
+
10
+ **Files touched:**
11
+ - `src/messaging/TelegramAdapter.ts` — adds `setTunnelConsentHandler`
12
+ (handler injection), `sendOwnerConsentPrompt(text, nonce)` (DM with
13
+ inline buttons carrying `tc:g:<nonce>` / `tc:d:<nonce>`), and a new
14
+ `tc:`-prefixed early-return branch in `processCallbackQuery` that
15
+ enforces the owner-principal check, validates the nonce shape, calls
16
+ the injected handler, and clears the keyboard. `editMessageWithRetry`
17
+ gains an optional `chatId` param (defaults to `config.chatId`) so the
18
+ keyboard-clear edit can target the owner's private DM rather than the
19
+ group — without disturbing the existing prompt-gate callers.
20
+ - `src/tunnel/TunnelManager.ts` — `attachTelegram` captures the adapter
21
+ ref, registers the grant/decline handler, and passes
22
+ `suppressConsentDM: true` to the notifier; `requestConsent` sends the
23
+ button prompt (degrading to plain-text `sendToOwnerDM` when an adapter
24
+ lacks button support). Adds `consentPromptText`. The
25
+ `TunnelMessagingAdapter` interface gains two optional methods.
26
+ - `src/tunnel/TunnelNotifier.ts` — adds `suppressConsentDM` option so
27
+ the manager-sent button prompt isn't duplicated by the notifier's
28
+ plain-text owner-DM.
29
+ - `tests/unit/tunnel-consent-telegram.test.ts` — NEW. 10 tests: 6 on
30
+ the manager↔adapter seam (handler registration, button prompt carries
31
+ the live nonce, no double-send, grant→relay-active, decline→exhausted
32
+ +cooldown, stale-nonce no-op) and 4 on the adapter-side callback gate
33
+ (owner grant/decline, NON-owner rejection, malformed `tc:` data).
34
+
35
+ **Over-block:** None. The `tc:` prefix is unique in the codebase (a grep
36
+ for `callback_data:` literals returns only PR 6's two buttons), the
37
+ regex is strict (`^tc:([gd]):([0-9a-f]{32})$`), and any non-`tc:`
38
+ callback falls through unchanged to the existing prompt-gate handler
39
+ below. Malformed `tc:` data is answered with "Invalid consent button"
40
+ and consumed, never forwarded.
41
+
42
+ **Under-block:** One edge worth naming. The owner gate is
43
+ `if (owner && query.from.id !== owner) reject`. When **no** owner is
44
+ configured (`ownerUserId` and `promptGate.ownerId` both unset), the gate
45
+ is skipped. This is benign by construction: with no owner there is no DM
46
+ target, so `sendOwnerConsentPrompt` returns null and **no button is ever
47
+ sent** — there is nothing to click. Even a hand-crafted callback can't
48
+ activate a relay, because `grantConsent` requires a single-use CSPRNG
49
+ nonce that matches the live pending-consent record (cleared-before-start
50
+ so a replay loses). Security is layered: owner-principal check **and**
51
+ nonce match. The adapter regex is only a low-level validator (signal);
52
+ the manager's `grantConsent` holds the blocking authority.
53
+
54
+ **Level-of-abstraction fit:** Clean. The adapter owns the Telegram
55
+ protocol (parse, owner check, answer toast, keyboard clear); the manager
56
+ owns the tunnel-state transition via the injected handler. The adapter
57
+ never imports tunnel types and the manager never imports Telegram types
58
+ (the duck-typed `TunnelMessagingAdapter` interface is the seam).
59
+
60
+ **Interactions:**
61
+ - `query.data` is guarded (`if (!query.data) return`) immediately above
62
+ the `tc:` block, so `.startsWith` can't throw.
63
+ - The path is reached both by the poll loop and by
64
+ `handleForwardedCallback` — the Lifeline forwards callbacks in
65
+ send-only mode, which is exactly the tunnel-down scenario this feature
66
+ exists for.
67
+ - `suppressConsentDM` prevents a double owner-DM (button prompt from the
68
+ manager + plain-text prompt from the notifier).
69
+ - No HTTP surface in this PR, so the Tier-2/Tier-3 "feature is alive"
70
+ HTTP tests are N/A. Wiring is verified two ways: the unit test asserts
71
+ `attachTelegram` registers the handler, and the production call site
72
+ exists at `src/commands/server.ts` (`tunnel.attachTelegram(telegram, …)`).
73
+
74
+ **Rollback cost:** Low. Purely additive; `suppressConsentDM` defaults
75
+ false (back-compat for existing notifier callers). No config schema,
76
+ migration, or persistent state. Revert = drop the three method additions
77
+ + the `tc:` block + the `attachTelegram` handler-registration lines.