instar 1.3.673 → 1.3.674

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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * F3 (Inbound Delivery Is Sacred) — pure routing plan for inbound-queue loss
3
+ * notices. A lost inbound user message must reach the user who sent it (each
4
+ * loss item's `sessionKey` IS the topic id they messaged from) OR, if it has no
5
+ * resolvable destination, be surfaced LOUDLY — never silently expired. This pure
6
+ * function decides the routing; server.ts does the actual notify()/loud-surface.
7
+ * Constitution: "The User Experience Is the Product" → sub-standard #3 Inbound
8
+ * Delivery Is Sacred. Spec: docs/specs/inbound-delivery-sacred.md.
9
+ */
10
+ export interface InboundLossRoutePlan {
11
+ /** Per-ORIGINATING-topic notice — each affected topic + how many of its
12
+ * messages were lost (delivered IN that topic, the proven path). */
13
+ perTopic: Array<{
14
+ topicId: number;
15
+ count: number;
16
+ }>;
17
+ /** Count of lost items whose sessionKey is not a resolvable numeric topic.
18
+ * These fall back to the attention topic; if that is unset too, they MUST be
19
+ * surfaced loudly (the one seam where a loss could otherwise go silent). */
20
+ unresolved: number;
21
+ }
22
+ /**
23
+ * Group inbound loss items by their originating topic. Pure: same input → same
24
+ * output. A sessionKey that is a positive finite number is a topic id; anything
25
+ * else (empty, non-numeric, legacy single-file key) is counted as `unresolved`.
26
+ */
27
+ export declare function planInboundLossNotices(items: ReadonlyArray<{
28
+ sessionKey: string;
29
+ }>): InboundLossRoutePlan;
30
+ //# sourceMappingURL=inboundLossRouting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inboundLossRouting.d.ts","sourceRoot":"","sources":["../../src/core/inboundLossRouting.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,oBAAoB;IACnC;yEACqE;IACrE,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD;;iFAE6E;IAC7E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,aAAa,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3C,oBAAoB,CAkBtB"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * F3 (Inbound Delivery Is Sacred) — pure routing plan for inbound-queue loss
3
+ * notices. A lost inbound user message must reach the user who sent it (each
4
+ * loss item's `sessionKey` IS the topic id they messaged from) OR, if it has no
5
+ * resolvable destination, be surfaced LOUDLY — never silently expired. This pure
6
+ * function decides the routing; server.ts does the actual notify()/loud-surface.
7
+ * Constitution: "The User Experience Is the Product" → sub-standard #3 Inbound
8
+ * Delivery Is Sacred. Spec: docs/specs/inbound-delivery-sacred.md.
9
+ */
10
+ /**
11
+ * Group inbound loss items by their originating topic. Pure: same input → same
12
+ * output. A sessionKey that is a positive finite number is a topic id; anything
13
+ * else (empty, non-numeric, legacy single-file key) is counted as `unresolved`.
14
+ */
15
+ export function planInboundLossNotices(items) {
16
+ const byTopic = new Map();
17
+ let unresolved = 0;
18
+ for (const it of items) {
19
+ const tid = Number(it.sessionKey);
20
+ if (Number.isFinite(tid) && tid > 0) {
21
+ byTopic.set(tid, (byTopic.get(tid) ?? 0) + 1);
22
+ }
23
+ else {
24
+ unresolved++;
25
+ }
26
+ }
27
+ return {
28
+ // Deterministic order (ascending topic id) for stable notices + tests.
29
+ perTopic: [...byTopic.entries()]
30
+ .sort((a, b) => a[0] - b[0])
31
+ .map(([topicId, count]) => ({ topicId, count })),
32
+ unresolved,
33
+ };
34
+ }
35
+ //# sourceMappingURL=inboundLossRouting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inboundLossRouting.js","sourceRoot":"","sources":["../../src/core/inboundLossRouting.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAA4C;IAE5C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAClC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,UAAU,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO;QACL,uEAAuE;QACvE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;aAC7B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,UAAU;KACX,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.673",
3
+ "version": "1.3.674",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
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-06-26T09:25:54.721Z",
5
- "instarVersion": "1.3.673",
4
+ "generatedAt": "2026-06-26T09:54:44.118Z",
5
+ "instarVersion": "1.3.674",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,43 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The durable inbound holding-queue's loss notices ("I didn't get to your messages")
9
+ were delivered to a single internal attention-topic and **silently dropped when that
10
+ topic was unset** — so a held inbound user message could expire AND the user never be
11
+ told (postmortem Failure 3, the "why aren't you responding?" failure). This routes each
12
+ loss notice to the **originating topic** the user actually messaged from (every loss
13
+ item carries its `sessionKey` = topic id), and surfaces LOUDLY (`console.error`) the one
14
+ residual case where a loss has no resolvable topic and no attention topic is configured.
15
+ A lost inbound message is now never silently expired.
16
+
17
+ New pure router `planInboundLossNotices` (`src/core/inboundLossRouting.ts`, unit-tested)
18
+ + a `notifyInboundLoss` helper in server.ts, applied to all 5 inbound-queue loss sites.
19
+ Constitution: *The User Experience Is the Product* → sub-standard #3.
20
+
21
+ ## What to Tell Your User
22
+
23
+ Nothing changes day-to-day — the inbound holding-queue ships **dark** (off by default),
24
+ so this code only runs when it is deliberately enabled. The guarantee it adds: when the
25
+ queue IS in use and can't deliver one of your messages, you hear about it **in your own
26
+ conversation**, not in a side channel you might never see. A message you send either
27
+ gets through or you're told it didn't — never silence.
28
+
29
+ ## Summary of New Capabilities
30
+
31
+ - Inbound-queue loss notices now route to the originating topic (per-user, in their
32
+ conversation) instead of a single attention topic.
33
+ - A loss with no resolvable topic and no attention topic configured is surfaced loudly
34
+ (error log) instead of silently dropped.
35
+ - No new config, route, or surface; no behavior change while the inbound queue is dark.
36
+
37
+ ## Evidence
38
+
39
+ - 6 unit tests for the pure router (`tests/unit/inboundLossRouting.test.ts`) — originating-topic
40
+ routing, unresolved counting, zero/negative/non-numeric → unresolved, deterministic order.
41
+ - Full `npm run lint` (tsc + ~20 lint scripts) exits 0; clean tsc.
42
+ - Side-effects review: `upgrades/side-effects/inbound-delivery-sacred.md`.
43
+ - Spec: `docs/specs/inbound-delivery-sacred.md`.
@@ -0,0 +1,62 @@
1
+ # Side-Effects Review — Inbound Delivery Is Sacred (Postmortem F3)
2
+
3
+ **Version / slug:** `inbound-delivery-sacred`
4
+ **Date:** `2026-06-26`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `not-required — Phase 5 triggers on a block/allow DECISION on inbound messaging; this makes no block/allow decision (it is loss-notice delivery ROUTING, no authority added), runs only while the queue is dark, and is backed by a pure unit-tested router. Rigor is the 6-case unit suite + this review.`
7
+
8
+ ## Summary
9
+
10
+ Routes inbound-queue loss notices to the ORIGINATING topic (each loss item's
11
+ `sessionKey` is the topic id) instead of the single `agent-attention-topic` that
12
+ `notify()` silently drops when unset. A loss with no resolvable topic and no
13
+ attention topic surfaces LOUDLY (`console.error`) instead of vanishing. New pure
14
+ function `planInboundLossNotices` (`src/core/inboundLossRouting.ts`) + a thin
15
+ `notifyInboundLoss` helper in server.ts, applied to all 5 inbound-queue loss sites.
16
+
17
+ ## The 8 questions
18
+
19
+ 1. **Over-block** — N/A. This is not a gate; it adds no block/reject. It only
20
+ changes WHERE a loss notice is delivered. It cannot reject a legitimate message.
21
+ 2. **Under-block** — N/A (no blocking). The failure mode it closes is *under-notice*
22
+ (a silent drop), which is exactly what it fixes.
23
+ 3. **Level-of-abstraction fit** — Correct layer. The loss is detected in
24
+ `QueueDrainLoop` (already complete); the DELIVERY of the notice is a server-bootstrap
25
+ concern (where `notify` + the attention-topic state live). The grouping logic is
26
+ extracted to a pure, testable core (`inboundLossRouting.ts`); server.ts only does
27
+ the side-effecting notify/console.error. Right split.
28
+ 4. **Signal vs authority** — Pure signal-delivery. No blocking authority added. The
29
+ pure router holds no authority; the helper only emits notices. Complies with
30
+ `docs/signal-vs-authority.md`.
31
+ 5. **Interactions** — Reuses the existing `notify()` funnel (batcher, attention
32
+ topic, Slack mirror) — does not bypass it, only passes a `topicId` so it lands in
33
+ the originating topic. The `stuck-recovery` notice already routed per-topic and is
34
+ UNCHANGED. No double-fire: each loss item is counted once, per topic.
35
+ 6. **External surfaces** — Changes the destination + wording of a user-facing loss
36
+ notice (now per-topic, drops the redundant "topics:" list). It depends on the loss
37
+ item's `sessionKey` being the topic id (verified — that is how the queue keys
38
+ custody). No new external dependency.
39
+ 7. **Multi-machine posture** — Machine-local BY DESIGN. Each machine reports the loss
40
+ of the messages IT held; the notice goes to the originating topic on that machine's
41
+ Telegram path. No replication needed (a loss is a local custody event). The
42
+ attention-topic fallback + loud `console.error` are also machine-local.
43
+ 8. **Rollback cost** — Trivial. The change is inert while the inbound queue is dark
44
+ (default). Revert is a code-only back-out (no migration, no state). The queue's
45
+ loss-DETECTION is unchanged; only the notice routing differs.
46
+
47
+ ## What it does NOT do
48
+
49
+ - Does not change the queue's loss-detection, retry, or expiry logic (all already
50
+ complete). It only hardens the notice *channel*.
51
+ - Does not touch the fail-OPEN direct-inject fallback (already comprehensive).
52
+ - Does not run while the queue is dark (default) — zero behavior change on the fleet.
53
+
54
+ ## Rollback
55
+
56
+ Revert the commit. No persisted state, no migration. Inert while `inboundQueueConfig`
57
+ ships `enabled:false`.
58
+
59
+ ## Second-pass reviewer verdict
60
+
61
+ Not required — see the header rationale (no block/allow decision; delivery-routing only;
62
+ dark; pure-function-backed). The decision boundary is covered by the 6-case unit suite.