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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +6 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/TopicIntent.d.ts +162 -0
- package/dist/core/TopicIntent.d.ts.map +1 -0
- package/dist/core/TopicIntent.js +355 -0
- package/dist/core/TopicIntent.js.map +1 -0
- package/dist/core/TopicIntentArcCheck.d.ts +101 -0
- package/dist/core/TopicIntentArcCheck.d.ts.map +1 -0
- package/dist/core/TopicIntentArcCheck.js +146 -0
- package/dist/core/TopicIntentArcCheck.js.map +1 -0
- package/dist/core/TopicIntentBriefing.d.ts +44 -0
- package/dist/core/TopicIntentBriefing.d.ts.map +1 -0
- package/dist/core/TopicIntentBriefing.js +88 -0
- package/dist/core/TopicIntentBriefing.js.map +1 -0
- package/dist/core/TopicIntentExtractor.d.ts +90 -0
- package/dist/core/TopicIntentExtractor.d.ts.map +1 -0
- package/dist/core/TopicIntentExtractor.js +153 -0
- package/dist/core/TopicIntentExtractor.js.map +1 -0
- package/dist/core/TopicIntentPendingConfirm.d.ts +96 -0
- package/dist/core/TopicIntentPendingConfirm.d.ts.map +1 -0
- package/dist/core/TopicIntentPendingConfirm.js +223 -0
- package/dist/core/TopicIntentPendingConfirm.js.map +1 -0
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +10 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/topicIntentRoutes.d.ts +27 -0
- package/dist/server/topicIntentRoutes.d.ts.map +1 -0
- package/dist/server/topicIntentRoutes.js +189 -0
- package/dist/server/topicIntentRoutes.js.map +1 -0
- package/dist/tunnel/LocaltunnelProvider.d.ts +63 -0
- package/dist/tunnel/LocaltunnelProvider.d.ts.map +1 -0
- package/dist/tunnel/LocaltunnelProvider.js +130 -0
- package/dist/tunnel/LocaltunnelProvider.js.map +1 -0
- package/dist/tunnel/TunnelManager.d.ts +44 -1
- package/dist/tunnel/TunnelManager.d.ts.map +1 -1
- package/dist/tunnel/TunnelManager.js +199 -15
- package/dist/tunnel/TunnelManager.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +4 -4
- package/src/templates/hooks/telegram-topic-context.sh +19 -0
- package/upgrades/1.2.39.md +54 -0
- package/upgrades/1.2.40.md +119 -0
- package/upgrades/side-effects/consent-state-machine.md +135 -0
- package/upgrades/side-effects/localtunnel-provider.md +95 -0
- package/upgrades/side-effects/topic-intent-layer.md +86 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-
|
|
5
|
-
"instarVersion": "1.2.
|
|
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": "
|
|
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": "
|
|
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).
|