cli-jaw 2.2.25 → 2.3.0

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 (159) hide show
  1. package/README.md +5 -2
  2. package/dist/bin/cli-jaw.js +6 -1
  3. package/dist/bin/cli-jaw.js.map +1 -1
  4. package/dist/bin/commands/doctor.js +39 -18
  5. package/dist/bin/commands/doctor.js.map +1 -1
  6. package/dist/bin/commands/init.js +82 -23
  7. package/dist/bin/commands/init.js.map +1 -1
  8. package/dist/bin/commands/messaging.js +262 -0
  9. package/dist/bin/commands/messaging.js.map +1 -0
  10. package/dist/server.js +30 -6
  11. package/dist/server.js.map +1 -1
  12. package/dist/src/cli/commands.js +6 -2
  13. package/dist/src/cli/commands.js.map +1 -1
  14. package/dist/src/cli/handlers/remote-session-commands.js +94 -0
  15. package/dist/src/cli/handlers/remote-session-commands.js.map +1 -0
  16. package/dist/src/command-contract/catalog.js +1 -1
  17. package/dist/src/command-contract/catalog.js.map +1 -1
  18. package/dist/src/core/chat-sessions.js +4 -0
  19. package/dist/src/core/chat-sessions.js.map +1 -1
  20. package/dist/src/core/config.js +63 -22
  21. package/dist/src/core/config.js.map +1 -1
  22. package/dist/src/core/db.js +6 -0
  23. package/dist/src/core/db.js.map +1 -1
  24. package/dist/src/core/dispatch-approval-ingress.js +45 -3
  25. package/dist/src/core/dispatch-approval-ingress.js.map +1 -1
  26. package/dist/src/core/dispatch-approval.js +46 -0
  27. package/dist/src/core/dispatch-approval.js.map +1 -1
  28. package/dist/src/core/logger.js +67 -0
  29. package/dist/src/core/logger.js.map +1 -1
  30. package/dist/src/core/session-generation.js +74 -0
  31. package/dist/src/core/session-generation.js.map +1 -0
  32. package/dist/src/discord/bot.js +91 -5
  33. package/dist/src/discord/bot.js.map +1 -1
  34. package/dist/src/discord/commands.js +34 -1
  35. package/dist/src/discord/commands.js.map +1 -1
  36. package/dist/src/discord/forwarder.js +4 -1
  37. package/dist/src/discord/forwarder.js.map +1 -1
  38. package/dist/src/discord/send-only-client.js +14 -7
  39. package/dist/src/discord/send-only-client.js.map +1 -1
  40. package/dist/src/messaging/access-policy.js +26 -0
  41. package/dist/src/messaging/access-policy.js.map +1 -0
  42. package/dist/src/messaging/approval-presentation.js +89 -0
  43. package/dist/src/messaging/approval-presentation.js.map +1 -0
  44. package/dist/src/messaging/channel-adapter.js +47 -0
  45. package/dist/src/messaging/channel-adapter.js.map +1 -0
  46. package/dist/src/messaging/channel-capabilities.js +47 -15
  47. package/dist/src/messaging/channel-capabilities.js.map +1 -1
  48. package/dist/src/messaging/channel-health.js +23 -3
  49. package/dist/src/messaging/channel-health.js.map +1 -1
  50. package/dist/src/messaging/delivery-outcome.js +12 -0
  51. package/dist/src/messaging/delivery-outcome.js.map +1 -1
  52. package/dist/src/messaging/durable-ingress.js +458 -0
  53. package/dist/src/messaging/durable-ingress.js.map +1 -0
  54. package/dist/src/messaging/effect-once.js +204 -0
  55. package/dist/src/messaging/effect-once.js.map +1 -0
  56. package/dist/src/messaging/inbound-envelope.js +168 -0
  57. package/dist/src/messaging/inbound-envelope.js.map +1 -0
  58. package/dist/src/messaging/ingress-audit.js +41 -0
  59. package/dist/src/messaging/ingress-audit.js.map +1 -0
  60. package/dist/src/messaging/ingress-generation.js +22 -0
  61. package/dist/src/messaging/ingress-generation.js.map +1 -0
  62. package/dist/src/messaging/metrics.js +69 -0
  63. package/dist/src/messaging/metrics.js.map +1 -0
  64. package/dist/src/messaging/outbound-outbox.js +184 -0
  65. package/dist/src/messaging/outbound-outbox.js.map +1 -0
  66. package/dist/src/messaging/remote-command-context.js +30 -0
  67. package/dist/src/messaging/remote-command-context.js.map +1 -0
  68. package/dist/src/messaging/retry.js +11 -2
  69. package/dist/src/messaging/retry.js.map +1 -1
  70. package/dist/src/messaging/runtime.js +129 -30
  71. package/dist/src/messaging/runtime.js.map +1 -1
  72. package/dist/src/messaging/send.js +31 -6
  73. package/dist/src/messaging/send.js.map +1 -1
  74. package/dist/src/messaging/trace-context.js +56 -0
  75. package/dist/src/messaging/trace-context.js.map +1 -0
  76. package/dist/src/messaging/types.js +39 -1
  77. package/dist/src/messaging/types.js.map +1 -1
  78. package/dist/src/routes/orchestrate.js +10 -3
  79. package/dist/src/routes/orchestrate.js.map +1 -1
  80. package/dist/src/routes/settings.js +26 -1
  81. package/dist/src/routes/settings.js.map +1 -1
  82. package/dist/src/slack/bot.js +182 -70
  83. package/dist/src/slack/bot.js.map +1 -1
  84. package/dist/src/slack/commands.js +16 -1
  85. package/dist/src/slack/commands.js.map +1 -1
  86. package/dist/src/slack/format.js +4 -1
  87. package/dist/src/slack/format.js.map +1 -1
  88. package/dist/src/slack/manifest.js +5 -0
  89. package/dist/src/slack/manifest.js.map +1 -1
  90. package/dist/src/slack/send-handler.js +26 -4
  91. package/dist/src/slack/send-handler.js.map +1 -1
  92. package/dist/src/slack/send-only-client.js +26 -2
  93. package/dist/src/slack/send-only-client.js.map +1 -1
  94. package/dist/src/slack/socket.js +36 -13
  95. package/dist/src/slack/socket.js.map +1 -1
  96. package/dist/src/telegram/bot.js +122 -14
  97. package/dist/src/telegram/bot.js.map +1 -1
  98. package/package.json +4 -1
  99. package/public/dist/assets/ChannelEnablementControl-Da_t_jq6.js +1 -0
  100. package/public/dist/assets/ChannelsDiscord-kNjCCJcA.js +1 -0
  101. package/public/dist/assets/ChannelsSlack-BOuoCfeN.js +1 -0
  102. package/public/dist/assets/ChannelsTelegram-DBIFDz4R.js +1 -0
  103. package/public/dist/assets/{app-DUbf-v7B.js → app-TWwXw3iD.js} +4 -4
  104. package/public/dist/assets/bgtask-badge-BhzSqQHG.js +1 -0
  105. package/public/dist/assets/{bgtask-badge-jIH69Dp3.js → bgtask-badge-DaAUnoKD.js} +1 -1
  106. package/public/dist/assets/{employees-BNy8a6XX.js → employees-BFKkaVAe.js} +1 -1
  107. package/public/dist/assets/iframe-renderer-BxEzdKIA.js +1 -0
  108. package/public/dist/assets/{iframe-renderer-C6Zwg9w4.js → iframe-renderer-CvnKi77F.js} +2 -2
  109. package/public/dist/assets/{manager-DzCR6Pam.js → manager-BTpvAFwX.js} +1 -1
  110. package/public/dist/assets/memory-DHOtCiw5.js +1 -0
  111. package/public/dist/assets/{memory-C4O44HMr.js → memory-db_s-38T.js} +1 -1
  112. package/public/dist/assets/{message-history-DCxD21J1.js → message-history-BX9shNKA.js} +1 -1
  113. package/public/dist/assets/message-history-DaSxkyy0.js +1 -0
  114. package/public/dist/assets/{render-A--hkukv.js → render-DZnFK8Kv.js} +1 -1
  115. package/public/dist/assets/{settings-3ZdoKHIf.js → settings-BuFMKcQ-.js} +1 -1
  116. package/public/dist/assets/settings-DotRDITm.js +1 -0
  117. package/public/dist/assets/{settings-core-CQe1uP6x.js → settings-core-3fj0DVoa.js} +17 -17
  118. package/public/dist/assets/settings-core-DohSnMad.js +1 -0
  119. package/public/dist/assets/{sidebar-DxFp_449.js → sidebar-D5TP7TCO.js} +3 -3
  120. package/public/dist/assets/{skills-BU4q8Q8n.js → skills-CQzHFYJS.js} +1 -1
  121. package/public/dist/assets/skills-DZHEvl1x.js +1 -0
  122. package/public/dist/assets/{slash-commands-BiXOgZ5B.js → slash-commands-BWEWWf-c.js} +1 -1
  123. package/public/dist/assets/slash-commands-CuJdloNC.js +1 -0
  124. package/public/dist/assets/{trace-drawer-2BTQCor6.js → trace-drawer-CBR-PZB4.js} +1 -1
  125. package/public/dist/assets/ui-CWl27eMl.js +1 -0
  126. package/public/dist/assets/{ui-Csy42uNW.js → ui-Ca1Q-VFQ.js} +1 -1
  127. package/public/dist/index.html +1 -1
  128. package/public/dist/manager/index.html +1 -1
  129. package/public/js/features/settings-channel.ts +35 -5
  130. package/public/js/features/settings-types.ts +7 -0
  131. package/public/js/features/transport-status-row.ts +23 -4
  132. package/public/manager/src/settings/pages/ChannelsDiscord.tsx +39 -11
  133. package/public/manager/src/settings/pages/ChannelsSlack.tsx +24 -7
  134. package/public/manager/src/settings/pages/ChannelsTelegram.tsx +38 -10
  135. package/public/manager/src/settings/pages/components/ChannelEnablementControl.tsx +98 -0
  136. package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +23 -2
  137. package/scripts/docs/extract-commands.mts +3 -0
  138. package/scripts/generate-channel-capability-table.mts +161 -0
  139. package/scripts/promote-to-main.sh +175 -0
  140. package/scripts/release-gates.mjs +57 -3
  141. package/scripts/release-preview.sh +19 -0
  142. package/scripts/require-release-evidence.mjs +35 -1
  143. package/scripts/validate-messaging-certification.mts +83 -0
  144. package/scripts/write-messaging-certification.mts +65 -0
  145. package/public/dist/assets/ChannelsDiscord-D4fD-9hP.js +0 -1
  146. package/public/dist/assets/ChannelsSlack-_KBwRonM.js +0 -1
  147. package/public/dist/assets/ChannelsTelegram-tihzSWVE.js +0 -1
  148. package/public/dist/assets/TransportStatusChips-B0NR0ljO.js +0 -1
  149. package/public/dist/assets/bgtask-badge-B8KhuBa7.js +0 -1
  150. package/public/dist/assets/iframe-renderer-DBmV8gCQ.js +0 -1
  151. package/public/dist/assets/memory-Dt_Ji6aP.js +0 -1
  152. package/public/dist/assets/message-history-DyISauiv.js +0 -1
  153. package/public/dist/assets/settings-C6Pku2GC.js +0 -1
  154. package/public/dist/assets/settings-core-DqwLJkgP.js +0 -1
  155. package/public/dist/assets/skills-x2gH-1Ry.js +0 -1
  156. package/public/dist/assets/slash-commands-s8eR7Kd1.js +0 -1
  157. package/public/dist/assets/ui-CB0L33o0.js +0 -1
  158. package/scripts/release.sh +0 -224
  159. /package/public/dist/assets/{HealthBadge-CUaJ1PQH.js → HealthBadge-CiNMS-1k.js} +0 -0
@@ -0,0 +1,458 @@
1
+ // ─── Durable ingress journal ─────────────────────────
2
+ // One persistent record of every inbound event, so "already handled" survives a
3
+ // restart the same way "still to handle" does. Each channel arrived at a different
4
+ // answer for that — Telegram an offset frontier, Slack a ten-minute dedupe table,
5
+ // Discord a memory set — and this is where those converge.
6
+ //
7
+ // The connection is injected rather than imported. `src/core/db.ts` opens the real
8
+ // database and runs its DDL at import time, so a module that reaches for that
9
+ // singleton cannot be tested against a temporary one.
10
+ import { randomUUID } from 'node:crypto';
11
+ import { isInboundEnvelope } from './types.js';
12
+ import { enterMessagingTrace } from './trace-context.js';
13
+ import { inc } from './metrics.js';
14
+ export const INGRESS_EVENTS_TABLE = 'ingress_events';
15
+ export const CREATE_INGRESS_EVENTS_SQL = `
16
+ CREATE TABLE IF NOT EXISTS ingress_events (
17
+ channel TEXT NOT NULL CHECK(channel IN ('telegram', 'slack', 'discord')),
18
+ account_id TEXT NOT NULL,
19
+ event_id TEXT NOT NULL,
20
+ conversation_key TEXT NOT NULL,
21
+ thread_key TEXT,
22
+ actor_id TEXT NOT NULL,
23
+ target_json TEXT NOT NULL,
24
+ ack_policy TEXT NOT NULL,
25
+ trace_id TEXT NOT NULL,
26
+ payload_digest TEXT NOT NULL,
27
+ payload_json TEXT,
28
+ state TEXT NOT NULL DEFAULT 'received'
29
+ CHECK(state IN ('received', 'processing', 'completed', 'dead_letter')),
30
+ attempt_count INTEGER NOT NULL DEFAULT 0 CHECK(attempt_count >= 0),
31
+ received_at INTEGER NOT NULL,
32
+ started_at INTEGER,
33
+ completed_at INTEGER,
34
+ next_attempt_at INTEGER,
35
+ last_error TEXT,
36
+ tombstone_until INTEGER,
37
+ session_generation INTEGER NOT NULL DEFAULT 0,
38
+ PRIMARY KEY (channel, account_id, event_id)
39
+ );
40
+ CREATE INDEX IF NOT EXISTS idx_ingress_events_ready
41
+ ON ingress_events (state, next_attempt_at, received_at);
42
+ CREATE INDEX IF NOT EXISTS idx_ingress_events_retention
43
+ ON ingress_events (channel, tombstone_until, completed_at);
44
+ `;
45
+ const INGRESS_STATES = new Set(['received', 'processing', 'completed', 'dead_letter']);
46
+ const childRetentionPredicates = new Map();
47
+ export function registerChildRetentionPredicate(predicate) {
48
+ childRetentionPredicates.set(predicate.table, predicate);
49
+ }
50
+ /** Test seam: the registry is module state, and a suite that adds one must undo it. */
51
+ export function __resetChildRetentionPredicatesForTests() {
52
+ childRetentionPredicates.clear();
53
+ }
54
+ /**
55
+ * Refuse to run if some table references the journal without saying when its rows are
56
+ * done. Without this, the sweeper stays correct only by accident: today no child
57
+ * exists, so any DELETE passes, and the day an outbox lands the sweeper would start
58
+ * removing parents of live attempts. That regression is authored by a different
59
+ * milestone, so no test inside this one could catch it — this guard can.
60
+ */
61
+ export function assertChildRetentionPredicatesRegistered(database) {
62
+ const tables = database
63
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
64
+ .all();
65
+ const unregistered = [];
66
+ for (const { name } of tables) {
67
+ if (name === INGRESS_EVENTS_TABLE)
68
+ continue;
69
+ const foreignKeys = database.pragma(`foreign_key_list(${JSON.stringify(name)})`);
70
+ const referencesJournal = foreignKeys.some(fk => fk.table === INGRESS_EVENTS_TABLE);
71
+ if (referencesJournal && !childRetentionPredicates.has(name))
72
+ unregistered.push(name);
73
+ }
74
+ if (unregistered.length > 0) {
75
+ throw new Error(`ingress retention: ${unregistered.join(', ')} reference ${INGRESS_EVENTS_TABLE} `
76
+ + 'without a registered retention predicate. Register one with '
77
+ + 'registerChildRetentionPredicate() so the sweeper cannot delete a parent '
78
+ + 'whose child rows are still live.');
79
+ }
80
+ }
81
+ function rowToRecord(row) {
82
+ const state = row['state'];
83
+ // The CHECK constraint is the first boundary, but a row can also arrive from an
84
+ // older or newer binary sharing this file. An unknown state is not something to
85
+ // guess at.
86
+ if (typeof state !== 'string' || !INGRESS_STATES.has(state)) {
87
+ throw new Error(`ingress journal: unknown state ${JSON.stringify(state)}`);
88
+ }
89
+ const threadKey = row['thread_key'];
90
+ return {
91
+ channel: row['channel'],
92
+ accountId: String(row['account_id']),
93
+ eventId: String(row['event_id']),
94
+ conversationKey: String(row['conversation_key']),
95
+ ...(typeof threadKey === 'string' ? { threadKey } : {}),
96
+ actorId: String(row['actor_id']),
97
+ ackPolicy: String(row['ack_policy']),
98
+ traceId: String(row['trace_id']),
99
+ payloadDigest: String(row['payload_digest']),
100
+ payloadJson: row['payload_json'] ?? null,
101
+ state: state,
102
+ attemptCount: Number(row['attempt_count']),
103
+ receivedAt: Number(row['received_at']),
104
+ startedAt: row['started_at'] ?? null,
105
+ completedAt: row['completed_at'] ?? null,
106
+ nextAttemptAt: row['next_attempt_at'] ?? null,
107
+ lastError: row['last_error'] ?? null,
108
+ tombstoneUntil: row['tombstone_until'] ?? null,
109
+ sessionGeneration: Number(row['session_generation'] ?? 0),
110
+ };
111
+ }
112
+ // Telegram keeps updates for 24h and Slack's delayed-events retry can run hourly for
113
+ // 24h, so both TTLs cover their vendor replay horizon with room for one recovery.
114
+ // Discord publishes no replay-retention number at all, so 24h is a deliberate
115
+ // placeholder to be revised by measurement rather than a claim about the platform.
116
+ const DEFAULT_TOMBSTONE_TTL_MS = {
117
+ telegram: 48 * 60 * 60 * 1000,
118
+ slack: 26 * 60 * 60 * 1000,
119
+ discord: 24 * 60 * 60 * 1000,
120
+ };
121
+ export class IngressJournal {
122
+ database;
123
+ now;
124
+ bootId;
125
+ tombstoneTtlMs;
126
+ constructor(database, options = {}) {
127
+ this.database = database;
128
+ this.now = options.now ?? (() => Date.now());
129
+ this.bootId = options.bootId ?? randomUUID();
130
+ this.tombstoneTtlMs = { ...DEFAULT_TOMBSTONE_TTL_MS, ...options.tombstoneTtlMs };
131
+ this.database.exec(CREATE_INGRESS_EVENTS_SQL);
132
+ const cols = this.database.prepare('PRAGMA table_info(ingress_events)').all();
133
+ if (!cols.some(c => c.name === 'session_generation')) {
134
+ this.database.exec('ALTER TABLE ingress_events ADD COLUMN session_generation INTEGER NOT NULL DEFAULT 0');
135
+ }
136
+ }
137
+ /**
138
+ * Only a validated envelope gets in. A raw vendor id would be bound with its own
139
+ * type, and a numeric Telegram update_id lands in this TEXT column as "12345.0"
140
+ * while the same id as a string lands as "12345" — two rows for one logical event,
141
+ * and dedupe silently stops working.
142
+ */
143
+ append(envelope, payloadDigest, payloadJson, sessionGeneration = 0) {
144
+ if (!isInboundEnvelope(envelope)) {
145
+ throw new Error('ingress journal: append requires a valid InboundEnvelope');
146
+ }
147
+ const existing = this.find(envelope.channel, envelope.accountId, envelope.eventId);
148
+ if (existing)
149
+ return { appended: false, reason: 'duplicate', record: existing };
150
+ const traceId = this.mintTraceId(envelope);
151
+ try {
152
+ this.database.prepare(`
153
+ INSERT INTO ingress_events (
154
+ channel, account_id, event_id, conversation_key, thread_key, actor_id,
155
+ target_json, ack_policy, trace_id, payload_digest, payload_json,
156
+ state, attempt_count, received_at, session_generation
157
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'received', 0, ?, ?)
158
+ `).run(envelope.channel, envelope.accountId, envelope.eventId, envelope.conversationKey, envelope.threadKey ?? null, envelope.actorId, JSON.stringify(envelope.target), envelope.ackPolicy, traceId, payloadDigest, payloadJson ?? null, envelope.receivedAt, sessionGeneration);
159
+ }
160
+ catch (error) {
161
+ // Another connection won the insert. Locked / disk-full must still throw
162
+ // so Slack can refuse ACK; only a primary-key clash is a duplicate.
163
+ if (!isUniqueConstraint(error))
164
+ throw error;
165
+ const raced = this.find(envelope.channel, envelope.accountId, envelope.eventId);
166
+ if (!raced)
167
+ throw error;
168
+ return { appended: false, reason: 'duplicate', record: raced };
169
+ }
170
+ const record = this.find(envelope.channel, envelope.accountId, envelope.eventId);
171
+ if (!record)
172
+ throw new Error('ingress journal: append did not persist');
173
+ return { appended: true, record };
174
+ }
175
+ /**
176
+ * This tree has no correlation-id producer — the one `traceId` declaration in it has
177
+ * no consumers — so the journal mints its own rather than requiring every future
178
+ * caller to invent one for a NOT NULL column.
179
+ */
180
+ mintTraceId(envelope) {
181
+ const base = `${this.bootId}:${envelope.channel}:${envelope.eventId}`;
182
+ return envelope.rawEnvelopeRef ? `${base}:${envelope.rawEnvelopeRef}` : base;
183
+ }
184
+ find(channel, accountId, eventId) {
185
+ const row = this.database.prepare(`
186
+ SELECT * FROM ingress_events
187
+ WHERE channel = ? AND account_id = ? AND event_id = ?
188
+ `).get(channel, accountId, eventId);
189
+ return row ? rowToRecord(row) : null;
190
+ }
191
+ /**
192
+ * Anything not already completed -> processing. Bumps the attempt so a retry is
193
+ * visible.
194
+ *
195
+ * `processing` is deliberately an allowed source. A row sits in that state only
196
+ * because a previous run died between claiming the event and finishing it; the
197
+ * transport is about to redeliver precisely because nothing was ever acknowledged.
198
+ * Refusing to re-claim it would strand the one delivery the message has left.
199
+ */
200
+ markProcessing(channel, accountId, eventId) {
201
+ const changes = this.database.prepare(`
202
+ UPDATE ingress_events
203
+ SET state = 'processing', started_at = ?, attempt_count = attempt_count + 1,
204
+ last_error = NULL, next_attempt_at = NULL
205
+ WHERE channel = ? AND account_id = ? AND event_id = ?
206
+ AND state IN ('received', 'processing', 'dead_letter')
207
+ `).run(this.now(), channel, accountId, eventId).changes;
208
+ return changes === 1;
209
+ }
210
+ /**
211
+ * True when this event has already been handled to completion. The append path
212
+ * uses it to tell a genuine duplicate from a row a crash left mid-flight: only the
213
+ * former may be skipped.
214
+ */
215
+ isSettled(channel, accountId, eventId) {
216
+ return this.find(channel, accountId, eventId)?.state === 'completed';
217
+ }
218
+ /**
219
+ * Completion drops the payload and sets the tombstone in one transaction. Deleting
220
+ * the row outright would lose the fact that this event was handled, which is the
221
+ * one thing a redelivery needs to know.
222
+ */
223
+ markCompleted(channel, accountId, eventId) {
224
+ const completedAt = this.now();
225
+ const tombstoneUntil = completedAt + this.tombstoneTtlMs[channel];
226
+ const run = this.database.transaction(() => this.database.prepare(`
227
+ UPDATE ingress_events
228
+ SET state = 'completed', completed_at = ?, tombstone_until = ?,
229
+ payload_json = NULL, last_error = NULL, next_attempt_at = NULL
230
+ WHERE channel = ? AND account_id = ? AND event_id = ? AND state = 'processing'
231
+ `).run(completedAt, tombstoneUntil, channel, accountId, eventId).changes);
232
+ return run.immediate() === 1;
233
+ }
234
+ /** Keeps the payload: a dead letter exists to be replayed, and replay needs input. */
235
+ markDeadLetter(channel, accountId, eventId, lastError) {
236
+ const changes = this.database.prepare(`
237
+ UPDATE ingress_events
238
+ SET state = 'dead_letter', last_error = ?, next_attempt_at = NULL
239
+ WHERE channel = ? AND account_id = ? AND event_id = ? AND state = 'processing'
240
+ `).run(lastError, channel, accountId, eventId).changes;
241
+ return changes === 1;
242
+ }
243
+ /** Schedules a retry without consuming the dead-letter budget. */
244
+ markRetryScheduled(channel, accountId, eventId, nextAttemptAt, lastError) {
245
+ const changes = this.database.prepare(`
246
+ UPDATE ingress_events
247
+ SET state = 'received', next_attempt_at = ?, last_error = ?
248
+ WHERE channel = ? AND account_id = ? AND event_id = ? AND state = 'processing'
249
+ `).run(nextAttemptAt, lastError, channel, accountId, eventId).changes;
250
+ return changes === 1;
251
+ }
252
+ /** Operator listing. Filters are all optional so an empty journal reads cleanly. */
253
+ list(filter = {}) {
254
+ const clauses = [];
255
+ const params = [];
256
+ if (filter.channel) {
257
+ clauses.push('channel = ?');
258
+ params.push(filter.channel);
259
+ }
260
+ if (filter.state) {
261
+ clauses.push('state = ?');
262
+ params.push(filter.state);
263
+ }
264
+ if (filter.olderThanMs !== undefined) {
265
+ clauses.push('received_at <= ?');
266
+ params.push(this.now() - filter.olderThanMs);
267
+ }
268
+ const where = clauses.length ? 'WHERE ' + clauses.join(' AND ') : '';
269
+ params.push(filter.limit ?? 50);
270
+ const rows = this.database.prepare('SELECT * FROM ingress_events ' + where + ' ORDER BY received_at DESC LIMIT ?').all(...params);
271
+ return rows.map(rowToRecord);
272
+ }
273
+ /**
274
+ * Hand a row back for another attempt. Returns why it was refused rather than
275
+ * throwing, because every refusal here is a normal operator answer.
276
+ *
277
+ * A completed row is refused unless forced: its effects already happened, and
278
+ * re-running them is a worse outcome than not replaying. A tombstone whose payload
279
+ * was dropped at completion cannot be replayed at all, forced or not.
280
+ */
281
+ requestReplay(channel, accountId, eventId, options = {}) {
282
+ const record = this.find(channel, accountId, eventId);
283
+ if (!record)
284
+ return { replayed: false, reason: 'not_found' };
285
+ if (record.state === 'processing')
286
+ return { replayed: false, reason: 'in_flight' };
287
+ if (record.state === 'completed') {
288
+ if (!options.force)
289
+ return { replayed: false, reason: 'already_completed' };
290
+ if (record.payloadJson === null)
291
+ return { replayed: false, reason: 'payload_discarded' };
292
+ }
293
+ // CAS on the state we just classified. Without this, a handler that claimed
294
+ // the row after find() would be reset to received and run twice.
295
+ const eligible = record.state === 'completed'
296
+ ? "state = 'completed' AND payload_json IS NOT NULL"
297
+ : "state IN ('received', 'dead_letter')";
298
+ const changes = this.database.prepare("UPDATE ingress_events SET state = 'received', next_attempt_at = ?, " +
299
+ 'completed_at = NULL, tombstone_until = NULL, last_error = NULL ' +
300
+ 'WHERE channel = ? AND account_id = ? AND event_id = ? AND ' + eligible).run(this.now(), channel, accountId, eventId).changes;
301
+ if (changes !== 1) {
302
+ const latest = this.find(channel, accountId, eventId);
303
+ if (!latest)
304
+ return { replayed: false, reason: 'not_found' };
305
+ if (latest.state === 'processing')
306
+ return { replayed: false, reason: 'in_flight' };
307
+ if (latest.state === 'completed' && latest.payloadJson === null) {
308
+ return { replayed: false, reason: 'payload_discarded' };
309
+ }
310
+ if (latest.state === 'completed')
311
+ return { replayed: false, reason: 'already_completed' };
312
+ return { replayed: false, reason: 'not_found' };
313
+ }
314
+ const replayed = this.find(channel, accountId, eventId);
315
+ if (!replayed)
316
+ return { replayed: false, reason: 'not_found' };
317
+ return { replayed: true, record: replayed };
318
+ }
319
+ /** Aggregate counts for a health or status surface. */
320
+ counts() {
321
+ const rows = this.database.prepare('SELECT state, COUNT(*) AS n FROM ingress_events GROUP BY state').all();
322
+ const out = {
323
+ received: 0, processing: 0, completed: 0, dead_letter: 0,
324
+ };
325
+ for (const row of rows) {
326
+ if (INGRESS_STATES.has(row.state))
327
+ out[row.state] = row.n;
328
+ }
329
+ return out;
330
+ }
331
+ /** Oldest still-open row. Completed tombstones are history, not backlog. */
332
+ oldestOpenReceivedAt() {
333
+ const row = this.database.prepare("SELECT MIN(received_at) AS n FROM ingress_events WHERE state != 'completed'").get();
334
+ return typeof row?.n === 'number' ? row.n : null;
335
+ }
336
+ listByState(state, limit = 100) {
337
+ const rows = this.database.prepare(`
338
+ SELECT * FROM ingress_events WHERE state = ?
339
+ ORDER BY received_at LIMIT ?
340
+ `).all(state, limit);
341
+ return rows.map(rowToRecord);
342
+ }
343
+ /**
344
+ * Removes expired completed tombstones only. Every registered child predicate is
345
+ * ANDed in as a NOT EXISTS, so a parent whose child rows are still live stays put.
346
+ */
347
+ sweepExpiredTombstones(limit = 1000) {
348
+ assertChildRetentionPredicatesRegistered(this.database);
349
+ const predicates = [...childRetentionPredicates.values()];
350
+ const blocking = predicates
351
+ .map(p => `AND NOT EXISTS (${p.blockingExistsSql})`)
352
+ .join('\n ');
353
+ const selectSweepable = this.database.prepare(`
354
+ SELECT e.channel, e.account_id, e.event_id FROM ingress_events e
355
+ WHERE e.state = 'completed'
356
+ AND e.tombstone_until IS NOT NULL
357
+ AND e.tombstone_until <= ?
358
+ ${blocking}
359
+ LIMIT ?
360
+ `);
361
+ const deleteParent = this.database.prepare(`
362
+ DELETE FROM ingress_events
363
+ WHERE channel = ? AND account_id = ? AND event_id = ?
364
+ `);
365
+ const deleteChildren = predicates.map(p => this.database.prepare(p.deleteTerminalSql));
366
+ const run = this.database.transaction(() => {
367
+ const rows = selectSweepable.all(this.now(), limit);
368
+ let removed = 0;
369
+ for (const row of rows) {
370
+ const key = [row.channel, row.account_id, row.event_id];
371
+ // Children first: foreign_keys is ON, so a parent DELETE with a surviving
372
+ // child fails outright rather than orphaning anything.
373
+ for (const statement of deleteChildren)
374
+ statement.run(...key);
375
+ removed += deleteParent.run(...key).changes;
376
+ }
377
+ return removed;
378
+ });
379
+ return run.immediate();
380
+ }
381
+ }
382
+ /**
383
+ * The append/claim half of the durable-ingress protocol, shared by every transport so
384
+ * the ordering is written once. A transport calls this before handling an event and
385
+ * `settleIngress` after, and the caller's own acknowledgement (Telegram's offset,
386
+ * Slack's ACK) must come after that settle.
387
+ *
388
+ * `admit: false` means the event completed on an earlier run and must not be handled
389
+ * again. Anything else is admitted — including a row a crash left mid-flight, because
390
+ * the transport is redelivering precisely because nothing was ever acknowledged.
391
+ */
392
+ function isUniqueConstraint(error) {
393
+ if (!error || typeof error !== 'object')
394
+ return false;
395
+ const code = 'code' in error ? String(error.code) : '';
396
+ const message = error instanceof Error ? error.message : String(error);
397
+ return code === 'SQLITE_CONSTRAINT_PRIMARYKEY'
398
+ || code === 'SQLITE_CONSTRAINT_UNIQUE'
399
+ || /UNIQUE constraint failed: ingress_events/i.test(message);
400
+ }
401
+ export function admitIngress(journal, envelope, payloadDigest, payloadJson, sessionGeneration = 0) {
402
+ // No journal (CLI processes, tests) or an event with no durable identity: behave
403
+ // exactly as this path did before the journal existed.
404
+ if (!journal || !envelope) {
405
+ inc('ingress.admit', { result: 'unjournaled' });
406
+ return { admit: true, journaled: false };
407
+ }
408
+ const result = journal.append(envelope, payloadDigest, payloadJson, sessionGeneration);
409
+ if (!result.appended && result.record.state === 'completed') {
410
+ inc('ingress.admit', { channel: envelope.channel, result: 'already_handled' });
411
+ return { admit: false, reason: 'already_handled' };
412
+ }
413
+ if (!result.appended && result.record.sessionGeneration !== sessionGeneration) {
414
+ inc('ingress.admit', { channel: envelope.channel, result: 'stale_generation' });
415
+ return { admit: false, reason: 'stale_generation' };
416
+ }
417
+ journal.markProcessing(envelope.channel, envelope.accountId, envelope.eventId);
418
+ // Reuse the journal row's identity. A second minted id here is the bug M5 exists to stop.
419
+ enterMessagingTrace({ traceId: result.record.traceId, channel: envelope.channel });
420
+ inc('ingress.admit', {
421
+ channel: envelope.channel,
422
+ result: result.appended ? 'fresh' : 'redelivery',
423
+ });
424
+ return { admit: true, journaled: true, envelope };
425
+ }
426
+ /** Terminal half of the protocol. `error` present means the run failed. */
427
+ export function settleIngress(journal, admission, error) {
428
+ if (!journal || !admission.admit || !admission.journaled)
429
+ return;
430
+ const { channel, accountId, eventId } = admission.envelope;
431
+ if (error === undefined) {
432
+ journal.markCompleted(channel, accountId, eventId);
433
+ return;
434
+ }
435
+ // Back to received rather than dead-lettered: the transport is about to redeliver,
436
+ // so the redelivery is itself the retry.
437
+ const message = error instanceof Error ? error.message : String(error);
438
+ journal.markRetryScheduled(channel, accountId, eventId, Date.now(), message);
439
+ }
440
+ let journal = null;
441
+ /**
442
+ * Called explicitly at boot rather than on import: an owner module that creates its
443
+ * tables as a side effect of being imported would also drag the database singleton in
444
+ * behind it, and the table's existence would depend on who happened to import what.
445
+ */
446
+ export function initIngressJournal(database, options = {}) {
447
+ journal = new IngressJournal(database, options);
448
+ assertChildRetentionPredicatesRegistered(database);
449
+ return journal;
450
+ }
451
+ export function getIngressJournal() {
452
+ return journal;
453
+ }
454
+ /** Test seam: the module-level handle outlives a single test otherwise. */
455
+ export function __resetIngressJournalForTests() {
456
+ journal = null;
457
+ }
458
+ //# sourceMappingURL=durable-ingress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-ingress.js","sourceRoot":"","sources":["../../../src/messaging/durable-ingress.ts"],"names":[],"mappings":"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"}