instar 1.3.374 → 1.3.375

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.
@@ -5,6 +5,17 @@
5
5
 
6
6
  ## What Changed
7
7
 
8
+ The verified topic operator is now recorded automatically on BOTH inbound
9
+ ingress paths (Know Your Principal standard, security-build increment 2e — the
10
+ final operator-binding gap). Increment 2d covered only the lifeline-forward
11
+ route; the adapter long-poll (no-lifeline) path never bound. The bind now lives
12
+ at the shared `onTopicMessage` seam (`wireTelegramRouting`), guarded by
13
+ `isAuthorizedSender` — load-bearing there, because the seam also fires for
14
+ unauthorized senders. The seam resolves the server's own store instance
15
+ late-bound (a second instance on the same file would lose updates between the
16
+ two in-memory caches), and `TopicOperatorStore.setOperator` now skips the disk
17
+ write when the record is unchanged (per-message re-binds become pure reads).
18
+
8
19
  `POST /pool/transfer` of a QUIET topic (never-seen or released ownership
9
20
  record) previously journaled NOTHING: the placement pin is router-local and
10
21
  the handler's release half only fires when the router itself held ownership.
@@ -25,6 +36,12 @@ other in-flight shape is left strictly untouched.
25
36
 
26
37
  ## What to Tell Your User
27
38
 
39
+ Nothing user-facing changes. Foundation wiring (experimental) for the identity
40
+ isolation security work: the agent now learns its verified operator from
41
+ authorized inbound messages no matter how its Telegram connection is set up.
42
+ Only authorized senders are ever recorded, and a recording failure never
43
+ affects message handling.
44
+
28
45
  Nothing proactively — this is internal coherence plumbing. If they previously
29
46
  moved a quiet conversation between machines and the receiving machine couldn't
30
47
  fetch its files, that path now works: the move itself writes the ownership
@@ -35,6 +52,14 @@ record and it travels to the receiving machine automatically.
35
52
 
36
53
  ## Summary of New Capabilities
37
54
 
55
+ - Operator auto-bind at the `onTopicMessage` convergence seam (covers the
56
+ polling path; authorized senders only; fail-soft; pre-boot messages bind
57
+ nothing, fail-safe).
58
+ - `AgentServer.getTopicOperatorStore()` — public accessor for the seam's
59
+ late-bound store resolution.
60
+ - `TopicOperatorStore.setOperator` idempotency: identical record → no disk
61
+ write.
62
+
38
63
  - Quiet-topic transfers journal ownership evidence (place + claim halves, real
39
64
  epochs, `reason: 'user-move'`) that replicates to the pinned-to machine —
40
65
  unblocking its working-set fetch reflex (#930 fallback finally has a producer).
@@ -45,6 +70,15 @@ record and it travels to the receiving machine automatically.
45
70
 
46
71
  ## Evidence
47
72
 
73
+ Verified by 6 Tier-1 unit tests (`topic-operator-polling-bind`: authorized
74
+ binds, unauthorized Caroline refusal, null store / getter throw / setOperator
75
+ throw all fail-soft with routing continuing, missing uid no-op), 2 Tier-1 store
76
+ tests (idempotent skip both sides), and 3 Tier-2 integration tests
77
+ (`topic-operator-polling-bind`: full Caroline replay through the seam,
78
+ single-instance no-clobber across two topics on disk, pre-construction →
79
+ post-construction lifecycle). All 60 existing topic-operator/session-context
80
+ canaries and 13 e2e lifecycle tests stay green. Clean `tsc --noEmit`.
81
+
48
82
  - `tests/integration/pool-placement-transfer-routes.test.ts` — 6 new boundary
49
83
  tests: quiet/never-seen (both halves journaled, record lands `active`),
50
84
  self-owned (release + place + confirm, 3 entries), active-other (never
@@ -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).