instar 1.3.1638 → 1.3.1639

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 (42) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +22 -8
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/SessionDrainRunner.d.ts +4 -2
  5. package/dist/core/SessionDrainRunner.d.ts.map +1 -1
  6. package/dist/core/SessionDrainRunner.js +2 -2
  7. package/dist/core/SessionDrainRunner.js.map +1 -1
  8. package/dist/core/TopicProfileOrchestrator.d.ts.map +1 -1
  9. package/dist/core/TopicProfileOrchestrator.js +2 -1
  10. package/dist/core/TopicProfileOrchestrator.js.map +1 -1
  11. package/dist/data/standards-guard-index.json +1 -1
  12. package/dist/data/standards-guard-index.meta.json +2 -2
  13. package/dist/data/standards-registry.meta.json +1 -1
  14. package/dist/messaging/plainNoticeCheck.d.ts +9 -0
  15. package/dist/messaging/plainNoticeCheck.d.ts.map +1 -0
  16. package/dist/messaging/plainNoticeCheck.js +68 -0
  17. package/dist/messaging/plainNoticeCheck.js.map +1 -0
  18. package/dist/messaging/shared/isSystemOrProxyMessage.d.ts +0 -24
  19. package/dist/messaging/shared/isSystemOrProxyMessage.d.ts.map +1 -1
  20. package/dist/messaging/shared/isSystemOrProxyMessage.js +6 -3
  21. package/dist/messaging/shared/isSystemOrProxyMessage.js.map +1 -1
  22. package/dist/messaging/userNotices.d.ts +47 -0
  23. package/dist/messaging/userNotices.d.ts.map +1 -0
  24. package/dist/messaging/userNotices.js +79 -0
  25. package/dist/messaging/userNotices.js.map +1 -0
  26. package/dist/native/InstarWedgeCanary.app/Contents/MacOS/InstarWedgeCanary +0 -0
  27. package/dist/native/instar-desktop-host +0 -0
  28. package/dist/scaffold/templates.d.ts.map +1 -1
  29. package/dist/scaffold/templates.js +1 -0
  30. package/dist/scaffold/templates.js.map +1 -1
  31. package/package.json +4 -3
  32. package/scripts/lint-plain-user-notices.mjs +191 -0
  33. package/scripts/plain-user-notices-baseline.json +6 -0
  34. package/src/data/builtin-manifest.json +2 -2
  35. package/src/data/plain-notice-terms.json +38 -0
  36. package/src/data/standards-guard-index.json +1 -1
  37. package/src/data/standards-guard-index.meta.json +2 -2
  38. package/src/data/standards-registry.meta.json +1 -1
  39. package/src/scaffold/templates.ts +1 -0
  40. package/upgrades/1.3.1639.md +24 -0
  41. package/upgrades/side-effects/plain-user-notices.eli16.md +15 -0
  42. package/upgrades/side-effects/plain-user-notices.md +76 -0
@@ -0,0 +1,76 @@
1
+ # Side-Effects Review — Plain and Complete server notices
2
+
3
+ **Version / slug:** `plain-user-notices`
4
+ **Date:** `2026-11-03`
5
+ **Author:** `Echo`
6
+ **Tier:** 1 (a revert fully undoes it: wording, a test-time check, a build-time lint; no state, no migration of data)
7
+ **Second-pass reviewer:** see the end of this file
8
+
9
+ ## Summary of the change
10
+
11
+ Justin (2026-11-03, topic 10000001507) flagged the forced-close move notice: `This conversation moved machines mid-task (drain bound (30000ms) reached — final turn may be partial). Work resumes on the new machine — the final turn before the move may be partial.` It pasted `SessionDrainRunner`'s internal reason in raw, said the same thing twice and did not name the machine. He asked for a standard covering every message.
12
+
13
+ 1. **The notice.** `SessionDrainRunnerDeps.notifyInterrupted` drops its `detail` argument, so no reason text can cross that seam; the bound now goes in the `drain-completed-interrupted` audit row as `drainBoundMs`. The server wiring looks up the target's nickname (`meshIdMgr.getActiveMachines()`) and sends `movedMachinesMidStepNotice()`: "I moved this conversation to the Mac Studio before I'd finished my last step, so that step may be incomplete. The conversation carries on there. If that step matters, ask me to check it." Nothing reads `interrupted_mid_task` on the resuming side (`git grep` finds only the writer), so the notice does not promise a check. An id or missing name reads "another machine" (Rule 7). **Still sent when the session announced the move itself**: that note is optional and written before the cut, so this is the only record that the step was cut off.
14
+ 2. **Restart notices.** `🔄 Session restarting — message queued.` → `SESSION_RESTARTING_NOTICE`. `respawnSessionForTopic` takes a new `quietAfterRespawn` option. The two callers that already told the person a restart is under way for their message set it: the dead-session path ("restarting" notice) and the context-exhausted path ("conversation got too long"). The reply then confirms the restart, and a second note would be lifecycle narration (Near-Silent Notifications). Every other caller, including the queue-drain respawn and the SessionRefresh respawner, whose waiting text may be an internal prompt, still confirms once with `SESSION_BACK_NOTICE`, which is true for all of them.
15
+ 3. **Pinned-profile note** → `PROFILE_REAPPLY_NOTICE` (the `[#seq]` stamp is kept: it defeats the relay's duplicate window).
16
+ 4. **Mechanism.** `src/messaging/userNotices.ts` (catalog), `src/messaging/plainNoticeCheck.ts` (deterministic check, test-time only), `src/data/plain-notice-terms.json` (one term list for the check and the lint), `scripts/lint-plain-user-notices.mjs` + frozen baseline (in `npm run lint`).
17
+ 5. **Standard.** Rule 8 in `docs/UX-AND-AGENT-AGENCY-STANDARD.md`, plus one bullet in the generated CLAUDE.md's Agent-as-Interface block (a core heading, regenerated on every update by the reference layout, so existing agents get it).
18
+
19
+ Choice of rewrites was measured, not guessed: automation-provenance rows in Echo's `telegram-messages.jsonl`, 2026-10-30 → 11-03. Heavier 30-day offenders (background-shutdown, passkey health) had already gone quiet after earlier fixes; the sibling-owned SessionMonitor/PresenceProxy texts (topics 10000001496, 10000001309) were left alone. The rest are tracked as fb-d1c2f259-243.
20
+
21
+ ## Decision-point inventory
22
+
23
+ - `isSystemOrProxyMessage` — modify — reads the catalog's exact texts and prefixes instead of its own literals; old wordings kept.
24
+ - Post-respawn notice — modify — suppressed only for the two callers that announced the restart first (`quietAfterRespawn`).
25
+ - Build gate — add — `lint-plain-user-notices` (dev-time ratchet, no runtime authority), registered in `REQUIRED_LINTS` (`tests/unit/lint-chain-completeness.test.ts`) so a merge cannot drop it.
26
+
27
+ **unbounded-self-action: n/a.** No self-triggered action is added or changed. Every respawn and notify call site keeps its trigger; the change rewords existing notices and suppresses one post-respawn notice for two callers.
28
+
29
+ ## 1. Over-block
30
+
31
+ The lint can flag a legitimate interpolation named `reason`/`detail` that already holds plain text. The waiver `@plain-notice-ok: <why>` covers it, and the baseline grandfathers today's 12 hits. The test-time check could flag a catalog wording that is fine ("null" as an English word); the term list is small and deliberate. Nothing at runtime can be blocked.
32
+
33
+ ## 2. Under-block
34
+
35
+ The lint only sees text built inline at a send call; text built in a variable elsewhere is missed (stated in the script header). The catalog covers only notices moved into it. The too-vague side has only a word floor; "enough information" beyond that is review judgment. Most of the 75 server.ts send lines build text elsewhere, so coverage grows as notices move into the catalog (fb-d1c2f259-243).
36
+
37
+ The per-file count ratchet lets a fixed hit be swapped for a new one in the same file. That is a known limit of the pattern, and lowering the baseline (`--update`) after each fix closes the room. Text returned as a command reply, not sent through a send call (`/route` and `/local-model` replies still say "Session respawned."; `topicProfileWriteSurface.ts`), is not seen by the lint. Those are listed for the follow-up.
38
+
39
+ ## 3. Level-of-abstraction fit
40
+
41
+ The send path is the wrong place: these are deterministic alarms that must not wait on a model (*The Operator Channel Is Sacred*). Build/test time is the right layer for fixed strings. The tone gate stays the authority for session-written text; this does not parallel it.
42
+
43
+ ## 4. Signal vs authority compliance
44
+
45
+ `findPlainNoticeProblems` is a brittle signal used only in tests. The lint is a dev-time ratchet with a waiver and a baseline, the same pattern as `lint-no-direct-destructive` and `lint-no-wholefile-sync-read`. Neither holds runtime authority over any message. Compliant with `docs/signal-vs-authority.md`.
46
+
47
+ ## 5. Interactions
48
+
49
+ - `isSystemOrProxyMessage` feeds CompactionSentinel recovery, the PresenceProxy race guard and PromiseBeacon. If a reworded notice were missed, it would count as an agent reply and could suppress a recovery. Covered: the classifier reads the catalog and the test asserts every new and legacy wording. The moved-machines notice is now also classified as a system message (before, it counted as a reply), which is the correct reading.
50
+ - Sibling branches touch PresenceProxy, SessionMonitor and SlackAdapter; this change touches none of their lines.
51
+ - `TopicProfileOrchestrator` tests matched the old wording; updated.
52
+
53
+ ## 6. External surfaces
54
+
55
+ User-visible wording changes in four notices, and one notice is no longer sent. Every agent gets these on update. No API, config or state shape changes; `notifyInterrupted` is an internal seam with one implementation.
56
+
57
+ ## 7. Multi-machine posture
58
+
59
+ The move notice runs on the source machine (the drain owner) and names the target from the mesh identity registry, which is replicated to every machine, so there is no machine-local assumption. A missing nickname reads "another machine". The notice is sent once by the one drain owner (one voice). The catalog and lint are code, so no state is involved.
60
+
61
+ ## 8. Rollback cost
62
+
63
+ Revert the commit. No data, config or migration to undo.
64
+
65
+ ## Second-pass review
66
+
67
+ **Reviewer:** independent reviewer subagent (general-purpose, Opus 5.5), given the diff and this artifact with no conclusions.
68
+
69
+ Not required by Phase 5's list in the strict sense (no block/allow decision on messaging), but the classifier and the dropped post-respawn notice touch session-lifecycle messaging, so an independent reviewer subagent read the diff and this artifact.
70
+
71
+ **Result: concur, with four findings, all addressed.**
72
+ 1. *Two respawn callers passing a message had no prior notice* (the queue-drain respawn, and the SessionRefresh respawner, where the waiting text can be an internal prompt that never gets a reply). The first version silenced the post-respawn note for any caller with a message. Fixed: the `quietAfterRespawn` option is set only by the two callers that announce first, and every other caller still confirms.
73
+ 2. *The lint missed `${err instanceof Error ? err.message : String(err)}` and `${(err as Error).message}`*, the most common forms. Fixed: any expression reading an error's message or stringifying an error is flagged, case-insensitively. A test case was added, and the baseline was re-frozen at 12.
74
+ 3. *The count ratchet allows swaps within a file.* Accepted as a known limit and stated under Under-block.
75
+ 4. *Old wording remains in command replies* not visible to the lint. Listed for the follow-up.
76
+ The reviewer confirmed the classifier change is correct (the moved-machines notice previously counted as a reply), `meshIdMgr` is in scope at the wiring point, and the term file resolves in a published package (`src/data` is in `files`).