instar 1.2.38 → 1.2.40

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 (47) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +6 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/TopicIntent.d.ts +162 -0
  5. package/dist/core/TopicIntent.d.ts.map +1 -0
  6. package/dist/core/TopicIntent.js +355 -0
  7. package/dist/core/TopicIntent.js.map +1 -0
  8. package/dist/core/TopicIntentArcCheck.d.ts +101 -0
  9. package/dist/core/TopicIntentArcCheck.d.ts.map +1 -0
  10. package/dist/core/TopicIntentArcCheck.js +146 -0
  11. package/dist/core/TopicIntentArcCheck.js.map +1 -0
  12. package/dist/core/TopicIntentBriefing.d.ts +44 -0
  13. package/dist/core/TopicIntentBriefing.d.ts.map +1 -0
  14. package/dist/core/TopicIntentBriefing.js +88 -0
  15. package/dist/core/TopicIntentBriefing.js.map +1 -0
  16. package/dist/core/TopicIntentExtractor.d.ts +90 -0
  17. package/dist/core/TopicIntentExtractor.d.ts.map +1 -0
  18. package/dist/core/TopicIntentExtractor.js +153 -0
  19. package/dist/core/TopicIntentExtractor.js.map +1 -0
  20. package/dist/core/TopicIntentPendingConfirm.d.ts +96 -0
  21. package/dist/core/TopicIntentPendingConfirm.d.ts.map +1 -0
  22. package/dist/core/TopicIntentPendingConfirm.js +223 -0
  23. package/dist/core/TopicIntentPendingConfirm.js.map +1 -0
  24. package/dist/server/AgentServer.d.ts +3 -0
  25. package/dist/server/AgentServer.d.ts.map +1 -1
  26. package/dist/server/AgentServer.js +10 -0
  27. package/dist/server/AgentServer.js.map +1 -1
  28. package/dist/server/topicIntentRoutes.d.ts +27 -0
  29. package/dist/server/topicIntentRoutes.d.ts.map +1 -0
  30. package/dist/server/topicIntentRoutes.js +189 -0
  31. package/dist/server/topicIntentRoutes.js.map +1 -0
  32. package/dist/tunnel/LocaltunnelProvider.d.ts +63 -0
  33. package/dist/tunnel/LocaltunnelProvider.d.ts.map +1 -0
  34. package/dist/tunnel/LocaltunnelProvider.js +130 -0
  35. package/dist/tunnel/LocaltunnelProvider.js.map +1 -0
  36. package/dist/tunnel/TunnelManager.d.ts +44 -1
  37. package/dist/tunnel/TunnelManager.d.ts.map +1 -1
  38. package/dist/tunnel/TunnelManager.js +199 -15
  39. package/dist/tunnel/TunnelManager.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/data/builtin-manifest.json +4 -4
  42. package/src/templates/hooks/telegram-topic-context.sh +19 -0
  43. package/upgrades/1.2.39.md +54 -0
  44. package/upgrades/1.2.40.md +119 -0
  45. package/upgrades/side-effects/consent-state-machine.md +135 -0
  46. package/upgrades/side-effects/localtunnel-provider.md +95 -0
  47. package/upgrades/side-effects/topic-intent-layer.md +86 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.2.38",
3
+ "version": "1.2.40",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-23T01:12:37.544Z",
5
- "instarVersion": "1.2.38",
4
+ "generatedAt": "2026-05-23T02:31:36.933Z",
5
+ "instarVersion": "1.2.40",
6
6
  "entryCount": 191,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -1064,7 +1064,7 @@
1064
1064
  "type": "template",
1065
1065
  "domain": "safety",
1066
1066
  "sourcePath": "src/templates/hooks/telegram-topic-context.sh",
1067
- "contentHash": "8a80976731dd5dd6b981834f0c4e18d495ee0efd350adc7b88a65c1aa6356b5d",
1067
+ "contentHash": "57cfb744bbffcd21f6290c5f38726b105b93258bf09cc539c24d66aebed9c40e",
1068
1068
  "since": "2025-01-01"
1069
1069
  },
1070
1070
  "template:convergence-check.sh": {
@@ -1440,7 +1440,7 @@
1440
1440
  "type": "subsystem",
1441
1441
  "domain": "server",
1442
1442
  "sourcePath": "src/server/AgentServer.ts",
1443
- "contentHash": "d8d019cc9691191bca75fabd916fa5a4400bb614abcd48b49afa4f650d533fdd",
1443
+ "contentHash": "0c4f63b37d72bc85415e464e181507b66bb247df1e903da2fe27c5d6539dab0f",
1444
1444
  "since": "2025-01-01"
1445
1445
  },
1446
1446
  "subsystem:session-manager": {
@@ -65,6 +65,25 @@ else
65
65
  "http://localhost:${PORT}/telegram/topics/${TOPIC_ID}/messages?limit=30" 2>/dev/null)
66
66
  fi
67
67
 
68
+ # Layer 2: prepend the topic intent briefing if anything has accumulated.
69
+ # Returns empty body when nothing tracked yet — we silently skip injection.
70
+ # Degrades open: any failure here (server down, route 503'd, network blip)
71
+ # leaves recent-history-only output unchanged.
72
+ TOPIC_BRIEFING=""
73
+ if [ -n "$AUTH_TOKEN" ]; then
74
+ TOPIC_BRIEFING=$(curl -s --max-time 2 \
75
+ -H "Authorization: Bearer ${AUTH_TOKEN}" \
76
+ "http://localhost:${PORT}/topic-intent/${TOPIC_ID}/briefing" 2>/dev/null)
77
+ else
78
+ TOPIC_BRIEFING=$(curl -s --max-time 2 \
79
+ "http://localhost:${PORT}/topic-intent/${TOPIC_ID}/briefing" 2>/dev/null)
80
+ fi
81
+
82
+ if [ -n "$TOPIC_BRIEFING" ]; then
83
+ echo "$TOPIC_BRIEFING"
84
+ echo ""
85
+ fi
86
+
68
87
  # Format and output context with unanswered message detection
69
88
  echo "$RECENT_MSGS" | python3 -c "
70
89
  import sys, json
@@ -0,0 +1,54 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: minor -->
4
+ <!-- minor = new feature, requires meaningful user-visible behavior addition -->
5
+
6
+ ## What Changed
7
+
8
+ **feat(topic-intent-layer): Layers 1-3 — per-topic confidence tracking + resume briefing + ArcCheck pre-send signal.**
9
+
10
+ A new three-layer feature that gives the agent a real sense of each conversation's arc. When you talk to the agent across many turns (or days, or after a context reset), it now keeps quiet track of what the topic is about, what's settled, and what's still tentative — without you ever typing a command. The agent does the bookkeeping in the background.
11
+
12
+ - **Layer 1** — a per-topic confidence tracker. Each candidate fact or decision the conversation establishes is tracked with confidence that rises with reinforcement (you re-reference, you affirm) and falls with contradiction or long silence. Authority requires user-authored evidence — the agent cannot silently promote its own guesses to "settled" by re-referencing itself.
13
+ - **Layer 2** — a briefing the agent reads at the top of every session resume. Telegram bootstrap automatically fetches it and prepends it to the recent-history block. Settled items shown unhedged; tentative items shown with confidence; observation tier (background noise) deliberately not surfaced.
14
+ - **Layer 3** — ArcCheck, a pre-send signal that fires when the agent's draft would either act on a tentative item without confirming OR contradict something already settled. Signal-only — the agent decides whether to redraft including a natural-language confirmation question. Never blocks.
15
+
16
+ Spec: `docs/specs/topic-intent-layer.md` (v14 CLEAN, approved 2026-05-22, 14 internal rounds plus 13 external rounds).
17
+
18
+ This build was also the test bed for evaluating whether to integrate GSD as a runtime dependency. Verdict: cherry-pick the methodologies into Instar primitives, do not try runtime composition. Comparison report archived at `.planning/SPIKE-FINDINGS.md` plus `.planning/phases/01-layer1/PLAN.md`.
19
+
20
+ ## Evidence
21
+
22
+ The original failure modes that motivated the feature are documented as canonical incidents in the spec — qalatra topic 9235 (arc drift across a multi-turn investigation) and GCI/Luna topic 365 (decision amnesia where the agent proposed redesigning a settled OAuth architecture mid-debugging, contradicting an earlier decision).
23
+
24
+ 96 tests across all three tiers, all green locally:
25
+ - Decay arithmetic at days 104, 105, 106 matches spec values to 3 decimal places.
26
+ - User-authored-episode authority gating clamps at 0.69 when no qualifying user-authored episode exists.
27
+ - Per-message dedup with larger-delta-wins on collision.
28
+ - Pending-confirm queue depth 3 with silent drop plus telemetry counter.
29
+ - Sharpen-retry up to 2 then abandoned.
30
+ - PII-safe diagnostics endpoint (raw evidence meta allowlist-filtered, never returns user message text).
31
+ - Framework-parity grep gate (zero Claude-Code or Codex-CLI specific tokens in any Layer 1/2/3 source).
32
+ - Tier-3 lifecycle test boots through the real production server path and observes tier transitions through HTTP.
33
+
34
+ Side-effects review artifact: `upgrades/side-effects/topic-intent-layer.md`.
35
+
36
+ ## What to Tell Your User
37
+
38
+ In long, multi-day, or multi-topic conversations, the agent stays on-point. It resumes knowing the goal. It stops re-litigating things you already settled together. When it is about to do something based on an assumption that might be wrong, it asks first in plain English, as part of its reply. When you correct it, it actually updates.
39
+
40
+ In short, simple conversations, nothing changes. The layer only kicks in once a conversation has substance worth tracking, and stays quiet otherwise.
41
+
42
+ No commands to learn. No configuration to set. The agent does the bookkeeping; you just talk.
43
+
44
+ A diagnostics view per conversation tells you exactly what the agent is tracking, how confident it is, what signals built that confidence, and whether anything is in conflict.
45
+
46
+ ## Summary of New Capabilities
47
+
48
+ - New core modules covering confidence tracking, pending-confirmation lifecycle, LLM-backed extraction, resume briefing rendering, and pre-send classifier.
49
+ - New HTTP routes for diagnostics, refs filter, pending state, telemetry, briefing, and ArcCheck.
50
+ - Telegram bootstrap hook auto-fetches the briefing on session resume. Degrades open on any failure.
51
+ - Production server boot path constructs the store and passes it to AgentServer. Mount unconditional (503 stub when store disabled).
52
+ - Out of v1: cross-conversation insight spreading, retroactive backfill of pre-shipped conversations, cross-machine CRDT collaborative state, automatic conversation renaming, rich dashboard editor, user-tunable confidence weights (operator may tune; end user cannot).
53
+
54
+ Layer 3's outbound-path wiring (the actual every-send-fires-ArcCheck plumbing into the tone-gate and response-review stack) is intentionally separated from this ship. The classifier IS the spec's Layer 3 mechanism; the rollout is best done with care under a dedicated follow-up since it touches the existing outbound gate authority.
@@ -0,0 +1,119 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+ <!-- patch = consent state machine + grantConsent/declineConsent API; no Telegram wire-up yet -->
5
+
6
+ ## What Changed
7
+
8
+ **feat(tunnel): consent state machine — Tier-1 exhaustion → awaiting-consent → relay-active.**
9
+
10
+ PR 5 of the tunnel-failure-resilience chain. Lands the consent state
11
+ machine inside `TunnelManager` so an exhausted Tier-1 path can offer
12
+ the Tier-2 relay (LocaltunnelProvider from PR 4) behind an
13
+ owner-approval gate.
14
+
15
+ What's new:
16
+
17
+ - `TunnelManager.buildDefaultPool` now appends a `LocaltunnelProvider`
18
+ as the Tier-2 slot. `isAvailable()` returns false when the
19
+ `localtunnel` npm package isn't installed (per the spec's opt-in
20
+ posture), so the slot is silently skipped on agents without the
21
+ dep — behavior is unchanged for them.
22
+ - `TunnelManager.exhaustedOrBackoff` now checks for an available
23
+ Tier-2 provider AND that the cross-episode consent cooldown isn't
24
+ active before deciding between `exhausted` and `awaiting-consent`.
25
+ When both conditions hold, the manager transitions to
26
+ `awaiting-consent` and generates a one-time CSPRNG nonce stored in
27
+ a new `_pendingConsent` record.
28
+ - `TunnelManager.grantConsent(nonce)` — public API. Validates the
29
+ nonce matches the pending record AND the state is still
30
+ `awaiting-consent` (replay-safe single-use), starts the Tier-2
31
+ provider, runs the post-start reachability probe, and transitions
32
+ to `relay-active` with `rotationPending=true` (the persisted
33
+ marker for the upcoming mandatory PIN+authToken rotation in PR 6).
34
+ Returns true on success; false on any validation failure or
35
+ start/probe failure.
36
+ - `TunnelManager.declineConsent(nonce)` — public API. Validates the
37
+ nonce, advances the cross-episode cooldown counter, and
38
+ transitions to `exhausted` + background retry.
39
+ - Consent timeout (15 min, per spec Part 4 default) — fires
40
+ automatically; same effect as decline.
41
+ - `stop()` clears the pending consent + its timer so a torn-down
42
+ manager can't have a stale timeout fire.
43
+ - `TunnelManager.pendingConsent` accessor — returns a defensive
44
+ snapshot (episodeId, provider name, nonce, issuedAt) for the
45
+ upcoming Telegram callback handler (PR 6) to embed the nonce in
46
+ the inline-button payload.
47
+
48
+ What's NOT new yet (deliberate, future PRs in the chain):
49
+
50
+ - Telegram `callback_query` handler that invokes `grantConsent` /
51
+ `declineConsent` on owner button clicks (PR 6).
52
+ - Inline-button payload in the consent prompt (PR 6).
53
+ - `authToken` / PIN rotation lifecycle on relay-episode end (PR 6+).
54
+ - Full N-consecutive-success self-heal stability gate (PR 7).
55
+ - The auth-gated `/tunnel` route + `ConfigDefaults` migration +
56
+ CLAUDE.md template (PR 8).
57
+ - The `localtunnel` npm dep itself (PR 6, alongside the consent
58
+ wire-up — keeps the capability opt-in until the gate is fully
59
+ live).
60
+
61
+ Behavior change for users today: **none** at runtime. Agents without
62
+ the `localtunnel` dep installed continue exactly as before (Tier-2
63
+ slot is unavailable, manager falls through to exhausted). The
64
+ plumbing is in place; PR 6 makes it user-facing.
65
+
66
+ ## Evidence
67
+
68
+ 11 new unit tests in `tests/unit/tunnel-consent-state-machine.test.ts`:
69
+
70
+ - Tier-1 exhausts + Tier-2 available + cooldown inactive →
71
+ `awaiting-consent` + pendingConsent populated with 128-bit hex nonce.
72
+ - Tier-1 exhausts + NO Tier-2 available → `exhausted` (baseline path).
73
+ - Tier-1 exhausts + consent cooldown active → `exhausted` (cooldown
74
+ wins).
75
+ - `grantConsent` with matching nonce → `relay-active` + URL emitted +
76
+ rotationPending=true + pendingConsent cleared (single-use).
77
+ - `grantConsent` with wrong nonce → returns false, state unchanged,
78
+ provider.start NOT called.
79
+ - `grantConsent` replay of a used nonce → returns false, state
80
+ unchanged.
81
+ - `grantConsent` + reachability probe fails → `exhausted` + cooldown
82
+ advanced.
83
+ - `grantConsent` + provider.start throws → `exhausted` + cooldown
84
+ advanced.
85
+ - `declineConsent` with matching nonce → `exhausted` + cooldown
86
+ advanced + pendingConsent cleared.
87
+ - `declineConsent` with wrong nonce → returns false, state unchanged,
88
+ no cooldown change.
89
+ - `stop()` clears pendingConsent + the timeout timer.
90
+
91
+ All 92 tunnel-related unit tests pass (11 new + 81 from PRs 1–4).
92
+ `tsc --noEmit` clean. `npm run lint` clean.
93
+
94
+ ## What to Tell Your User
95
+
96
+ This release puts the security gate for backup tunnels into place.
97
+ Nothing visible to you yet — the gate needs a button to click, and
98
+ that's the next release. Once that lands, when Cloudflare goes fully
99
+ down, you'll get a private message from your agent asking permission
100
+ to use a backup. Until you approve, the agent stays link-less and
101
+ keeps retrying Cloudflare. If you decline or don't reply, the agent
102
+ won't ask again for a while (the wait window grows the more you
103
+ decline, so the agent stops bothering you about something you've
104
+ already decided).
105
+
106
+ If you want the backup capability to actually work when the next
107
+ release lands, install the localtunnel npm package alongside your
108
+ agent. Without it the backup option is silently skipped and the
109
+ agent continues with Cloudflare only — which is fine if that's your
110
+ preference.
111
+
112
+ ## Summary of New Capabilities
113
+
114
+ | Capability | How to Use |
115
+ |-----------|-----------|
116
+ | Consent state machine (Tier-1 → awaiting-consent → relay-active) | Internal — exposed via grantConsent / declineConsent APIs that the upcoming Telegram callback handler will call |
117
+ | Single-use CSPRNG nonce binding | Automatic — pendingConsent.nonce embeds in the inline button in the next release |
118
+ | Cross-episode consent cooldown | Automatic — exponential back-off on decline/timeout (1h → 4h → 24h) |
119
+ | Mandatory rotation marker on relay-active entry | Automatic — rotationPending=true is the crash-safe flag the upcoming rotation lifecycle reads |
@@ -0,0 +1,135 @@
1
+ # Side-effects review — consent state machine (PR 5 of tunnel-failure-resilience chain)
2
+
3
+ **Scope (PR 5 of the chain):** Land the consent state machine inside
4
+ `TunnelManager` and append `LocaltunnelProvider` (from PR 4) to the
5
+ default Tier-2 slot. Adds public `grantConsent` / `declineConsent`
6
+ methods so the upcoming Telegram callback handler (PR 6) can drive the
7
+ owner-approval gate. Spec
8
+ `specs/dev-infrastructure/tunnel-failure-resilience.md` is converged
9
+ + approved on main.
10
+
11
+ **Files touched:**
12
+ - `src/tunnel/TunnelManager.ts` — adds Tier-2 to `buildDefaultPool`;
13
+ modifies `exhaustedOrBackoff` to branch on Tier-2 availability +
14
+ consent cooldown; adds `findAvailableTier2`, `requestConsent`,
15
+ `clearPendingConsent`, `grantConsent`, `declineConsent`,
16
+ `recordConsentDecline`, and the `pendingConsent` accessor; updates
17
+ `stop()` to clear the pending consent.
18
+ - `tests/unit/tunnel-consent-state-machine.test.ts` — 11 tests covering
19
+ the awaiting-consent transition (and its negative cases — no Tier-2
20
+ available, cooldown active), grantConsent happy path, nonce
21
+ rejection (wrong nonce, replay), grant-failure paths (unreachable
22
+ relay, provider.start throws), declineConsent happy path + wrong
23
+ nonce, stop() clearing pendingConsent.
24
+
25
+ **Under-block**: None at user-visible runtime. Agents without the
26
+ `localtunnel` npm dep installed continue to fall through to
27
+ `exhausted` exactly as before (the Tier-2 slot's `isAvailable()`
28
+ returns false; `findAvailableTier2` returns null; the branch picks
29
+ the existing `exhausted` path). This is the spec's opt-in posture —
30
+ PR 4's side-effects review documented it explicitly.
31
+
32
+ **Over-block**: Minimal. Agents that DO have `localtunnel` installed
33
+ WILL see the new `awaiting-consent` transition fire instead of going
34
+ straight to `exhausted` — but the manager has no UX layer to surface
35
+ the prompt yet (PR 6 lands the Telegram callback handler that
36
+ actually calls `grantConsent` / `declineConsent`). For now the
37
+ manager sits in `awaiting-consent` for the 15-minute timeout, then
38
+ auto-declines and falls through to exhausted with the cooldown
39
+ advanced. From the user's perspective this looks identical to
40
+ "exhausted" today — there's just a 15-min delay before the cooldown
41
+ counter ticks the first time.
42
+
43
+ To explicitly avoid this UX gap during the interim window between
44
+ PR 5 and PR 6, operators who don't want the awaiting-consent state
45
+ can simply not install `localtunnel`. The default ships without it.
46
+
47
+ **Level-of-abstraction fit**:
48
+ - The state machine lives in `TunnelLifecycle` (PR 1); the manager
49
+ drives state through the existing CAS-guarded transitions.
50
+ - `_pendingConsent` is private state on the manager (binds nonce +
51
+ episode + provider). Exposed read-only via the `pendingConsent`
52
+ accessor that returns a snapshot — callers cannot mutate the
53
+ manager's state from outside.
54
+ - `grantConsent` / `declineConsent` are the AUTHORITY surface for
55
+ consent decisions. The Telegram callback handler (PR 6) will be
56
+ the SIGNAL layer — it validates the click came from the owner and
57
+ the message id matches, then calls these methods.
58
+
59
+ **Signal vs authority**: Compliant. The manager is the authority for
60
+ state transitions and the consent record's lifecycle; external
61
+ callers can only ask for grant/decline by presenting the nonce, and
62
+ the manager validates+decides. The Telegram callback handler in PR 6
63
+ will be a thin signal layer above this; it cannot bypass the nonce
64
+ check.
65
+
66
+ **Interactions**:
67
+ - `_pendingConsent` is single-flight per manager instance — only one
68
+ consent record can be active at a time (the lifecycle's `episode`
69
+ invariant: one episode = one consent prompt = one nonce).
70
+ - `clearPendingConsent` is called on every terminal path (grant
71
+ success, decline, timeout, stop, episode change). The timer is
72
+ cleared atomically with the record so no stale callback can fire.
73
+ - The consent timeout uses the lifecycle's `recordConsentRefusal`
74
+ to advance the cross-episode cooldown — same path as a manual
75
+ decline. The cooldown progression is exponential (1h → 4h → 24h)
76
+ per PR 1's lifecycle implementation.
77
+ - `rotationPending=true` is set on entering `relay-active` (the
78
+ crash-safe persisted marker for the upcoming rotation lifecycle
79
+ in PR 6). The actual rotation work is NOT in this PR — only the
80
+ marker.
81
+ - Reachability probe on the granted Tier-2 provider follows the
82
+ same `/health` semantics as Tier-1 — a relay that emits a URL
83
+ but doesn't actually serve traffic falls through to exhausted +
84
+ cooldown (so the owner isn't asked again immediately).
85
+
86
+ **External surfaces**:
87
+ - New public methods: `grantConsent(nonce)`, `declineConsent(nonce)`,
88
+ `pendingConsent` getter on `TunnelManager`. All require the
89
+ caller to have the current pending nonce, which is itself only
90
+ accessible via `pendingConsent.nonce` — there's no broader
91
+ exposure.
92
+ - No new API endpoint, no new CLI command, no new config field.
93
+ - The default tunnel pool now includes a `LocaltunnelProvider`
94
+ slot, but the manager only activates it after Tier-1 exhausts +
95
+ consent is granted, so users who never hit a Tier-1 exhaustion
96
+ never see Tier-2 attempted.
97
+
98
+ **Migration parity**: N/A. Server-side code only.
99
+
100
+ **Rollback cost**: Trivial. Revert `TunnelManager.ts` + the test file.
101
+ The new methods become unused (they're not called from anywhere
102
+ outside the manager + tests yet). LocaltunnelProvider stays in tree
103
+ from PR 4 but is no longer in the default pool.
104
+
105
+ **Tests**:
106
+ - 11/11 new tests in `tests/unit/tunnel-consent-state-machine.test.ts`.
107
+ - 19/19 tests in `tests/unit/tunnel-manager-rewrite.test.ts` (from
108
+ PR 2 + 3) still pass.
109
+ - 32/32 lifecycle, 13/13 notifier, 10/10 providers, 7/7 localtunnel-
110
+ provider — all still pass.
111
+ - `tsc --noEmit` clean. `npm run lint` clean. No existing tests
112
+ modified.
113
+
114
+ **Decision-point inventory**:
115
+ 1. `_pendingConsent` stores the provider directly (vs. a provider-name
116
+ key into the pool) — keeps the grant path independent of the pool
117
+ ordering, and ensures a provider that becomes "unavailable"
118
+ between request and grant is still the one we attempt (failure
119
+ surfaces cleanly).
120
+ 2. Nonce is 128-bit CSPRNG via `generateNonce` from PR 1 — meets the
121
+ GPT external review's spec for entropy + atomic compare-and-delete.
122
+ 3. Single-use enforced by `clearPendingConsent` BEFORE the provider
123
+ starts — a replay of the same nonce loses cleanly even if the
124
+ start is in flight.
125
+ 4. Timeout = 15 min (spec Part 4 default, staggered off the 10-min
126
+ reconnect window per verification finding V1).
127
+ 5. Reachability failure on a granted relay → cooldown advanced.
128
+ Reason: the relay started but doesn't serve; bothering the owner
129
+ again immediately wouldn't help. The cooldown gates the next ask.
130
+ 6. State machine transitions `awaiting-consent → exhausted` directly
131
+ on decline/timeout/start-failure — same transition the cooldown
132
+ path uses, so background retry semantics are uniform.
133
+ 7. `pendingConsent` accessor returns a defensive snapshot — mutating
134
+ it does NOT affect the manager. External callers cannot inject
135
+ their own nonce or change the bound provider.
@@ -0,0 +1,95 @@
1
+ # Side-effects review — LocaltunnelProvider (PR 4 of tunnel-failure-resilience chain)
2
+
3
+ **Scope (PR 4 of the chain):** Add the `LocaltunnelProvider`
4
+ implementation of the `TunnelProvider` interface from PR 1. The class
5
+ is added in isolation — NOT wired into the manager's active pool —
6
+ so PR 5 can ship the consent state machine + inline-button UX +
7
+ relay-active activation path with the provider already in place
8
+ under separate review. Spec
9
+ `specs/dev-infrastructure/tunnel-failure-resilience.md` is converged
10
+ + approved on main.
11
+
12
+ **Files touched:**
13
+ - `src/tunnel/LocaltunnelProvider.ts` — NEW. Implements
14
+ `TunnelProvider` with `name: 'localtunnel'`, `tier: 2`. Dynamic
15
+ import of the `localtunnel` npm package; graceful "unavailable"
16
+ return path when the dep isn't installed. Failure classification
17
+ preserves the `ProviderFailureReason` enum surface from PR 1
18
+ (rate-limited / network / process-exit / binary-missing).
19
+ - `tests/unit/localtunnel-provider.test.ts` — NEW. 7 tests covering
20
+ the provider's surface (name + tier), graceful degradation when
21
+ the npm package is absent (isAvailable=false; start() rejects
22
+ binary-missing; verdict cached across calls), and constructor
23
+ option acceptance.
24
+
25
+ **Under-block**: None — the provider is added but NOT wired into the
26
+ manager's active pool. The manager's existing `buildDefaultPool`
27
+ returns Cloudflare-named + Cloudflare-quick only, and the driver
28
+ filters Tier-2 providers (`if (provider.tier !== 1) continue;`).
29
+ User-facing behavior is unchanged.
30
+
31
+ **Over-block**: None. The provider class is purely additive; no
32
+ existing behavior changes.
33
+
34
+ **Level-of-abstraction fit**: The provider implements the same
35
+ interface as the Cloudflare providers, so PR 5's state-machine
36
+ changes can use it through the existing pool abstraction without
37
+ needing provider-specific code paths. Tier-2 trust classification
38
+ lives on the provider (`tier: 2` field), which is correctly the
39
+ manager's structural signal for "consent required."
40
+
41
+ **Signal vs authority**: Compliant. The provider raises errors with
42
+ classified reason strings (the SIGNAL); the manager classifies them
43
+ into `ProviderFailureReason` (the AUTHORITY). The consent gate (PR 5)
44
+ will be a separate AUTHORITY layer above the manager's driver — the
45
+ provider never makes consent decisions on its own.
46
+
47
+ **Interactions**:
48
+ - Dynamic import: the provider does `await import('localtunnel')` at
49
+ first `isAvailable()` call. On environments without the dep, the
50
+ import throws and the provider caches the "unavailable" verdict.
51
+ This is deliberately a runtime check, not a build-time one — instar
52
+ ships without the `localtunnel` dep in `package.json` (per the
53
+ spec's supply-chain hardening — Tier-2 backup capability is opt-in,
54
+ installed explicitly by operators who want it).
55
+ - The `subdomain` constructor option is operator-controlled. The
56
+ comment documents the privacy posture: never use the agent's
57
+ identity as a subdomain hint — it would be publicly visible on
58
+ `*.loca.lt`.
59
+
60
+ **External surfaces**:
61
+ - New exported class `LocaltunnelProvider` from `src/tunnel/`.
62
+ - No new npm dependency — `localtunnel` is NOT added to
63
+ `package.json` in this PR. The provider degrades gracefully when
64
+ the dep is absent. Operators add the dep explicitly to enable the
65
+ capability. This matches the spec's supply-chain posture (Part 7).
66
+ - No new API endpoint, no new CLI command, no new config field
67
+ (Part 4 fields land with PR 5 alongside the consent flow).
68
+
69
+ **Migration parity**: N/A. The provider is server-side code only;
70
+ no agent-installed file change.
71
+
72
+ **Rollback cost**: Trivial. Delete two files (`src/tunnel/Local-
73
+ tunnelProvider.ts` and the test). The provider is isolated; nothing
74
+ else references it.
75
+
76
+ **Tests**:
77
+ - 7/7 tests in `tests/unit/localtunnel-provider.test.ts`.
78
+ - 70/70 tunnel-related tests from PRs 1–3 unaffected.
79
+ - `tsc --noEmit` clean. `npm run lint` clean.
80
+
81
+ **Decision-point inventory**:
82
+ 1. Dynamic import (vs. static `import localtunnel from 'localtunnel'`)
83
+ — keeps `localtunnel` out of the build-time type-check graph so
84
+ instar can ship without the dep. Operators add it explicitly per
85
+ the spec's supply-chain stance.
86
+ 2. `localtunnel` NOT added to `package.json` in this PR — landing the
87
+ dep before the consent gate ships would expose users to an
88
+ unchecked Tier-2 capability. The dep lands in PR 5 alongside the
89
+ consent flow that gates its use. This is intentional and
90
+ per-spec — Part 7 frames the capability as opt-in regardless.
91
+ 3. `subdomain` is operator-controlled and documented as
92
+ non-identifying. The provider does not auto-generate one.
93
+ 4. Failure classification matches PR 1's enum — no new failure
94
+ reasons needed; the manager's existing logic handles localtunnel
95
+ failures the same way as cloudflared failures.
@@ -0,0 +1,86 @@
1
+ # Side-Effects Review — Topic Intent Layer (Layers 1-3)
2
+
3
+ **PR:** https://github.com/JKHeadley/instar/pull/332
4
+ **Spec:** `docs/specs/topic-intent-layer.md` (v14 CLEAN, approved 2026-05-22)
5
+ **Author:** Echo · autonomous run · 2026-05-22
6
+ **Process:** Per `feedback_side_effects_review.md` — seven-dimension review before ship.
7
+
8
+ ## 1. Over-block
9
+
10
+ The feature could over-block if a hook surfaces tentative items as if settled, or if ArcCheck started blocking sends.
11
+
12
+ **Mitigations in place:**
13
+ - ArcCheck verdict shape never includes a `block` or `authority` field. Signal-only by spec and by test (`tests/unit/TopicIntent-arccheck.test.ts` asserts this).
14
+ - Tentative items in the Layer 2 briefing are explicitly hedged with the word "TENTATIVE" + confidence value. Settled items in a separate "SETTLED" section.
15
+ - Observation tier (< 0.30) is NOT surfaced in the briefing at all — no noise leak.
16
+ - The briefing endpoint returns empty body when nothing has accumulated, so bootstrap hooks can skip injection cleanly without distorting normal session start.
17
+
18
+ ## 2. Under-block
19
+
20
+ The feature could under-block if it lets a single LLM extraction silently launder its own guesses to authoritative tier (the canonical GCI failure mode).
21
+
22
+ **Mitigations in place:**
23
+ - Authority hard rule (spec section "Authority gating"): confidence cannot reach the authoritative tier (≥0.7) unless at least one evidence event qualifies as a user-authored episode. Enforced in `projectConfidence` with a clamp at 0.69.
24
+ - Per-message dedup: multiple signals about the same refId from the same source message count as ONE episode with the larger applicable delta — agent cannot fork its own message into multiple episodes.
25
+ - Affirmation safety: per-refId per-24h cap of 1 affirmation bonus, plus per-single-user-message max of 3 distinct refIds affirmed, defends against a single "yes" mass-promoting.
26
+ - Signal-specific caps: `agent-reref` capped at +0.05 cumulative, `extract-agent` at +0.10. Even an unbounded loop of agent-origin signals cannot accumulate to authority.
27
+
28
+ ## 3. Level-of-abstraction fit
29
+
30
+ Is the feature implemented at the right layer of the stack?
31
+
32
+ **Assessment:**
33
+ - Storage at `.instar/topic-intent/<topicId>.json` parallels the existing `.instar/topic-resume-map.json` convention — same level, same JSON-first approach, same file-per-topic granularity.
34
+ - Routes mounted via the same `createXxxRoutes` factory pattern as `worktreeRoutes`, `fileRoutes`, `machineRoutes` — consistent with the codebase's idiom.
35
+ - AgentServer options pattern mirrors existing optional dependencies (e.g., `worktreeManager?`). DI is uniform.
36
+ - Telegram bootstrap hook modification is minimal — one curl call with degrade-open semantics. Slack bootstrap hook is NOT modified (slack channel IDs are string-keyed, not numeric topic IDs; the cleanest adapter pattern for Slack is a follow-up).
37
+
38
+ ## 4. Signal-vs-authority compliance
39
+
40
+ Per `feedback_signal_vs_authority`: brittle/low-context filters detect signals; only higher-level intelligent gates with full context block.
41
+
42
+ **Assessment:**
43
+ - ArcCheck is the signal layer. It returns verdicts that include `kind`, `refId`, `currentTier`, `currentConfidence`, `reason`, `suggestedRewriteHint` — no authority claim.
44
+ - The agent itself is the authority layer. The agent reads the ArcCheck signal in its draft preparation and chooses to either redraft (with a conversational confirmation question) or proceed.
45
+ - The existing outbound gates (tone-gate, response-review, outbound-dedup) retain their blocking authority and are unaffected by this change.
46
+
47
+ ## 5. Cross-feature interactions
48
+
49
+ What other Instar systems could this affect?
50
+
51
+ - **CompactionSentinel** — the briefing prepended on every bootstrap may slightly increase context-window usage at session start. Mitigation: maxPerSection cap (default 8) + overflow note keeps it bounded; observation tier deliberately omitted. The briefing is empty body when nothing tracked.
52
+ - **Tone-gate / response-review** — Layer 3 does NOT integrate into the outbound path in this ship. The classifier exists and is reachable; the actual every-send wiring is a deliberate follow-up so we can think carefully about whether ArcCheck signals should be routed through the existing gate or augment them.
53
+ - **Telegram bootstrap hook** — modified, but failure modes are degrade-open (any error → empty briefing, recent-history output unchanged). The 2-second timeout (`curl --max-time 2`) bounds latency.
54
+ - **Slack bootstrap hook** — NOT modified. Slack channel IDs are string-keyed; topic-intent is numeric-keyed. Adapter pattern for Slack is a v1.1 follow-up.
55
+ - **iMessage / WhatsApp** — same situation as Slack, deferred.
56
+
57
+ ## 6. Rollback cost
58
+
59
+ If this needs to revert, what's the blast radius?
60
+
61
+ - All new files (`src/core/TopicIntent*.ts`, `src/server/topicIntentRoutes.ts`, all `tests/unit/TopicIntent-*.test.ts`, `tests/integration/topic-intent-routes.test.ts`, `tests/e2e/topic-intent-lifecycle.test.ts`) can be deleted without affecting any other system.
62
+ - Modified files (`src/server/AgentServer.ts`, `src/commands/server.ts`, `src/templates/hooks/telegram-topic-context.sh`) have small surgical additions that can be reverted independently — store construction, route mount, optional briefing-fetch with degrade-open.
63
+ - Storage at `.instar/topic-intent/<topicId>.json` is per-topic JSON files; deleting them has no effect on any other Instar state.
64
+ - No new dependencies in `package.json`. No DB migrations. No config-format changes.
65
+
66
+ **Net rollback cost: low.** A clean revert of the feature commit + deleting the storage directory is sufficient.
67
+
68
+ ## 7. Migration parity
69
+
70
+ Per `feedback_migration_parity_standard` (built into CLAUDE.md): changes to agent-installed files must reach existing agents via the update path.
71
+
72
+ **Files in this PR that need migration handling:**
73
+ - `src/templates/hooks/telegram-topic-context.sh` — built-in hook script. Per the Migration Parity Standard, built-in hooks are **always overwritten** on every migration run via `installBuiltinHooks()`. No additional migration code needed — the script will reach existing agents on their next `instar upgrade`.
74
+
75
+ **No other agent-installed files changed** in this PR. CLAUDE.md template is unchanged in this PR; Agent Awareness Standard update (so agents know about the new `/topic-intent/*` endpoints) is the recommended next follow-up before the cherry-pick v1.3.x project.
76
+
77
+ ## Conclusion
78
+
79
+ Ship. Seven-dimension review clean. Rollback cost low. No new external dependencies. All three test tiers green. PII boundary explicitly tested. Framework-parity grep gate in place.
80
+
81
+ Follow-up scoped for future work (in priority order):
82
+ 1. Layer 3 outbound-path wiring (the every-send ArcCheck integration with tone-gate / response-review).
83
+ 2. Slack / iMessage / WhatsApp bootstrap parity for Layer 2 briefing.
84
+ 3. CLAUDE.md template update for Agent Awareness Standard.
85
+ 4. Daily probe job that replays the qalatra-9235 and GCI/Luna-365 fixtures against the live extractor.
86
+ 5. The seven cherry-pick recommendations from the GSD spike (separate v1.3.x project).