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.
- package/README.md +5 -2
- package/dist/bin/cli-jaw.js +6 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/doctor.js +39 -18
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +82 -23
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/messaging.js +262 -0
- package/dist/bin/commands/messaging.js.map +1 -0
- package/dist/server.js +30 -6
- package/dist/server.js.map +1 -1
- package/dist/src/cli/commands.js +6 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers/remote-session-commands.js +94 -0
- package/dist/src/cli/handlers/remote-session-commands.js.map +1 -0
- package/dist/src/command-contract/catalog.js +1 -1
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/chat-sessions.js +4 -0
- package/dist/src/core/chat-sessions.js.map +1 -1
- package/dist/src/core/config.js +63 -22
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +6 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/dispatch-approval-ingress.js +45 -3
- package/dist/src/core/dispatch-approval-ingress.js.map +1 -1
- package/dist/src/core/dispatch-approval.js +46 -0
- package/dist/src/core/dispatch-approval.js.map +1 -1
- package/dist/src/core/logger.js +67 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/session-generation.js +74 -0
- package/dist/src/core/session-generation.js.map +1 -0
- package/dist/src/discord/bot.js +91 -5
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +34 -1
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/forwarder.js +4 -1
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/discord/send-only-client.js +14 -7
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/messaging/access-policy.js +26 -0
- package/dist/src/messaging/access-policy.js.map +1 -0
- package/dist/src/messaging/approval-presentation.js +89 -0
- package/dist/src/messaging/approval-presentation.js.map +1 -0
- package/dist/src/messaging/channel-adapter.js +47 -0
- package/dist/src/messaging/channel-adapter.js.map +1 -0
- package/dist/src/messaging/channel-capabilities.js +47 -15
- package/dist/src/messaging/channel-capabilities.js.map +1 -1
- package/dist/src/messaging/channel-health.js +23 -3
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/delivery-outcome.js +12 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -1
- package/dist/src/messaging/durable-ingress.js +458 -0
- package/dist/src/messaging/durable-ingress.js.map +1 -0
- package/dist/src/messaging/effect-once.js +204 -0
- package/dist/src/messaging/effect-once.js.map +1 -0
- package/dist/src/messaging/inbound-envelope.js +168 -0
- package/dist/src/messaging/inbound-envelope.js.map +1 -0
- package/dist/src/messaging/ingress-audit.js +41 -0
- package/dist/src/messaging/ingress-audit.js.map +1 -0
- package/dist/src/messaging/ingress-generation.js +22 -0
- package/dist/src/messaging/ingress-generation.js.map +1 -0
- package/dist/src/messaging/metrics.js +69 -0
- package/dist/src/messaging/metrics.js.map +1 -0
- package/dist/src/messaging/outbound-outbox.js +184 -0
- package/dist/src/messaging/outbound-outbox.js.map +1 -0
- package/dist/src/messaging/remote-command-context.js +30 -0
- package/dist/src/messaging/remote-command-context.js.map +1 -0
- package/dist/src/messaging/retry.js +11 -2
- package/dist/src/messaging/retry.js.map +1 -1
- package/dist/src/messaging/runtime.js +129 -30
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +31 -6
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/trace-context.js +56 -0
- package/dist/src/messaging/trace-context.js.map +1 -0
- package/dist/src/messaging/types.js +39 -1
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/routes/orchestrate.js +10 -3
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +26 -1
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/slack/bot.js +182 -70
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/commands.js +16 -1
- package/dist/src/slack/commands.js.map +1 -1
- package/dist/src/slack/format.js +4 -1
- package/dist/src/slack/format.js.map +1 -1
- package/dist/src/slack/manifest.js +5 -0
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/send-handler.js +26 -4
- package/dist/src/slack/send-handler.js.map +1 -1
- package/dist/src/slack/send-only-client.js +26 -2
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/socket.js +36 -13
- package/dist/src/slack/socket.js.map +1 -1
- package/dist/src/telegram/bot.js +122 -14
- package/dist/src/telegram/bot.js.map +1 -1
- package/package.json +4 -1
- package/public/dist/assets/ChannelEnablementControl-Da_t_jq6.js +1 -0
- package/public/dist/assets/ChannelsDiscord-kNjCCJcA.js +1 -0
- package/public/dist/assets/ChannelsSlack-BOuoCfeN.js +1 -0
- package/public/dist/assets/ChannelsTelegram-DBIFDz4R.js +1 -0
- package/public/dist/assets/{app-DUbf-v7B.js → app-TWwXw3iD.js} +4 -4
- package/public/dist/assets/bgtask-badge-BhzSqQHG.js +1 -0
- package/public/dist/assets/{bgtask-badge-jIH69Dp3.js → bgtask-badge-DaAUnoKD.js} +1 -1
- package/public/dist/assets/{employees-BNy8a6XX.js → employees-BFKkaVAe.js} +1 -1
- package/public/dist/assets/iframe-renderer-BxEzdKIA.js +1 -0
- package/public/dist/assets/{iframe-renderer-C6Zwg9w4.js → iframe-renderer-CvnKi77F.js} +2 -2
- package/public/dist/assets/{manager-DzCR6Pam.js → manager-BTpvAFwX.js} +1 -1
- package/public/dist/assets/memory-DHOtCiw5.js +1 -0
- package/public/dist/assets/{memory-C4O44HMr.js → memory-db_s-38T.js} +1 -1
- package/public/dist/assets/{message-history-DCxD21J1.js → message-history-BX9shNKA.js} +1 -1
- package/public/dist/assets/message-history-DaSxkyy0.js +1 -0
- package/public/dist/assets/{render-A--hkukv.js → render-DZnFK8Kv.js} +1 -1
- package/public/dist/assets/{settings-3ZdoKHIf.js → settings-BuFMKcQ-.js} +1 -1
- package/public/dist/assets/settings-DotRDITm.js +1 -0
- package/public/dist/assets/{settings-core-CQe1uP6x.js → settings-core-3fj0DVoa.js} +17 -17
- package/public/dist/assets/settings-core-DohSnMad.js +1 -0
- package/public/dist/assets/{sidebar-DxFp_449.js → sidebar-D5TP7TCO.js} +3 -3
- package/public/dist/assets/{skills-BU4q8Q8n.js → skills-CQzHFYJS.js} +1 -1
- package/public/dist/assets/skills-DZHEvl1x.js +1 -0
- package/public/dist/assets/{slash-commands-BiXOgZ5B.js → slash-commands-BWEWWf-c.js} +1 -1
- package/public/dist/assets/slash-commands-CuJdloNC.js +1 -0
- package/public/dist/assets/{trace-drawer-2BTQCor6.js → trace-drawer-CBR-PZB4.js} +1 -1
- package/public/dist/assets/ui-CWl27eMl.js +1 -0
- package/public/dist/assets/{ui-Csy42uNW.js → ui-Ca1Q-VFQ.js} +1 -1
- package/public/dist/index.html +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/js/features/settings-channel.ts +35 -5
- package/public/js/features/settings-types.ts +7 -0
- package/public/js/features/transport-status-row.ts +23 -4
- package/public/manager/src/settings/pages/ChannelsDiscord.tsx +39 -11
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +24 -7
- package/public/manager/src/settings/pages/ChannelsTelegram.tsx +38 -10
- package/public/manager/src/settings/pages/components/ChannelEnablementControl.tsx +98 -0
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +23 -2
- package/scripts/docs/extract-commands.mts +3 -0
- package/scripts/generate-channel-capability-table.mts +161 -0
- package/scripts/promote-to-main.sh +175 -0
- package/scripts/release-gates.mjs +57 -3
- package/scripts/release-preview.sh +19 -0
- package/scripts/require-release-evidence.mjs +35 -1
- package/scripts/validate-messaging-certification.mts +83 -0
- package/scripts/write-messaging-certification.mts +65 -0
- package/public/dist/assets/ChannelsDiscord-D4fD-9hP.js +0 -1
- package/public/dist/assets/ChannelsSlack-_KBwRonM.js +0 -1
- package/public/dist/assets/ChannelsTelegram-tihzSWVE.js +0 -1
- package/public/dist/assets/TransportStatusChips-B0NR0ljO.js +0 -1
- package/public/dist/assets/bgtask-badge-B8KhuBa7.js +0 -1
- package/public/dist/assets/iframe-renderer-DBmV8gCQ.js +0 -1
- package/public/dist/assets/memory-Dt_Ji6aP.js +0 -1
- package/public/dist/assets/message-history-DyISauiv.js +0 -1
- package/public/dist/assets/settings-C6Pku2GC.js +0 -1
- package/public/dist/assets/settings-core-DqwLJkgP.js +0 -1
- package/public/dist/assets/skills-x2gH-1Ry.js +0 -1
- package/public/dist/assets/slash-commands-s8eR7Kd1.js +0 -1
- package/public/dist/assets/ui-CB0L33o0.js +0 -1
- package/scripts/release.sh +0 -224
- /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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"}
|