deepline 0.3.267 → 0.3.268
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/app-runtime-api.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +64 -16
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +429 -108
- 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.268',
|
|
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: {
|
|
@@ -265,6 +265,7 @@ type RuntimeApiRequest =
|
|
|
265
265
|
schedulerBackend?: string | null;
|
|
266
266
|
schedulerSchema?: string | null;
|
|
267
267
|
runLogStore?: 'planet_scale';
|
|
268
|
+
runEventStore?: 'planet_scale';
|
|
268
269
|
executionProfile?: string | null;
|
|
269
270
|
runtimeReleaseId?: string | null;
|
|
270
271
|
runtimeDeployVersion?: string | null;
|
|
@@ -25,7 +25,10 @@ import type {
|
|
|
25
25
|
PlayRunnerRuntimeTiming,
|
|
26
26
|
} from '../protocol';
|
|
27
27
|
import { PROD_DEFAULT_RUNTIME_SCHEDULER_SCHEMA } from '../runtime-scheduler-topology';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
RUNTIME_EVENT_CANONICAL_RETENTION_INDEX_READY_MARKER,
|
|
30
|
+
RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER,
|
|
31
|
+
} from './runtime-event-search';
|
|
29
32
|
import type { PlayRunnerRuntimeResource } from '../runner-backends/types';
|
|
30
33
|
import {
|
|
31
34
|
RuntimeSandboxOperationState,
|
|
@@ -317,7 +320,9 @@ const POSTGRES_SCHEDULER_SCHEMA_INIT_STATEMENT_TIMEOUT_MS = 60_000;
|
|
|
317
320
|
// v53 refreshes the indexed runtime log reconciliation projection. Its query
|
|
318
321
|
// indexes remain operator-prepared concurrently, outside shared bootstrap.
|
|
319
322
|
// v54 expands the optional per-run canonical event-store marker.
|
|
320
|
-
|
|
323
|
+
// v55 refreshes the unified event journal projection. The canonical retention
|
|
324
|
+
// index stays operator-prepared concurrently before marked-run admission.
|
|
325
|
+
export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 55;
|
|
321
326
|
const POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE =
|
|
322
327
|
'scheduler_schema_migrations';
|
|
323
328
|
export const POSTGRES_SCHEDULER_OPEN_RECOVERY_INDEX =
|
|
@@ -985,6 +990,36 @@ function newRunLogStoreSql(
|
|
|
985
990
|
) THEN 'planet_scale' END`;
|
|
986
991
|
}
|
|
987
992
|
|
|
993
|
+
/** Only new immutable admissions opt into the full typed event journal. */
|
|
994
|
+
function newRunEventStoreSql(
|
|
995
|
+
launchSql: 'launch_payload.launch_json',
|
|
996
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
997
|
+
): string {
|
|
998
|
+
return `CASE WHEN ${launchSql} ? 'playReference' AND EXISTS (
|
|
999
|
+
SELECT 1 FROM pg_proc AS proc
|
|
1000
|
+
JOIN pg_namespace AS namespace ON namespace.oid = proc.pronamespace
|
|
1001
|
+
WHERE namespace.nspname = '${schemaName(options).replace(/'/g, "''")}'
|
|
1002
|
+
AND proc.proname = 'search_outbox_event'
|
|
1003
|
+
AND obj_description(proc.oid, 'pg_proc') = '${RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER.replace(/'/g, "''")}'
|
|
1004
|
+
) AND EXISTS (
|
|
1005
|
+
SELECT 1 FROM pg_index AS index_row
|
|
1006
|
+
WHERE index_row.indexrelid = to_regclass('runtime_observability.runtime_events_canonical_retention_idx')
|
|
1007
|
+
AND index_row.indisready AND index_row.indisvalid
|
|
1008
|
+
AND obj_description(index_row.indexrelid, 'pg_class') = '${RUNTIME_EVENT_CANONICAL_RETENTION_INDEX_READY_MARKER.replace(/'/g, "''")}'
|
|
1009
|
+
) AND to_regclass('runtime_observability.runtime_event_order_seq') IS NOT NULL
|
|
1010
|
+
AND EXISTS (
|
|
1011
|
+
SELECT 1 FROM pg_attribute AS event_column
|
|
1012
|
+
WHERE event_column.attrelid = to_regclass('runtime_observability.events')
|
|
1013
|
+
AND event_column.attname = 'event_seq' AND event_column.attnum > 0
|
|
1014
|
+
AND NOT event_column.attisdropped
|
|
1015
|
+
) AND EXISTS (
|
|
1016
|
+
SELECT 1 FROM pg_attribute AS payload_column
|
|
1017
|
+
WHERE payload_column.attrelid = to_regclass('runtime_observability.events')
|
|
1018
|
+
AND payload_column.attname = 'event_payload' AND payload_column.attnum > 0
|
|
1019
|
+
AND NOT payload_column.attisdropped
|
|
1020
|
+
) THEN 'planet_scale' END`;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
988
1023
|
function tableName(
|
|
989
1024
|
table: string,
|
|
990
1025
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
@@ -5041,7 +5076,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
5041
5076
|
status,
|
|
5042
5077
|
absurd_task_id,
|
|
5043
5078
|
admission_json,
|
|
5044
|
-
run_log_store
|
|
5079
|
+
run_log_store,
|
|
5080
|
+
run_event_store
|
|
5045
5081
|
)
|
|
5046
5082
|
SELECT $1, $2, $3, $4, $5, $6, $7, launch_payload.launch_json, coalesce(nullif(launch_payload.launch_json -> 'input', 'null'::jsonb), '{}'::jsonb), 'queued',
|
|
5047
5083
|
(SELECT task_id FROM task_spawn),
|
|
@@ -5054,7 +5090,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
5054
5090
|
'coordinatorInternalToken',
|
|
5055
5091
|
'vercelProtectionBypassToken'
|
|
5056
5092
|
]::text[],
|
|
5057
|
-
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
5093
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)},
|
|
5094
|
+
${newRunEventStoreSql('launch_payload.launch_json', options)}
|
|
5058
5095
|
FROM admission
|
|
5059
5096
|
CROSS JOIN launch_payload
|
|
5060
5097
|
WHERE NOT existing_run
|
|
@@ -5062,7 +5099,7 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
5062
5099
|
AND parent_accepting
|
|
5063
5100
|
AND NOT ${existingOnlySql}
|
|
5064
5101
|
ON CONFLICT (run_id) DO NOTHING
|
|
5065
|
-
RETURNING run_id, absurd_task_id, run_log_store
|
|
5102
|
+
RETURNING run_id, absurd_task_id, run_log_store, run_event_store
|
|
5066
5103
|
), run_recovery AS (
|
|
5067
5104
|
UPDATE ${tableName('runs', options)} AS runs
|
|
5068
5105
|
SET absurd_task_id = (SELECT task_id FROM task_spawn),
|
|
@@ -5111,6 +5148,7 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
5111
5148
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
5112
5149
|
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
5113
5150
|
'runLogStore', run_insert.run_log_store,
|
|
5151
|
+
'runEventStore', run_insert.run_event_store,
|
|
5114
5152
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
5115
5153
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
5116
5154
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -6113,7 +6151,7 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6113
6151
|
INSERT INTO ${tableName('runs', options)} (
|
|
6114
6152
|
run_id, org_id, play_id, play_name, graph_hash, runtime_profile,
|
|
6115
6153
|
artifact_storage_key, launch_json, input_json, status,
|
|
6116
|
-
absurd_task_id, admission_json, run_log_store
|
|
6154
|
+
absurd_task_id, admission_json, run_log_store, run_event_store
|
|
6117
6155
|
)
|
|
6118
6156
|
SELECT $5::text,
|
|
6119
6157
|
launch_payload.launch_json ->> 'orgId',
|
|
@@ -6135,12 +6173,13 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6135
6173
|
'coordinatorInternalToken',
|
|
6136
6174
|
'vercelProtectionBypassToken'
|
|
6137
6175
|
]::text[],
|
|
6138
|
-
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
6176
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)},
|
|
6177
|
+
${newRunEventStoreSql('launch_payload.launch_json', options)}
|
|
6139
6178
|
FROM occurrence
|
|
6140
6179
|
CROSS JOIN launch_payload
|
|
6141
6180
|
WHERE occurrence.schedule_current
|
|
6142
6181
|
AND NOT occurrence.existing_run
|
|
6143
|
-
RETURNING run_id, run_log_store
|
|
6182
|
+
RETURNING run_id, run_log_store, run_event_store
|
|
6144
6183
|
), occurrence_admitted AS MATERIALIZED (
|
|
6145
6184
|
SELECT true AS admitted
|
|
6146
6185
|
FROM occurrence
|
|
@@ -6188,6 +6227,7 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6188
6227
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
6189
6228
|
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
6190
6229
|
'runLogStore', run_insert.run_log_store,
|
|
6230
|
+
'runEventStore', run_insert.run_event_store,
|
|
6191
6231
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
6192
6232
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
6193
6233
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -18040,18 +18080,23 @@ export async function markPostgresSchedulerOutboxDeliveredRows(
|
|
|
18040
18080
|
}>(
|
|
18041
18081
|
`
|
|
18042
18082
|
WITH delivered AS (
|
|
18043
|
-
UPDATE ${tableName('outbox', options)}
|
|
18083
|
+
UPDATE ${tableName('outbox', options)} AS outbox
|
|
18044
18084
|
SET delivered_at = now(),
|
|
18045
18085
|
next_attempt_at = NULL,
|
|
18046
18086
|
last_error_json = NULL,
|
|
18047
18087
|
lease_owner = NULL,
|
|
18048
|
-
lease_expires_at = NULL
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18088
|
+
lease_expires_at = NULL,
|
|
18089
|
+
payload_json = CASE WHEN EXISTS (
|
|
18090
|
+
SELECT 1 FROM ${tableName('runs', options)} AS run
|
|
18091
|
+
WHERE run.run_id = outbox.run_id
|
|
18092
|
+
AND run.run_event_store = 'planet_scale'
|
|
18093
|
+
) THEN '{}'::jsonb ELSE outbox.payload_json END
|
|
18094
|
+
WHERE outbox.event_id = ANY($1::text[])
|
|
18095
|
+
AND outbox.delivered_at IS NULL
|
|
18096
|
+
AND ($2::text IS NULL OR outbox.lease_owner = $2)
|
|
18097
|
+
RETURNING outbox.event_id,
|
|
18098
|
+
outbox.event_type,
|
|
18099
|
+
(extract(epoch FROM (outbox.delivered_at - outbox.created_at)) * 1000)::float8
|
|
18055
18100
|
AS lag_ms
|
|
18056
18101
|
)
|
|
18057
18102
|
SELECT event_id,
|
|
@@ -19238,6 +19283,9 @@ async function projectPostgresSchedulerCreatedEvent(
|
|
|
19238
19283
|
...(value('runLogStore') === 'planet_scale'
|
|
19239
19284
|
? { runLogStore: 'planet_scale' as const }
|
|
19240
19285
|
: {}),
|
|
19286
|
+
...(value('runEventStore') === 'planet_scale'
|
|
19287
|
+
? { runEventStore: 'planet_scale' as const }
|
|
19288
|
+
: {}),
|
|
19241
19289
|
executionProfile: value('executionProfile'),
|
|
19242
19290
|
runtimeReleaseId: value('runtimeReleaseId'),
|
|
19243
19291
|
runtimeDeployVersion: value('runtimeDeployVersion'),
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts
CHANGED
|
@@ -41,7 +41,9 @@ const RUNTIME_EVENT_LOG_POSITION_EXPRESSION = `jsonb_set(
|
|
|
41
41
|
// Migration readiness tracks this projection body independently of scheduler
|
|
42
42
|
// wire and schema versions. Bump when a changed trigger must be reinstalled.
|
|
43
43
|
export const RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER =
|
|
44
|
-
'runtime-event-search-log-context-
|
|
44
|
+
'runtime-event-search-log-context-v11-unified-event-journal';
|
|
45
|
+
export const RUNTIME_EVENT_CANONICAL_RETENTION_INDEX_READY_MARKER =
|
|
46
|
+
'runtime-event-canonical-retention-v1';
|
|
45
47
|
export const RUNTIME_EVENTS_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
46
48
|
const MAX_SEARCH_WINDOW_MS = RUNTIME_EVENTS_RETENTION_MS;
|
|
47
49
|
const MAX_SEARCH_RESULTS = 200;
|
|
@@ -66,6 +68,8 @@ export type RuntimeSearchEvent = {
|
|
|
66
68
|
message: string | null;
|
|
67
69
|
receiptRef: string | null;
|
|
68
70
|
searchDoc: Record<string, unknown>;
|
|
71
|
+
/** Full accepted fact, read only through the explicit bounded payload path. */
|
|
72
|
+
eventPayload?: Record<string, unknown>;
|
|
69
73
|
};
|
|
70
74
|
|
|
71
75
|
export type RuntimeSearchCursor = { occurredAt: string; eventId: string };
|
|
@@ -80,6 +84,7 @@ export type RuntimeEventSearchInput = {
|
|
|
80
84
|
playName?: string;
|
|
81
85
|
kind?: string;
|
|
82
86
|
jsonpath?: string;
|
|
87
|
+
includePayloads?: boolean;
|
|
83
88
|
cursor?: RuntimeSearchCursor;
|
|
84
89
|
};
|
|
85
90
|
|
|
@@ -252,6 +257,7 @@ type EventRow = {
|
|
|
252
257
|
message: string | null;
|
|
253
258
|
receipt_ref: string | null;
|
|
254
259
|
search_doc: Record<string, unknown>;
|
|
260
|
+
event_payload: unknown;
|
|
255
261
|
};
|
|
256
262
|
|
|
257
263
|
function iso(value: Date | string): string {
|
|
@@ -357,7 +363,10 @@ export async function searchRuntimeEvents(
|
|
|
357
363
|
`(occurred_at, event_id) < ($${params.length - 1}::timestamptz, $${params.length})`,
|
|
358
364
|
);
|
|
359
365
|
}
|
|
366
|
+
const limitParam = params.length + 1;
|
|
360
367
|
params.push(input.limit + 1);
|
|
368
|
+
const payloadParam = params.length + 1;
|
|
369
|
+
params.push(input.includePayloads === true);
|
|
361
370
|
const result = await client.query<EventRow>(
|
|
362
371
|
`SELECT event_id, occurred_at, ingested_at, run_id, play_name, kind, level,
|
|
363
372
|
coalesce(message, CASE WHEN kind = 'log.appended' THEN
|
|
@@ -366,14 +375,27 @@ export async function searchRuntimeEvents(
|
|
|
366
375
|
'^' || chr(1) || 'prov:[^' || chr(1) || ']*' || chr(1), '')
|
|
367
376
|
ELSE log_body END, 2048)
|
|
368
377
|
END) AS message,
|
|
369
|
-
receipt_ref, search_doc
|
|
378
|
+
receipt_ref, search_doc,
|
|
379
|
+
CASE WHEN $${payloadParam}::boolean THEN event_payload END AS event_payload
|
|
370
380
|
FROM ${EVENTS_TABLE}
|
|
371
381
|
WHERE ${predicates.join(' AND ')}
|
|
372
382
|
ORDER BY occurred_at DESC, event_id DESC
|
|
373
|
-
LIMIT $${
|
|
383
|
+
LIMIT $${limitParam}::integer`,
|
|
374
384
|
params,
|
|
375
385
|
);
|
|
376
386
|
const hasMore = result.rows.length > input.limit;
|
|
387
|
+
if (
|
|
388
|
+
input.includePayloads === true &&
|
|
389
|
+
result.rows.some(
|
|
390
|
+
(row) =>
|
|
391
|
+
row.event_payload !== null &&
|
|
392
|
+
row.event_payload !== undefined &&
|
|
393
|
+
(typeof row.event_payload !== 'object' ||
|
|
394
|
+
Array.isArray(row.event_payload)),
|
|
395
|
+
)
|
|
396
|
+
) {
|
|
397
|
+
throw new Error('Runtime canonical event payload is not a JSON object.');
|
|
398
|
+
}
|
|
377
399
|
const events = result.rows.slice(0, input.limit).map((row) => ({
|
|
378
400
|
eventId: row.event_id,
|
|
379
401
|
occurredAt: iso(row.occurred_at),
|
|
@@ -384,6 +406,11 @@ export async function searchRuntimeEvents(
|
|
|
384
406
|
level: row.level,
|
|
385
407
|
message: row.message,
|
|
386
408
|
receiptRef: row.receipt_ref,
|
|
409
|
+
...(row.event_payload &&
|
|
410
|
+
typeof row.event_payload === 'object' &&
|
|
411
|
+
!Array.isArray(row.event_payload)
|
|
412
|
+
? { eventPayload: row.event_payload as Record<string, unknown> }
|
|
413
|
+
: {}),
|
|
387
414
|
searchDoc: {
|
|
388
415
|
...row.search_doc,
|
|
389
416
|
orgId: org,
|
|
@@ -561,10 +588,154 @@ export async function getRuntimeRunLogPage(
|
|
|
561
588
|
};
|
|
562
589
|
}
|
|
563
590
|
|
|
591
|
+
async function ensureRuntimeEventFetchReceiptFunction(
|
|
592
|
+
client: PostgresSchedulerQueryClient,
|
|
593
|
+
): Promise<void> {
|
|
594
|
+
await client.query(`
|
|
595
|
+
CREATE OR REPLACE FUNCTION runtime_observability.fetch_receipt(
|
|
596
|
+
p_scope text, p_org text, p_run text, p_ref uuid
|
|
597
|
+
) RETURNS jsonb
|
|
598
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
599
|
+
SET search_path = pg_catalog
|
|
600
|
+
AS $function$
|
|
601
|
+
DECLARE answer jsonb; receipt_key_value text;
|
|
602
|
+
BEGIN
|
|
603
|
+
IF p_scope !~ '^[a-z_][a-z0-9_]{0,62}$'
|
|
604
|
+
OR p_org IS NULL OR p_run IS NULL OR p_ref IS NULL
|
|
605
|
+
OR p_org IS DISTINCT FROM current_setting('app.org_id', true)
|
|
606
|
+
OR p_scope IS DISTINCT FROM current_setting('app.runtime_scope', true)
|
|
607
|
+
THEN RETURN NULL; END IF;
|
|
608
|
+
SELECT event.receipt_key INTO receipt_key_value
|
|
609
|
+
FROM runtime_observability.events AS event
|
|
610
|
+
WHERE event.runtime_scope = p_scope AND event.org_id = p_org
|
|
611
|
+
AND event.run_id = p_run AND event.receipt_ref = p_ref
|
|
612
|
+
AND event.ingested_at >= clock_timestamp() - interval '7 days';
|
|
613
|
+
IF receipt_key_value IS NULL THEN RETURN NULL; END IF;
|
|
614
|
+
EXECUTE format(
|
|
615
|
+
'SELECT jsonb_build_object(''receiptKey'', k, ''status'', status, ''output'', output_json, ''outputRef'', output_ref, ''error'', error, ''errorPayload'', error_payload) FROM %I.receipts WHERE org_id = $1 AND run_id = $2 AND k = $3 AND status IN (''completed'', ''failed'', ''skipped'')',
|
|
616
|
+
p_scope
|
|
617
|
+
) INTO answer USING p_org, p_run, receipt_key_value;
|
|
618
|
+
RETURN answer;
|
|
619
|
+
END
|
|
620
|
+
$function$
|
|
621
|
+
`);
|
|
622
|
+
await client.query(
|
|
623
|
+
`REVOKE ALL ON FUNCTION runtime_observability.fetch_receipt(text,text,text,uuid) FROM PUBLIC`,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
564
627
|
/** Attach only on the explicit migration/bootstrap path, never on a hot read. */
|
|
628
|
+
export async function hasRuntimeEventSearchStorageShape(
|
|
629
|
+
client: PostgresSchedulerQueryClient,
|
|
630
|
+
): Promise<boolean> {
|
|
631
|
+
const result = await client.query<{
|
|
632
|
+
ready: boolean;
|
|
633
|
+
events_policy: string | null;
|
|
634
|
+
cursors_policy: string | null;
|
|
635
|
+
}>(
|
|
636
|
+
`SELECT to_regclass('runtime_observability.events') IS NOT NULL
|
|
637
|
+
AND to_regclass('runtime_observability.run_log_cursors') IS NOT NULL
|
|
638
|
+
AND to_regclass('runtime_observability.runtime_event_order_seq') IS NOT NULL
|
|
639
|
+
AND to_regprocedure('runtime_observability.fetch_receipt(text,text,text,uuid)') IS NOT NULL
|
|
640
|
+
AND (SELECT count(*) = 19 FROM pg_attribute
|
|
641
|
+
WHERE attrelid = to_regclass('runtime_observability.events')
|
|
642
|
+
AND attname = ANY(ARRAY[
|
|
643
|
+
'runtime_scope', 'org_id', 'event_id', 'event_seq', 'event_payload',
|
|
644
|
+
'log_seq', 'occurred_at', 'ingested_at', 'run_id', 'play_name',
|
|
645
|
+
'kind', 'level', 'message', 'log_body', 'receipt_ref', 'receipt_key',
|
|
646
|
+
'source_digest', 'search_doc', 'log_text_digest'
|
|
647
|
+
]::text[]) AND NOT attisdropped)
|
|
648
|
+
AND (SELECT count(*) = 5 FROM pg_attribute
|
|
649
|
+
WHERE attrelid = to_regclass('runtime_observability.run_log_cursors')
|
|
650
|
+
AND attname = ANY(ARRAY[
|
|
651
|
+
'runtime_scope', 'org_id', 'run_id', 'last_seq', 'updated_at'
|
|
652
|
+
]::text[]) AND NOT attisdropped)
|
|
653
|
+
AND EXISTS (SELECT 1 FROM pg_class
|
|
654
|
+
WHERE oid = to_regclass('runtime_observability.events') AND relrowsecurity)
|
|
655
|
+
AND EXISTS (SELECT 1 FROM pg_class
|
|
656
|
+
WHERE oid = to_regclass('runtime_observability.run_log_cursors') AND relrowsecurity)
|
|
657
|
+
AND NOT EXISTS (SELECT 1 FROM pg_namespace AS namespace,
|
|
658
|
+
LATERAL aclexplode(coalesce(namespace.nspacl,
|
|
659
|
+
acldefault('n', namespace.nspowner))) AS privilege
|
|
660
|
+
WHERE namespace.oid = to_regnamespace('runtime_observability')
|
|
661
|
+
AND privilege.grantee = 0)
|
|
662
|
+
AND NOT EXISTS (SELECT 1 FROM pg_class AS relation,
|
|
663
|
+
LATERAL aclexplode(coalesce(relation.relacl,
|
|
664
|
+
acldefault('r', relation.relowner))) AS privilege
|
|
665
|
+
WHERE relation.oid IN (
|
|
666
|
+
to_regclass('runtime_observability.events'),
|
|
667
|
+
to_regclass('runtime_observability.run_log_cursors')
|
|
668
|
+
) AND privilege.grantee = 0)
|
|
669
|
+
AND NOT EXISTS (SELECT 1 FROM pg_class AS sequence_relation,
|
|
670
|
+
LATERAL aclexplode(coalesce(sequence_relation.relacl,
|
|
671
|
+
acldefault('S', sequence_relation.relowner))) AS privilege
|
|
672
|
+
WHERE sequence_relation.oid =
|
|
673
|
+
to_regclass('runtime_observability.runtime_event_order_seq')
|
|
674
|
+
AND privilege.grantee = 0)
|
|
675
|
+
AND NOT EXISTS (SELECT 1 FROM pg_proc AS receipt_function,
|
|
676
|
+
LATERAL aclexplode(coalesce(receipt_function.proacl,
|
|
677
|
+
acldefault('f', receipt_function.proowner))) AS privilege
|
|
678
|
+
WHERE receipt_function.oid = to_regprocedure(
|
|
679
|
+
'runtime_observability.fetch_receipt(text,text,text,uuid)')
|
|
680
|
+
AND privilege.grantee = 0)
|
|
681
|
+
AND EXISTS (SELECT 1 FROM pg_policy
|
|
682
|
+
WHERE polrelid = to_regclass('runtime_observability.events')
|
|
683
|
+
AND polname = 'runtime_events_read_scope' AND polcmd = 'r'
|
|
684
|
+
AND polroles @> ARRAY[0]::oid[] AND polqual IS NOT NULL
|
|
685
|
+
AND polwithcheck IS NULL)
|
|
686
|
+
AND EXISTS (SELECT 1 FROM pg_policy
|
|
687
|
+
WHERE polrelid = to_regclass('runtime_observability.run_log_cursors')
|
|
688
|
+
AND polname = 'runtime_run_log_read_scope' AND polcmd = 'r'
|
|
689
|
+
AND polroles @> ARRAY[0]::oid[] AND polqual IS NOT NULL
|
|
690
|
+
AND polwithcheck IS NULL)
|
|
691
|
+
AND (SELECT count(*) = 6 FROM pg_index AS index_row
|
|
692
|
+
WHERE index_row.indrelid = to_regclass('runtime_observability.events')
|
|
693
|
+
AND index_row.indisready AND index_row.indisvalid
|
|
694
|
+
AND index_row.indexrelid = ANY(ARRAY[
|
|
695
|
+
to_regclass('runtime_observability.runtime_events_run_log_seq_idx'),
|
|
696
|
+
to_regclass('runtime_observability.runtime_events_org_time_idx'),
|
|
697
|
+
to_regclass('runtime_observability.runtime_events_run_time_idx'),
|
|
698
|
+
to_regclass('runtime_observability.runtime_events_doc_idx'),
|
|
699
|
+
to_regclass('runtime_observability.runtime_events_retention_idx'),
|
|
700
|
+
to_regclass('runtime_observability.runtime_events_receipt_ref_idx')
|
|
701
|
+
]::oid[]))
|
|
702
|
+
AND to_regclass('runtime_observability.runtime_run_log_cursors_retention_idx') IS NULL
|
|
703
|
+
AS ready,
|
|
704
|
+
(SELECT pg_get_expr(polqual, polrelid) FROM pg_policy
|
|
705
|
+
WHERE polrelid = to_regclass('runtime_observability.events')
|
|
706
|
+
AND polname = 'runtime_events_read_scope') AS events_policy,
|
|
707
|
+
(SELECT pg_get_expr(polqual, polrelid) FROM pg_policy
|
|
708
|
+
WHERE polrelid = to_regclass('runtime_observability.run_log_cursors')
|
|
709
|
+
AND polname = 'runtime_run_log_read_scope') AS cursors_policy`,
|
|
710
|
+
);
|
|
711
|
+
const shape = result.rows[0];
|
|
712
|
+
const expectedPolicy = `org_id = current_setting('app.org_id', true)
|
|
713
|
+
AND runtime_scope = current_setting('app.runtime_scope', true)`;
|
|
714
|
+
return (
|
|
715
|
+
shape?.ready === true &&
|
|
716
|
+
shape.events_policy !== null &&
|
|
717
|
+
shape.events_policy !== undefined &&
|
|
718
|
+
normalizedRetentionPredicate(shape.events_policy) ===
|
|
719
|
+
normalizedRetentionPredicate(expectedPolicy) &&
|
|
720
|
+
shape.cursors_policy !== null &&
|
|
721
|
+
shape.cursors_policy !== undefined &&
|
|
722
|
+
normalizedRetentionPredicate(shape.cursors_policy) ===
|
|
723
|
+
normalizedRetentionPredicate(expectedPolicy)
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
565
727
|
export async function ensureRuntimeEventSearchSchema(
|
|
566
728
|
client: PostgresSchedulerQueryClient,
|
|
567
729
|
): Promise<void> {
|
|
730
|
+
// Projection marker refreshes only replace function bodies and triggers.
|
|
731
|
+
// Replaying ADD COLUMN IF NOT EXISTS, DROP POLICY, or CREATE INDEX against
|
|
732
|
+
// this busy journal unnecessarily takes heavyweight locks even when the
|
|
733
|
+
// reader-first storage expansion is already complete.
|
|
734
|
+
if (await hasRuntimeEventSearchStorageShape(client)) {
|
|
735
|
+
await ensureRuntimeEventFetchReceiptFunction(client);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
568
739
|
await client.query('CREATE SCHEMA IF NOT EXISTS runtime_observability');
|
|
569
740
|
await client.query(`
|
|
570
741
|
CREATE TABLE IF NOT EXISTS runtime_observability.events (
|
|
@@ -663,37 +834,7 @@ export async function ensureRuntimeEventSearchSchema(
|
|
|
663
834
|
)`);
|
|
664
835
|
await client.query(`REVOKE ALL ON SCHEMA runtime_observability FROM PUBLIC`);
|
|
665
836
|
await client.query(`REVOKE ALL ON runtime_observability.events FROM PUBLIC`);
|
|
666
|
-
await client
|
|
667
|
-
CREATE OR REPLACE FUNCTION runtime_observability.fetch_receipt(
|
|
668
|
-
p_scope text, p_org text, p_run text, p_ref uuid
|
|
669
|
-
) RETURNS jsonb
|
|
670
|
-
LANGUAGE plpgsql SECURITY DEFINER
|
|
671
|
-
SET search_path = pg_catalog
|
|
672
|
-
AS $function$
|
|
673
|
-
DECLARE answer jsonb; receipt_key_value text;
|
|
674
|
-
BEGIN
|
|
675
|
-
IF p_scope !~ '^[a-z_][a-z0-9_]{0,62}$'
|
|
676
|
-
OR p_org IS NULL OR p_run IS NULL OR p_ref IS NULL
|
|
677
|
-
OR p_org IS DISTINCT FROM current_setting('app.org_id', true)
|
|
678
|
-
OR p_scope IS DISTINCT FROM current_setting('app.runtime_scope', true)
|
|
679
|
-
THEN RETURN NULL; END IF;
|
|
680
|
-
SELECT event.receipt_key INTO receipt_key_value
|
|
681
|
-
FROM runtime_observability.events AS event
|
|
682
|
-
WHERE event.runtime_scope = p_scope AND event.org_id = p_org
|
|
683
|
-
AND event.run_id = p_run AND event.receipt_ref = p_ref
|
|
684
|
-
AND event.ingested_at >= clock_timestamp() - interval '7 days';
|
|
685
|
-
IF receipt_key_value IS NULL THEN RETURN NULL; END IF;
|
|
686
|
-
EXECUTE format(
|
|
687
|
-
'SELECT jsonb_build_object(''receiptKey'', k, ''status'', status, ''output'', output_json, ''outputRef'', output_ref, ''error'', error, ''errorPayload'', error_payload) FROM %I.receipts WHERE org_id = $1 AND run_id = $2 AND k = $3 AND status IN (''completed'', ''failed'', ''skipped'')',
|
|
688
|
-
p_scope
|
|
689
|
-
) INTO answer USING p_org, p_run, receipt_key_value;
|
|
690
|
-
RETURN answer;
|
|
691
|
-
END
|
|
692
|
-
$function$
|
|
693
|
-
`);
|
|
694
|
-
await client.query(
|
|
695
|
-
`REVOKE ALL ON FUNCTION runtime_observability.fetch_receipt(text,text,text,uuid) FROM PUBLIC`,
|
|
696
|
-
);
|
|
837
|
+
await ensureRuntimeEventFetchReceiptFunction(client);
|
|
697
838
|
}
|
|
698
839
|
|
|
699
840
|
/** The exact provisioned PlanetScale reader role; never grant to a guessed role. */
|
|
@@ -710,7 +851,8 @@ export async function grantRuntimeEventReader(
|
|
|
710
851
|
await client.query(`GRANT USAGE ON SCHEMA runtime_observability TO ${role}`);
|
|
711
852
|
await client.query(`GRANT SELECT (
|
|
712
853
|
runtime_scope, org_id, event_id, occurred_at, ingested_at, run_id,
|
|
713
|
-
log_seq, log_body, play_name, kind, level, message, receipt_ref, search_doc
|
|
854
|
+
log_seq, log_body, play_name, kind, level, message, receipt_ref, search_doc,
|
|
855
|
+
event_payload
|
|
714
856
|
) ON runtime_observability.events TO ${role}`);
|
|
715
857
|
await client.query(`GRANT SELECT (runtime_scope, org_id, run_id, last_seq)
|
|
716
858
|
ON runtime_observability.run_log_cursors TO ${role}`);
|
|
@@ -736,6 +878,7 @@ export async function hasRuntimeEventReaderGrant(
|
|
|
736
878
|
AND has_column_privilege(roles.oid, 'runtime_observability.events', 'event_id', 'SELECT')
|
|
737
879
|
AND has_column_privilege(roles.oid, 'runtime_observability.events', 'log_seq', 'SELECT')
|
|
738
880
|
AND has_column_privilege(roles.oid, 'runtime_observability.events', 'log_body', 'SELECT')
|
|
881
|
+
AND has_column_privilege(roles.oid, 'runtime_observability.events', 'event_payload', 'SELECT')
|
|
739
882
|
AND NOT has_column_privilege(roles.oid, 'runtime_observability.events', 'receipt_key', 'SELECT')
|
|
740
883
|
AND NOT has_table_privilege(roles.oid, 'runtime_observability.events', 'INSERT')
|
|
741
884
|
AND NOT has_any_column_privilege(roles.oid, 'runtime_observability.events', 'INSERT')
|
|
@@ -807,7 +950,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
807
950
|
await client.query(`
|
|
808
951
|
CREATE OR REPLACE FUNCTION ${schema}.search_receipt_event() RETURNS trigger
|
|
809
952
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
810
|
-
DECLARE play text; ref uuid;
|
|
953
|
+
DECLARE play text; ref uuid; canonical_events boolean;
|
|
811
954
|
BEGIN
|
|
812
955
|
IF NEW.status NOT IN ('completed', 'failed', 'skipped') THEN RETURN NEW; END IF;
|
|
813
956
|
IF TG_OP = 'UPDATE' THEN
|
|
@@ -820,17 +963,26 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
820
963
|
IF NEW.run_id IS NULL OR NEW.org_id IS NULL THEN
|
|
821
964
|
RAISE EXCEPTION 'Terminal receipt search projection requires run and organization';
|
|
822
965
|
END IF;
|
|
823
|
-
|
|
966
|
+
-- Serialize event ordering with the same per-run lock as outbox facts.
|
|
967
|
+
PERFORM pg_advisory_xact_lock(hashtext(${quoteLiteral(scope)}), hashtext(NEW.run_id));
|
|
968
|
+
SELECT play_name, run_event_store = 'planet_scale'
|
|
969
|
+
INTO play, canonical_events FROM ${schema}.runs
|
|
824
970
|
WHERE run_id = NEW.run_id AND org_id = NEW.org_id;
|
|
825
971
|
IF play IS NULL THEN
|
|
826
972
|
RAISE EXCEPTION 'Terminal receipt search projection has no matching run';
|
|
827
973
|
END IF;
|
|
828
974
|
ref := gen_random_uuid();
|
|
829
975
|
INSERT INTO runtime_observability.events (
|
|
830
|
-
runtime_scope, org_id, event_id,
|
|
831
|
-
kind, receipt_ref, receipt_key, search_doc
|
|
976
|
+
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
977
|
+
occurred_at, run_id, play_name, kind, receipt_ref, receipt_key, search_doc
|
|
832
978
|
) VALUES (
|
|
833
979
|
${quoteLiteral(scope)}, NEW.org_id, 'receipt:' || ref::text,
|
|
980
|
+
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
981
|
+
CASE WHEN canonical_events THEN jsonb_build_object(
|
|
982
|
+
'type', 'receipt.' || NEW.status, 'runId', NEW.run_id,
|
|
983
|
+
'occurredAt', floor(extract(epoch FROM NEW.updated_at) * 1000)::bigint,
|
|
984
|
+
'source', 'receipt', 'status', NEW.status
|
|
985
|
+
) END,
|
|
834
986
|
NEW.updated_at, NEW.run_id, play, 'receipt.' || NEW.status,
|
|
835
987
|
ref, NEW.k, jsonb_strip_nulls(jsonb_build_object(
|
|
836
988
|
'kind', 'receipt.' || NEW.status, 'status', NEW.status,
|
|
@@ -851,7 +1003,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
851
1003
|
await client.query(`
|
|
852
1004
|
CREATE OR REPLACE FUNCTION ${schema}.project_outbox_event(observation ${schema}.outbox) RETURNS void
|
|
853
1005
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
854
|
-
DECLARE run_org text; play text; compact_logs boolean; run_status text; item jsonb; line jsonb; event_batch jsonb;
|
|
1006
|
+
DECLARE run_org text; play text; compact_logs boolean; canonical_events boolean; run_status text; item jsonb; line jsonb; event_batch jsonb;
|
|
855
1007
|
ordinal bigint; line_ordinal bigint; kind text; at_time timestamptz;
|
|
856
1008
|
doc jsonb; line_doc jsonb; line_context jsonb;
|
|
857
1009
|
eid text; existing_digest text; digest text; assigned_seq bigint; terminal_progress_identity boolean;
|
|
@@ -868,8 +1020,9 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
868
1020
|
-- scheduler's Run/attempt row-lock order. In particular, old completion
|
|
869
1021
|
-- writers lock attempt before Run; this recorder must not invert that.
|
|
870
1022
|
PERFORM pg_advisory_xact_lock(hashtext(${quoteLiteral(scope)}), hashtext(observation.run_id));
|
|
871
|
-
SELECT org_id, play_name, run_log_store = 'planet_scale', status
|
|
872
|
-
|
|
1023
|
+
SELECT org_id, play_name, run_log_store = 'planet_scale', status,
|
|
1024
|
+
run_event_store = 'planet_scale'
|
|
1025
|
+
INTO run_org, play, compact_logs, run_status, canonical_events
|
|
873
1026
|
FROM ${schema}.runs WHERE run_id = observation.run_id;
|
|
874
1027
|
IF run_org IS NULL OR play IS NULL THEN
|
|
875
1028
|
RAISE EXCEPTION 'Outbox search projection has no matching run';
|
|
@@ -940,7 +1093,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
940
1093
|
'run.completed', 'run.failed', 'run.cancelled',
|
|
941
1094
|
'step.started', 'step.progress', 'step.completed',
|
|
942
1095
|
'step.failed', 'step.skipped', 'log.appended',
|
|
943
|
-
'dataset.lifecycle', 'activity.observed'
|
|
1096
|
+
'dataset.lifecycle', 'activity.observed', 'work.counters'
|
|
944
1097
|
) THEN
|
|
945
1098
|
kind := 'runtime.event';
|
|
946
1099
|
END IF;
|
|
@@ -1161,10 +1314,20 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1161
1314
|
RAISE EXCEPTION 'Run log ingress is closed after terminal acceptance';
|
|
1162
1315
|
END IF;
|
|
1163
1316
|
INSERT INTO runtime_observability.events (
|
|
1164
|
-
runtime_scope, org_id, event_id,
|
|
1165
|
-
|
|
1317
|
+
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
1318
|
+
occurred_at, run_id, play_name, kind, level, message, log_body,
|
|
1319
|
+
source_digest, log_text_digest, search_doc
|
|
1166
1320
|
) VALUES (
|
|
1167
|
-
${quoteLiteral(scope)}, run_org, eid,
|
|
1321
|
+
${quoteLiteral(scope)}, run_org, eid,
|
|
1322
|
+
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
1323
|
+
CASE WHEN canonical_events THEN jsonb_strip_nulls(
|
|
1324
|
+
(item - 'lines' - 'lineContexts') || jsonb_build_object(
|
|
1325
|
+
'type', 'log.appended', 'runId', observation.run_id,
|
|
1326
|
+
'occurredAt', floor(extract(epoch FROM at_time) * 1000)::bigint,
|
|
1327
|
+
'lineOrdinal', line_ordinal,
|
|
1328
|
+
'context', line_context
|
|
1329
|
+
)) END,
|
|
1330
|
+
at_time, observation.run_id, play,
|
|
1168
1331
|
'log.appended', NULL, CASE WHEN compact_logs THEN NULL ELSE left(line #>> '{}', 2048) END,
|
|
1169
1332
|
line #>> '{}', digest,
|
|
1170
1333
|
encode(sha256(convert_to(btrim(line #>> '{}'), 'UTF8')), 'hex'), line_doc
|
|
@@ -1193,10 +1356,12 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1193
1356
|
digest := CASE WHEN terminal_progress_identity
|
|
1194
1357
|
THEN encode(sha256(convert_to(item::text, 'UTF8')), 'hex') ELSE NULL END;
|
|
1195
1358
|
INSERT INTO runtime_observability.events (
|
|
1196
|
-
runtime_scope, org_id, event_id,
|
|
1197
|
-
kind, source_digest, search_doc
|
|
1359
|
+
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
1360
|
+
occurred_at, run_id, play_name, kind, source_digest, search_doc
|
|
1198
1361
|
) VALUES (
|
|
1199
1362
|
${quoteLiteral(scope)}, run_org, eid,
|
|
1363
|
+
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
1364
|
+
CASE WHEN canonical_events THEN item END,
|
|
1200
1365
|
at_time, observation.run_id, play, kind, digest, doc
|
|
1201
1366
|
) ON CONFLICT DO NOTHING;
|
|
1202
1367
|
IF NOT FOUND AND terminal_progress_identity THEN
|
|
@@ -1215,10 +1380,22 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1215
1380
|
) AND NOT (observation.event_type IN ('run.waiting', 'run.resumed')
|
|
1216
1381
|
AND observation.payload_json->>'kind' = 'detached_runner') THEN
|
|
1217
1382
|
INSERT INTO runtime_observability.events (
|
|
1218
|
-
runtime_scope, org_id, event_id,
|
|
1219
|
-
kind, search_doc
|
|
1383
|
+
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
1384
|
+
occurred_at, run_id, play_name, kind, search_doc
|
|
1220
1385
|
) VALUES (
|
|
1221
|
-
${quoteLiteral(scope)}, run_org, observation.event_id,
|
|
1386
|
+
${quoteLiteral(scope)}, run_org, observation.event_id,
|
|
1387
|
+
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
1388
|
+
CASE WHEN canonical_events THEN jsonb_strip_nulls(jsonb_build_object(
|
|
1389
|
+
'type', observation.event_type, 'runId', observation.run_id,
|
|
1390
|
+
'occurredAt', floor(extract(epoch FROM observation.created_at) * 1000)::bigint,
|
|
1391
|
+
'source', 'system', 'playName', play,
|
|
1392
|
+
'status', observation.payload_json->>'status',
|
|
1393
|
+
'error', observation.payload_json->>'error',
|
|
1394
|
+
'waitKind', observation.payload_json->>'waitKind',
|
|
1395
|
+
'waitUntil', observation.payload_json->'waitUntil',
|
|
1396
|
+
'resultSummary', observation.payload_json->'resultSummary'
|
|
1397
|
+
)) END,
|
|
1398
|
+
observation.created_at,
|
|
1222
1399
|
observation.run_id, play, observation.event_type, jsonb_build_object('kind', observation.event_type, 'source', 'system')
|
|
1223
1400
|
) ON CONFLICT DO NOTHING;
|
|
1224
1401
|
END IF;
|
|
@@ -1329,10 +1506,18 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1329
1506
|
line_doc := line_doc || jsonb_build_object('context', line_context);
|
|
1330
1507
|
END IF;
|
|
1331
1508
|
INSERT INTO runtime_observability.events (
|
|
1332
|
-
runtime_scope, org_id, event_id,
|
|
1333
|
-
|
|
1509
|
+
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
1510
|
+
occurred_at, run_id, play_name, kind, level, message, log_body,
|
|
1511
|
+
source_digest, log_text_digest, search_doc
|
|
1334
1512
|
) VALUES (
|
|
1335
1513
|
${quoteLiteral(scope)}, run_org, observation.event_id || ':log:' || line_ordinal,
|
|
1514
|
+
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
1515
|
+
CASE WHEN canonical_events THEN jsonb_strip_nulls(jsonb_build_object(
|
|
1516
|
+
'type', 'log.appended', 'runId', observation.run_id,
|
|
1517
|
+
'occurredAt', floor(extract(epoch FROM observation.created_at) * 1000)::bigint,
|
|
1518
|
+
'source', 'terminal', 'producerAttempt', observation.payload_json->'producerAttempt',
|
|
1519
|
+
'lineOrdinal', line_ordinal, 'context', line_context
|
|
1520
|
+
)) END,
|
|
1336
1521
|
observation.created_at, observation.run_id, play, 'log.appended', NULL,
|
|
1337
1522
|
CASE WHEN compact_logs THEN NULL ELSE left(regexp_replace(line #>> '{}',
|
|
1338
1523
|
'^' || chr(1) || 'prov:[^' || chr(1) || ']*' || chr(1), ''), 2048) END,
|
|
@@ -1380,6 +1565,92 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1380
1565
|
WHERE coalesce(item.value->>'type', item.value->>'key') IS DISTINCT FROM 'log.appended'
|
|
1381
1566
|
$function$
|
|
1382
1567
|
`);
|
|
1568
|
+
await client.query(`
|
|
1569
|
+
CREATE OR REPLACE FUNCTION ${schema}.convex_projection_event(value jsonb) RETURNS jsonb
|
|
1570
|
+
LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $function$
|
|
1571
|
+
WITH event AS (
|
|
1572
|
+
SELECT coalesce(value->>'type', value->>'key') AS kind
|
|
1573
|
+
)
|
|
1574
|
+
SELECT CASE
|
|
1575
|
+
WHEN event.kind IN ('rollup.membership.added', 'rollup.membership.removed')
|
|
1576
|
+
AND value->>'opaque' = 'true' THEN jsonb_strip_nulls(jsonb_build_object(
|
|
1577
|
+
'type', event.kind, 'runId', value->'runId',
|
|
1578
|
+
'occurredAt', value->'occurredAt', 'source', value->'source',
|
|
1579
|
+
'opaque', true, 'payload', value->'payload'))
|
|
1580
|
+
WHEN event.kind IN (
|
|
1581
|
+
'run.created', 'run.started', 'run.waiting', 'run.resumed',
|
|
1582
|
+
'run.completed', 'run.failed', 'run.cancelled',
|
|
1583
|
+
'step.started', 'step.progress', 'step.completed',
|
|
1584
|
+
'step.failed', 'step.skipped', 'dataset.lifecycle',
|
|
1585
|
+
'activity.observed', 'work.counters'
|
|
1586
|
+
) THEN jsonb_strip_nulls(jsonb_build_object(
|
|
1587
|
+
'type', event.kind, 'runId', value->'runId',
|
|
1588
|
+
'occurredAt', value->'occurredAt', 'source', value->'source'
|
|
1589
|
+
) || CASE event.kind
|
|
1590
|
+
WHEN 'run.created' THEN jsonb_build_object(
|
|
1591
|
+
'playName', value->'playName', 'status', value->'status',
|
|
1592
|
+
'runtimeBackend', value->'runtimeBackend')
|
|
1593
|
+
WHEN 'run.started' THEN jsonb_build_object(
|
|
1594
|
+
'playName', value->'playName', 'runtimeBackend', value->'runtimeBackend')
|
|
1595
|
+
WHEN 'run.waiting' THEN jsonb_build_object(
|
|
1596
|
+
'waitKind', value->'waitKind', 'waitUntil', value->'waitUntil')
|
|
1597
|
+
WHEN 'run.completed' THEN jsonb_build_object(
|
|
1598
|
+
'result', value->'result', 'resultSummary', value->'resultSummary',
|
|
1599
|
+
'terminalWorkCounterSegment', value->'terminalWorkCounterSegment')
|
|
1600
|
+
WHEN 'run.failed' THEN jsonb_build_object(
|
|
1601
|
+
'error', value->'error', 'result', value->'result',
|
|
1602
|
+
'resultSummary', value->'resultSummary',
|
|
1603
|
+
'terminalWorkCounterSegment', value->'terminalWorkCounterSegment')
|
|
1604
|
+
WHEN 'run.cancelled' THEN jsonb_build_object(
|
|
1605
|
+
'error', value->'error', 'result', value->'result')
|
|
1606
|
+
WHEN 'step.started' THEN jsonb_build_object(
|
|
1607
|
+
'stepId', value->'stepId', 'label', value->'label',
|
|
1608
|
+
'kind', value->'kind', 'artifactTableNamespace', value->'artifactTableNamespace')
|
|
1609
|
+
WHEN 'step.progress' THEN jsonb_build_object(
|
|
1610
|
+
'stepId', value->'stepId', 'label', value->'label',
|
|
1611
|
+
'kind', value->'kind', 'status', value->'status', 'progress', value->'progress')
|
|
1612
|
+
WHEN 'step.completed' THEN jsonb_build_object(
|
|
1613
|
+
'stepId', value->'stepId', 'label', value->'label',
|
|
1614
|
+
'kind', value->'kind', 'artifactTableNamespace', value->'artifactTableNamespace')
|
|
1615
|
+
WHEN 'step.failed' THEN jsonb_build_object(
|
|
1616
|
+
'stepId', value->'stepId', 'label', value->'label', 'kind', value->'kind',
|
|
1617
|
+
'error', value->'error', 'artifactTableNamespace', value->'artifactTableNamespace')
|
|
1618
|
+
WHEN 'step.skipped' THEN jsonb_build_object(
|
|
1619
|
+
'stepId', value->'stepId', 'label', value->'label',
|
|
1620
|
+
'kind', value->'kind', 'artifactTableNamespace', value->'artifactTableNamespace')
|
|
1621
|
+
WHEN 'dataset.lifecycle' THEN jsonb_build_object(
|
|
1622
|
+
'datasetId', value->'datasetId', 'path', value->'path',
|
|
1623
|
+
'tableNamespace', value->'tableNamespace', 'phase', value->'phase',
|
|
1624
|
+
'persistedRows', value->'persistedRows', 'succeededRows', value->'succeededRows',
|
|
1625
|
+
'failedRows', value->'failedRows', 'complete', value->'complete',
|
|
1626
|
+
'bornFrom', value->'bornFrom')
|
|
1627
|
+
WHEN 'activity.observed' THEN jsonb_build_object(
|
|
1628
|
+
'observation', value->'observation')
|
|
1629
|
+
WHEN 'work.counters' THEN jsonb_build_object(
|
|
1630
|
+
'attempt', value->'attempt', 'invocationSeq', value->'invocationSeq',
|
|
1631
|
+
'segmentId', value->'segmentId', 'logicalCalls', value->'logicalCalls',
|
|
1632
|
+
'providerRequests', value->'providerRequests', 'provider429s', value->'provider429s',
|
|
1633
|
+
'final', value->'final')
|
|
1634
|
+
ELSE '{}'::jsonb
|
|
1635
|
+
END)
|
|
1636
|
+
ELSE NULL
|
|
1637
|
+
END
|
|
1638
|
+
FROM event
|
|
1639
|
+
$function$
|
|
1640
|
+
`);
|
|
1641
|
+
await client.query(`
|
|
1642
|
+
CREATE OR REPLACE FUNCTION ${schema}.convex_projection_events(batch jsonb) RETURNS jsonb
|
|
1643
|
+
LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $function$
|
|
1644
|
+
WITH projected AS (
|
|
1645
|
+
SELECT item.ordinality,
|
|
1646
|
+
${schema}.convex_projection_event(item.value) AS value
|
|
1647
|
+
FROM jsonb_array_elements(batch) WITH ORDINALITY AS item(value, ordinality)
|
|
1648
|
+
)
|
|
1649
|
+
SELECT coalesce(jsonb_agg(projected.value ORDER BY projected.ordinality)
|
|
1650
|
+
FILTER (WHERE projected.value IS NOT NULL), '[]'::jsonb)
|
|
1651
|
+
FROM projected
|
|
1652
|
+
$function$
|
|
1653
|
+
`);
|
|
1383
1654
|
await client.query(`
|
|
1384
1655
|
CREATE OR REPLACE FUNCTION ${schema}.without_runtime_log_tail(value jsonb) RETURNS jsonb
|
|
1385
1656
|
LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $function$
|
|
@@ -1408,14 +1679,15 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1408
1679
|
await client.query(`
|
|
1409
1680
|
CREATE OR REPLACE FUNCTION ${schema}.search_outbox_event() RETURNS trigger
|
|
1410
1681
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
1411
|
-
DECLARE compact_logs boolean;
|
|
1682
|
+
DECLARE compact_logs boolean; canonical_events boolean;
|
|
1412
1683
|
BEGIN
|
|
1413
1684
|
-- Project the original validated observation before replacing its
|
|
1414
1685
|
-- transport body. Failure rolls back the outbox and canonical rows.
|
|
1415
1686
|
PERFORM ${schema}.project_outbox_event(NEW);
|
|
1416
|
-
SELECT run_log_store = 'planet_scale'
|
|
1687
|
+
SELECT run_log_store = 'planet_scale', run_event_store = 'planet_scale'
|
|
1688
|
+
INTO compact_logs, canonical_events
|
|
1417
1689
|
FROM ${schema}.runs WHERE run_id = NEW.run_id;
|
|
1418
|
-
IF compact_logs THEN
|
|
1690
|
+
IF compact_logs OR canonical_events THEN
|
|
1419
1691
|
NEW.payload_json := NEW.payload_json - 'terminalLogLines' - 'terminalLogContexts';
|
|
1420
1692
|
IF NEW.event_type = 'run.events' THEN
|
|
1421
1693
|
NEW.payload_json := jsonb_set(NEW.payload_json, '{events}',
|
|
@@ -1435,6 +1707,16 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1435
1707
|
${schema}.without_runtime_log_tail(NEW.payload_json->'output'));
|
|
1436
1708
|
END IF;
|
|
1437
1709
|
END IF;
|
|
1710
|
+
IF canonical_events THEN
|
|
1711
|
+
IF NEW.event_type = 'run.events' THEN
|
|
1712
|
+
NEW.payload_json := jsonb_set(NEW.payload_json, '{events}',
|
|
1713
|
+
${schema}.convex_projection_events(NEW.payload_json->'events'));
|
|
1714
|
+
END IF;
|
|
1715
|
+
IF NEW.payload_json ? 'terminalProgressEvents' THEN
|
|
1716
|
+
NEW.payload_json := jsonb_set(NEW.payload_json, '{terminalProgressEvents}',
|
|
1717
|
+
${schema}.convex_projection_events(NEW.payload_json->'terminalProgressEvents'));
|
|
1718
|
+
END IF;
|
|
1719
|
+
END IF;
|
|
1438
1720
|
RETURN NEW;
|
|
1439
1721
|
END
|
|
1440
1722
|
$function$
|
|
@@ -1444,7 +1726,8 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1444
1726
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
1445
1727
|
DECLARE observation ${schema}.outbox; compact_logs boolean;
|
|
1446
1728
|
BEGIN
|
|
1447
|
-
SELECT run_log_store = 'planet_scale'
|
|
1729
|
+
SELECT run_log_store = 'planet_scale' OR run_event_store = 'planet_scale'
|
|
1730
|
+
INTO compact_logs
|
|
1448
1731
|
FROM ${schema}.runs WHERE run_id = NEW.run_id;
|
|
1449
1732
|
IF NOT coalesce(compact_logs, false) OR NEW.runner_log_payload_json IS NULL THEN
|
|
1450
1733
|
RETURN NEW;
|
|
@@ -1493,7 +1776,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1493
1776
|
CREATE OR REPLACE FUNCTION ${schema}.compact_run_log_result() RETURNS trigger
|
|
1494
1777
|
LANGUAGE plpgsql SET search_path = pg_catalog AS $function$
|
|
1495
1778
|
BEGIN
|
|
1496
|
-
IF NEW.run_log_store = 'planet_scale' THEN
|
|
1779
|
+
IF NEW.run_log_store = 'planet_scale' OR NEW.run_event_store = 'planet_scale' THEN
|
|
1497
1780
|
IF NEW.status = 'failed' THEN
|
|
1498
1781
|
NEW.terminal_result_json := ${schema}.compact_runner_log_envelope(NEW.terminal_result_json);
|
|
1499
1782
|
ELSIF NEW.status = 'completed' THEN
|
|
@@ -1554,6 +1837,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
1554
1837
|
await client.query(`REVOKE ALL ON FUNCTION ${schema}.project_outbox_event(${schema}.outbox),
|
|
1555
1838
|
${schema}.accept_runner_log_payload(), ${schema}.search_outbox_event(),
|
|
1556
1839
|
${schema}.compact_run_log_result(), ${schema}.without_log_events(jsonb),
|
|
1840
|
+
${schema}.convex_projection_event(jsonb), ${schema}.convex_projection_events(jsonb),
|
|
1557
1841
|
${schema}.compact_runner_log_envelope(jsonb), ${schema}.without_runtime_log_tail(jsonb)
|
|
1558
1842
|
FROM PUBLIC`);
|
|
1559
1843
|
// Publish readiness last, after all acceptance/compaction functions exist.
|
|
@@ -1612,6 +1896,10 @@ export async function hasRuntimeEventSearchShape(
|
|
|
1612
1896
|
|
|
1613
1897
|
const RUNTIME_EVENT_BODY_RETENTION_PREDICATE = `kind <> 'log.appended' OR log_body IS NOT NULL
|
|
1614
1898
|
OR message IS NOT NULL OR search_doc ? 'context'`;
|
|
1899
|
+
const RUNTIME_EVENT_CANONICAL_RETENTION_PREDICATE = `(kind <> 'log.appended' AND event_payload IS NULL)
|
|
1900
|
+
OR (substr(kind, 1, 8) = 'receipt.' AND event_payload IS NOT NULL
|
|
1901
|
+
AND (receipt_ref IS NOT NULL OR receipt_key IS NOT NULL))
|
|
1902
|
+
OR (kind = 'log.appended' AND (log_body IS NOT NULL OR message IS NOT NULL OR search_doc ? 'context'))`;
|
|
1615
1903
|
|
|
1616
1904
|
// pg_get_expr adds grouping and text casts. Preserve quoted literals exactly;
|
|
1617
1905
|
// this known OR-only predicate rejects other operators, keys and literal values.
|
|
@@ -1835,54 +2123,76 @@ export async function prepareRuntimeEventRetentionIndex(
|
|
|
1835
2123
|
key2: string | null;
|
|
1836
2124
|
predicate: string | null;
|
|
1837
2125
|
};
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2126
|
+
const prepare = async (input: {
|
|
2127
|
+
name: string;
|
|
2128
|
+
predicate: string;
|
|
2129
|
+
marksJournalReady?: boolean;
|
|
2130
|
+
}) => {
|
|
2131
|
+
const inspect = () =>
|
|
2132
|
+
client.query<IndexShape>(
|
|
2133
|
+
`SELECT index.indisvalid AS valid, index.indisready AS ready, index.indisunique AS is_unique,
|
|
2134
|
+
index.indrelid = to_regclass('runtime_observability.events') AS table_matches,
|
|
2135
|
+
index.indnkeyatts AS key_count, index.indnatts AS attribute_count,
|
|
2136
|
+
method.amname AS method,
|
|
2137
|
+
pg_get_indexdef(index.indexrelid, 1, true) AS key1,
|
|
2138
|
+
pg_get_indexdef(index.indexrelid, 2, true) AS key2,
|
|
2139
|
+
pg_get_expr(index.indpred, index.indrelid) AS predicate
|
|
2140
|
+
FROM pg_index AS index
|
|
2141
|
+
JOIN pg_class AS relation ON relation.oid = index.indexrelid
|
|
2142
|
+
JOIN pg_am AS method ON method.oid = relation.relam
|
|
2143
|
+
WHERE index.indexrelid = to_regclass($1)`,
|
|
2144
|
+
[`runtime_observability.${input.name}`],
|
|
2145
|
+
);
|
|
2146
|
+
const assertReady = (shape: IndexShape): void => {
|
|
2147
|
+
if (!shape.valid || !shape.ready) {
|
|
2148
|
+
throw new Error(
|
|
2149
|
+
`Runtime retention index ${input.name} is invalid or incomplete. Inspect the failed build; repair it explicitly before retrying.`,
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
if (
|
|
2153
|
+
!shape.table_matches ||
|
|
2154
|
+
shape.is_unique ||
|
|
2155
|
+
shape.key_count !== 2 ||
|
|
2156
|
+
shape.attribute_count !== 2 ||
|
|
2157
|
+
shape.method !== 'btree' ||
|
|
2158
|
+
shape.key1 !== 'ingested_at' ||
|
|
2159
|
+
shape.key2 !== 'event_id' ||
|
|
2160
|
+
shape.predicate === null ||
|
|
2161
|
+
normalizedRetentionPredicate(shape.predicate) !==
|
|
2162
|
+
normalizedRetentionPredicate(input.predicate)
|
|
2163
|
+
) {
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
`Runtime retention index ${input.name} definition differs from its expected table, uniqueness, keys or predicate. Actual catalog shape: ${JSON.stringify(shape)}. Inspect and repair it explicitly.`,
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
const existing = (await inspect()).rows[0];
|
|
2170
|
+
if (existing) assertReady(existing);
|
|
2171
|
+
await client.query(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${input.name}
|
|
2172
|
+
ON runtime_observability.events (ingested_at, event_id)
|
|
2173
|
+
WHERE ${input.predicate}`);
|
|
2174
|
+
const built = (await inspect()).rows[0];
|
|
2175
|
+
if (!built) {
|
|
1854
2176
|
throw new Error(
|
|
1855
|
-
|
|
2177
|
+
`Runtime retention index ${input.name} was not found after preparation.`,
|
|
1856
2178
|
);
|
|
1857
2179
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
shape.attribute_count !== 2 ||
|
|
1863
|
-
shape.method !== 'btree' ||
|
|
1864
|
-
shape.key1 !== 'ingested_at' ||
|
|
1865
|
-
shape.key2 !== 'event_id' ||
|
|
1866
|
-
shape.predicate === null ||
|
|
1867
|
-
normalizedRetentionPredicate(shape.predicate) !==
|
|
1868
|
-
normalizedRetentionPredicate(RUNTIME_EVENT_BODY_RETENTION_PREDICATE)
|
|
1869
|
-
) {
|
|
1870
|
-
throw new Error(
|
|
1871
|
-
'Runtime body retention index definition differs from its expected table, uniqueness, keys or predicate. Inspect and repair it explicitly.',
|
|
2180
|
+
assertReady(built);
|
|
2181
|
+
if (input.marksJournalReady) {
|
|
2182
|
+
await client.query(
|
|
2183
|
+
`COMMENT ON INDEX runtime_observability.runtime_events_canonical_retention_idx IS ${quoteLiteral(RUNTIME_EVENT_CANONICAL_RETENTION_INDEX_READY_MARKER)}`,
|
|
1872
2184
|
);
|
|
1873
2185
|
}
|
|
1874
2186
|
};
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
);
|
|
1885
|
-
assertReady(built);
|
|
2187
|
+
await prepare({
|
|
2188
|
+
name: 'runtime_events_body_retention_idx',
|
|
2189
|
+
predicate: RUNTIME_EVENT_BODY_RETENTION_PREDICATE,
|
|
2190
|
+
});
|
|
2191
|
+
await prepare({
|
|
2192
|
+
name: 'runtime_events_canonical_retention_idx',
|
|
2193
|
+
predicate: RUNTIME_EVENT_CANONICAL_RETENTION_PREDICATE,
|
|
2194
|
+
marksJournalReady: true,
|
|
2195
|
+
});
|
|
1886
2196
|
}
|
|
1887
2197
|
|
|
1888
2198
|
/** One bounded operator-owned TTL pass. Never called by a customer request. */
|
|
@@ -1907,10 +2217,9 @@ export async function pruneRuntimeEvents(
|
|
|
1907
2217
|
}
|
|
1908
2218
|
const result = await client.query<{ event_id: string }>(
|
|
1909
2219
|
`WITH candidates AS (
|
|
1910
|
-
|
|
2220
|
+
SELECT ctid FROM runtime_observability.events
|
|
1911
2221
|
WHERE ingested_at < $1::timestamptz
|
|
1912
|
-
AND (
|
|
1913
|
-
OR message IS NOT NULL OR search_doc ? 'context')
|
|
2222
|
+
AND (${RUNTIME_EVENT_CANONICAL_RETENTION_PREDICATE})
|
|
1914
2223
|
ORDER BY ingested_at, event_id
|
|
1915
2224
|
LIMIT $2::integer FOR UPDATE SKIP LOCKED
|
|
1916
2225
|
), scrubbed AS (
|
|
@@ -1918,6 +2227,8 @@ export async function pruneRuntimeEvents(
|
|
|
1918
2227
|
SET log_text_digest = coalesce(event.log_text_digest,
|
|
1919
2228
|
encode(sha256(convert_to(btrim(coalesce(event.log_body, event.message)), 'UTF8')), 'hex')),
|
|
1920
2229
|
log_body = NULL, message = NULL,
|
|
2230
|
+
event_payload = CASE WHEN event.event_payload IS NULL THEN NULL
|
|
2231
|
+
ELSE event.event_payload - 'context' END,
|
|
1921
2232
|
search_doc = jsonb_strip_nulls(jsonb_build_object(
|
|
1922
2233
|
'kind', 'log.appended', 'source', event.search_doc->'source',
|
|
1923
2234
|
'producerAttempt', event.search_doc->'producerAttempt',
|
|
@@ -1925,12 +2236,24 @@ export async function pruneRuntimeEvents(
|
|
|
1925
2236
|
'logContentDigest', event.search_doc->'logContentDigest'))
|
|
1926
2237
|
FROM candidates WHERE event.ctid = candidates.ctid AND event.kind = 'log.appended'
|
|
1927
2238
|
RETURNING event.event_id
|
|
2239
|
+
), scrubbed_receipts AS (
|
|
2240
|
+
UPDATE runtime_observability.events AS event
|
|
2241
|
+
SET receipt_ref = NULL, receipt_key = NULL
|
|
2242
|
+
FROM candidates
|
|
2243
|
+
WHERE event.ctid = candidates.ctid
|
|
2244
|
+
AND left(event.kind, 8) = 'receipt.'
|
|
2245
|
+
AND event.event_payload IS NOT NULL
|
|
2246
|
+
AND (event.receipt_ref IS NOT NULL OR event.receipt_key IS NOT NULL)
|
|
2247
|
+
RETURNING event.event_id
|
|
1928
2248
|
), deleted AS (
|
|
1929
2249
|
DELETE FROM runtime_observability.events AS event USING candidates
|
|
1930
2250
|
WHERE event.ctid = candidates.ctid AND event.kind <> 'log.appended'
|
|
2251
|
+
AND event.event_payload IS NULL
|
|
1931
2252
|
RETURNING event.event_id
|
|
1932
2253
|
)
|
|
1933
|
-
SELECT event_id FROM scrubbed
|
|
2254
|
+
SELECT event_id FROM scrubbed
|
|
2255
|
+
UNION ALL SELECT event_id FROM scrubbed_receipts
|
|
2256
|
+
UNION ALL SELECT event_id FROM deleted`,
|
|
1934
2257
|
[input.before.toISOString(), input.limit],
|
|
1935
2258
|
);
|
|
1936
2259
|
// Keep identity/digests until fenced ingress and pending delivery drain. A
|
|
@@ -1956,14 +2279,12 @@ export async function inspectRuntimeEventRetention(
|
|
|
1956
2279
|
}>(
|
|
1957
2280
|
`WITH oldest AS (
|
|
1958
2281
|
SELECT ingested_at FROM runtime_observability.events
|
|
1959
|
-
WHERE
|
|
1960
|
-
OR message IS NOT NULL OR search_doc ? 'context'
|
|
2282
|
+
WHERE (${RUNTIME_EVENT_CANONICAL_RETENTION_PREDICATE})
|
|
1961
2283
|
ORDER BY ingested_at, event_id LIMIT 1
|
|
1962
2284
|
), bounded AS (
|
|
1963
2285
|
SELECT 1 FROM runtime_observability.events
|
|
1964
2286
|
WHERE ingested_at < $1::timestamptz
|
|
1965
|
-
AND (
|
|
1966
|
-
OR message IS NOT NULL OR search_doc ? 'context')
|
|
2287
|
+
AND (${RUNTIME_EVENT_CANONICAL_RETENTION_PREDICATE})
|
|
1967
2288
|
ORDER BY ingested_at, event_id LIMIT 10001
|
|
1968
2289
|
)
|
|
1969
2290
|
SELECT (SELECT ingested_at FROM oldest) AS oldest,
|
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.268",
|
|
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.268",
|
|
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.268",
|
|
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.268",
|
|
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.268";
|
|
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.268";
|
|
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.268",
|
|
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.268",
|
|
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
|