instar 1.3.584 → 1.3.586

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,47 @@
1
+ # Side-effects — wedge-recovery terminal-abandon + loss notice (gap #2 / CMT-1563)
2
+
3
+ ## What changed (3 src files + 2 test files)
4
+
5
+ - `src/messaging/MessageProcessingLedger.ts` — new terminal `abandoned` state: `LedgerState` +
6
+ `LedgerEntry.abandonedAt` + `abandoned_at` schema column (idempotent ALTER, self-migrating);
7
+ `markAbandoned(dedupeKey, epoch)` (`processing → abandoned`, sets `abandoned_at`, leaves
8
+ `reply_committed_at` NULL); `isActedOn` + `beginProcessing` now treat `abandoned` as terminal.
9
+ - `src/messaging/stuckMessageRecovery.ts` — the give-up branch calls `markAbandoned` and pushes the
10
+ entry to a new `StuckRecoveryResult.abandoned: Array<{topic, dedupeKey}>`.
11
+ - `src/commands/server.ts` — `runStuckRecovery` emits ONE per-topic loss notice from
12
+ `result.abandoned` via `notify(...)` (targeted to the topic).
13
+
14
+ ## Behavioral side-effects
15
+
16
+ - **The give-up log-loop ends.** An exhausted stuck entry is moved out of `processing`, so
17
+ `reclaimStuck` stops re-selecting it every cycle (was firing `giving up on … after 3 attempts`
18
+ every ~10 min for hours on the same entries).
19
+ - **Loss is no longer silent.** Each abandoned entry produces ONE "I didn't get to N message(s) you
20
+ sent earlier — resend anything still needed" notice to its topic, exactly once.
21
+ - **A redelivery of an abandoned event is dropped** (`isActedOn` true) — a genuine resend has a
22
+ fresh dedupeKey and is processed normally.
23
+ - **No false reply-evidence:** `abandoned` leaves `reply_committed_at` NULL, so
24
+ `hasReplyCommittedForTopicSince` never returns true for it — it can't wrongly suppress recovery of
25
+ a sibling stuck entry on the same topic.
26
+
27
+ ## Risk + rollback
28
+
29
+ - Highest-risk subsystem (exactly-once message ledger). Fail-safe: `markAbandoned` acts ONLY on an
30
+ already-exhausted `processing` entry — it cannot touch an in-flight or still-recoverable entry,
31
+ and never marks anything replied. The new state is additive (free-TEXT `state` column).
32
+ - No flag — a correctness fix to a live loss + log-loop, not a dark feature. Revert = restore the
33
+ prior `skipped++; continue` give-up branch (but that reinstates the silent drop + the loop).
34
+
35
+ ## Tests
36
+
37
+ - `tests/unit/MessageProcessingLedger.test.ts` — 2 new: `markAbandoned` terminal semantics (no fake
38
+ reply, terminal, no false topic reply-evidence, not re-selected); no-op when not `processing`.
39
+ - `tests/unit/stuck-message-recovery.test.ts` — 1 new: exhausted entry abandoned + surfaced + not
40
+ re-looped; the standby-result assertion updated for the new `abandoned: []` field.
41
+ - 30/30 ledger + recovery unit tests green; tsc clean.
42
+
43
+ ## Migration parity
44
+
45
+ Self-migrating SQLite (idempotent `ALTER TABLE ADD COLUMN abandoned_at`) — existing agents' ledgers
46
+ upgrade in place on first access, no PostUpdateMigrator step. Internal recovery mechanism; no
47
+ agent-facing route/capability, so no CLAUDE.md template change required.