instar 1.3.374 → 1.3.376
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 +6 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +42 -4
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +16 -2
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +25 -5
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/monitoring/CompactionSentinel.d.ts.map +1 -1
- package/dist/monitoring/CompactionSentinel.js +10 -4
- package/dist/monitoring/CompactionSentinel.js.map +1 -1
- package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
- package/dist/monitoring/RateLimitSentinel.js +9 -1
- package/dist/monitoring/RateLimitSentinel.js.map +1 -1
- package/dist/server/AgentServer.d.ts +9 -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/routes.d.ts.map +1 -1
- package/dist/server/routes.js +11 -3
- package/dist/server/routes.js.map +1 -1
- package/dist/users/TopicOperatorStore.d.ts.map +1 -1
- package/dist/users/TopicOperatorStore.js +8 -0
- package/dist/users/TopicOperatorStore.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/{1.3.374.md → 1.3.375.md} +34 -0
- package/upgrades/1.3.376.md +72 -0
- package/upgrades/sentinel-stale-uuid-fallback.eli16.md +15 -0
- package/upgrades/side-effects/sentinel-stale-uuid-fallback.md +75 -0
- package/upgrades/side-effects/topic-operator-polling-bind-inc2e.md +62 -0
- package/upgrades/topic-operator-polling-bind-inc2e.eli16.md +56 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Side-Effects Review — Sentinel stale-uuid fallback + last-writer-wins bridge
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `sentinel-stale-uuid-fallback`
|
|
4
|
+
**Date:** `2026-06-06`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `pending (Phase 3 review agents)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two-layer fix for the 2026-06-06 false-escalation incident (echo-api-errors: 6
|
|
11
|
+
futile resume nudges against an actively-answering session; echo-exo-3-0: false
|
|
12
|
+
"no jsonl growth after 6 attempts over 22m" escalation at 02:26 PDT):
|
|
13
|
+
|
|
14
|
+
1. `SessionManager.setClaudeSessionId` + the `/hooks/events` route guard change
|
|
15
|
+
from write-once to last-writer-wins, so the claudeSessionId record follows
|
|
16
|
+
conversation-UUID rotation (fresh respawn / `claude --resume`).
|
|
17
|
+
2. `RateLimitSentinel.readJsonlBaseline` + `CompactionSentinel.readJsonlBaseline`
|
|
18
|
+
degrade a stale uuid (recorded transcript missing on disk) to the existing
|
|
19
|
+
newest-jsonl heuristic instead of returning null (which made recovery
|
|
20
|
+
verification permanently unable to succeed).
|
|
21
|
+
|
|
22
|
+
## Decision-point inventory
|
|
23
|
+
|
|
24
|
+
- Bridge update condition: `session.claudeSessionId !== payload.session_id`
|
|
25
|
+
(route) / identical-id no-op (manager). Both sides tested.
|
|
26
|
+
- Sentinel baseline resolution: exact-uuid file exists → exact (unchanged);
|
|
27
|
+
uuid recorded but file missing → newest-jsonl fallback (NEW); no uuid →
|
|
28
|
+
newest-jsonl (unchanged). All three arms tested.
|
|
29
|
+
|
|
30
|
+
## 1. Over-block
|
|
31
|
+
|
|
32
|
+
Nothing new is rejected. The bridge accepts strictly MORE updates than before
|
|
33
|
+
(rotations were previously dropped). The sentinels accept strictly MORE
|
|
34
|
+
verification evidence than before.
|
|
35
|
+
|
|
36
|
+
## 2. Under-block / residual risk
|
|
37
|
+
|
|
38
|
+
- **Sibling false-positive on the fallback arm:** in a multi-session project
|
|
39
|
+
root, the newest jsonl can belong to ANOTHER session, so a genuinely-stuck
|
|
40
|
+
session with a stale pointer could be falsely marked "recovered" when a
|
|
41
|
+
sibling produces output. This is the SAME accepted risk profile as the
|
|
42
|
+
pre-existing no-uuid arm, it only applies while the pointer is stale (layer 1
|
|
43
|
+
re-freshens it on the next hook event, typically within seconds on an active
|
|
44
|
+
session), and the failure mode it replaces — guaranteed false escalation +
|
|
45
|
+
6 wasted nudge-refires on healthy sessions — is strictly worse. A falsely
|
|
46
|
+
"recovered" stuck session is still backstopped by ActiveWorkSilenceSentinel.
|
|
47
|
+
- **Stale event flip-back:** a late-arriving hook event from a dying old
|
|
48
|
+
conversation could briefly flip the pointer backwards; the next event from
|
|
49
|
+
the live conversation flips it forward again, and all consumers re-resolve
|
|
50
|
+
per call (no cached snapshot), so the effect is transient.
|
|
51
|
+
- Sessions that emit no hook events (none expected — the reporter fires on
|
|
52
|
+
every tool use/stop) keep whatever pointer they have; the sentinel fallback
|
|
53
|
+
covers them.
|
|
54
|
+
|
|
55
|
+
## 3. Level-of-abstraction fit
|
|
56
|
+
|
|
57
|
+
The rotation fix lives at the single bridge chokepoint both consumers share
|
|
58
|
+
(`setClaudeSessionId` + its one route caller). The degrade-not-fail fix lives
|
|
59
|
+
inside each sentinel's existing `readJsonlBaseline`, the single resolution
|
|
60
|
+
point per sentinel; codex/gemini framework arms are untouched (they never used
|
|
61
|
+
the uuid path). No migration needed: the fix is pure server code; existing
|
|
62
|
+
stale records self-heal on the first post-deploy hook event per session.
|
|
63
|
+
|
|
64
|
+
## Rollback
|
|
65
|
+
|
|
66
|
+
Single revert of the PR restores write-once + null-on-missing exactly (no
|
|
67
|
+
state-shape changes, no config, no new files).
|
|
68
|
+
|
|
69
|
+
## Blast radius
|
|
70
|
+
|
|
71
|
+
Server-side only. No agent-installed files, no hook templates, no config
|
|
72
|
+
defaults, no CLAUDE.md template changes → no Migration Parity obligations.
|
|
73
|
+
Consumers of `claudeSessionId` (session resume save, reaper guards,
|
|
74
|
+
TopicResumeMap, sentinels) now see a FRESHER value — the value they were
|
|
75
|
+
always designed to expect.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Side-effects review — Topic Operator polling-path auto-bind (Know Your Principal #898, increment 2e)
|
|
2
|
+
|
|
3
|
+
## What this change does
|
|
4
|
+
Closes the auto-bind gap #909 documented: the adapter long-poll (no-lifeline)
|
|
5
|
+
ingress path never bound the verified operator. The bind now lives at the
|
|
6
|
+
`onTopicMessage` seam in `wireTelegramRouting` (src/commands/server.ts) — the
|
|
7
|
+
convergence point BOTH ingress paths reach — so a no-lifeline install learns its
|
|
8
|
+
operator too. Three coordinated pieces:
|
|
9
|
+
|
|
10
|
+
- `wireTelegramRouting` gains a late-bound `getTopicOperatorStore` parameter
|
|
11
|
+
(the `getHubDeps` precedent in the same function) and an additive bind block
|
|
12
|
+
early in the callback: an AUTHENTICATED + AUTHORIZED sender is recorded as the
|
|
13
|
+
topic's verified operator.
|
|
14
|
+
- `AgentServer.getTopicOperatorStore()` — public read-only accessor so the seam
|
|
15
|
+
resolves the server's OWN store instance at message-time.
|
|
16
|
+
- `TopicOperatorStore.setOperator` idempotency guard: an identical record skips
|
|
17
|
+
the disk write (both paths re-bind per message; unchanged = pure read).
|
|
18
|
+
|
|
19
|
+
## The load-bearing security property
|
|
20
|
+
The seam fires for unauthorized senders too — the lifeline path only skips its
|
|
21
|
+
own bind, it does not drop the message. So the `isAuthorizedSender` check INSIDE
|
|
22
|
+
the seam bind is load-bearing: without it, an unauthorized group member could
|
|
23
|
+
seat themselves as operator (the cross-principal "Caroline" bug). The
|
|
24
|
+
integration Caroline replay proves the refusal — an unauthorized sender with
|
|
25
|
+
`firstName: "Caroline"` cannot displace the bound operator.
|
|
26
|
+
|
|
27
|
+
## Why the SAME store instance (the lost-update hazard)
|
|
28
|
+
`TopicOperatorStore` caches its map in memory (`this.cache`). A second instance
|
|
29
|
+
on the same file would hold a divergent cache: a record written through one
|
|
30
|
+
instance disappears from the other's next full-map save. The original Inc-2e
|
|
31
|
+
scout suggested constructing a fresh store inside the callback — that design is
|
|
32
|
+
REJECTED here for exactly this reason. Instead the seam resolves
|
|
33
|
+
`AgentServer.getTopicOperatorStore()` late-bound (module-level `_agentServerRef`
|
|
34
|
+
assigned right after construction; the server is built long after routing is
|
|
35
|
+
wired). The integration no-clobber test pins the invariant.
|
|
36
|
+
|
|
37
|
+
## Blast radius (hot path — reviewed carefully)
|
|
38
|
+
- **Additive + fail-soft.** The bind block is wrapped in try/catch; a getter
|
|
39
|
+
error, store error, or missing uid logs and falls through — message routing
|
|
40
|
+
is never affected. Proven by unit tests (getter throws / setOperator throws /
|
|
41
|
+
null store → handleCommand still runs).
|
|
42
|
+
- **Pre-construction window.** Messages arriving before `_agentServerRef` is
|
|
43
|
+
assigned bind nothing (getter → null). Fail-safe: no binding means "unknown",
|
|
44
|
+
never a wrong operator. Lifecycle test covers the transition.
|
|
45
|
+
- **Lifeline double-bind.** On the lifeline path both the routes-side bind
|
|
46
|
+
(#909) and the seam bind run — same instance, same record; the new
|
|
47
|
+
idempotency guard makes the second a no-op read.
|
|
48
|
+
- **Disk-write reduction (behavior change, benign).** `setOperator` with a
|
|
49
|
+
byte-identical record no longer rewrites the file. Callers only ever read the
|
|
50
|
+
returned record or the store state — both unchanged. Existing store tests
|
|
51
|
+
(12) stay green; 2 new tests pin both sides (identical skips, changed
|
|
52
|
+
writes).
|
|
53
|
+
- **No new route / config key / dependency.** `wireTelegramRouting` is now
|
|
54
|
+
exported (test seam only; no runtime caller change beyond the two existing
|
|
55
|
+
callsites gaining the getter argument).
|
|
56
|
+
|
|
57
|
+
## No-allowlist trust model (unchanged, documented)
|
|
58
|
+
`isAuthorizedSender` semantics are #909's: with no `authorizedUserIds`
|
|
59
|
+
allowlist, every authenticated sender is accepted, so the operator is the
|
|
60
|
+
most-recent authenticated sender. Consistent with the existing trust model;
|
|
61
|
+
the uid is Telegram-authenticated, never a content name
|
|
62
|
+
(`TopicOperatorStore.setOperator` enforces that by construction, #904).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ELI16 — Remembering the boss on the simpler phone line too
|
|
2
|
+
|
|
3
|
+
## The one-sentence version
|
|
4
|
+
The agent now writes down "this is the verified boss of this chat" no matter
|
|
5
|
+
which of its two message pipes the boss's message arrived through — before, one
|
|
6
|
+
pipe did this and the other forgot.
|
|
7
|
+
|
|
8
|
+
## The backstory
|
|
9
|
+
We've been closing a real security hole: an agent on a shared computer slowly
|
|
10
|
+
started treating a *different real person* (call her Caroline) as its boss. The
|
|
11
|
+
fix was a filing cabinet that records, per chat, who the verified boss is —
|
|
12
|
+
decided ONLY by the verified ID of whoever actually sent the message, never by a
|
|
13
|
+
name typed in a document. The last step (increment 2d) made the cabinet fill in
|
|
14
|
+
automatically — but only on the agent's MAIN message pipe (the relay most of the
|
|
15
|
+
fleet uses). A simpler setup, where the agent talks to Telegram directly without
|
|
16
|
+
the relay, still never filled in the cabinet. That was safe (an empty cabinet
|
|
17
|
+
just means "I don't know," never a wrong answer), but it meant some agents never
|
|
18
|
+
learned who their boss was.
|
|
19
|
+
|
|
20
|
+
## What this change adds
|
|
21
|
+
Both pipes eventually flow through one shared doorway in the code (the
|
|
22
|
+
`onTopicMessage` seam). This change puts the "write down the boss" step at that
|
|
23
|
+
doorway, so it runs no matter which pipe delivered the message. Two careful
|
|
24
|
+
details:
|
|
25
|
+
|
|
26
|
+
1. **The doorway still checks the allowed list.** The main pipe lets messages
|
|
27
|
+
from non-allowed people *through the doorway* (it just refuses to bind them
|
|
28
|
+
earlier) — so the doorway check is load-bearing. Without it, an outsider in
|
|
29
|
+
the group could seat themselves as boss — the exact Caroline bug. We test
|
|
30
|
+
this with a literal unauthorized "Caroline" message and prove nobody gets
|
|
31
|
+
written down.
|
|
32
|
+
2. **One cabinet, not two.** The cabinet keeps a copy of its contents in memory.
|
|
33
|
+
If the doorway opened its OWN second cabinet on the same file, the two copies
|
|
34
|
+
could silently overwrite each other's entries. So the doorway asks the server
|
|
35
|
+
for its existing cabinet (resolved late, because the server is built after
|
|
36
|
+
the doorway is wired) — same instance, no lost entries. A test proves an
|
|
37
|
+
entry written one way survives an entry written the other way.
|
|
38
|
+
|
|
39
|
+
## A small bonus fix
|
|
40
|
+
Since both pipes now re-write the boss on every message, the cabinet learned to
|
|
41
|
+
notice "this is exactly what I already have" and skip the disk write — a
|
|
42
|
+
re-delivered or repeated message is now a pure read instead of a pointless file
|
|
43
|
+
rewrite.
|
|
44
|
+
|
|
45
|
+
## Why it's safe
|
|
46
|
+
- Wrapped so any failure is logged and swallowed — recording the boss can never
|
|
47
|
+
break message handling.
|
|
48
|
+
- Before the server finishes booting, the doorway just skips the step (no
|
|
49
|
+
cabinet yet = nothing written = fail-safe).
|
|
50
|
+
- On the main pipe both the old write (increment 2d) and the new doorway write
|
|
51
|
+
run — same cabinet, same record, harmless.
|
|
52
|
+
|
|
53
|
+
## What's deliberately left for next time
|
|
54
|
+
Nothing in this family — the operator-binding loop is now closed on every
|
|
55
|
+
ingress path. The remaining Know Your Principal work is elsewhere (per-agent
|
|
56
|
+
credential isolation, Phase 3).
|