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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +37 -9
- package/dist/commands/server.js.map +1 -1
- package/dist/core/inboundLossRouting.d.ts +30 -0
- package/dist/core/inboundLossRouting.d.ts.map +1 -0
- package/dist/core/inboundLossRouting.js +35 -0
- package/dist/core/inboundLossRouting.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.674.md +43 -0
- package/upgrades/side-effects/inbound-delivery-sacred.md +62 -0
|
@@ -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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-26T09:
|
|
5
|
-
"instarVersion": "1.3.
|
|
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.
|