@dsh-enhanced/assistant-delivery 0.1.7 → 0.1.14
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 +89 -10
- package/cordis.patch.yml +1 -0
- package/lib/agent-runtime.d.ts +18 -4
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +240 -14
- package/lib/agent-runtime.js.map +1 -1
- package/lib/canonical.d.ts +11 -1
- package/lib/canonical.d.ts.map +1 -1
- package/lib/canonical.js +107 -0
- package/lib/canonical.js.map +1 -1
- package/lib/coordinator.d.ts +6 -1
- package/lib/coordinator.d.ts.map +1 -1
- package/lib/coordinator.js +63 -24
- package/lib/coordinator.js.map +1 -1
- package/lib/feedback-command.d.ts +48 -2
- package/lib/feedback-command.d.ts.map +1 -1
- package/lib/feedback-command.js +226 -10
- package/lib/feedback-command.js.map +1 -1
- package/lib/index.d.ts +5 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -3
- package/lib/index.js.map +1 -1
- package/lib/learning-command.d.ts +40 -0
- package/lib/learning-command.d.ts.map +1 -0
- package/lib/learning-command.js +57 -0
- package/lib/learning-command.js.map +1 -0
- package/lib/service.d.ts +142 -10
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +1841 -62
- package/lib/service.js.map +1 -1
- package/lib/session-commands.d.ts +14 -0
- package/lib/session-commands.d.ts.map +1 -1
- package/lib/session-commands.js +30 -0
- package/lib/session-commands.js.map +1 -1
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +648 -20
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +375 -3
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +2956 -74
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +364 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +9 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.d.ts.map +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/lib/workflow-auto-producer.d.ts +37 -0
- package/lib/workflow-auto-producer.d.ts.map +1 -0
- package/lib/workflow-auto-producer.js +58 -0
- package/lib/workflow-auto-producer.js.map +1 -0
- package/lib/workflow-command.d.ts +16 -0
- package/lib/workflow-command.d.ts.map +1 -0
- package/lib/workflow-command.js +52 -0
- package/lib/workflow-command.js.map +1 -0
- package/package.json +23 -3
package/lib/sqlite.js
CHANGED
|
@@ -1,7 +1,381 @@
|
|
|
1
1
|
import { chmodSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute } from 'node:path';
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
-
export const deliverySchemaVersion =
|
|
4
|
+
export const deliverySchemaVersion = 16;
|
|
5
|
+
/**
|
|
6
|
+
* A database-local, durable total order for every admitted Inbox. The trigger
|
|
7
|
+
* deliberately lives in SQLite so an already-open v12 process also receives
|
|
8
|
+
* a cursor after a v13 process migrates the shared database.
|
|
9
|
+
*/
|
|
10
|
+
const inboxAdmissionSchema = `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS delivery_inbox_admission_clock (
|
|
12
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
13
|
+
current_sequence INTEGER NOT NULL CHECK (
|
|
14
|
+
current_sequence BETWEEN 0 AND 9007199254740991
|
|
15
|
+
)
|
|
16
|
+
) STRICT;
|
|
17
|
+
INSERT INTO delivery_inbox_admission_clock(singleton, current_sequence)
|
|
18
|
+
VALUES (1, 0) ON CONFLICT(singleton) DO NOTHING;
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS delivery_inbox_admissions (
|
|
21
|
+
inbox_id TEXT PRIMARY KEY,
|
|
22
|
+
epoch TEXT NOT NULL CHECK (
|
|
23
|
+
length(epoch) = 32 AND epoch NOT GLOB '*[^0-9a-f]*'
|
|
24
|
+
),
|
|
25
|
+
admission_sequence INTEGER NOT NULL UNIQUE CHECK (
|
|
26
|
+
admission_sequence BETWEEN 1 AND 9007199254740991
|
|
27
|
+
),
|
|
28
|
+
FOREIGN KEY (inbox_id) REFERENCES inbox_messages(id) ON DELETE CASCADE
|
|
29
|
+
) STRICT;
|
|
30
|
+
|
|
31
|
+
INSERT OR IGNORE INTO delivery_inbox_admissions(inbox_id, epoch, admission_sequence)
|
|
32
|
+
SELECT message.id, instance.instance_id,
|
|
33
|
+
ROW_NUMBER() OVER (ORDER BY message.rowid)
|
|
34
|
+
FROM inbox_messages AS message
|
|
35
|
+
CROSS JOIN delivery_instance AS instance
|
|
36
|
+
WHERE instance.singleton = 1;
|
|
37
|
+
|
|
38
|
+
UPDATE delivery_inbox_admission_clock
|
|
39
|
+
SET current_sequence = MAX(current_sequence,
|
|
40
|
+
(SELECT COALESCE(MAX(admission_sequence), 0) FROM delivery_inbox_admissions))
|
|
41
|
+
WHERE singleton = 1;
|
|
42
|
+
|
|
43
|
+
CREATE TRIGGER IF NOT EXISTS delivery_inbox_admission_after_insert
|
|
44
|
+
AFTER INSERT ON inbox_messages
|
|
45
|
+
BEGIN
|
|
46
|
+
UPDATE delivery_inbox_admission_clock
|
|
47
|
+
SET current_sequence = current_sequence + 1
|
|
48
|
+
WHERE singleton = 1;
|
|
49
|
+
INSERT INTO delivery_inbox_admissions(inbox_id, epoch, admission_sequence)
|
|
50
|
+
SELECT NEW.id, instance.instance_id, clock.current_sequence
|
|
51
|
+
FROM delivery_instance AS instance
|
|
52
|
+
CROSS JOIN delivery_inbox_admission_clock AS clock
|
|
53
|
+
WHERE instance.singleton = 1 AND clock.singleton = 1;
|
|
54
|
+
END;
|
|
55
|
+
`;
|
|
56
|
+
const preferenceProjectionSchema = `
|
|
57
|
+
CREATE TABLE delivery_preference_projection_outbox (
|
|
58
|
+
batch_key TEXT PRIMARY KEY,
|
|
59
|
+
payload_digest TEXT NOT NULL CHECK (
|
|
60
|
+
length(payload_digest) = 64 AND payload_digest NOT GLOB '*[^0-9a-f]*'
|
|
61
|
+
),
|
|
62
|
+
events_json TEXT NOT NULL CHECK (
|
|
63
|
+
json_valid(events_json) AND json_type(events_json) = 'array'
|
|
64
|
+
AND json_array_length(events_json) BETWEEN 1 AND 16
|
|
65
|
+
),
|
|
66
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'retry_wait')),
|
|
67
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
68
|
+
next_attempt_at INTEGER NOT NULL,
|
|
69
|
+
failure_code TEXT,
|
|
70
|
+
lane_kind TEXT NOT NULL DEFAULT 'unclassified' CHECK (
|
|
71
|
+
lane_kind IN ('exact', 'legacy', 'unclassified')
|
|
72
|
+
),
|
|
73
|
+
lane_epoch TEXT CHECK (
|
|
74
|
+
lane_epoch IS NULL OR (
|
|
75
|
+
length(lane_epoch) = 32 AND lane_epoch NOT GLOB '*[^0-9a-f]*'
|
|
76
|
+
)
|
|
77
|
+
),
|
|
78
|
+
lane_workspace TEXT,
|
|
79
|
+
lane_preset TEXT,
|
|
80
|
+
lane_principal_record_id TEXT,
|
|
81
|
+
lane_principal_version INTEGER CHECK (
|
|
82
|
+
lane_principal_version IS NULL OR lane_principal_version >= 1
|
|
83
|
+
),
|
|
84
|
+
admission_sequence INTEGER CHECK (
|
|
85
|
+
admission_sequence IS NULL OR admission_sequence BETWEEN 1 AND 9007199254740991
|
|
86
|
+
),
|
|
87
|
+
terminal_at INTEGER CHECK (terminal_at IS NULL OR terminal_at >= 0),
|
|
88
|
+
created_at INTEGER NOT NULL,
|
|
89
|
+
updated_at INTEGER NOT NULL
|
|
90
|
+
) STRICT;
|
|
91
|
+
CREATE INDEX delivery_preference_projection_due
|
|
92
|
+
ON delivery_preference_projection_outbox(
|
|
93
|
+
terminal_at, lane_kind, status, next_attempt_at, updated_at, batch_key
|
|
94
|
+
);
|
|
95
|
+
CREATE INDEX delivery_preference_projection_lane
|
|
96
|
+
ON delivery_preference_projection_outbox(
|
|
97
|
+
terminal_at, lane_epoch, lane_workspace, lane_preset,
|
|
98
|
+
lane_principal_record_id, lane_principal_version, admission_sequence,
|
|
99
|
+
created_at, batch_key
|
|
100
|
+
);
|
|
101
|
+
`;
|
|
102
|
+
const workflowTraceSchema = `
|
|
103
|
+
CREATE TABLE workflow_trace_source (
|
|
104
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
105
|
+
contract_version INTEGER NOT NULL CHECK (contract_version = 1),
|
|
106
|
+
generation INTEGER NOT NULL CHECK (generation >= 1),
|
|
107
|
+
authority_digest TEXT NOT NULL CHECK (
|
|
108
|
+
length(authority_digest) = 64 AND authority_digest NOT GLOB '*[^0-9a-f]*'
|
|
109
|
+
),
|
|
110
|
+
created_at INTEGER NOT NULL,
|
|
111
|
+
updated_at INTEGER NOT NULL
|
|
112
|
+
) STRICT;
|
|
113
|
+
|
|
114
|
+
CREATE TABLE workflow_template_registry (
|
|
115
|
+
template_ref TEXT PRIMARY KEY,
|
|
116
|
+
template_digest TEXT NOT NULL CHECK (
|
|
117
|
+
length(template_digest) = 64 AND template_digest NOT GLOB '*[^0-9a-f]*'
|
|
118
|
+
),
|
|
119
|
+
scope_key TEXT NOT NULL,
|
|
120
|
+
workspace TEXT NOT NULL,
|
|
121
|
+
preset TEXT NOT NULL,
|
|
122
|
+
owner_binding_id TEXT NOT NULL,
|
|
123
|
+
content_json TEXT NOT NULL,
|
|
124
|
+
privacy_kind TEXT NOT NULL CHECK (
|
|
125
|
+
privacy_kind IN ('deterministic-deidentification', 'owner-explicit')
|
|
126
|
+
),
|
|
127
|
+
privacy_attestation_id TEXT NOT NULL UNIQUE,
|
|
128
|
+
privacy_attestation_digest TEXT NOT NULL CHECK (
|
|
129
|
+
length(privacy_attestation_digest) = 64
|
|
130
|
+
AND privacy_attestation_digest NOT GLOB '*[^0-9a-f]*'
|
|
131
|
+
),
|
|
132
|
+
review_receipt_json TEXT NOT NULL,
|
|
133
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'revoked')),
|
|
134
|
+
review_inbox_id TEXT NOT NULL,
|
|
135
|
+
source_inbox_id TEXT NOT NULL,
|
|
136
|
+
source_outbox_id TEXT NOT NULL,
|
|
137
|
+
created_at INTEGER NOT NULL,
|
|
138
|
+
updated_at INTEGER NOT NULL,
|
|
139
|
+
version INTEGER NOT NULL CHECK (version >= 1),
|
|
140
|
+
FOREIGN KEY (owner_binding_id) REFERENCES conversation_bindings(id),
|
|
141
|
+
FOREIGN KEY (review_inbox_id) REFERENCES inbox_messages(id),
|
|
142
|
+
FOREIGN KEY (source_inbox_id) REFERENCES inbox_messages(id),
|
|
143
|
+
FOREIGN KEY (source_outbox_id) REFERENCES outbox_messages(id)
|
|
144
|
+
) STRICT;
|
|
145
|
+
CREATE INDEX workflow_template_scope
|
|
146
|
+
ON workflow_template_registry(scope_key, status, updated_at, template_ref);
|
|
147
|
+
|
|
148
|
+
CREATE TABLE workflow_trace_revisions (
|
|
149
|
+
subject_ref TEXT NOT NULL,
|
|
150
|
+
version INTEGER NOT NULL CHECK (version >= 1),
|
|
151
|
+
source_generation INTEGER NOT NULL CHECK (source_generation >= 1),
|
|
152
|
+
source_authority_digest TEXT NOT NULL CHECK (
|
|
153
|
+
length(source_authority_digest) = 64
|
|
154
|
+
AND source_authority_digest NOT GLOB '*[^0-9a-f]*'
|
|
155
|
+
),
|
|
156
|
+
scope_key TEXT NOT NULL,
|
|
157
|
+
workspace TEXT NOT NULL,
|
|
158
|
+
preset TEXT NOT NULL,
|
|
159
|
+
disposition TEXT NOT NULL CHECK (disposition IN ('upsert', 'retract')),
|
|
160
|
+
digest TEXT NOT NULL CHECK (length(digest) = 64 AND digest NOT GLOB '*[^0-9a-f]*'),
|
|
161
|
+
payload_json TEXT NOT NULL,
|
|
162
|
+
created_at INTEGER NOT NULL,
|
|
163
|
+
PRIMARY KEY (subject_ref, version)
|
|
164
|
+
) STRICT;
|
|
165
|
+
|
|
166
|
+
CREATE TABLE workflow_trace_current (
|
|
167
|
+
subject_ref TEXT PRIMARY KEY,
|
|
168
|
+
version INTEGER NOT NULL CHECK (version >= 1),
|
|
169
|
+
digest TEXT NOT NULL,
|
|
170
|
+
disposition TEXT NOT NULL CHECK (disposition IN ('upsert', 'retract')),
|
|
171
|
+
payload_json TEXT NOT NULL,
|
|
172
|
+
updated_at INTEGER NOT NULL,
|
|
173
|
+
FOREIGN KEY (subject_ref, version) REFERENCES workflow_trace_revisions(subject_ref, version)
|
|
174
|
+
) STRICT;
|
|
175
|
+
|
|
176
|
+
CREATE TABLE workflow_trace_outbox (
|
|
177
|
+
subject_ref TEXT NOT NULL,
|
|
178
|
+
version INTEGER NOT NULL CHECK (version >= 1),
|
|
179
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'delivered', 'retry_wait')),
|
|
180
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
181
|
+
next_attempt_at INTEGER NOT NULL,
|
|
182
|
+
failure_code TEXT,
|
|
183
|
+
created_at INTEGER NOT NULL,
|
|
184
|
+
updated_at INTEGER NOT NULL,
|
|
185
|
+
PRIMARY KEY (subject_ref, version),
|
|
186
|
+
FOREIGN KEY (subject_ref, version) REFERENCES workflow_trace_revisions(subject_ref, version)
|
|
187
|
+
) STRICT;
|
|
188
|
+
CREATE INDEX workflow_trace_outbox_due
|
|
189
|
+
ON workflow_trace_outbox(status, next_attempt_at, updated_at, subject_ref, version);
|
|
190
|
+
|
|
191
|
+
CREATE TABLE workflow_trace_commands (
|
|
192
|
+
operation_id TEXT PRIMARY KEY,
|
|
193
|
+
payload_digest TEXT NOT NULL CHECK (
|
|
194
|
+
length(payload_digest) = 64 AND payload_digest NOT GLOB '*[^0-9a-f]*'
|
|
195
|
+
),
|
|
196
|
+
result_json TEXT NOT NULL,
|
|
197
|
+
created_at INTEGER NOT NULL
|
|
198
|
+
) STRICT;
|
|
199
|
+
|
|
200
|
+
CREATE TABLE trusted_delivery_evaluation_outbox (
|
|
201
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
202
|
+
payload_digest TEXT NOT NULL CHECK (
|
|
203
|
+
length(payload_digest) = 64 AND payload_digest NOT GLOB '*[^0-9a-f]*'
|
|
204
|
+
),
|
|
205
|
+
claims_json TEXT NOT NULL,
|
|
206
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'retry_wait', 'delivered')),
|
|
207
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
208
|
+
next_attempt_at INTEGER NOT NULL,
|
|
209
|
+
failure_code TEXT,
|
|
210
|
+
created_at INTEGER NOT NULL,
|
|
211
|
+
updated_at INTEGER NOT NULL
|
|
212
|
+
) STRICT;
|
|
213
|
+
CREATE INDEX trusted_delivery_evaluation_due
|
|
214
|
+
ON trusted_delivery_evaluation_outbox(status, next_attempt_at, updated_at, idempotency_key);
|
|
215
|
+
`;
|
|
216
|
+
/**
|
|
217
|
+
* One authenticated owner objective judgement for one ordinary Delivery turn.
|
|
218
|
+
* This is intentionally separate from Evaluation's cross-plugin ledger: the
|
|
219
|
+
* local receipt is the atomic proof coupled to a WorkflowTrace, so a restart
|
|
220
|
+
* cannot leave a supposedly verified trace without its exact owner fence.
|
|
221
|
+
*/
|
|
222
|
+
const workflowVerifiedTaskFeedbackSchema = `
|
|
223
|
+
CREATE TABLE IF NOT EXISTS workflow_verified_task_feedback (
|
|
224
|
+
source_outbox_id TEXT PRIMARY KEY,
|
|
225
|
+
source_inbox_id TEXT NOT NULL UNIQUE,
|
|
226
|
+
feedback_inbox_id TEXT NOT NULL UNIQUE,
|
|
227
|
+
binding_id TEXT NOT NULL,
|
|
228
|
+
binding_version INTEGER NOT NULL CHECK (binding_version >= 1),
|
|
229
|
+
binding_generation INTEGER NOT NULL CHECK (binding_generation >= 1),
|
|
230
|
+
principal_record_id TEXT NOT NULL,
|
|
231
|
+
objective_status TEXT NOT NULL CHECK (
|
|
232
|
+
objective_status IN ('achieved', 'partial', 'not-achieved')
|
|
233
|
+
),
|
|
234
|
+
task_ref TEXT NOT NULL CHECK (
|
|
235
|
+
length(task_ref) = 64 AND task_ref NOT GLOB '*[^0-9a-f]*'
|
|
236
|
+
),
|
|
237
|
+
task_evidence_digest TEXT NOT NULL CHECK (
|
|
238
|
+
length(task_evidence_digest) = 64
|
|
239
|
+
AND task_evidence_digest NOT GLOB '*[^0-9a-f]*'
|
|
240
|
+
),
|
|
241
|
+
trace_subject_ref TEXT CHECK (
|
|
242
|
+
trace_subject_ref IS NULL
|
|
243
|
+
OR (length(trace_subject_ref) = 64 AND trace_subject_ref NOT GLOB '*[^0-9a-f]*')
|
|
244
|
+
),
|
|
245
|
+
trace_version INTEGER CHECK (trace_version IS NULL OR trace_version >= 1),
|
|
246
|
+
trace_digest TEXT CHECK (
|
|
247
|
+
trace_digest IS NULL
|
|
248
|
+
OR (length(trace_digest) = 64 AND trace_digest NOT GLOB '*[^0-9a-f]*')
|
|
249
|
+
),
|
|
250
|
+
template_ref TEXT,
|
|
251
|
+
created_at INTEGER NOT NULL,
|
|
252
|
+
CHECK (
|
|
253
|
+
(trace_subject_ref IS NOT NULL AND trace_version IS NOT NULL
|
|
254
|
+
AND trace_digest IS NOT NULL AND template_ref IS NOT NULL)
|
|
255
|
+
OR
|
|
256
|
+
(trace_subject_ref IS NULL AND trace_version IS NULL
|
|
257
|
+
AND trace_digest IS NULL AND template_ref IS NULL)
|
|
258
|
+
),
|
|
259
|
+
FOREIGN KEY (source_outbox_id) REFERENCES outbox_messages(id),
|
|
260
|
+
FOREIGN KEY (source_inbox_id) REFERENCES inbox_messages(id),
|
|
261
|
+
FOREIGN KEY (feedback_inbox_id) REFERENCES inbox_messages(id),
|
|
262
|
+
FOREIGN KEY (binding_id) REFERENCES conversation_bindings(id),
|
|
263
|
+
FOREIGN KEY (principal_record_id) REFERENCES delivery_principals(id),
|
|
264
|
+
FOREIGN KEY (template_ref) REFERENCES workflow_template_registry(template_ref),
|
|
265
|
+
FOREIGN KEY (trace_subject_ref, trace_version)
|
|
266
|
+
REFERENCES workflow_trace_revisions(subject_ref, version)
|
|
267
|
+
) STRICT;
|
|
268
|
+
CREATE INDEX IF NOT EXISTS workflow_verified_task_feedback_binding
|
|
269
|
+
ON workflow_verified_task_feedback(binding_id, created_at, source_outbox_id);
|
|
270
|
+
`;
|
|
271
|
+
const deliveryPresentationSchema = `
|
|
272
|
+
CREATE TABLE IF NOT EXISTS delivery_presentations (
|
|
273
|
+
presentation_key TEXT PRIMARY KEY,
|
|
274
|
+
original_outbox_idempotency_key TEXT NOT NULL,
|
|
275
|
+
revision INTEGER NOT NULL CHECK (revision >= 1),
|
|
276
|
+
payload_hash TEXT NOT NULL,
|
|
277
|
+
payload_json TEXT NOT NULL,
|
|
278
|
+
status TEXT NOT NULL CHECK (
|
|
279
|
+
status IN ('pending', 'attempting', 'presented', 'retry_wait', 'dead')
|
|
280
|
+
),
|
|
281
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
282
|
+
presented_revision INTEGER NOT NULL DEFAULT 0 CHECK (presented_revision >= 0),
|
|
283
|
+
next_attempt_at INTEGER,
|
|
284
|
+
claimed_by TEXT,
|
|
285
|
+
fencing_token INTEGER,
|
|
286
|
+
lease_until INTEGER,
|
|
287
|
+
provider_message_id TEXT,
|
|
288
|
+
failure_code TEXT,
|
|
289
|
+
created_at INTEGER NOT NULL,
|
|
290
|
+
updated_at INTEGER NOT NULL
|
|
291
|
+
) STRICT;
|
|
292
|
+
CREATE INDEX IF NOT EXISTS delivery_presentation_claim
|
|
293
|
+
ON delivery_presentations(status, next_attempt_at, lease_until, created_at, presentation_key);
|
|
294
|
+
`;
|
|
295
|
+
const deadLetterResolutionSchema = `
|
|
296
|
+
CREATE TABLE dead_letter_resolutions (
|
|
297
|
+
kind TEXT NOT NULL CHECK (kind IN ('inbox', 'outbox')),
|
|
298
|
+
message_id TEXT NOT NULL,
|
|
299
|
+
attempt_count INTEGER NOT NULL CHECK (attempt_count >= 0),
|
|
300
|
+
receipt_version INTEGER NOT NULL CHECK (receipt_version = 1),
|
|
301
|
+
resolution TEXT NOT NULL CHECK (resolution IN ('cancel', 'retry')),
|
|
302
|
+
original_status TEXT NOT NULL CHECK (
|
|
303
|
+
(kind = 'inbox' AND original_status = 'dead_letter')
|
|
304
|
+
OR (kind = 'outbox' AND original_status IN ('dead', 'unknown_after_send'))
|
|
305
|
+
),
|
|
306
|
+
original_failure_code TEXT,
|
|
307
|
+
operator_id TEXT NOT NULL,
|
|
308
|
+
created_at INTEGER NOT NULL,
|
|
309
|
+
PRIMARY KEY (kind, message_id, attempt_count)
|
|
310
|
+
) STRICT;
|
|
311
|
+
CREATE INDEX dead_letter_resolution_projection
|
|
312
|
+
ON dead_letter_resolutions(kind, resolution, message_id, attempt_count);
|
|
313
|
+
`;
|
|
314
|
+
// These triggers are a rolling-upgrade fence for a v8 process that opened the
|
|
315
|
+
// database before v9 changed PRAGMA user_version. SQLite does not evict that
|
|
316
|
+
// already-open writer. A v9 writer first persists the exact operator receipt
|
|
317
|
+
// in the same transaction, so any receipt-less v8 resolution is rejected.
|
|
318
|
+
const deadLetterResolutionCompatibilitySchema = `
|
|
319
|
+
CREATE TRIGGER dead_letter_inbox_resolution_fence
|
|
320
|
+
BEFORE UPDATE ON inbox_messages
|
|
321
|
+
WHEN OLD.status = 'dead_letter' AND NEW.attempt_count = OLD.attempt_count AND (
|
|
322
|
+
NEW.status = 'queued'
|
|
323
|
+
OR (NEW.status = 'dead_letter' AND NEW.failure_code = 'operator-cancelled')
|
|
324
|
+
) AND NOT EXISTS (
|
|
325
|
+
SELECT 1 FROM dead_letter_resolutions AS resolution
|
|
326
|
+
WHERE resolution.kind = 'inbox' AND resolution.message_id = OLD.id
|
|
327
|
+
AND resolution.attempt_count = OLD.attempt_count
|
|
328
|
+
AND resolution.resolution = CASE WHEN NEW.status = 'queued' THEN 'retry' ELSE 'cancel' END
|
|
329
|
+
AND resolution.original_status = OLD.status
|
|
330
|
+
AND resolution.original_failure_code IS OLD.failure_code
|
|
331
|
+
)
|
|
332
|
+
BEGIN
|
|
333
|
+
SELECT RAISE(ABORT, 'inbox resolution requires an exact v9 receipt');
|
|
334
|
+
END;
|
|
335
|
+
|
|
336
|
+
CREATE TRIGGER dead_letter_outbox_resolution_fence
|
|
337
|
+
BEFORE UPDATE ON outbox_messages
|
|
338
|
+
WHEN OLD.status IN ('dead', 'unknown_after_send')
|
|
339
|
+
AND NEW.attempt_count = OLD.attempt_count AND (
|
|
340
|
+
NEW.status = 'pending'
|
|
341
|
+
OR (NEW.status = 'dead' AND NEW.failure_code = 'operator-cancelled')
|
|
342
|
+
OR (NEW.status = 'unknown_after_send' AND NEW.failure_code = 'operator-cancelled-unknown')
|
|
343
|
+
) AND NOT EXISTS (
|
|
344
|
+
SELECT 1 FROM dead_letter_resolutions AS resolution
|
|
345
|
+
WHERE resolution.kind = 'outbox' AND resolution.message_id = OLD.id
|
|
346
|
+
AND resolution.attempt_count = OLD.attempt_count
|
|
347
|
+
AND resolution.resolution = CASE WHEN NEW.status = 'pending' THEN 'retry' ELSE 'cancel' END
|
|
348
|
+
AND resolution.original_status = OLD.status
|
|
349
|
+
AND resolution.original_failure_code IS OLD.failure_code
|
|
350
|
+
)
|
|
351
|
+
BEGIN
|
|
352
|
+
SELECT RAISE(ABORT, 'outbox resolution requires an exact v9 receipt');
|
|
353
|
+
END;
|
|
354
|
+
|
|
355
|
+
CREATE TRIGGER dead_letter_outbox_cancelled_unknown_fence
|
|
356
|
+
BEFORE UPDATE ON outbox_messages
|
|
357
|
+
WHEN OLD.status = 'unknown_after_send' AND EXISTS (
|
|
358
|
+
SELECT 1 FROM dead_letter_resolutions AS resolution
|
|
359
|
+
WHERE resolution.kind = 'outbox' AND resolution.message_id = OLD.id
|
|
360
|
+
AND resolution.attempt_count = OLD.attempt_count AND resolution.resolution = 'cancel'
|
|
361
|
+
AND resolution.original_status = 'unknown_after_send'
|
|
362
|
+
) AND NOT (
|
|
363
|
+
NEW.attempt_count = OLD.attempt_count AND (
|
|
364
|
+
(NEW.status = 'unknown_after_send' AND NEW.failure_code = 'operator-cancelled-unknown')
|
|
365
|
+
OR (
|
|
366
|
+
NEW.status IN ('accepted', 'delivered', 'read') AND EXISTS (
|
|
367
|
+
SELECT 1 FROM delivery_receipts AS receipt
|
|
368
|
+
WHERE receipt.channel = OLD.channel AND receipt.account = OLD.account
|
|
369
|
+
AND receipt.provider_message_id = OLD.provider_message_id
|
|
370
|
+
AND receipt.status = NEW.status
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
BEGIN
|
|
376
|
+
SELECT RAISE(ABORT, 'cancelled unknown outbox attempt cannot be reclaimed');
|
|
377
|
+
END;
|
|
378
|
+
`;
|
|
5
379
|
const deliveryInstanceSchema = `
|
|
6
380
|
CREATE TABLE delivery_instance (
|
|
7
381
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
@@ -22,6 +396,58 @@ const approvalDispatchCursorSchema = `
|
|
|
22
396
|
CHECK ((after_created_at IS NULL) = (after_proposal_id IS NULL))
|
|
23
397
|
) STRICT;
|
|
24
398
|
`;
|
|
399
|
+
/** Immutable v2 route receipt coupled to the approval Outbox insertion. */
|
|
400
|
+
const approvalOutboxRouteSchema = `
|
|
401
|
+
CREATE TABLE IF NOT EXISTS approval_outbox_routes (
|
|
402
|
+
outbox_id TEXT PRIMARY KEY,
|
|
403
|
+
route_version INTEGER NOT NULL CHECK (route_version = 2),
|
|
404
|
+
source_id TEXT NOT NULL,
|
|
405
|
+
binding_id TEXT NOT NULL,
|
|
406
|
+
binding_version INTEGER NOT NULL CHECK (binding_version >= 1),
|
|
407
|
+
binding_generation INTEGER NOT NULL CHECK (binding_generation >= 1),
|
|
408
|
+
workspace TEXT NOT NULL,
|
|
409
|
+
principal TEXT NOT NULL,
|
|
410
|
+
principal_record_id TEXT NOT NULL,
|
|
411
|
+
principal_version INTEGER NOT NULL CHECK (principal_version >= 1),
|
|
412
|
+
FOREIGN KEY (outbox_id) REFERENCES outbox_messages(id) ON DELETE CASCADE,
|
|
413
|
+
FOREIGN KEY (binding_id) REFERENCES conversation_bindings(id),
|
|
414
|
+
FOREIGN KEY (principal_record_id) REFERENCES delivery_principals(id)
|
|
415
|
+
) STRICT;
|
|
416
|
+
CREATE INDEX IF NOT EXISTS approval_outbox_route_binding
|
|
417
|
+
ON approval_outbox_routes(binding_id, binding_version, binding_generation);
|
|
418
|
+
`;
|
|
419
|
+
function assertApprovalOutboxRouteSchema(database) {
|
|
420
|
+
const columns = database.prepare('PRAGMA table_info(approval_outbox_routes)').all();
|
|
421
|
+
const expected = [
|
|
422
|
+
['outbox_id', 'TEXT', 1, 1],
|
|
423
|
+
['route_version', 'INTEGER', 1, 0],
|
|
424
|
+
['source_id', 'TEXT', 1, 0],
|
|
425
|
+
['binding_id', 'TEXT', 1, 0],
|
|
426
|
+
['binding_version', 'INTEGER', 1, 0],
|
|
427
|
+
['binding_generation', 'INTEGER', 1, 0],
|
|
428
|
+
['workspace', 'TEXT', 1, 0],
|
|
429
|
+
['principal', 'TEXT', 1, 0],
|
|
430
|
+
['principal_record_id', 'TEXT', 1, 0],
|
|
431
|
+
['principal_version', 'INTEGER', 1, 0],
|
|
432
|
+
];
|
|
433
|
+
if (columns.length !== expected.length || expected.some((entry, index) => {
|
|
434
|
+
const column = columns[index];
|
|
435
|
+
return column === undefined || column.name !== entry[0] || column.type !== entry[1]
|
|
436
|
+
|| column.notnull !== entry[2] || column.pk !== entry[3];
|
|
437
|
+
})) {
|
|
438
|
+
throw new Error('delivery approval route receipt schema is invalid');
|
|
439
|
+
}
|
|
440
|
+
const foreignKeys = database.prepare('PRAGMA foreign_key_list(approval_outbox_routes)').all();
|
|
441
|
+
const expectedForeignKeys = [
|
|
442
|
+
['binding_id', 'conversation_bindings', 'id'],
|
|
443
|
+
['outbox_id', 'outbox_messages', 'id'],
|
|
444
|
+
['principal_record_id', 'delivery_principals', 'id'],
|
|
445
|
+
];
|
|
446
|
+
const actualForeignKeys = foreignKeys.map(row => [row.from, row.table, row.to]).sort();
|
|
447
|
+
if (JSON.stringify(actualForeignKeys) !== JSON.stringify(expectedForeignKeys)) {
|
|
448
|
+
throw new Error('delivery approval route receipt foreign keys are invalid');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
25
451
|
const modelPickerStateSchema = `
|
|
26
452
|
CREATE TABLE conversation_model_epochs (
|
|
27
453
|
conversation_hash TEXT PRIMARY KEY,
|
|
@@ -72,7 +498,71 @@ export class DeliveryDatabaseError extends Error {
|
|
|
72
498
|
this.name = 'DeliveryDatabaseError';
|
|
73
499
|
}
|
|
74
500
|
}
|
|
75
|
-
function
|
|
501
|
+
function hasTable(database, name) {
|
|
502
|
+
return database.prepare(`
|
|
503
|
+
SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = ?
|
|
504
|
+
`).get(name) !== undefined;
|
|
505
|
+
}
|
|
506
|
+
function hasColumn(database, table, column) {
|
|
507
|
+
return database.prepare(`PRAGMA table_info(${table})`).all()
|
|
508
|
+
.some(entry => entry.name === column);
|
|
509
|
+
}
|
|
510
|
+
function migratePreferenceProjectionLane(database) {
|
|
511
|
+
const lockedVersion = database.prepare('PRAGMA user_version').get()
|
|
512
|
+
.user_version;
|
|
513
|
+
if (lockedVersion >= 14)
|
|
514
|
+
return;
|
|
515
|
+
if (lockedVersion !== 13) {
|
|
516
|
+
throw new Error(`delivery preference lane migration expected schema 13, received ${lockedVersion}`);
|
|
517
|
+
}
|
|
518
|
+
if (!hasTable(database, 'delivery_preference_projection_outbox')) {
|
|
519
|
+
database.exec('PRAGMA user_version = 14');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const additions = [
|
|
523
|
+
['lane_kind', `TEXT NOT NULL DEFAULT 'unclassified' CHECK (
|
|
524
|
+
lane_kind IN ('exact', 'legacy', 'unclassified')
|
|
525
|
+
)`],
|
|
526
|
+
['lane_epoch', `TEXT CHECK (
|
|
527
|
+
lane_epoch IS NULL OR (
|
|
528
|
+
length(lane_epoch) = 32 AND lane_epoch NOT GLOB '*[^0-9a-f]*'
|
|
529
|
+
)
|
|
530
|
+
)`],
|
|
531
|
+
['lane_workspace', 'TEXT'],
|
|
532
|
+
['lane_preset', 'TEXT'],
|
|
533
|
+
['lane_principal_record_id', 'TEXT'],
|
|
534
|
+
['lane_principal_version', `INTEGER CHECK (
|
|
535
|
+
lane_principal_version IS NULL OR lane_principal_version >= 1
|
|
536
|
+
)`],
|
|
537
|
+
['admission_sequence', `INTEGER CHECK (
|
|
538
|
+
admission_sequence IS NULL OR admission_sequence BETWEEN 1 AND 9007199254740991
|
|
539
|
+
)`],
|
|
540
|
+
['terminal_at', 'INTEGER CHECK (terminal_at IS NULL OR terminal_at >= 0)'],
|
|
541
|
+
];
|
|
542
|
+
const alter = additions
|
|
543
|
+
.filter(([column]) => !hasColumn(database, 'delivery_preference_projection_outbox', column))
|
|
544
|
+
.map(([column, definition]) => `
|
|
545
|
+
ALTER TABLE delivery_preference_projection_outbox
|
|
546
|
+
ADD COLUMN ${column} ${definition};
|
|
547
|
+
`)
|
|
548
|
+
.join('');
|
|
549
|
+
database.exec(`
|
|
550
|
+
${alter}
|
|
551
|
+
DROP INDEX IF EXISTS delivery_preference_projection_due;
|
|
552
|
+
CREATE INDEX delivery_preference_projection_due
|
|
553
|
+
ON delivery_preference_projection_outbox(
|
|
554
|
+
terminal_at, lane_kind, status, next_attempt_at, updated_at, batch_key
|
|
555
|
+
);
|
|
556
|
+
CREATE INDEX IF NOT EXISTS delivery_preference_projection_lane
|
|
557
|
+
ON delivery_preference_projection_outbox(
|
|
558
|
+
terminal_at, lane_epoch, lane_workspace, lane_preset,
|
|
559
|
+
lane_principal_record_id, lane_principal_version, admission_sequence,
|
|
560
|
+
created_at, batch_key
|
|
561
|
+
);
|
|
562
|
+
PRAGMA user_version = 14;
|
|
563
|
+
`);
|
|
564
|
+
}
|
|
565
|
+
function migrateObserved(database) {
|
|
76
566
|
const row = database.prepare('PRAGMA user_version').get();
|
|
77
567
|
let version = row.user_version;
|
|
78
568
|
if (version > deliverySchemaVersion) {
|
|
@@ -82,18 +572,15 @@ function migrate(database) {
|
|
|
82
572
|
return;
|
|
83
573
|
if (version === 1) {
|
|
84
574
|
database.exec(`
|
|
85
|
-
BEGIN IMMEDIATE;
|
|
86
575
|
ALTER TABLE delivery_attachments ADD COLUMN resource_kind TEXT;
|
|
87
576
|
ALTER TABLE delivery_attachments ADD COLUMN provider_ref TEXT;
|
|
88
577
|
ALTER TABLE delivery_attachments ADD COLUMN file_name TEXT;
|
|
89
578
|
PRAGMA user_version = 2;
|
|
90
|
-
COMMIT;
|
|
91
579
|
`);
|
|
92
580
|
version = 2;
|
|
93
581
|
}
|
|
94
582
|
if (version === 2) {
|
|
95
583
|
database.exec(`
|
|
96
|
-
BEGIN IMMEDIATE;
|
|
97
584
|
CREATE TABLE conversation_model_selections (
|
|
98
585
|
conversation_hash TEXT PRIMARY KEY,
|
|
99
586
|
conversation_json TEXT NOT NULL,
|
|
@@ -104,40 +591,32 @@ function migrate(database) {
|
|
|
104
591
|
version INTEGER NOT NULL CHECK (version >= 1)
|
|
105
592
|
) STRICT;
|
|
106
593
|
PRAGMA user_version = 4;
|
|
107
|
-
COMMIT;
|
|
108
594
|
`);
|
|
109
595
|
version = 4;
|
|
110
596
|
}
|
|
111
597
|
if (version === 3) {
|
|
112
598
|
database.exec(`
|
|
113
|
-
BEGIN IMMEDIATE;
|
|
114
599
|
ALTER TABLE conversation_model_selections ADD COLUMN reasoning_effort TEXT;
|
|
115
600
|
PRAGMA user_version = 4;
|
|
116
|
-
COMMIT;
|
|
117
601
|
`);
|
|
118
602
|
version = 4;
|
|
119
603
|
}
|
|
120
604
|
if (version === 4) {
|
|
121
605
|
database.exec(`
|
|
122
|
-
BEGIN IMMEDIATE;
|
|
123
606
|
${modelPickerStateSchema}
|
|
124
607
|
PRAGMA user_version = 5;
|
|
125
|
-
COMMIT;
|
|
126
608
|
`);
|
|
127
609
|
version = 5;
|
|
128
610
|
}
|
|
129
611
|
if (version === 5) {
|
|
130
612
|
database.exec(`
|
|
131
|
-
BEGIN IMMEDIATE;
|
|
132
613
|
${approvalDispatchCursorSchema}
|
|
133
614
|
PRAGMA user_version = 6;
|
|
134
|
-
COMMIT;
|
|
135
615
|
`);
|
|
136
616
|
version = 6;
|
|
137
617
|
}
|
|
138
618
|
if (version === 6) {
|
|
139
619
|
database.exec(`
|
|
140
|
-
BEGIN IMMEDIATE;
|
|
141
620
|
CREATE TABLE delivery_attachments_v7 (
|
|
142
621
|
id TEXT PRIMARY KEY,
|
|
143
622
|
owner_kind TEXT NOT NULL CHECK (owner_kind IN ('inbox', 'outbox')),
|
|
@@ -172,21 +651,139 @@ function migrate(database) {
|
|
|
172
651
|
CREATE UNIQUE INDEX delivery_attachment_owner_ordinal
|
|
173
652
|
ON delivery_attachments(owner_kind, owner_id, ordinal);
|
|
174
653
|
PRAGMA user_version = 7;
|
|
175
|
-
COMMIT;
|
|
176
654
|
`);
|
|
177
655
|
version = 7;
|
|
178
656
|
}
|
|
179
657
|
if (version === 7) {
|
|
180
658
|
database.exec(`
|
|
181
|
-
BEGIN IMMEDIATE;
|
|
182
659
|
${deliveryInstanceSchema}
|
|
183
660
|
PRAGMA user_version = 8;
|
|
184
|
-
COMMIT;
|
|
185
661
|
`);
|
|
186
|
-
|
|
662
|
+
version = 8;
|
|
663
|
+
}
|
|
664
|
+
if (version === 8) {
|
|
665
|
+
// v8 represented an operator cancel only by overwriting failure_code. A
|
|
666
|
+
// plain table creation would revive those terminal attempts as actionable
|
|
667
|
+
// and allow an old ambiguous send to be retried after upgrade. Preserve
|
|
668
|
+
// the lost decision as an immutable legacy receipt before publishing v9.
|
|
669
|
+
const hasInbox = hasTable(database, 'inbox_messages');
|
|
670
|
+
const hasOutbox = hasTable(database, 'outbox_messages');
|
|
671
|
+
const hasInboxAttempts = hasTable(database, 'inbox_attempts');
|
|
672
|
+
const hasOutboxAttempts = hasTable(database, 'outbox_attempts');
|
|
673
|
+
const inboxOriginalFailure = hasInboxAttempts ? `COALESCE((
|
|
674
|
+
SELECT attempt.failure_code FROM inbox_attempts AS attempt
|
|
675
|
+
WHERE attempt.inbox_id = message.id
|
|
676
|
+
AND attempt.attempt_number = message.attempt_count
|
|
677
|
+
AND attempt.failure_code IS NOT NULL
|
|
678
|
+
ORDER BY attempt.rowid DESC LIMIT 1
|
|
679
|
+
), 'legacy-unknown')` : `'legacy-unknown'`;
|
|
680
|
+
const outboxOriginalFailure = hasOutboxAttempts ? `COALESCE((
|
|
681
|
+
SELECT attempt.failure_code FROM outbox_attempts AS attempt
|
|
682
|
+
WHERE attempt.outbox_id = message.id
|
|
683
|
+
AND attempt.attempt_number = message.attempt_count
|
|
684
|
+
AND attempt.failure_code IS NOT NULL
|
|
685
|
+
ORDER BY attempt.rowid DESC LIMIT 1
|
|
686
|
+
), 'legacy-unknown')` : `'legacy-unknown'`;
|
|
687
|
+
const inboxBackfill = hasInbox ? `
|
|
688
|
+
INSERT INTO dead_letter_resolutions (
|
|
689
|
+
kind, message_id, attempt_count, receipt_version, resolution, original_status,
|
|
690
|
+
original_failure_code, operator_id, created_at
|
|
691
|
+
)
|
|
692
|
+
SELECT 'inbox', message.id, message.attempt_count, 1, 'cancel', 'dead_letter',
|
|
693
|
+
${inboxOriginalFailure}, 'legacy-v8-migration', message.updated_at
|
|
694
|
+
FROM inbox_messages AS message
|
|
695
|
+
WHERE message.status = 'dead_letter' AND message.failure_code = 'operator-cancelled';
|
|
696
|
+
` : '';
|
|
697
|
+
const outboxBackfill = hasOutbox ? `
|
|
698
|
+
INSERT INTO dead_letter_resolutions (
|
|
699
|
+
kind, message_id, attempt_count, receipt_version, resolution, original_status,
|
|
700
|
+
original_failure_code, operator_id, created_at
|
|
701
|
+
)
|
|
702
|
+
SELECT 'outbox', message.id, message.attempt_count, 1, 'cancel',
|
|
703
|
+
CASE WHEN ${hasOutboxAttempts ? `EXISTS (
|
|
704
|
+
SELECT 1 FROM outbox_attempts AS attempt
|
|
705
|
+
WHERE attempt.outbox_id = message.id
|
|
706
|
+
AND attempt.attempt_number = message.attempt_count
|
|
707
|
+
AND attempt.status = 'unknown_after_send'
|
|
708
|
+
)` : '0'} THEN 'unknown_after_send' ELSE 'dead' END,
|
|
709
|
+
${outboxOriginalFailure}, 'legacy-v8-migration', message.updated_at
|
|
710
|
+
FROM outbox_messages AS message
|
|
711
|
+
WHERE message.status = 'dead' AND message.failure_code = 'operator-cancelled';
|
|
712
|
+
|
|
713
|
+
UPDATE outbox_messages AS message
|
|
714
|
+
SET status = 'unknown_after_send', failure_code = 'operator-cancelled-unknown'
|
|
715
|
+
WHERE message.status = 'dead' AND message.failure_code = 'operator-cancelled'
|
|
716
|
+
AND EXISTS (
|
|
717
|
+
SELECT 1 FROM dead_letter_resolutions AS resolution
|
|
718
|
+
WHERE resolution.kind = 'outbox' AND resolution.message_id = message.id
|
|
719
|
+
AND resolution.attempt_count = message.attempt_count
|
|
720
|
+
AND resolution.original_status = 'unknown_after_send'
|
|
721
|
+
);
|
|
722
|
+
` : '';
|
|
723
|
+
database.exec(`
|
|
724
|
+
${deadLetterResolutionSchema}
|
|
725
|
+
${inboxBackfill}
|
|
726
|
+
${outboxBackfill}
|
|
727
|
+
${hasInbox && hasOutbox ? deadLetterResolutionCompatibilitySchema : ''}
|
|
728
|
+
PRAGMA user_version = 9;
|
|
729
|
+
`);
|
|
730
|
+
version = 9;
|
|
731
|
+
}
|
|
732
|
+
if (version === 9) {
|
|
733
|
+
database.exec(`
|
|
734
|
+
${deliveryPresentationSchema}
|
|
735
|
+
PRAGMA user_version = 10;
|
|
736
|
+
`);
|
|
737
|
+
version = 10;
|
|
187
738
|
}
|
|
739
|
+
if (version === 10) {
|
|
740
|
+
database.exec(`
|
|
741
|
+
${workflowTraceSchema}
|
|
742
|
+
PRAGMA user_version = 11;
|
|
743
|
+
`);
|
|
744
|
+
version = 11;
|
|
745
|
+
}
|
|
746
|
+
if (version === 11) {
|
|
747
|
+
database.exec(`
|
|
748
|
+
${preferenceProjectionSchema}
|
|
749
|
+
PRAGMA user_version = 12;
|
|
750
|
+
`);
|
|
751
|
+
version = 12;
|
|
752
|
+
}
|
|
753
|
+
if (version === 12) {
|
|
754
|
+
database.exec(hasTable(database, 'inbox_messages')
|
|
755
|
+
&& hasTable(database, 'delivery_instance')
|
|
756
|
+
? `
|
|
757
|
+
${inboxAdmissionSchema}
|
|
758
|
+
PRAGMA user_version = 13;
|
|
759
|
+
`
|
|
760
|
+
: `
|
|
761
|
+
PRAGMA user_version = 13;
|
|
762
|
+
`);
|
|
763
|
+
version = 13;
|
|
764
|
+
}
|
|
765
|
+
if (version === 13) {
|
|
766
|
+
migratePreferenceProjectionLane(database);
|
|
767
|
+
version = 14;
|
|
768
|
+
}
|
|
769
|
+
if (version === 14) {
|
|
770
|
+
database.exec(`
|
|
771
|
+
${workflowVerifiedTaskFeedbackSchema}
|
|
772
|
+
PRAGMA user_version = 15;
|
|
773
|
+
`);
|
|
774
|
+
version = 15;
|
|
775
|
+
}
|
|
776
|
+
if (version === 15) {
|
|
777
|
+
database.exec(`
|
|
778
|
+
${approvalOutboxRouteSchema}
|
|
779
|
+
PRAGMA user_version = 16;
|
|
780
|
+
`);
|
|
781
|
+
assertApprovalOutboxRouteSchema(database);
|
|
782
|
+
version = 16;
|
|
783
|
+
}
|
|
784
|
+
if (version === deliverySchemaVersion)
|
|
785
|
+
return;
|
|
188
786
|
database.exec(`
|
|
189
|
-
BEGIN IMMEDIATE;
|
|
190
787
|
${deliveryInstanceSchema}
|
|
191
788
|
CREATE TABLE delivery_principals (
|
|
192
789
|
id TEXT PRIMARY KEY,
|
|
@@ -378,10 +975,41 @@ function migrate(database) {
|
|
|
378
975
|
|
|
379
976
|
${approvalDispatchCursorSchema}
|
|
380
977
|
|
|
381
|
-
|
|
382
|
-
|
|
978
|
+
${approvalOutboxRouteSchema}
|
|
979
|
+
|
|
980
|
+
${deadLetterResolutionSchema}
|
|
981
|
+
|
|
982
|
+
${deadLetterResolutionCompatibilitySchema}
|
|
983
|
+
|
|
984
|
+
${deliveryPresentationSchema}
|
|
985
|
+
|
|
986
|
+
${workflowTraceSchema}
|
|
987
|
+
|
|
988
|
+
${workflowVerifiedTaskFeedbackSchema}
|
|
989
|
+
|
|
990
|
+
${preferenceProjectionSchema}
|
|
991
|
+
|
|
992
|
+
${inboxAdmissionSchema}
|
|
993
|
+
|
|
994
|
+
PRAGMA user_version = 16;
|
|
383
995
|
`);
|
|
384
996
|
}
|
|
997
|
+
/** Serialize the complete forward migration chain across every Host process. */
|
|
998
|
+
function migrate(database) {
|
|
999
|
+
database.exec('BEGIN IMMEDIATE');
|
|
1000
|
+
try {
|
|
1001
|
+
migrateObserved(database);
|
|
1002
|
+
assertApprovalOutboxRouteSchema(database);
|
|
1003
|
+
database.exec('COMMIT');
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
try {
|
|
1007
|
+
database.exec('ROLLBACK');
|
|
1008
|
+
}
|
|
1009
|
+
catch { }
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
385
1013
|
export function openDeliveryDatabase(path) {
|
|
386
1014
|
if (path !== ':memory:' && !isAbsolute(path)) {
|
|
387
1015
|
throw new DeliveryDatabaseError('invalid-path', 'delivery database path must be absolute');
|