deepline 0.3.261 → 0.3.262
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/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +324 -16
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.262',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|
|
@@ -314,7 +314,9 @@ const POSTGRES_SCHEDULER_SCHEMA_INIT_STATEMENT_TIMEOUT_MS = 60_000;
|
|
|
314
314
|
// deploy readiness require it; v50 only built it on the concurrent path.
|
|
315
315
|
// v52 indexes only delivered outbox rows that still contain raw log bodies.
|
|
316
316
|
// The concurrent build lets retention skip already-scrubbed history.
|
|
317
|
-
|
|
317
|
+
// v53 refreshes the indexed runtime log reconciliation projection. Its query
|
|
318
|
+
// indexes remain operator-prepared concurrently, outside shared bootstrap.
|
|
319
|
+
export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 53;
|
|
318
320
|
const POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE =
|
|
319
321
|
'scheduler_schema_migrations';
|
|
320
322
|
export const POSTGRES_SCHEDULER_OPEN_RECOVERY_INDEX =
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts
CHANGED
|
@@ -9,10 +9,39 @@ import { normalizeRuntimeSchedulerSchema } from '../runtime-scheduler-topology';
|
|
|
9
9
|
import type { PostgresSchedulerQueryClient } from './postgres';
|
|
10
10
|
|
|
11
11
|
const EVENTS_TABLE = 'runtime_observability.events';
|
|
12
|
+
const RUNTIME_EVENT_LOG_DEDUPE_INDEX = 'runtime_events_log_attempt_digest_idx';
|
|
13
|
+
const RUNTIME_EVENT_LOG_POSITION_INDEX =
|
|
14
|
+
'runtime_events_log_attempt_position_idx';
|
|
15
|
+
const RUNTIME_EVENT_LOG_LEGACY_INDEX =
|
|
16
|
+
'runtime_events_legacy_log_normalize_idx';
|
|
17
|
+
const RUNTIME_EVENT_LOG_BODY_PAGE_INDEX = 'runtime_events_run_log_body_seq_idx';
|
|
18
|
+
const RUNTIME_EVENT_LOG_LEGACY_PREDICATE = `kind = 'log.appended'
|
|
19
|
+
AND source_digest IS NOT NULL
|
|
20
|
+
AND NOT (search_doc ? 'logContentDigest')
|
|
21
|
+
AND (search_doc->>'producerAttempt') ~ '^[1-9][0-9]{0,5}$'`;
|
|
22
|
+
const RUNTIME_EVENT_LOG_BODY_PAGE_PREDICATE = `kind = 'log.appended'
|
|
23
|
+
AND log_seq IS NOT NULL
|
|
24
|
+
AND (log_body IS NOT NULL OR message IS NOT NULL)`;
|
|
25
|
+
const RUNTIME_EVENT_LOG_DEDUPE_PREDICATE = `kind = 'log.appended'
|
|
26
|
+
AND source_digest IS NOT NULL
|
|
27
|
+
AND search_doc ? 'producerAttempt'
|
|
28
|
+
AND search_doc ? 'logContentDigest'`;
|
|
29
|
+
const RUNTIME_EVENT_LOG_POSITION_PREDICATE = `kind = 'log.appended'
|
|
30
|
+
AND search_doc ? 'producerAttempt'
|
|
31
|
+
AND search_doc ? 'logPositions'`;
|
|
32
|
+
const RUNTIME_EVENT_LOG_POSITION_EXPRESSION = `jsonb_set(
|
|
33
|
+
jsonb_set(
|
|
34
|
+
jsonb_set(
|
|
35
|
+
jsonb_set(coalesce(search_doc->'logPositions', '{}'::jsonb),
|
|
36
|
+
ARRAY['producerAttempt'], search_doc->'producerAttempt', true),
|
|
37
|
+
ARRAY['_runtimeScope:' || runtime_scope], 'true'::jsonb, true),
|
|
38
|
+
ARRAY['_orgId:' || org_id], 'true'::jsonb, true),
|
|
39
|
+
ARRAY['_runId:' || run_id], 'true'::jsonb, true
|
|
40
|
+
)`;
|
|
12
41
|
// Migration readiness tracks this projection body independently of scheduler
|
|
13
42
|
// wire and schema versions. Bump when a changed trigger must be reinstalled.
|
|
14
43
|
export const RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER =
|
|
15
|
-
'runtime-event-search-log-context-
|
|
44
|
+
'runtime-event-search-log-context-v9-indexed-channel-reconciliation';
|
|
16
45
|
export const RUNTIME_EVENTS_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
17
46
|
const MAX_SEARCH_WINDOW_MS = RUNTIME_EVENTS_RETENTION_MS;
|
|
18
47
|
const MAX_SEARCH_RESULTS = 200;
|
|
@@ -455,12 +484,21 @@ export async function getRuntimeRunLogPage(
|
|
|
455
484
|
// Match the event-then-cursor relation lock order used by migrations and
|
|
456
485
|
// log projection. Cursor-first reads can deadlock a schema expansion.
|
|
457
486
|
`WITH retained AS (
|
|
458
|
-
SELECT
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
487
|
+
SELECT
|
|
488
|
+
(SELECT log_seq
|
|
489
|
+
FROM runtime_observability.events
|
|
490
|
+
WHERE runtime_scope = $1 AND org_id = $2 AND run_id = $3
|
|
491
|
+
AND kind = 'log.appended' AND log_seq IS NOT NULL
|
|
492
|
+
AND (log_body IS NOT NULL OR message IS NOT NULL)
|
|
493
|
+
AND ingested_at >= clock_timestamp() - interval '7 days'
|
|
494
|
+
ORDER BY log_seq ASC LIMIT 1) AS first_seq,
|
|
495
|
+
(SELECT log_seq
|
|
496
|
+
FROM runtime_observability.events
|
|
497
|
+
WHERE runtime_scope = $1 AND org_id = $2 AND run_id = $3
|
|
498
|
+
AND kind = 'log.appended' AND log_seq IS NOT NULL
|
|
499
|
+
AND (log_body IS NOT NULL OR message IS NOT NULL)
|
|
500
|
+
AND ingested_at >= clock_timestamp() - interval '7 days'
|
|
501
|
+
ORDER BY log_seq DESC LIMIT 1) AS last_seq
|
|
464
502
|
), run_cursor AS (
|
|
465
503
|
SELECT last_seq
|
|
466
504
|
FROM runtime_observability.run_log_cursors
|
|
@@ -807,7 +845,8 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
807
845
|
doc jsonb; line_doc jsonb; line_context jsonb;
|
|
808
846
|
eid text; existing_digest text; digest text; assigned_seq bigint; terminal_progress_identity boolean;
|
|
809
847
|
log_channel text; log_position bigint; matched_event_id text; matched_doc jsonb;
|
|
810
|
-
legacy_position_prefix text;
|
|
848
|
+
legacy_position_prefix text; position_event_ids text[];
|
|
849
|
+
log_alias jsonb; position_alias jsonb; envelope_attempt jsonb;
|
|
811
850
|
line_count bigint; fresh_event_ids text[] := ARRAY[]::text[];
|
|
812
851
|
recent_log_keys jsonb := '{}'::jsonb; dedupe_key text;
|
|
813
852
|
terminal_overlap_counts jsonb := '{}'::jsonb;
|
|
@@ -1013,15 +1052,53 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1013
1052
|
END IF;
|
|
1014
1053
|
log_alias := jsonb_build_object('producerAttempt', (item->>'producerAttempt')::integer,
|
|
1015
1054
|
'logPositions', jsonb_build_object(log_channel, log_position));
|
|
1055
|
+
position_alias := jsonb_build_object(
|
|
1056
|
+
'producerAttempt', (item->>'producerAttempt')::integer,
|
|
1057
|
+
'_runtimeScope:' || ${quoteLiteral(scope)}, true,
|
|
1058
|
+
'_orgId:' || run_org, true,
|
|
1059
|
+
'_runId:' || observation.run_id, true)
|
|
1060
|
+
|| jsonb_build_object(log_channel, log_position);
|
|
1016
1061
|
matched_event_id := NULL;
|
|
1017
|
-
|
|
1018
|
-
|
|
1062
|
+
position_event_ids := ARRAY[
|
|
1063
|
+
legacy_position_prefix || log_position::text,
|
|
1064
|
+
'log-worker:' || observation.run_id || ':' || (item->>'producerAttempt') || ':' || log_position::text,
|
|
1065
|
+
'log-replay:' || observation.run_id || ':' || (item->>'producerAttempt') || ':' || log_position::text,
|
|
1066
|
+
'log-envelope:' || observation.run_id || ':' || (item->>'producerAttempt') || ':' || log_position::text
|
|
1067
|
+
];
|
|
1068
|
+
-- Positional identities have a bounded set of historical IDs.
|
|
1069
|
+
-- Probe those primary keys directly; scanning every event in
|
|
1070
|
+
-- the run for JSON containment made one lookup grow with run age.
|
|
1019
1071
|
SELECT event.event_id, event.source_digest, event.search_doc
|
|
1020
1072
|
INTO matched_event_id, existing_digest, matched_doc
|
|
1021
1073
|
FROM runtime_observability.events AS event
|
|
1022
1074
|
WHERE event.runtime_scope = ${quoteLiteral(scope)} AND event.org_id = run_org
|
|
1023
1075
|
AND event.run_id = observation.run_id AND event.kind = 'log.appended'
|
|
1024
|
-
AND event.
|
|
1076
|
+
AND event.event_id = ANY(position_event_ids)
|
|
1077
|
+
AND (
|
|
1078
|
+
event.search_doc->'logPositions'->>log_channel = log_position::text
|
|
1079
|
+
OR (NOT (event.search_doc ? 'logPositions')
|
|
1080
|
+
AND event.event_id = legacy_position_prefix || log_position::text
|
|
1081
|
+
AND (CASE WHEN event.search_doc->>'source' = 'coordinator'
|
|
1082
|
+
THEN 'replay' ELSE 'worker' END) = log_channel)
|
|
1083
|
+
)
|
|
1084
|
+
ORDER BY event.event_id
|
|
1085
|
+
LIMIT 1;
|
|
1086
|
+
IF matched_event_id IS NULL THEN
|
|
1087
|
+
-- A transport may already be aliased onto an event whose
|
|
1088
|
+
-- primary key belongs to another channel. Find that alias
|
|
1089
|
+
-- independently of content so changed bytes still fail the
|
|
1090
|
+
-- immutable-position check below.
|
|
1091
|
+
SELECT event.event_id, event.source_digest, event.search_doc
|
|
1092
|
+
INTO matched_event_id, existing_digest, matched_doc
|
|
1093
|
+
FROM runtime_observability.events AS event
|
|
1094
|
+
WHERE event.runtime_scope = ${quoteLiteral(scope)} AND event.org_id = run_org
|
|
1095
|
+
AND event.run_id = observation.run_id AND event.kind = 'log.appended'
|
|
1096
|
+
AND event.search_doc ? 'producerAttempt'
|
|
1097
|
+
AND event.search_doc ? 'logPositions'
|
|
1098
|
+
AND (${RUNTIME_EVENT_LOG_POSITION_EXPRESSION.replaceAll('search_doc', 'event.search_doc')}) @> position_alias
|
|
1099
|
+
ORDER BY event.log_seq NULLS LAST, event.event_id
|
|
1100
|
+
LIMIT 1;
|
|
1101
|
+
END IF;
|
|
1025
1102
|
IF matched_event_id IS NOT NULL THEN
|
|
1026
1103
|
IF existing_digest IS DISTINCT FROM digest THEN
|
|
1027
1104
|
RAISE EXCEPTION 'Positional log event identity changed its content';
|
|
@@ -1036,9 +1113,12 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1036
1113
|
FROM runtime_observability.events AS event
|
|
1037
1114
|
WHERE event.runtime_scope = ${quoteLiteral(scope)} AND event.org_id = run_org
|
|
1038
1115
|
AND event.run_id = observation.run_id AND event.kind = 'log.appended'
|
|
1039
|
-
AND event.
|
|
1040
|
-
|
|
1116
|
+
AND event.source_digest IS NOT NULL
|
|
1117
|
+
AND event.search_doc ? 'producerAttempt'
|
|
1118
|
+
AND event.search_doc ? 'logContentDigest'
|
|
1119
|
+
AND event.search_doc->>'producerAttempt' = ((item->>'producerAttempt')::integer)::text
|
|
1041
1120
|
AND event.source_digest = digest
|
|
1121
|
+
AND event.search_doc @> jsonb_build_object('logContentDigest', digest)
|
|
1042
1122
|
AND NOT (coalesce(event.search_doc->'logPositions', '{}'::jsonb) ? log_channel)
|
|
1043
1123
|
AND (event.search_doc ? 'logPositions' OR log_channel <>
|
|
1044
1124
|
CASE WHEN event.search_doc->>'source' = 'coordinator' THEN 'replay' ELSE 'worker' END)
|
|
@@ -1408,10 +1488,40 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1408
1488
|
`);
|
|
1409
1489
|
// Replacing AFTER with BEFORE is one DDL transaction, so live inserts cannot
|
|
1410
1490
|
// observe an interval with no projector. All older unmarked runs stay full.
|
|
1491
|
+
// Once installed, leave the matching trigger attached: dropping it takes an
|
|
1492
|
+
// ACCESS EXCLUSIVE lock on the busy outbox table even when only its function
|
|
1493
|
+
// body or projection marker changed.
|
|
1494
|
+
const outboxTrigger = await client.query<{ matches: boolean }>(
|
|
1495
|
+
`SELECT EXISTS (
|
|
1496
|
+
SELECT 1
|
|
1497
|
+
FROM pg_trigger AS trg
|
|
1498
|
+
JOIN pg_class AS rel ON rel.oid = trg.tgrelid
|
|
1499
|
+
JOIN pg_namespace AS rel_ns ON rel_ns.oid = rel.relnamespace
|
|
1500
|
+
JOIN pg_proc AS fn ON fn.oid = trg.tgfoid
|
|
1501
|
+
JOIN pg_namespace AS fn_ns ON fn_ns.oid = fn.pronamespace
|
|
1502
|
+
WHERE rel_ns.nspname = $1 AND rel.relname = 'outbox'
|
|
1503
|
+
AND trg.tgname = 'runtime_outbox_event_v1'
|
|
1504
|
+
AND NOT trg.tgisinternal
|
|
1505
|
+
AND trg.tgenabled IN ('O', 'A')
|
|
1506
|
+
AND trg.tgtype = 7
|
|
1507
|
+
AND trg.tgqual IS NULL
|
|
1508
|
+
AND trg.tgargs = ''::bytea
|
|
1509
|
+
AND fn_ns.nspname = $1 AND fn.proname = 'search_outbox_event'
|
|
1510
|
+
AND fn.pronargs = 0
|
|
1511
|
+
) AS matches`,
|
|
1512
|
+
[scope],
|
|
1513
|
+
);
|
|
1514
|
+
if (outboxTrigger.rows[0]?.matches !== true) {
|
|
1515
|
+
await client.query(`DO $replace$ BEGIN
|
|
1516
|
+
IF EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = '${schema}.outbox'::regclass
|
|
1517
|
+
AND tgname = 'runtime_outbox_event_v1' AND NOT tgisinternal) THEN
|
|
1518
|
+
DROP TRIGGER runtime_outbox_event_v1 ON ${schema}.outbox;
|
|
1519
|
+
END IF;
|
|
1520
|
+
CREATE TRIGGER runtime_outbox_event_v1 BEFORE INSERT ON ${schema}.outbox
|
|
1521
|
+
FOR EACH ROW EXECUTE FUNCTION ${schema}.search_outbox_event();
|
|
1522
|
+
END $replace$`);
|
|
1523
|
+
}
|
|
1411
1524
|
await client.query(`DO $create$ BEGIN
|
|
1412
|
-
DROP TRIGGER IF EXISTS runtime_outbox_event_v1 ON ${schema}.outbox;
|
|
1413
|
-
CREATE TRIGGER runtime_outbox_event_v1 BEFORE INSERT ON ${schema}.outbox
|
|
1414
|
-
FOR EACH ROW EXECUTE FUNCTION ${schema}.search_outbox_event();
|
|
1415
1525
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = '${schema}.attempts'::regclass
|
|
1416
1526
|
AND tgname = 'runtime_attempt_log_acceptance') THEN
|
|
1417
1527
|
CREATE TRIGGER runtime_attempt_log_acceptance BEFORE UPDATE OF runner_result_json ON ${schema}.attempts
|
|
@@ -1486,6 +1596,204 @@ function normalizedRetentionPredicate(value: string): string {
|
|
|
1486
1596
|
.join('');
|
|
1487
1597
|
}
|
|
1488
1598
|
|
|
1599
|
+
// Query indexes include mixed AND/OR predicates. Remove catalog-added wrappers
|
|
1600
|
+
// around atomic terms, but keep boolean grouping so a changed predicate cannot
|
|
1601
|
+
// silently widen an index to unrelated tenant/event rows.
|
|
1602
|
+
function normalizedQueryIndexPredicate(value: string): string {
|
|
1603
|
+
let normalized = (
|
|
1604
|
+
value.match(/'(?:''|[^'])*'|::text\b|\b(?:AND|OR)\b|[()\s]+|./gi) ?? []
|
|
1605
|
+
)
|
|
1606
|
+
.filter((token) => token !== '::text')
|
|
1607
|
+
.map((token) => {
|
|
1608
|
+
if (/^(AND|OR)$/i.test(token))
|
|
1609
|
+
return `\u0001${token.toUpperCase()}\u0001`;
|
|
1610
|
+
return token.startsWith("'") ? token : token.replace(/\s/g, '');
|
|
1611
|
+
})
|
|
1612
|
+
.join('');
|
|
1613
|
+
let previous: string;
|
|
1614
|
+
do {
|
|
1615
|
+
previous = normalized;
|
|
1616
|
+
normalized = normalized
|
|
1617
|
+
.replace(/\(([^()]*)\)/g, (group, expression: string) =>
|
|
1618
|
+
/\u0001(?:AND|OR)\u0001/.test(expression) ? group : expression,
|
|
1619
|
+
)
|
|
1620
|
+
.replace(
|
|
1621
|
+
/\(\(([^()]*(?:\u0001AND\u0001|\u0001OR\u0001)[^()]*)\)\)/g,
|
|
1622
|
+
'($1)',
|
|
1623
|
+
);
|
|
1624
|
+
while (isWholeExpressionWrapped(normalized)) {
|
|
1625
|
+
normalized = normalized.slice(1, -1);
|
|
1626
|
+
}
|
|
1627
|
+
} while (normalized !== previous);
|
|
1628
|
+
return normalized;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
function isWholeExpressionWrapped(value: string): boolean {
|
|
1632
|
+
if (!value.startsWith('(') || !value.endsWith(')')) return false;
|
|
1633
|
+
let depth = 0;
|
|
1634
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1635
|
+
if (value[index] === "'") {
|
|
1636
|
+
if (value[index + 1] === "'") index += 1;
|
|
1637
|
+
else {
|
|
1638
|
+
const closingQuote = value.indexOf("'", index + 1);
|
|
1639
|
+
if (closingQuote === -1) return false;
|
|
1640
|
+
index = closingQuote;
|
|
1641
|
+
}
|
|
1642
|
+
} else if (value[index] === '(') depth += 1;
|
|
1643
|
+
else if (value[index] === ')') {
|
|
1644
|
+
depth -= 1;
|
|
1645
|
+
if (depth === 0 && index !== value.length - 1) return false;
|
|
1646
|
+
if (depth < 0) return false;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return depth === 0;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
type RuntimeEventIndexExpectation = {
|
|
1653
|
+
name: string;
|
|
1654
|
+
keys: string[];
|
|
1655
|
+
predicate: string;
|
|
1656
|
+
createSql: string;
|
|
1657
|
+
method?: 'btree' | 'gin';
|
|
1658
|
+
opclasses?: string[];
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
const RUNTIME_EVENT_QUERY_INDEXES: RuntimeEventIndexExpectation[] = [
|
|
1662
|
+
{
|
|
1663
|
+
name: RUNTIME_EVENT_LOG_POSITION_INDEX,
|
|
1664
|
+
method: 'gin',
|
|
1665
|
+
opclasses: ['jsonb_path_ops'],
|
|
1666
|
+
keys: [
|
|
1667
|
+
"jsonb_set(jsonb_set(jsonb_set(jsonb_set(COALESCE(search_doc -> 'logPositions'::text, '{}'::jsonb), ARRAY['producerAttempt'::text], search_doc -> 'producerAttempt'::text, true), ARRAY['_runtimeScope:'::text || runtime_scope], 'true'::jsonb, true), ARRAY['_orgId:'::text || org_id], 'true'::jsonb, true), ARRAY['_runId:'::text || run_id], 'true'::jsonb, true)",
|
|
1668
|
+
],
|
|
1669
|
+
predicate: RUNTIME_EVENT_LOG_POSITION_PREDICATE,
|
|
1670
|
+
createSql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${RUNTIME_EVENT_LOG_POSITION_INDEX}
|
|
1671
|
+
ON runtime_observability.events USING gin
|
|
1672
|
+
((${RUNTIME_EVENT_LOG_POSITION_EXPRESSION}) jsonb_path_ops)
|
|
1673
|
+
WHERE ${RUNTIME_EVENT_LOG_POSITION_PREDICATE}`,
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
name: RUNTIME_EVENT_LOG_DEDUPE_INDEX,
|
|
1677
|
+
keys: [
|
|
1678
|
+
'runtime_scope',
|
|
1679
|
+
'org_id',
|
|
1680
|
+
'run_id',
|
|
1681
|
+
"(search_doc ->> 'producerAttempt'::text)",
|
|
1682
|
+
'source_digest',
|
|
1683
|
+
],
|
|
1684
|
+
predicate: RUNTIME_EVENT_LOG_DEDUPE_PREDICATE,
|
|
1685
|
+
createSql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${RUNTIME_EVENT_LOG_DEDUPE_INDEX}
|
|
1686
|
+
ON runtime_observability.events
|
|
1687
|
+
(runtime_scope, org_id, run_id, (search_doc->>'producerAttempt'), source_digest)
|
|
1688
|
+
WHERE ${RUNTIME_EVENT_LOG_DEDUPE_PREDICATE}`,
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
name: RUNTIME_EVENT_LOG_LEGACY_INDEX,
|
|
1692
|
+
keys: ['runtime_scope', 'org_id', 'run_id'],
|
|
1693
|
+
predicate: RUNTIME_EVENT_LOG_LEGACY_PREDICATE,
|
|
1694
|
+
createSql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${RUNTIME_EVENT_LOG_LEGACY_INDEX}
|
|
1695
|
+
ON runtime_observability.events (runtime_scope, org_id, run_id)
|
|
1696
|
+
WHERE ${RUNTIME_EVENT_LOG_LEGACY_PREDICATE}`,
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
name: RUNTIME_EVENT_LOG_BODY_PAGE_INDEX,
|
|
1700
|
+
keys: ['runtime_scope', 'org_id', 'run_id', 'log_seq'],
|
|
1701
|
+
predicate: RUNTIME_EVENT_LOG_BODY_PAGE_PREDICATE,
|
|
1702
|
+
createSql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${RUNTIME_EVENT_LOG_BODY_PAGE_INDEX}
|
|
1703
|
+
ON runtime_observability.events (runtime_scope, org_id, run_id, log_seq)
|
|
1704
|
+
WHERE ${RUNTIME_EVENT_LOG_BODY_PAGE_PREDICATE}`,
|
|
1705
|
+
},
|
|
1706
|
+
];
|
|
1707
|
+
|
|
1708
|
+
/** Build only operator-owned indexes, concurrently and outside a transaction. */
|
|
1709
|
+
export async function prepareRuntimeEventQueryIndexes(
|
|
1710
|
+
client: PostgresSchedulerQueryClient,
|
|
1711
|
+
): Promise<void> {
|
|
1712
|
+
for (const expected of RUNTIME_EVENT_QUERY_INDEXES) {
|
|
1713
|
+
type IndexShape = {
|
|
1714
|
+
valid: boolean;
|
|
1715
|
+
ready: boolean;
|
|
1716
|
+
is_unique: boolean;
|
|
1717
|
+
table_matches: boolean;
|
|
1718
|
+
key_count: number;
|
|
1719
|
+
attribute_count: number;
|
|
1720
|
+
method: string;
|
|
1721
|
+
keys: string[];
|
|
1722
|
+
opclasses: string[];
|
|
1723
|
+
predicate: string | null;
|
|
1724
|
+
};
|
|
1725
|
+
const inspect = () =>
|
|
1726
|
+
client.query<IndexShape>(
|
|
1727
|
+
`SELECT index_row.indisvalid AS valid,
|
|
1728
|
+
index_row.indisready AS ready,
|
|
1729
|
+
index_row.indisunique AS is_unique,
|
|
1730
|
+
index_row.indrelid = to_regclass('runtime_observability.events') AS table_matches,
|
|
1731
|
+
index_row.indnkeyatts AS key_count,
|
|
1732
|
+
index_row.indnatts AS attribute_count,
|
|
1733
|
+
method.amname AS method,
|
|
1734
|
+
ARRAY(
|
|
1735
|
+
SELECT pg_get_indexdef(index_row.indexrelid, key_number.position, true)
|
|
1736
|
+
FROM generate_series(1, index_row.indnkeyatts) AS key_number(position)
|
|
1737
|
+
ORDER BY key_number.position
|
|
1738
|
+
) AS keys,
|
|
1739
|
+
(
|
|
1740
|
+
SELECT coalesce(json_agg(operator_class.opcname ORDER BY classes.position), '[]'::json)
|
|
1741
|
+
FROM unnest(index_row.indclass) WITH ORDINALITY AS classes(opclass_oid, position)
|
|
1742
|
+
JOIN pg_opclass AS operator_class ON operator_class.oid = classes.opclass_oid
|
|
1743
|
+
) AS opclasses,
|
|
1744
|
+
pg_get_expr(index_row.indpred, index_row.indrelid) AS predicate
|
|
1745
|
+
FROM pg_index AS index_row
|
|
1746
|
+
JOIN pg_class AS index_class ON index_class.oid = index_row.indexrelid
|
|
1747
|
+
JOIN pg_am AS method ON method.oid = index_class.relam
|
|
1748
|
+
WHERE index_class.oid = to_regclass($1)`,
|
|
1749
|
+
[`runtime_observability.${expected.name}`],
|
|
1750
|
+
);
|
|
1751
|
+
const assertShape = (shape: IndexShape): void => {
|
|
1752
|
+
if (!shape.valid || !shape.ready) {
|
|
1753
|
+
throw new Error(
|
|
1754
|
+
`Runtime event index ${expected.name} is invalid or incomplete; inspect and repair the failed build explicitly.`,
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
if (
|
|
1758
|
+
!shape.table_matches ||
|
|
1759
|
+
shape.is_unique ||
|
|
1760
|
+
shape.method !== (expected.method ?? 'btree') ||
|
|
1761
|
+
(expected.opclasses !== undefined &&
|
|
1762
|
+
(shape.opclasses.length !== expected.opclasses.length ||
|
|
1763
|
+
shape.opclasses.some(
|
|
1764
|
+
(name, index) => name !== expected.opclasses![index],
|
|
1765
|
+
))) ||
|
|
1766
|
+
shape.key_count !== expected.keys.length ||
|
|
1767
|
+
shape.attribute_count !== expected.keys.length ||
|
|
1768
|
+
shape.keys.length !== expected.keys.length ||
|
|
1769
|
+
shape.keys.some(
|
|
1770
|
+
(key, index) =>
|
|
1771
|
+
normalizedRetentionPredicate(key) !==
|
|
1772
|
+
normalizedRetentionPredicate(expected.keys[index]!),
|
|
1773
|
+
) ||
|
|
1774
|
+
shape.predicate === null ||
|
|
1775
|
+
normalizedQueryIndexPredicate(shape.predicate) !==
|
|
1776
|
+
normalizedQueryIndexPredicate(expected.predicate)
|
|
1777
|
+
) {
|
|
1778
|
+
throw new Error(
|
|
1779
|
+
`Runtime event index ${expected.name} differs from its expected table, keys, method, uniqueness or predicate. Actual catalog shape: ${JSON.stringify(shape)}. Inspect and repair it explicitly.`,
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
const existing = (await inspect()).rows[0];
|
|
1785
|
+
if (existing) assertShape(existing);
|
|
1786
|
+
await client.query(expected.createSql);
|
|
1787
|
+
const built = (await inspect()).rows[0];
|
|
1788
|
+
if (!built) {
|
|
1789
|
+
throw new Error(
|
|
1790
|
+
`Runtime event index ${expected.name} was not found after preparation.`,
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
assertShape(built);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1489
1797
|
/** Operator-only concurrent index build; never part of runtime bootstrap. */
|
|
1490
1798
|
export async function prepareRuntimeEventRetentionIndex(
|
|
1491
1799
|
client: PostgresSchedulerQueryClient,
|
package/dist/cli/index.js
CHANGED
|
@@ -3512,7 +3512,7 @@ var SDK_RELEASE = {
|
|
|
3512
3512
|
// getters keep their established compatibility behavior.
|
|
3513
3513
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
3514
3514
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
3515
|
-
version: "0.3.
|
|
3515
|
+
version: "0.3.262",
|
|
3516
3516
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
3517
3517
|
packageCapabilities: {
|
|
3518
3518
|
updatePreferences: 1
|
package/dist/cli/index.mjs
CHANGED
|
@@ -3507,7 +3507,7 @@ var SDK_RELEASE = {
|
|
|
3507
3507
|
// getters keep their established compatibility behavior.
|
|
3508
3508
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
3509
3509
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
3510
|
-
version: "0.3.
|
|
3510
|
+
version: "0.3.262",
|
|
3511
3511
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
3512
3512
|
packageCapabilities: {
|
|
3513
3513
|
updatePreferences: 1
|
package/dist/index.js
CHANGED
|
@@ -1325,7 +1325,7 @@ var SDK_RELEASE = {
|
|
|
1325
1325
|
// getters keep their established compatibility behavior.
|
|
1326
1326
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
1327
1327
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
1328
|
-
version: "0.3.
|
|
1328
|
+
version: "0.3.262",
|
|
1329
1329
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
1330
1330
|
packageCapabilities: {
|
|
1331
1331
|
updatePreferences: 1
|
package/dist/index.mjs
CHANGED
|
@@ -1229,7 +1229,7 @@ var SDK_RELEASE = {
|
|
|
1229
1229
|
// getters keep their established compatibility behavior.
|
|
1230
1230
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
1231
1231
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
1232
|
-
version: "0.3.
|
|
1232
|
+
version: "0.3.262",
|
|
1233
1233
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
1234
1234
|
packageCapabilities: {
|
|
1235
1235
|
updatePreferences: 1
|
package/dist/release.d.mts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
152
|
+
readonly version: "0.3.262";
|
|
153
153
|
readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
|
|
154
154
|
readonly packageCapabilities: {
|
|
155
155
|
readonly updatePreferences: 1;
|
package/dist/release.d.ts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
152
|
+
readonly version: "0.3.262";
|
|
153
153
|
readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
|
|
154
154
|
readonly packageCapabilities: {
|
|
155
155
|
readonly updatePreferences: 1;
|
package/dist/release.js
CHANGED
|
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
|
|
|
74
74
|
// getters keep their established compatibility behavior.
|
|
75
75
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
76
76
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
77
|
-
version: "0.3.
|
|
77
|
+
version: "0.3.262",
|
|
78
78
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
79
79
|
packageCapabilities: {
|
|
80
80
|
updatePreferences: 1
|
package/dist/release.mjs
CHANGED
|
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
|
|
|
48
48
|
// getters keep their established compatibility behavior.
|
|
49
49
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
50
50
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
51
|
-
version: "0.3.
|
|
51
|
+
version: "0.3.262",
|
|
52
52
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
53
53
|
packageCapabilities: {
|
|
54
54
|
updatePreferences: 1
|