instar 1.3.383 → 1.3.384
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/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +20 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/messaging/OutboundContentDedup.d.ts +60 -0
- package/dist/messaging/OutboundContentDedup.d.ts.map +1 -0
- package/dist/messaging/OutboundContentDedup.js +110 -0
- package/dist/messaging/OutboundContentDedup.js.map +1 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +28 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/upgrades/1.3.384.md +44 -0
- package/upgrades/outbound-content-dedup.eli16.md +32 -0
- package/upgrades/side-effects/outbound-content-dedup.md +79 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Side-Effects Review — Outbound Content-Dedup
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `outbound-content-dedup`
|
|
4
|
+
**Date:** `2026-06-06`
|
|
5
|
+
**Author:** `Echo (instar-dev agent — Justin: "We really need to work on not sending duplicate messages. Let's make this much more robust.")`
|
|
6
|
+
**Second-pass reviewer:** `self-adversarial pass over the one real risk — suppressing a message the user actually needed`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
A pure `OutboundContentDedup` (per-topic, windowed, length-gated content
|
|
11
|
+
fingerprint) wired at the `/telegram/reply` route, after the delivery-id LRU and
|
|
12
|
+
before the tone gate. An identical long message to the same topic within ~15min
|
|
13
|
+
is suppressed (200, not re-sent); the first send still goes. Files:
|
|
14
|
+
`OutboundContentDedup.ts` (new), `routes.ts` (instance + check/record),
|
|
15
|
+
`PostUpdateMigrator.ts` (CLAUDE.md note), 3 test files.
|
|
16
|
+
|
|
17
|
+
## Decision-point inventory
|
|
18
|
+
|
|
19
|
+
- content-dedup check — **add (suppress)** — before the send.
|
|
20
|
+
- length floor (minLength 40) — **add** — brief acks always pass.
|
|
21
|
+
- `allowDuplicate` bypass — **reuse (existing metadata)** — caller can force a repeat.
|
|
22
|
+
- record-after-success — **deliberate** — a failed send's retry isn't lost.
|
|
23
|
+
|
|
24
|
+
## 1. Over-block (the real risk — suppressing a wanted message)
|
|
25
|
+
|
|
26
|
+
The danger is dropping a message the user needed. Defenses, each tested:
|
|
27
|
+
- **Length floor:** brief acks (the most common legitimate repeat — two "Got it"
|
|
28
|
+
for two user messages) are exempt (test: "brief acks never suppressed").
|
|
29
|
+
- **Per-topic:** the same text to a different topic sends (tested).
|
|
30
|
+
- **`allowDuplicate` escape hatch:** a caller that means to repeat bypasses it
|
|
31
|
+
(tested).
|
|
32
|
+
- **Record-after-success:** a send that throws is never recorded, so its
|
|
33
|
+
legitimate retry (same content, new id) is not suppressed.
|
|
34
|
+
- **Scope = `/telegram/reply` only:** command responses (bot command handler) and
|
|
35
|
+
sentinel/standby sends go through other paths and are untouched, so a user
|
|
36
|
+
re-running a command still gets fresh output.
|
|
37
|
+
Residual: a caller that legitimately re-sends the EXACT same ≥40-char text to the
|
|
38
|
+
SAME topic within 15min without `allowDuplicate` is suppressed — which is exactly
|
|
39
|
+
the reported bug, and the escape hatch covers the rare intentional case.
|
|
40
|
+
|
|
41
|
+
## 2. Under-block
|
|
42
|
+
|
|
43
|
+
Near-duplicates (reworded status, e.g. the 21:15 variant in the incident) are
|
|
44
|
+
NOT caught — only byte-identical (whitespace-normalized) text. Catching
|
|
45
|
+
semantic near-dups would require an LLM and risks false suppression; the
|
|
46
|
+
deterministic exact-match is the safe, robust core. The reworded-variant case is
|
|
47
|
+
a separate, lower-frequency concern.
|
|
48
|
+
|
|
49
|
+
## 3. Level-of-abstraction fit
|
|
50
|
+
|
|
51
|
+
The dedup is a pure module in `messaging/`, instantiated once per route
|
|
52
|
+
construction beside the existing delivery-id LRU (same lifecycle, same
|
|
53
|
+
chokepoint). It reuses the route's existing `allowDuplicate` metadata contract.
|
|
54
|
+
|
|
55
|
+
## 4. Signal vs authority compliance
|
|
56
|
+
|
|
57
|
+
**Required reference:** `docs/signal-vs-authority.md`
|
|
58
|
+
|
|
59
|
+
- [x] Deterministic guard, no LLM. It removes a redundant send; it never alters
|
|
60
|
+
content and never blocks a non-duplicate. The `allowDuplicate` hatch preserves
|
|
61
|
+
caller authority for intentional repeats. Strictly subtractive on exact dups.
|
|
62
|
+
|
|
63
|
+
## 5. Interactions
|
|
64
|
+
|
|
65
|
+
- **Delivery-id LRU:** complementary — id-dedup catches a re-POST of the same id;
|
|
66
|
+
content-dedup catches a fresh-id re-send of the same text. Runs after it.
|
|
67
|
+
- **Tone gate:** the content-dedup runs BEFORE it (so a duplicate skips the LLM
|
|
68
|
+
call entirely) and independent of it (covers the proxy/relay paths the gate
|
|
69
|
+
skips).
|
|
70
|
+
- **Tokenless-standby relay:** unaffected — the dedup decides before the send;
|
|
71
|
+
the relay still carries the real messageId on a non-duplicate.
|
|
72
|
+
|
|
73
|
+
## 6. External surfaces / 7. Rollback
|
|
74
|
+
|
|
75
|
+
New response field `suppressedDuplicate: true` on a suppressed `/telegram/reply`
|
|
76
|
+
(additive; callers `.catch`/ignore the body today). Optional config
|
|
77
|
+
`outboundContentDedup` (window/minLength/maxPerTopic/enabled) with safe
|
|
78
|
+
defaults; absent ⇒ defaults. One idempotent CLAUDE.md note. Rollback = revert;
|
|
79
|
+
duplicates flow again.
|