@jungjaehoon/mama-core 2.1.0 → 2.1.2
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/db/migrations/{043-add-tool-trace-failure-code.sql → 061-add-tool-trace-failure-code.sql} +9 -1
- package/db/migrations/062-refresh-connector-event-sequences.sql +114 -0
- package/db/migrations/063-refresh-legacy-connector-event-updates.sql +33 -0
- package/dist/connectors/event-index.js +21 -1
- package/dist/connectors/types.d.ts +2 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +3 -0
- package/dist/db-adapter/node-sqlite-adapter.js +255 -0
- package/package.json +1 -1
package/db/migrations/{043-add-tool-trace-failure-code.sql → 061-add-tool-trace-failure-code.sql}
RENAMED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
-- Migration
|
|
1
|
+
-- Migration 061: tool_traces.failure_code — carry the thrower's closed cause.
|
|
2
|
+
--
|
|
3
|
+
-- WHY 061, NOT 043: live DBs carry schema_version rows 44-60 from a RETIRED
|
|
4
|
+
-- 2026-05/06 migration chain that shared this ledger table. The runner skips
|
|
5
|
+
-- any file numbered at or below MAX(version), so this migration shipped as
|
|
6
|
+
-- 043 in core 2.1.0 and silently never applied anywhere the retired chain
|
|
7
|
+
-- had run. New core migrations must number ABOVE that ceiling (60) - the
|
|
8
|
+
-- migration-chain test pins the jump so nobody "fixes" the gap back into
|
|
9
|
+
-- the dead zone.
|
|
2
10
|
--
|
|
3
11
|
-- The failure choke already emits a structured code (envelope_missing,
|
|
4
12
|
-- context_compile_scope_denied, ...) and the sanitizer explicitly preserves
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
ALTER TABLE connector_event_index
|
|
2
|
+
ADD COLUMN operator_observation_seq INTEGER CHECK (
|
|
3
|
+
operator_observation_seq IS NULL OR operator_observation_seq >= 1
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS connector_event_index_observation_cursors (
|
|
7
|
+
source_connector TEXT PRIMARY KEY,
|
|
8
|
+
next_seq INTEGER NOT NULL CHECK (next_seq >= 1)
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
WITH ranked AS (
|
|
12
|
+
SELECT event_index_id,
|
|
13
|
+
ROW_NUMBER() OVER (
|
|
14
|
+
PARTITION BY source_connector
|
|
15
|
+
ORDER BY source_timestamp_ms, rowid
|
|
16
|
+
) AS seq
|
|
17
|
+
FROM connector_event_index
|
|
18
|
+
)
|
|
19
|
+
UPDATE connector_event_index
|
|
20
|
+
SET operator_observation_seq = (
|
|
21
|
+
SELECT seq FROM ranked WHERE ranked.event_index_id = connector_event_index.event_index_id
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
INSERT INTO connector_event_index_observation_cursors (source_connector, next_seq)
|
|
25
|
+
SELECT source_connector, MAX(operator_observation_seq) + 1
|
|
26
|
+
FROM connector_event_index
|
|
27
|
+
GROUP BY source_connector
|
|
28
|
+
ON CONFLICT(source_connector) DO UPDATE SET next_seq = CASE
|
|
29
|
+
WHEN connector_event_index_observation_cursors.next_seq < excluded.next_seq
|
|
30
|
+
THEN excluded.next_seq
|
|
31
|
+
ELSE connector_event_index_observation_cursors.next_seq
|
|
32
|
+
END;
|
|
33
|
+
|
|
34
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_connector_event_index_observation_seq
|
|
35
|
+
ON connector_event_index(source_connector, operator_observation_seq)
|
|
36
|
+
WHERE operator_observation_seq IS NOT NULL;
|
|
37
|
+
|
|
38
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_operator_ingest_seq_au
|
|
39
|
+
AFTER UPDATE OF operator_ingest_seq ON connector_event_index
|
|
40
|
+
WHEN NEW.operator_ingest_seq IS NULL AND OLD.operator_ingest_seq IS NOT NULL
|
|
41
|
+
BEGIN
|
|
42
|
+
INSERT INTO connector_event_index_operator_seq_cursors
|
|
43
|
+
(source_connector, channel, next_seq)
|
|
44
|
+
VALUES (NEW.source_connector, COALESCE(NEW.channel, ''), 1)
|
|
45
|
+
ON CONFLICT(source_connector, channel) DO NOTHING;
|
|
46
|
+
UPDATE connector_event_index
|
|
47
|
+
SET operator_ingest_seq = (
|
|
48
|
+
SELECT next_seq FROM connector_event_index_operator_seq_cursors
|
|
49
|
+
WHERE source_connector = NEW.source_connector
|
|
50
|
+
AND channel = COALESCE(NEW.channel, '')
|
|
51
|
+
)
|
|
52
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
53
|
+
UPDATE connector_event_index_operator_seq_cursors
|
|
54
|
+
SET next_seq = next_seq + 1
|
|
55
|
+
WHERE source_connector = NEW.source_connector
|
|
56
|
+
AND channel = COALESCE(NEW.channel, '');
|
|
57
|
+
END;
|
|
58
|
+
|
|
59
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_ai
|
|
60
|
+
AFTER INSERT ON connector_event_index
|
|
61
|
+
WHEN NEW.operator_observation_seq IS NULL
|
|
62
|
+
BEGIN
|
|
63
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
64
|
+
(source_connector, next_seq)
|
|
65
|
+
VALUES (NEW.source_connector, 1)
|
|
66
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
67
|
+
UPDATE connector_event_index
|
|
68
|
+
SET operator_observation_seq = (
|
|
69
|
+
SELECT next_seq FROM connector_event_index_observation_cursors
|
|
70
|
+
WHERE source_connector = NEW.source_connector
|
|
71
|
+
)
|
|
72
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
73
|
+
UPDATE connector_event_index_observation_cursors
|
|
74
|
+
SET next_seq = next_seq + 1
|
|
75
|
+
WHERE source_connector = NEW.source_connector;
|
|
76
|
+
END;
|
|
77
|
+
|
|
78
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_au
|
|
79
|
+
AFTER UPDATE OF operator_observation_seq ON connector_event_index
|
|
80
|
+
WHEN NEW.operator_observation_seq IS NULL AND OLD.operator_observation_seq IS NOT NULL
|
|
81
|
+
BEGIN
|
|
82
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
83
|
+
(source_connector, next_seq)
|
|
84
|
+
VALUES (NEW.source_connector, 1)
|
|
85
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
86
|
+
UPDATE connector_event_index
|
|
87
|
+
SET operator_observation_seq = (
|
|
88
|
+
SELECT next_seq FROM connector_event_index_observation_cursors
|
|
89
|
+
WHERE source_connector = NEW.source_connector
|
|
90
|
+
)
|
|
91
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
92
|
+
UPDATE connector_event_index_observation_cursors
|
|
93
|
+
SET next_seq = next_seq + 1
|
|
94
|
+
WHERE source_connector = NEW.source_connector;
|
|
95
|
+
END;
|
|
96
|
+
|
|
97
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_explicit_ai
|
|
98
|
+
AFTER INSERT ON connector_event_index
|
|
99
|
+
WHEN NEW.operator_observation_seq IS NOT NULL
|
|
100
|
+
BEGIN
|
|
101
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
102
|
+
(source_connector, next_seq)
|
|
103
|
+
VALUES (NEW.source_connector, 1)
|
|
104
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
105
|
+
UPDATE connector_event_index_observation_cursors
|
|
106
|
+
SET next_seq = CASE
|
|
107
|
+
WHEN next_seq <= NEW.operator_observation_seq THEN NEW.operator_observation_seq + 1
|
|
108
|
+
ELSE next_seq
|
|
109
|
+
END
|
|
110
|
+
WHERE source_connector = NEW.source_connector;
|
|
111
|
+
END;
|
|
112
|
+
|
|
113
|
+
INSERT INTO schema_version (version, description)
|
|
114
|
+
VALUES (62, 'Refresh connector event delivery and observation sequences');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
SELECT content_hash, metadata_json, source_timestamp_ms, source_type, channel,
|
|
2
|
+
operator_ingest_seq, operator_observation_seq
|
|
3
|
+
FROM connector_event_index
|
|
4
|
+
LIMIT 0;
|
|
5
|
+
|
|
6
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_legacy_content_refresh_au
|
|
7
|
+
AFTER UPDATE OF content_hash, metadata_json, source_timestamp_ms, source_type, channel
|
|
8
|
+
ON connector_event_index
|
|
9
|
+
WHEN (
|
|
10
|
+
OLD.content_hash IS NOT NEW.content_hash
|
|
11
|
+
OR OLD.metadata_json IS NOT NEW.metadata_json
|
|
12
|
+
OR OLD.source_timestamp_ms IS NOT NEW.source_timestamp_ms
|
|
13
|
+
OR OLD.source_type IS NOT NEW.source_type
|
|
14
|
+
OR OLD.channel IS NOT NEW.channel
|
|
15
|
+
) AND (
|
|
16
|
+
NEW.operator_ingest_seq IS OLD.operator_ingest_seq
|
|
17
|
+
OR NEW.operator_observation_seq IS OLD.operator_observation_seq
|
|
18
|
+
)
|
|
19
|
+
BEGIN
|
|
20
|
+
UPDATE connector_event_index
|
|
21
|
+
SET operator_ingest_seq = CASE
|
|
22
|
+
WHEN NEW.operator_ingest_seq IS OLD.operator_ingest_seq THEN NULL
|
|
23
|
+
ELSE NEW.operator_ingest_seq
|
|
24
|
+
END,
|
|
25
|
+
operator_observation_seq = CASE
|
|
26
|
+
WHEN NEW.operator_observation_seq IS OLD.operator_observation_seq THEN NULL
|
|
27
|
+
ELSE NEW.operator_observation_seq
|
|
28
|
+
END
|
|
29
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
30
|
+
END;
|
|
31
|
+
|
|
32
|
+
INSERT OR IGNORE INTO schema_version (version, description)
|
|
33
|
+
VALUES (63, 'Refresh sequences for legacy connector event updates');
|
|
@@ -77,6 +77,8 @@ function mapConnectorEventIndexRow(row) {
|
|
|
77
77
|
artifact_locator: row.artifact_locator === null ? null : String(row.artifact_locator),
|
|
78
78
|
artifact_title: row.artifact_title === null ? null : String(row.artifact_title),
|
|
79
79
|
content_hash: toBuffer(row.content_hash),
|
|
80
|
+
operator_ingest_seq: Number(row.operator_ingest_seq),
|
|
81
|
+
operator_observation_seq: Number(row.operator_observation_seq),
|
|
80
82
|
indexed_at: String(row.indexed_at),
|
|
81
83
|
updated_at: String(row.updated_at),
|
|
82
84
|
expires_at: row.expires_at === null ? null : String(row.expires_at),
|
|
@@ -160,7 +162,25 @@ function upsertConnectorEventIndex(adapter, input) {
|
|
|
160
162
|
artifact_title = excluded.artifact_title,
|
|
161
163
|
content_hash = excluded.content_hash,
|
|
162
164
|
updated_at = excluded.updated_at,
|
|
163
|
-
expires_at = excluded.expires_at
|
|
165
|
+
expires_at = excluded.expires_at,
|
|
166
|
+
operator_ingest_seq = CASE
|
|
167
|
+
WHEN connector_event_index.content_hash IS NOT excluded.content_hash
|
|
168
|
+
OR connector_event_index.metadata_json IS NOT excluded.metadata_json
|
|
169
|
+
OR connector_event_index.source_timestamp_ms IS NOT excluded.source_timestamp_ms
|
|
170
|
+
OR connector_event_index.source_type IS NOT excluded.source_type
|
|
171
|
+
OR connector_event_index.channel IS NOT excluded.channel
|
|
172
|
+
THEN NULL
|
|
173
|
+
ELSE connector_event_index.operator_ingest_seq
|
|
174
|
+
END,
|
|
175
|
+
operator_observation_seq = CASE
|
|
176
|
+
WHEN connector_event_index.content_hash IS NOT excluded.content_hash
|
|
177
|
+
OR connector_event_index.metadata_json IS NOT excluded.metadata_json
|
|
178
|
+
OR connector_event_index.source_timestamp_ms IS NOT excluded.source_timestamp_ms
|
|
179
|
+
OR connector_event_index.source_type IS NOT excluded.source_type
|
|
180
|
+
OR connector_event_index.channel IS NOT excluded.channel
|
|
181
|
+
THEN NULL
|
|
182
|
+
ELSE connector_event_index.operator_observation_seq
|
|
183
|
+
END
|
|
164
184
|
`)
|
|
165
185
|
.run(eventIndexId, input.source_connector, input.source_type, input.source_id, input.source_locator ?? null, input.channel ?? null, input.author ?? null, input.title ?? null, input.content, eventDatetime, eventDate, sourceTimestampMs, input.source_cursor ?? null, input.tenant_id ?? null, input.project_id ?? null, input.memory_scope_kind ?? null, input.memory_scope_id ?? null, metadataJson, input.artifact_locator ?? null, input.artifact_title ?? null, contentHash, input.indexed_at ?? timestamp, timestamp, input.expires_at ?? null);
|
|
166
186
|
const saved = getConnectorEventIndexRecord(adapter, input.source_connector, input.source_id);
|
|
@@ -22,6 +22,8 @@ export interface ConnectorEventIndexRecord {
|
|
|
22
22
|
artifact_locator: string | null;
|
|
23
23
|
artifact_title: string | null;
|
|
24
24
|
content_hash: Buffer;
|
|
25
|
+
operator_ingest_seq: number;
|
|
26
|
+
operator_observation_seq: number;
|
|
25
27
|
indexed_at: string;
|
|
26
28
|
updated_at: string;
|
|
27
29
|
expires_at: string | null;
|
|
@@ -72,6 +72,9 @@ export declare class NodeSQLiteAdapter extends DatabaseAdapter {
|
|
|
72
72
|
private assertMigration034Complete;
|
|
73
73
|
private recoverConnectorEventOperatorSeqMigration039;
|
|
74
74
|
private assertMigration039Complete;
|
|
75
|
+
private recoverConnectorEventSequencesMigration062;
|
|
76
|
+
private assertMigration062Complete;
|
|
77
|
+
private recoverConnectorEventLegacyRefreshMigration063;
|
|
75
78
|
private tableColumns;
|
|
76
79
|
private tableExists;
|
|
77
80
|
private indexExists;
|
|
@@ -404,6 +404,20 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
|
|
|
404
404
|
(0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} recovered successfully`);
|
|
405
405
|
continue;
|
|
406
406
|
}
|
|
407
|
+
if (message.includes('duplicate column') && version === 62) {
|
|
408
|
+
this.recoverConnectorEventSequencesMigration062();
|
|
409
|
+
(0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} recovered successfully`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (message.includes('no such column') && version === 62) {
|
|
413
|
+
(0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} deferred until connector structure recovery (${message})`);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (version === 63 &&
|
|
417
|
+
(message.includes('no such column') || message.includes('no such table'))) {
|
|
418
|
+
(0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} deferred until connector structure recovery (${message})`);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
407
421
|
if (message.includes('duplicate column')) {
|
|
408
422
|
(0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} skipped (duplicate column - already applied)`);
|
|
409
423
|
this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
|
|
@@ -480,6 +494,27 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
|
|
|
480
494
|
this.recoverConnectorEventOperatorSeqMigration039();
|
|
481
495
|
(0, debug_logger_js_1.info)('[node-sqlite-adapter] Repaired skipped connector event operator sequence migration');
|
|
482
496
|
}
|
|
497
|
+
const connectorColumnsAfterOperatorSeqRepair = this.tableColumns('connector_event_index');
|
|
498
|
+
if (connectorColumnsAfterOperatorSeqRepair.has('source_timestamp_ms')) {
|
|
499
|
+
const hasMissingObservationSeqFeature = !connectorColumnsAfterOperatorSeqRepair.has('operator_observation_seq') ||
|
|
500
|
+
!this.tableExists('connector_event_index_observation_cursors') ||
|
|
501
|
+
!this.indexExists('idx_connector_event_index_observation_seq') ||
|
|
502
|
+
!this.triggerExists('trg_connector_event_index_operator_ingest_seq_au') ||
|
|
503
|
+
!this.triggerExists('trg_connector_event_index_observation_seq_ai') ||
|
|
504
|
+
!this.triggerExists('trg_connector_event_index_observation_seq_au') ||
|
|
505
|
+
!this.triggerExists('trg_connector_event_index_observation_seq_explicit_ai') ||
|
|
506
|
+
!this.schemaVersionExists(62);
|
|
507
|
+
if (hasMissingObservationSeqFeature) {
|
|
508
|
+
this.recoverConnectorEventSequencesMigration062();
|
|
509
|
+
(0, debug_logger_js_1.info)('[node-sqlite-adapter] Repaired skipped connector event observation sequence migration');
|
|
510
|
+
}
|
|
511
|
+
const hasMissingLegacyRefreshFeature = !this.triggerExists('trg_connector_event_index_legacy_content_refresh_au') ||
|
|
512
|
+
!this.schemaVersionExists(63);
|
|
513
|
+
if (hasMissingLegacyRefreshFeature) {
|
|
514
|
+
this.recoverConnectorEventLegacyRefreshMigration063();
|
|
515
|
+
(0, debug_logger_js_1.info)('[node-sqlite-adapter] Repaired skipped legacy connector event refresh migration');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
483
518
|
}
|
|
484
519
|
if (!this.tableExists('twin_edges')) {
|
|
485
520
|
this.applyRepairMigration(migrationsDir, '035-create-twin-edges.sql', 'twin edge ledger');
|
|
@@ -844,6 +879,226 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
|
|
|
844
879
|
}
|
|
845
880
|
}
|
|
846
881
|
}
|
|
882
|
+
recoverConnectorEventSequencesMigration062() {
|
|
883
|
+
this.transaction(() => {
|
|
884
|
+
const columns = this.tableColumns('connector_event_index');
|
|
885
|
+
if (!columns.has('operator_observation_seq')) {
|
|
886
|
+
this.exec(`
|
|
887
|
+
ALTER TABLE connector_event_index
|
|
888
|
+
ADD COLUMN operator_observation_seq INTEGER CHECK (
|
|
889
|
+
operator_observation_seq IS NULL OR operator_observation_seq >= 1
|
|
890
|
+
)
|
|
891
|
+
`);
|
|
892
|
+
}
|
|
893
|
+
this.exec(`
|
|
894
|
+
CREATE TABLE IF NOT EXISTS connector_event_index_observation_cursors (
|
|
895
|
+
source_connector TEXT PRIMARY KEY,
|
|
896
|
+
next_seq INTEGER NOT NULL CHECK (next_seq >= 1)
|
|
897
|
+
)
|
|
898
|
+
`);
|
|
899
|
+
this.exec(`
|
|
900
|
+
WITH existing_max AS (
|
|
901
|
+
SELECT source_connector, MAX(operator_observation_seq) AS max_seq
|
|
902
|
+
FROM connector_event_index
|
|
903
|
+
WHERE operator_observation_seq IS NOT NULL
|
|
904
|
+
GROUP BY source_connector
|
|
905
|
+
), ranked_nulls AS (
|
|
906
|
+
SELECT event_index_id,
|
|
907
|
+
source_connector,
|
|
908
|
+
ROW_NUMBER() OVER (
|
|
909
|
+
PARTITION BY source_connector
|
|
910
|
+
ORDER BY source_timestamp_ms, rowid
|
|
911
|
+
) AS seq
|
|
912
|
+
FROM connector_event_index
|
|
913
|
+
WHERE operator_observation_seq IS NULL
|
|
914
|
+
)
|
|
915
|
+
UPDATE connector_event_index
|
|
916
|
+
SET operator_observation_seq = (
|
|
917
|
+
SELECT COALESCE(existing_max.max_seq, 0) + ranked_nulls.seq
|
|
918
|
+
FROM ranked_nulls
|
|
919
|
+
LEFT JOIN existing_max
|
|
920
|
+
ON existing_max.source_connector = ranked_nulls.source_connector
|
|
921
|
+
WHERE ranked_nulls.event_index_id = connector_event_index.event_index_id
|
|
922
|
+
)
|
|
923
|
+
WHERE operator_observation_seq IS NULL
|
|
924
|
+
`);
|
|
925
|
+
this.exec(`
|
|
926
|
+
INSERT INTO connector_event_index_observation_cursors (source_connector, next_seq)
|
|
927
|
+
SELECT source_connector, MAX(operator_observation_seq) + 1
|
|
928
|
+
FROM connector_event_index
|
|
929
|
+
GROUP BY source_connector
|
|
930
|
+
ON CONFLICT(source_connector) DO UPDATE SET next_seq = CASE
|
|
931
|
+
WHEN connector_event_index_observation_cursors.next_seq < excluded.next_seq
|
|
932
|
+
THEN excluded.next_seq
|
|
933
|
+
ELSE connector_event_index_observation_cursors.next_seq
|
|
934
|
+
END
|
|
935
|
+
`);
|
|
936
|
+
this.exec(`
|
|
937
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_connector_event_index_observation_seq
|
|
938
|
+
ON connector_event_index(source_connector, operator_observation_seq)
|
|
939
|
+
WHERE operator_observation_seq IS NOT NULL
|
|
940
|
+
`);
|
|
941
|
+
this.exec(`
|
|
942
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_operator_ingest_seq_au
|
|
943
|
+
AFTER UPDATE OF operator_ingest_seq ON connector_event_index
|
|
944
|
+
WHEN NEW.operator_ingest_seq IS NULL AND OLD.operator_ingest_seq IS NOT NULL
|
|
945
|
+
BEGIN
|
|
946
|
+
INSERT INTO connector_event_index_operator_seq_cursors
|
|
947
|
+
(source_connector, channel, next_seq)
|
|
948
|
+
VALUES (NEW.source_connector, COALESCE(NEW.channel, ''), 1)
|
|
949
|
+
ON CONFLICT(source_connector, channel) DO NOTHING;
|
|
950
|
+
UPDATE connector_event_index
|
|
951
|
+
SET operator_ingest_seq = (
|
|
952
|
+
SELECT next_seq FROM connector_event_index_operator_seq_cursors
|
|
953
|
+
WHERE source_connector = NEW.source_connector
|
|
954
|
+
AND channel = COALESCE(NEW.channel, '')
|
|
955
|
+
)
|
|
956
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
957
|
+
UPDATE connector_event_index_operator_seq_cursors
|
|
958
|
+
SET next_seq = next_seq + 1
|
|
959
|
+
WHERE source_connector = NEW.source_connector
|
|
960
|
+
AND channel = COALESCE(NEW.channel, '');
|
|
961
|
+
END
|
|
962
|
+
`);
|
|
963
|
+
this.exec(`
|
|
964
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_ai
|
|
965
|
+
AFTER INSERT ON connector_event_index
|
|
966
|
+
WHEN NEW.operator_observation_seq IS NULL
|
|
967
|
+
BEGIN
|
|
968
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
969
|
+
(source_connector, next_seq)
|
|
970
|
+
VALUES (NEW.source_connector, 1)
|
|
971
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
972
|
+
UPDATE connector_event_index
|
|
973
|
+
SET operator_observation_seq = (
|
|
974
|
+
SELECT next_seq FROM connector_event_index_observation_cursors
|
|
975
|
+
WHERE source_connector = NEW.source_connector
|
|
976
|
+
)
|
|
977
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
978
|
+
UPDATE connector_event_index_observation_cursors
|
|
979
|
+
SET next_seq = next_seq + 1
|
|
980
|
+
WHERE source_connector = NEW.source_connector;
|
|
981
|
+
END
|
|
982
|
+
`);
|
|
983
|
+
this.exec(`
|
|
984
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_au
|
|
985
|
+
AFTER UPDATE OF operator_observation_seq ON connector_event_index
|
|
986
|
+
WHEN NEW.operator_observation_seq IS NULL AND OLD.operator_observation_seq IS NOT NULL
|
|
987
|
+
BEGIN
|
|
988
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
989
|
+
(source_connector, next_seq)
|
|
990
|
+
VALUES (NEW.source_connector, 1)
|
|
991
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
992
|
+
UPDATE connector_event_index
|
|
993
|
+
SET operator_observation_seq = (
|
|
994
|
+
SELECT next_seq FROM connector_event_index_observation_cursors
|
|
995
|
+
WHERE source_connector = NEW.source_connector
|
|
996
|
+
)
|
|
997
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
998
|
+
UPDATE connector_event_index_observation_cursors
|
|
999
|
+
SET next_seq = next_seq + 1
|
|
1000
|
+
WHERE source_connector = NEW.source_connector;
|
|
1001
|
+
END
|
|
1002
|
+
`);
|
|
1003
|
+
this.exec(`
|
|
1004
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_observation_seq_explicit_ai
|
|
1005
|
+
AFTER INSERT ON connector_event_index
|
|
1006
|
+
WHEN NEW.operator_observation_seq IS NOT NULL
|
|
1007
|
+
BEGIN
|
|
1008
|
+
INSERT INTO connector_event_index_observation_cursors
|
|
1009
|
+
(source_connector, next_seq)
|
|
1010
|
+
VALUES (NEW.source_connector, 1)
|
|
1011
|
+
ON CONFLICT(source_connector) DO NOTHING;
|
|
1012
|
+
UPDATE connector_event_index_observation_cursors
|
|
1013
|
+
SET next_seq = CASE
|
|
1014
|
+
WHEN next_seq <= NEW.operator_observation_seq THEN NEW.operator_observation_seq + 1
|
|
1015
|
+
ELSE next_seq
|
|
1016
|
+
END
|
|
1017
|
+
WHERE source_connector = NEW.source_connector;
|
|
1018
|
+
END
|
|
1019
|
+
`);
|
|
1020
|
+
this.assertMigration062Complete();
|
|
1021
|
+
this.prepare('INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)').run(62, 'Refresh connector event delivery and observation sequences');
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
assertMigration062Complete() {
|
|
1025
|
+
const columns = this.tableColumns('connector_event_index');
|
|
1026
|
+
if (!columns.has('operator_observation_seq')) {
|
|
1027
|
+
throw new Error('Migration 062 recovery failed: missing connector_event_index.operator_observation_seq');
|
|
1028
|
+
}
|
|
1029
|
+
if (!this.tableExists('connector_event_index_observation_cursors')) {
|
|
1030
|
+
throw new Error('Migration 062 recovery failed: missing connector_event_index_observation_cursors');
|
|
1031
|
+
}
|
|
1032
|
+
const cursorColumns = this.tableColumns('connector_event_index_observation_cursors');
|
|
1033
|
+
for (const columnName of ['source_connector', 'next_seq']) {
|
|
1034
|
+
if (!cursorColumns.has(columnName)) {
|
|
1035
|
+
throw new Error(`Migration 062 recovery failed: missing connector_event_index_observation_cursors.${columnName}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (!this.indexExists('idx_connector_event_index_observation_seq')) {
|
|
1039
|
+
throw new Error('Migration 062 recovery failed: missing index idx_connector_event_index_observation_seq');
|
|
1040
|
+
}
|
|
1041
|
+
for (const triggerName of [
|
|
1042
|
+
'trg_connector_event_index_operator_ingest_seq_au',
|
|
1043
|
+
'trg_connector_event_index_observation_seq_ai',
|
|
1044
|
+
'trg_connector_event_index_observation_seq_au',
|
|
1045
|
+
'trg_connector_event_index_observation_seq_explicit_ai',
|
|
1046
|
+
]) {
|
|
1047
|
+
if (!this.triggerExists(triggerName)) {
|
|
1048
|
+
throw new Error(`Migration 062 recovery failed: missing trigger ${triggerName}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
recoverConnectorEventLegacyRefreshMigration063() {
|
|
1053
|
+
this.transaction(() => {
|
|
1054
|
+
this.assertMigration062Complete();
|
|
1055
|
+
const connectorColumns = this.tableColumns('connector_event_index');
|
|
1056
|
+
for (const columnName of [
|
|
1057
|
+
'content_hash',
|
|
1058
|
+
'metadata_json',
|
|
1059
|
+
'source_timestamp_ms',
|
|
1060
|
+
'source_type',
|
|
1061
|
+
'channel',
|
|
1062
|
+
'operator_ingest_seq',
|
|
1063
|
+
'operator_observation_seq',
|
|
1064
|
+
]) {
|
|
1065
|
+
if (!connectorColumns.has(columnName)) {
|
|
1066
|
+
throw new Error(`Migration 063 recovery failed: missing connector_event_index.${columnName}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
this.exec(`
|
|
1070
|
+
CREATE TRIGGER IF NOT EXISTS trg_connector_event_index_legacy_content_refresh_au
|
|
1071
|
+
AFTER UPDATE OF content_hash, metadata_json, source_timestamp_ms, source_type, channel
|
|
1072
|
+
ON connector_event_index
|
|
1073
|
+
WHEN (
|
|
1074
|
+
OLD.content_hash IS NOT NEW.content_hash
|
|
1075
|
+
OR OLD.metadata_json IS NOT NEW.metadata_json
|
|
1076
|
+
OR OLD.source_timestamp_ms IS NOT NEW.source_timestamp_ms
|
|
1077
|
+
OR OLD.source_type IS NOT NEW.source_type
|
|
1078
|
+
OR OLD.channel IS NOT NEW.channel
|
|
1079
|
+
) AND (
|
|
1080
|
+
NEW.operator_ingest_seq IS OLD.operator_ingest_seq
|
|
1081
|
+
OR NEW.operator_observation_seq IS OLD.operator_observation_seq
|
|
1082
|
+
)
|
|
1083
|
+
BEGIN
|
|
1084
|
+
UPDATE connector_event_index
|
|
1085
|
+
SET operator_ingest_seq = CASE
|
|
1086
|
+
WHEN NEW.operator_ingest_seq IS OLD.operator_ingest_seq THEN NULL
|
|
1087
|
+
ELSE NEW.operator_ingest_seq
|
|
1088
|
+
END,
|
|
1089
|
+
operator_observation_seq = CASE
|
|
1090
|
+
WHEN NEW.operator_observation_seq IS OLD.operator_observation_seq THEN NULL
|
|
1091
|
+
ELSE NEW.operator_observation_seq
|
|
1092
|
+
END
|
|
1093
|
+
WHERE event_index_id = NEW.event_index_id;
|
|
1094
|
+
END
|
|
1095
|
+
`);
|
|
1096
|
+
if (!this.triggerExists('trg_connector_event_index_legacy_content_refresh_au')) {
|
|
1097
|
+
throw new Error('Migration 063 recovery failed: missing trigger trg_connector_event_index_legacy_content_refresh_au');
|
|
1098
|
+
}
|
|
1099
|
+
this.prepare('INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)').run(63, 'Refresh sequences for legacy connector event updates');
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
847
1102
|
tableColumns(tableName) {
|
|
848
1103
|
if (!SQLITE_IDENTIFIER_PATTERN.test(tableName)) {
|
|
849
1104
|
throw new Error('Invalid SQLite table identifier');
|