deepline 0.3.403 → 0.3.405
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/client.ts +4 -4
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +114 -50
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +379 -133
- package/dist/cli/{auth-AHF355KV.mjs → auth-D4GXO4IN.mjs} +4 -4
- package/dist/cli/auth-IJKAKAHG.js +40 -0
- package/dist/cli/{billing-LSZECOCV.mjs → billing-NVV5VSUJ.mjs} +4 -4
- package/dist/cli/{billing-5L5X2OAC.js → billing-TQ4UM4D4.js} +73 -73
- package/dist/cli/{chunk-VNO3VAJT.mjs → chunk-3DWMYKWU.mjs} +1 -1
- package/dist/cli/{chunk-CC3QT7Q5.mjs → chunk-3FF3SPSE.mjs} +2 -2
- package/dist/cli/{chunk-JZ7TJL7G.js → chunk-3MVBOBV4.js} +2 -2
- package/dist/cli/{chunk-RXVJYS76.mjs → chunk-4ONG2MWB.mjs} +1 -1
- package/dist/cli/{chunk-57GMG2DV.js → chunk-6HXTHMHZ.js} +26 -26
- package/dist/cli/{chunk-SGPGIC3M.js → chunk-AAIV3ACM.js} +50 -50
- package/dist/cli/{chunk-QN55SQYI.mjs → chunk-B6ME2XIO.mjs} +5 -5
- package/dist/cli/{chunk-FQL75GML.js → chunk-BZBV2GY2.js} +8 -8
- package/dist/cli/{chunk-V4PLPRWQ.js → chunk-CB6AJNHI.js} +4 -4
- package/dist/cli/{chunk-JHNYOTWR.mjs → chunk-EKWJXPIX.mjs} +1 -1
- package/dist/cli/{chunk-GJECLPJP.mjs → chunk-EVLCUV64.mjs} +4 -4
- package/dist/cli/{chunk-C27DGNII.mjs → chunk-HG5PMETP.mjs} +5 -5
- package/dist/cli/{chunk-DJSPH5T2.js → chunk-HYAA3F6U.js} +10 -10
- package/dist/cli/{chunk-4BWZA57I.js → chunk-JPNBAZBR.js} +6 -6
- package/dist/cli/{chunk-77FZLY5L.js → chunk-LM2JHRZM.js} +1 -1
- package/dist/cli/{chunk-KTBMDDEQ.mjs → chunk-MPXPTSGJ.mjs} +3 -3
- package/dist/cli/{chunk-J3OZIYU4.js → chunk-MZ4LBCV7.js} +15 -15
- package/dist/cli/{chunk-MJ4OWXFM.js → chunk-NAJEB3N4.js} +44 -44
- package/dist/cli/{chunk-5SGQF4QF.js → chunk-NLT6GCWZ.js} +8 -8
- package/dist/cli/{chunk-HPEIY4UR.mjs → chunk-TECEX3K5.mjs} +3 -3
- package/dist/cli/{chunk-BYBRENKM.mjs → chunk-TPYRDAU7.mjs} +1 -1
- package/dist/cli/{chunk-VYDYDELF.js → chunk-WGX7SIGL.js} +65 -65
- package/dist/cli/{chunk-B4Y2CZKS.mjs → chunk-YRZIKG47.mjs} +2 -2
- package/dist/cli/{chunk-MQWZYCQJ.mjs → chunk-YX7JSSJ2.mjs} +2 -2
- package/dist/cli/{csv-IO2BKRNX.js → csv-5AASYDRF.js} +12 -12
- package/dist/cli/{csv-J6ZPUWE4.mjs → csv-H7LD6IQK.mjs} +3 -3
- package/dist/cli/{db-QICV6JWY.mjs → db-CG3UQUA7.mjs} +4 -4
- package/dist/cli/{db-ZVRBVSPU.js → db-MJBZBDWZ.js} +25 -25
- package/dist/cli/{describe-WX6VWGH6.js → describe-2A7GARI2.js} +10 -10
- package/dist/cli/{describe-YA35XS55.mjs → describe-INFC5ACQ.mjs} +6 -6
- package/dist/cli/{executions-YQAUBJLS.js → executions-AUDQ5HOJ.js} +6 -6
- package/dist/cli/{executions-X3PDLFKO.mjs → executions-DO47P6MX.mjs} +4 -4
- package/dist/cli/{feedback-M2WMOY5B.js → feedback-MNWNDZVU.js} +7 -7
- package/dist/cli/{feedback-HSERSHDV.mjs → feedback-XGZSJM2N.mjs} +3 -3
- package/dist/cli/{git-3JMX455D.mjs → git-2ECFQKAA.mjs} +4 -4
- package/dist/cli/git-6S64UBWC.js +24 -0
- package/dist/cli/index.js +89 -89
- package/dist/cli/index.mjs +32 -32
- package/dist/cli/{legacy-noops-EMUCJ5KW.js → legacy-noops-3UZTD4DS.js} +7 -7
- package/dist/cli/{legacy-noops-OKC6VDD3.mjs → legacy-noops-534TKEMO.mjs} +4 -4
- package/dist/cli/{monitors-RH3FIA63.mjs → monitors-5ARELE2S.mjs} +4 -4
- package/dist/cli/{monitors-M6DU2JXC.js → monitors-G4S54JSX.js} +84 -84
- package/dist/cli/{org-KK3C5J6E.mjs → org-BAMOOXQW.mjs} +4 -4
- package/dist/cli/{org-BZDZO32O.js → org-RIWF2TLI.js} +64 -64
- package/dist/cli/{play-ZKYHO2KC.js → play-32XSQMFK.js} +231 -231
- package/dist/cli/{play-QXBNDI6C.mjs → play-WV4UNGPR.mjs} +7 -7
- package/dist/cli/{providers-JRN5FNXI.mjs → providers-SMSRO23R.mjs} +4 -4
- package/dist/cli/{providers-VCT7HFYW.js → providers-XKY2CNKH.js} +7 -7
- package/dist/cli/{quickstart-Y33XAQDY.mjs → quickstart-BFYYEVSS.mjs} +3 -3
- package/dist/cli/{quickstart-X2IC2U6U.js → quickstart-Q2TA72HD.js} +5 -5
- package/dist/cli/search-GK7P3AZY.js +30 -0
- package/dist/cli/{search-R443WBN7.mjs → search-I2TVB2P5.mjs} +5 -5
- package/dist/cli/{secrets-U6V4M7R3.js → secrets-B4NYTSL7.js} +13 -13
- package/dist/cli/{secrets-EUKR5NG2.mjs → secrets-BHKICIE3.mjs} +4 -4
- package/dist/cli/{sessions-RZVHJSVB.mjs → sessions-IOR2ZNNH.mjs} +4 -4
- package/dist/cli/{sessions-IPKYJT4M.js → sessions-XY2UREJ7.js} +5 -5
- package/dist/cli/{settings-W4YFY5JJ.js → settings-ASGROQDE.js} +74 -74
- package/dist/cli/{settings-FKU7KJ24.mjs → settings-W6S7B4NH.mjs} +7 -7
- package/dist/cli/{setup-6WIECL4V.mjs → setup-UUWGI3IN.mjs} +6 -6
- package/dist/cli/{setup-BEIN6536.js → setup-YIYQ73OV.js} +41 -41
- package/dist/cli/{skills-CV72BOP7.mjs → skills-CHA4QCGI.mjs} +4 -4
- package/dist/cli/skills-LKNKHB2I.js +29 -0
- package/dist/cli/{tools-2ADAYBNB.js → tools-43ZFJWAQ.js} +7 -7
- package/dist/cli/{tools-2SH4RIFD.mjs → tools-PMZDYKTT.mjs} +6 -6
- package/dist/cli/{update-YTR4JUPG.mjs → update-6VSEGTSG.mjs} +6 -6
- package/dist/cli/update-R7FODDIW.js +29 -0
- package/dist/cli/{workflow-446CNNRV.js → workflow-26EOXZ7M.js} +15 -15
- package/dist/cli/{workflow-BBSL2QKK.mjs → workflow-SVMVLQOF.mjs} +5 -5
- package/dist/index.js +6 -6
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +70 -70
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-O7A7JS2M.js +0 -40
- package/dist/cli/git-IUDHAE5O.js +0 -24
- package/dist/cli/search-7257KVSQ.js +0 -30
- package/dist/cli/skills-A7ATFQEL.js +0 -29
- package/dist/cli/update-AUYYCJUT.js +0 -29
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts
CHANGED
|
@@ -1837,6 +1837,316 @@ async function installNewRunProjectionMode(
|
|
|
1837
1837
|
);
|
|
1838
1838
|
}
|
|
1839
1839
|
|
|
1840
|
+
function freshLogSequenceAssignmentSql(runtimeScope: string): string {
|
|
1841
|
+
const scope = requiredScope(runtimeScope);
|
|
1842
|
+
const schema = quoteIdent(scope);
|
|
1843
|
+
return `
|
|
1844
|
+
CREATE OR REPLACE FUNCTION ${schema}.assign_fresh_log_sequences(
|
|
1845
|
+
assigned_seq bigint, line_count bigint, fresh_tids tid[], fresh_event_ids text[],
|
|
1846
|
+
probe_org text, probe_run_id text
|
|
1847
|
+
) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
1848
|
+
SET enable_seqscan = off SET enable_tidscan = on AS $function$
|
|
1849
|
+
DECLARE probe_scope text := ${quoteLiteral(scope)};
|
|
1850
|
+
chunk_start integer; chunk_tids tid[]; assigned_count bigint;
|
|
1851
|
+
assigned_total bigint; mapped_count bigint; distinct_mapped_count bigint;
|
|
1852
|
+
BEGIN
|
|
1853
|
+
IF line_count IS NULL OR line_count < 1 OR assigned_seq IS NULL
|
|
1854
|
+
OR probe_scope IS NULL OR probe_org IS NULL OR probe_run_id IS NULL
|
|
1855
|
+
OR cardinality(fresh_tids) IS DISTINCT FROM line_count
|
|
1856
|
+
OR cardinality(fresh_event_ids) IS DISTINCT FROM line_count
|
|
1857
|
+
OR array_lower(fresh_tids, 1) IS DISTINCT FROM 1
|
|
1858
|
+
OR array_lower(fresh_event_ids, 1) IS DISTINCT FROM 1 THEN
|
|
1859
|
+
RAISE EXCEPTION 'Chunk sequence assignment requires aligned fresh targets';
|
|
1860
|
+
END IF;
|
|
1861
|
+
SELECT count(*), count(DISTINCT fresh.tid)
|
|
1862
|
+
INTO mapped_count, distinct_mapped_count
|
|
1863
|
+
FROM unnest(fresh_tids) AS fresh(tid)
|
|
1864
|
+
WHERE fresh.tid IS NOT NULL;
|
|
1865
|
+
IF mapped_count <> line_count OR distinct_mapped_count <> line_count
|
|
1866
|
+
OR EXISTS (SELECT 1 FROM unnest(fresh_event_ids) AS fresh(event_id) WHERE fresh.event_id IS NULL) THEN
|
|
1867
|
+
RAISE EXCEPTION 'Chunk sequence assignment requires every exact fresh target';
|
|
1868
|
+
END IF;
|
|
1869
|
+
assigned_total := 0;
|
|
1870
|
+
FOR chunk_start IN 1..line_count BY 256 LOOP
|
|
1871
|
+
chunk_tids := fresh_tids[chunk_start:LEAST(chunk_start + 255, line_count)];
|
|
1872
|
+
WITH updated AS (
|
|
1873
|
+
UPDATE runtime_observability.events AS event
|
|
1874
|
+
SET log_seq = assigned_seq - line_count + chunk_start - 1
|
|
1875
|
+
+ array_position(chunk_tids, event.ctid)
|
|
1876
|
+
WHERE event.ctid = ANY(chunk_tids)
|
|
1877
|
+
RETURNING event.runtime_scope, event.org_id, event.run_id, event.event_id, event.log_seq
|
|
1878
|
+
)
|
|
1879
|
+
SELECT count(*), count(*) FILTER (
|
|
1880
|
+
WHERE updated.runtime_scope IS NOT DISTINCT FROM probe_scope
|
|
1881
|
+
AND updated.org_id IS NOT DISTINCT FROM probe_org
|
|
1882
|
+
AND updated.run_id IS NOT DISTINCT FROM probe_run_id
|
|
1883
|
+
AND updated.log_seq BETWEEN assigned_seq - line_count + chunk_start
|
|
1884
|
+
AND assigned_seq - line_count + chunk_start - 1 + cardinality(chunk_tids)
|
|
1885
|
+
AND updated.event_id IS NOT DISTINCT FROM fresh_event_ids[
|
|
1886
|
+
(updated.log_seq - (assigned_seq - line_count))::integer
|
|
1887
|
+
]
|
|
1888
|
+
) INTO assigned_count, mapped_count FROM updated;
|
|
1889
|
+
IF assigned_count <> cardinality(chunk_tids) OR mapped_count <> assigned_count THEN
|
|
1890
|
+
RAISE EXCEPTION 'Chunk sequence assignment did not update every exact fresh target';
|
|
1891
|
+
END IF;
|
|
1892
|
+
assigned_total := assigned_total + assigned_count;
|
|
1893
|
+
END LOOP;
|
|
1894
|
+
IF assigned_total <> line_count THEN
|
|
1895
|
+
RAISE EXCEPTION 'Chunk sequence assignment did not account for the whole batch';
|
|
1896
|
+
END IF;
|
|
1897
|
+
RETURN assigned_total;
|
|
1898
|
+
END
|
|
1899
|
+
$function$
|
|
1900
|
+
`;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function originLogIdentitySeekSql(
|
|
1904
|
+
scope: string,
|
|
1905
|
+
org: string,
|
|
1906
|
+
id: string,
|
|
1907
|
+
): string {
|
|
1908
|
+
// Seek the first complete identity at or after the requested key, then check
|
|
1909
|
+
// equality outside LIMIT. An equality-only probe can choose an index with
|
|
1910
|
+
// unbound run/time columns before event_id and reread its prefix per line.
|
|
1911
|
+
return `SELECT candidate.* FROM (
|
|
1912
|
+
SELECT event.runtime_scope, event.org_id, event.event_id,
|
|
1913
|
+
event.run_id, event.kind, event.source_digest
|
|
1914
|
+
FROM runtime_observability.events event
|
|
1915
|
+
WHERE (event.runtime_scope, event.org_id, event.event_id)
|
|
1916
|
+
>= (${quoteLiteral(scope)}, ${org}, ${id})
|
|
1917
|
+
ORDER BY event.runtime_scope, event.org_id, event.event_id
|
|
1918
|
+
LIMIT 1
|
|
1919
|
+
) candidate
|
|
1920
|
+
WHERE candidate.runtime_scope = ${quoteLiteral(scope)}
|
|
1921
|
+
AND candidate.org_id = ${org} AND candidate.event_id = ${id}`;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function originLogBatchProjectionSql(scope: string, numbered: boolean): string {
|
|
1925
|
+
const schema = quoteIdent(scope);
|
|
1926
|
+
return `DO $install_origin_log_targets$ BEGIN
|
|
1927
|
+
CREATE OR REPLACE FUNCTION ${schema}.project_origin_log_batch_with_targets(observation ${schema}.outbox, item jsonb, run_org text, play text, compact_logs boolean, canonical_events boolean, at_time timestamptz, doc jsonb, closed boolean${numbered ? ', sequence_base bigint' : ''})
|
|
1928
|
+
RETURNS TABLE(fresh_id text, fresh_tid tid, fresh_ordinal bigint) LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
1929
|
+
DECLARE fresh text[]; target_tids tid[]; target_ordinals bigint[]; expected_fresh_count bigint; mismatches bigint; expected_keys jsonb; existing_identities jsonb;
|
|
1930
|
+
batch_lines jsonb := item->'lines';
|
|
1931
|
+
batch_ids jsonb := item->'lineIds';
|
|
1932
|
+
batch_contexts jsonb := item->'lineContexts';
|
|
1933
|
+
batch_metadata jsonb := item->'lineMetadata';
|
|
1934
|
+
batch_event jsonb := item - 'lines' - 'lineContexts' - 'lineIds' - 'lineMetadata';
|
|
1935
|
+
BEGIN
|
|
1936
|
+
IF jsonb_typeof(batch_ids) IS DISTINCT FROM 'array'
|
|
1937
|
+
OR jsonb_array_length(batch_ids) <> jsonb_array_length(batch_lines)
|
|
1938
|
+
OR EXISTS (SELECT 1 FROM jsonb_array_elements(batch_ids) id WHERE jsonb_typeof(id) <> 'string' OR (id #>> '{}') !~ '^[A-Za-z0-9:_-]{1,128}$')
|
|
1939
|
+
OR (SELECT count(DISTINCT id) FROM jsonb_array_elements(batch_ids) id) <> jsonb_array_length(batch_ids) THEN
|
|
1940
|
+
RAISE EXCEPTION 'Origin log identities must be distinct bounded strings aligned with lines';
|
|
1941
|
+
END IF;
|
|
1942
|
+
IF EXISTS (SELECT 1 FROM jsonb_array_elements(batch_lines) line WHERE jsonb_typeof(line) <> 'string') THEN
|
|
1943
|
+
RAISE EXCEPTION 'Search projection requires log line strings';
|
|
1944
|
+
END IF;
|
|
1945
|
+
IF item ? 'lineContexts' AND EXISTS (
|
|
1946
|
+
SELECT 1 FROM jsonb_array_elements(batch_contexts) ctx
|
|
1947
|
+
WHERE jsonb_typeof(ctx) NOT IN ('object', 'null')
|
|
1948
|
+
) THEN RAISE EXCEPTION 'Search projection log context must be an object'; END IF;
|
|
1949
|
+
IF item ? 'lineContexts' AND EXISTS (
|
|
1950
|
+
SELECT 1 FROM jsonb_array_elements(batch_contexts) ctx
|
|
1951
|
+
WHERE jsonb_typeof(ctx) = 'object' AND (
|
|
1952
|
+
(SELECT count(*) FROM jsonb_object_keys(ctx)) > 8 OR octet_length(ctx::text) > 2064
|
|
1953
|
+
OR EXISTS (SELECT 1 FROM jsonb_each(ctx) field WHERE
|
|
1954
|
+
field.key !~ '^[A-Za-z_][A-Za-z0-9_]{0,63}$' OR field.key IN ('__proto__', 'constructor', 'prototype')
|
|
1955
|
+
OR jsonb_typeof(field.value) NOT IN ('string', 'number', 'boolean')
|
|
1956
|
+
OR (jsonb_typeof(field.value) = 'string' AND octet_length(field.value #>> '{}') > 256)
|
|
1957
|
+
OR (jsonb_typeof(field.value) = 'number' AND octet_length(field.value #>> '{}') > 32))
|
|
1958
|
+
)
|
|
1959
|
+
) THEN RAISE EXCEPTION 'Search projection log context has an unsupported key or value'; END IF;
|
|
1960
|
+
-- Generic plans cannot fold item field extraction or removal. Cache the
|
|
1961
|
+
-- aligned arrays and event header once: rebuilding a multi-megabyte item
|
|
1962
|
+
-- per line makes retained terminal envelopes quadratic even on replay.
|
|
1963
|
+
-- Replan these two journal-reading statements once per batch. A cached
|
|
1964
|
+
-- plan chosen on a tiny journal can retain a Seq Scan after it grows.
|
|
1965
|
+
-- Values stay bound parameters; no per-identity parsing or planner GUC.
|
|
1966
|
+
EXECUTE $origin_insert$
|
|
1967
|
+
WITH entries AS MATERIALIZED (
|
|
1968
|
+
SELECT value #>> '{}' AS body, ordinality AS ordinal,
|
|
1969
|
+
'log-origin:' || $1 || ':' || ($3->>(ordinality-1)::int) AS eid,
|
|
1970
|
+
NULLIF($4->(ordinality-1)::int, 'null'::jsonb) AS ctx,
|
|
1971
|
+
${schema}.run_log_metadata(value #>> '{}', $5->(ordinality-1)::int, $11) AS metadata
|
|
1972
|
+
FROM jsonb_array_elements($2) WITH ORDINALITY
|
|
1973
|
+
)${
|
|
1974
|
+
numbered
|
|
1975
|
+
? `, fresh_entries AS MATERIALIZED (
|
|
1976
|
+
SELECT entries.*, row_number() OVER (ORDER BY ordinal) AS fresh_position
|
|
1977
|
+
FROM entries WHERE (
|
|
1978
|
+
SELECT identity.event_id
|
|
1979
|
+
FROM (${originLogIdentitySeekSql(scope, '$7', 'entries.eid')}) identity
|
|
1980
|
+
) IS NULL
|
|
1981
|
+
)`
|
|
1982
|
+
: ''
|
|
1983
|
+
}, inserted AS (
|
|
1984
|
+
INSERT INTO runtime_observability.events (
|
|
1985
|
+
runtime_scope, org_id, event_id, event_seq, event_payload, occurred_at,
|
|
1986
|
+
run_id, play_name, kind, level, message, log_body, source_digest, log_text_digest, search_doc${numbered ? ', log_seq' : ''}
|
|
1987
|
+
) SELECT ${quoteLiteral(scope)}, $7, eid,
|
|
1988
|
+
CASE WHEN $10 THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
1989
|
+
CASE WHEN $10 THEN jsonb_strip_nulls($6 || jsonb_build_object(
|
|
1990
|
+
'type', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'runId', $1,
|
|
1991
|
+
'occurredAt', metadata->'occurredAt', 'level', metadata->'level', 'stepId', metadata->'stepId', 'lineOrdinal', ordinal,
|
|
1992
|
+
'entryId', $3->(ordinal-1)::int, 'context', ctx)) END,
|
|
1993
|
+
to_timestamp((metadata->>'occurredAt')::double precision / 1000), $1, $8, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, metadata->>'level',
|
|
1994
|
+
CASE WHEN $9 THEN NULL ELSE left(body, 2048) END, body,
|
|
1995
|
+
CASE WHEN ctx IS NULL THEN encode(sha256(convert_to(body,'UTF8')),'hex')
|
|
1996
|
+
ELSE encode(sha256(convert_to(jsonb_build_array(body,ctx)::text,'UTF8')),'hex') END,
|
|
1997
|
+
encode(sha256(convert_to(btrim(body),'UTF8')),'hex'),
|
|
1998
|
+
$12 || (metadata - 'occurredAt') || jsonb_strip_nulls(jsonb_build_object('entryId', $3->(ordinal-1)::int, 'context', ctx))${numbered ? ', $14 + fresh_position' : ''}
|
|
1999
|
+
FROM ${numbered ? 'fresh_entries' : 'entries'} WHERE NOT $13 ORDER BY ordinal
|
|
2000
|
+
ON CONFLICT (runtime_scope, org_id, event_id) DO NOTHING RETURNING event_id, ctid AS inserted_tid
|
|
2001
|
+
) SELECT coalesce(array_agg(inserted.event_id ORDER BY entries.ordinal), ARRAY[]::text[]),
|
|
2002
|
+
coalesce(array_agg(inserted.inserted_tid ORDER BY entries.ordinal), ARRAY[]::tid[]),
|
|
2003
|
+
coalesce(array_agg(entries.ordinal ORDER BY entries.ordinal), ARRAY[]::bigint[]),
|
|
2004
|
+
${numbered ? '(SELECT count(*) FROM fresh_entries)' : 'NULL::bigint'}
|
|
2005
|
+
FROM inserted JOIN entries ON entries.eid = inserted.event_id
|
|
2006
|
+
$origin_insert$ INTO fresh, target_tids, target_ordinals, expected_fresh_count
|
|
2007
|
+
USING observation.run_id, batch_lines, batch_ids, batch_contexts, batch_metadata,
|
|
2008
|
+
batch_event, run_org, play, compact_logs, canonical_events, at_time, doc, closed${numbered ? ', sequence_base' : ''};
|
|
2009
|
+
${
|
|
2010
|
+
numbered
|
|
2011
|
+
? `-- A pre-upgrade inserter can win a uniqueness race without taking
|
|
2012
|
+
-- the cursor first. Never commit a hole in the precomputed fresh range.
|
|
2013
|
+
IF NOT closed AND cardinality(fresh) <> expected_fresh_count THEN
|
|
2014
|
+
RAISE EXCEPTION 'Origin log allocation changed during insertion' USING ERRCODE = '40001';
|
|
2015
|
+
END IF;`
|
|
2016
|
+
: ''
|
|
2017
|
+
}
|
|
2018
|
+
-- The INSERT already proves the content of every fresh identity. Only
|
|
2019
|
+
-- retries or a concurrent uniqueness winner need a journal read-back.
|
|
2020
|
+
-- This fast path serves both inline and background origin projection.
|
|
2021
|
+
IF cardinality(fresh) = jsonb_array_length(batch_lines) THEN
|
|
2022
|
+
RETURN QUERY SELECT * FROM unnest(fresh, target_tids, target_ordinals);
|
|
2023
|
+
RETURN;
|
|
2024
|
+
END IF;
|
|
2025
|
+
-- A new READ COMMITTED statement sees a concurrent uniqueness winner.
|
|
2026
|
+
-- Seek in complete identity order and verify the one candidate's key.
|
|
2027
|
+
-- Per-batch planning avoids retaining a tiny-table Seq Scan; the order
|
|
2028
|
+
-- also prevents choosing an index with an unbound run/time key gap.
|
|
2029
|
+
SELECT jsonb_object_agg('log-origin:' || observation.run_id || ':' || (id #>> '{}'), true)
|
|
2030
|
+
INTO expected_keys FROM jsonb_array_elements(batch_ids) AS ids(id);
|
|
2031
|
+
EXECUTE $origin_retained$
|
|
2032
|
+
SELECT jsonb_object_agg(keys.eid, (
|
|
2033
|
+
SELECT jsonb_build_object('runId', event.run_id, 'kind', event.kind, 'digest', event.source_digest)
|
|
2034
|
+
FROM (${originLogIdentitySeekSql(scope, '$1', 'keys.eid')}) event
|
|
2035
|
+
))
|
|
2036
|
+
FROM jsonb_object_keys($2) AS keys(eid)
|
|
2037
|
+
$origin_retained$ INTO existing_identities USING run_org, expected_keys;
|
|
2038
|
+
-- JSON key lookups preserve one proof per identity without a join whose
|
|
2039
|
+
-- generic plan can multiply the two variable-size sets. Tombstones keep
|
|
2040
|
+
-- their digest/identity: retry never restores an expired body.
|
|
2041
|
+
-- Stream aligned IDs with their lines: positional text[] subscripting
|
|
2042
|
+
-- walks variable-width elements from the start on every lookup.
|
|
2043
|
+
WITH entries AS MATERIALIZED (
|
|
2044
|
+
SELECT body, 'log-origin:' || observation.run_id || ':' || id AS eid,
|
|
2045
|
+
NULLIF(batch_contexts->(ordinal-1)::int, 'null'::jsonb) AS ctx
|
|
2046
|
+
FROM ROWS FROM (jsonb_array_elements(batch_lines), jsonb_array_elements_text(batch_ids))
|
|
2047
|
+
WITH ORDINALITY AS aligned(body, id, ordinal)
|
|
2048
|
+
)
|
|
2049
|
+
SELECT count(*) INTO mismatches FROM entries
|
|
2050
|
+
WHERE existing_identities->eid->>'runId' IS DISTINCT FROM observation.run_id
|
|
2051
|
+
OR existing_identities->eid->>'kind' IS DISTINCT FROM ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
|
|
2052
|
+
OR existing_identities->eid->>'digest' IS DISTINCT FROM CASE WHEN ctx IS NULL
|
|
2053
|
+
THEN encode(sha256(convert_to(body #>> '{}','UTF8')),'hex')
|
|
2054
|
+
ELSE encode(sha256(convert_to(jsonb_build_array(body #>> '{}',ctx)::text,'UTF8')),'hex') END;
|
|
2055
|
+
IF mismatches > 0 THEN
|
|
2056
|
+
RAISE EXCEPTION 'Origin log identity changed its content or ingress is closed after terminal acceptance';
|
|
2057
|
+
END IF;
|
|
2058
|
+
RETURN QUERY SELECT * FROM unnest(fresh, target_tids, target_ordinals);
|
|
2059
|
+
RETURN;
|
|
2060
|
+
END $function$;
|
|
2061
|
+
REVOKE ALL ON FUNCTION ${schema}.project_origin_log_batch_with_targets(${schema}.outbox,jsonb,text,text,boolean,boolean,timestamptz,jsonb,boolean${numbered ? ',bigint' : ''}) FROM PUBLIC;
|
|
2062
|
+
END; $install_origin_log_targets$
|
|
2063
|
+
`;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
/** Deployment-only body/configuration proof. Never used for run admission. */
|
|
2067
|
+
export async function hasCurrentFreshLogSequenceAssignment(
|
|
2068
|
+
client: PostgresSchedulerQueryClient,
|
|
2069
|
+
runtimeScope: string,
|
|
2070
|
+
): Promise<boolean> {
|
|
2071
|
+
const scope = requiredScope(runtimeScope);
|
|
2072
|
+
const schema = quoteIdent(scope);
|
|
2073
|
+
const sql = freshLogSequenceAssignmentSql(scope);
|
|
2074
|
+
const delimiter = '$function$';
|
|
2075
|
+
const expectedBody = sql
|
|
2076
|
+
.slice(
|
|
2077
|
+
sql.indexOf(delimiter) + delimiter.length,
|
|
2078
|
+
sql.lastIndexOf(delimiter),
|
|
2079
|
+
)
|
|
2080
|
+
.trim();
|
|
2081
|
+
const result = await client.query<{
|
|
2082
|
+
body: string;
|
|
2083
|
+
result: string;
|
|
2084
|
+
security_definer: boolean;
|
|
2085
|
+
configuration: string[];
|
|
2086
|
+
private: boolean;
|
|
2087
|
+
owner_matches_projector: boolean;
|
|
2088
|
+
projector_body: string;
|
|
2089
|
+
targets_present: boolean;
|
|
2090
|
+
numbered_body: string;
|
|
2091
|
+
numbered_private: boolean;
|
|
2092
|
+
numbered_configuration: string[];
|
|
2093
|
+
numbered_owner_matches: boolean;
|
|
2094
|
+
}>(
|
|
2095
|
+
`SELECT helper.prosrc AS body, pg_get_function_result(helper.oid) AS result,
|
|
2096
|
+
helper.prosecdef AS security_definer, helper.proconfig AS configuration,
|
|
2097
|
+
NOT EXISTS (SELECT 1 FROM aclexplode(coalesce(helper.proacl, acldefault('f', helper.proowner))) privilege
|
|
2098
|
+
WHERE privilege.grantee = 0 AND privilege.privilege_type = 'EXECUTE') AS private,
|
|
2099
|
+
helper.proowner = projector.proowner AS owner_matches_projector,
|
|
2100
|
+
projector.prosrc AS projector_body,
|
|
2101
|
+
to_regprocedure($3) IS NOT NULL AS targets_present,
|
|
2102
|
+
numbered.prosrc AS numbered_body, numbered.proconfig AS numbered_configuration,
|
|
2103
|
+
numbered.proowner = projector.proowner AND numbered.prosecdef AS numbered_owner_matches,
|
|
2104
|
+
NOT EXISTS (SELECT 1 FROM aclexplode(coalesce(numbered.proacl, acldefault('f', numbered.proowner))) privilege
|
|
2105
|
+
WHERE privilege.grantee = 0 AND privilege.privilege_type = 'EXECUTE') AS numbered_private
|
|
2106
|
+
FROM pg_proc numbered CROSS JOIN pg_proc helper JOIN pg_proc projector ON projector.oid = to_regprocedure($2)
|
|
2107
|
+
WHERE helper.oid = to_regprocedure($1) AND numbered.oid = to_regprocedure($4)`,
|
|
2108
|
+
[
|
|
2109
|
+
`${schema}.assign_fresh_log_sequences(bigint,bigint,tid[],text[],text,text)`,
|
|
2110
|
+
`${schema}.project_outbox_event(${schema}.outbox)`,
|
|
2111
|
+
`${schema}.project_origin_log_batch_with_targets(${schema}.outbox,jsonb,text,text,boolean,boolean,timestamptz,jsonb,boolean)`,
|
|
2112
|
+
`${schema}.project_origin_log_batch_with_targets(${schema}.outbox,jsonb,text,text,boolean,boolean,timestamptz,jsonb,boolean,bigint)`,
|
|
2113
|
+
],
|
|
2114
|
+
);
|
|
2115
|
+
const row = result.rows[0];
|
|
2116
|
+
const numberedSql = originLogBatchProjectionSql(scope, true);
|
|
2117
|
+
const numberedBody = numberedSql
|
|
2118
|
+
.slice(
|
|
2119
|
+
numberedSql.indexOf(delimiter) + delimiter.length,
|
|
2120
|
+
numberedSql.lastIndexOf(delimiter),
|
|
2121
|
+
)
|
|
2122
|
+
.trim();
|
|
2123
|
+
const expectedCall =
|
|
2124
|
+
'assigned_seq + cardinality(fresh_event_ids)) AS target;';
|
|
2125
|
+
return Boolean(
|
|
2126
|
+
row &&
|
|
2127
|
+
row.body.trim() === expectedBody &&
|
|
2128
|
+
row.result === 'bigint' &&
|
|
2129
|
+
row.security_definer &&
|
|
2130
|
+
row.private &&
|
|
2131
|
+
row.owner_matches_projector &&
|
|
2132
|
+
row.targets_present &&
|
|
2133
|
+
row.numbered_body.trim() === numberedBody &&
|
|
2134
|
+
row.numbered_private &&
|
|
2135
|
+
row.numbered_owner_matches &&
|
|
2136
|
+
JSON.stringify(row.numbered_configuration) ===
|
|
2137
|
+
JSON.stringify(['search_path=pg_catalog']) &&
|
|
2138
|
+
JSON.stringify([...(row.configuration ?? [])].sort()) ===
|
|
2139
|
+
JSON.stringify(
|
|
2140
|
+
[
|
|
2141
|
+
'search_path=pg_catalog',
|
|
2142
|
+
'enable_seqscan=off',
|
|
2143
|
+
'enable_tidscan=on',
|
|
2144
|
+
].sort(),
|
|
2145
|
+
) &&
|
|
2146
|
+
row.projector_body.includes(expectedCall),
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
1840
2150
|
/**
|
|
1841
2151
|
* Migration installs readers with synchronous production unchanged. After
|
|
1842
2152
|
* cutover, the producer persists raw input and only the leased dispatcher
|
|
@@ -2037,112 +2347,40 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2037
2347
|
END
|
|
2038
2348
|
$function$
|
|
2039
2349
|
`);
|
|
2350
|
+
for (const numbered of [false, true]) {
|
|
2351
|
+
await client.query(originLogBatchProjectionSql(scope, numbered));
|
|
2352
|
+
}
|
|
2040
2353
|
await client.query(`
|
|
2041
2354
|
CREATE OR REPLACE FUNCTION ${schema}.project_origin_log_batch(observation ${schema}.outbox, item jsonb, run_org text, play text, compact_logs boolean, canonical_events boolean, at_time timestamptz, doc jsonb, closed boolean)
|
|
2042
|
-
RETURNS text[] LANGUAGE
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
SELECT
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
OR EXISTS (SELECT 1 FROM jsonb_each(ctx) field WHERE
|
|
2068
|
-
field.key !~ '^[A-Za-z_][A-Za-z0-9_]{0,63}$' OR field.key IN ('__proto__', 'constructor', 'prototype')
|
|
2069
|
-
OR jsonb_typeof(field.value) NOT IN ('string', 'number', 'boolean')
|
|
2070
|
-
OR (jsonb_typeof(field.value) = 'string' AND octet_length(field.value #>> '{}') > 256)
|
|
2071
|
-
OR (jsonb_typeof(field.value) = 'number' AND octet_length(field.value #>> '{}') > 32))
|
|
2072
|
-
)
|
|
2073
|
-
) THEN RAISE EXCEPTION 'Search projection log context has an unsupported key or value'; END IF;
|
|
2074
|
-
-- Generic plans cannot fold item field extraction or removal. Cache the
|
|
2075
|
-
-- aligned arrays and event header once: rebuilding a multi-megabyte item
|
|
2076
|
-
-- per line makes retained terminal envelopes quadratic even on replay.
|
|
2077
|
-
WITH entries AS MATERIALIZED (
|
|
2078
|
-
SELECT value #>> '{}' AS body, ordinality AS ordinal,
|
|
2079
|
-
'log-origin:' || observation.run_id || ':' || (batch_ids->>(ordinality-1)::int) AS eid,
|
|
2080
|
-
NULLIF(batch_contexts->(ordinality-1)::int, 'null'::jsonb) AS ctx,
|
|
2081
|
-
${schema}.run_log_metadata(value #>> '{}', batch_metadata->(ordinality-1)::int, at_time) AS metadata
|
|
2082
|
-
FROM jsonb_array_elements(batch_lines) WITH ORDINALITY
|
|
2083
|
-
), inserted AS (
|
|
2084
|
-
INSERT INTO runtime_observability.events (
|
|
2085
|
-
runtime_scope, org_id, event_id, event_seq, event_payload, occurred_at,
|
|
2086
|
-
run_id, play_name, kind, level, message, log_body, source_digest, log_text_digest, search_doc
|
|
2087
|
-
) SELECT ${quoteLiteral(scope)}, run_org, eid,
|
|
2088
|
-
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
2089
|
-
CASE WHEN canonical_events THEN jsonb_strip_nulls(batch_event || jsonb_build_object(
|
|
2090
|
-
'type', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'runId', observation.run_id,
|
|
2091
|
-
'occurredAt', metadata->'occurredAt', 'level', metadata->'level', 'stepId', metadata->'stepId', 'lineOrdinal', ordinal,
|
|
2092
|
-
'entryId', batch_ids->(ordinal-1)::int, 'context', ctx)) END,
|
|
2093
|
-
to_timestamp((metadata->>'occurredAt')::double precision / 1000), observation.run_id, play, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, metadata->>'level',
|
|
2094
|
-
CASE WHEN compact_logs THEN NULL ELSE left(body, 2048) END, body,
|
|
2095
|
-
CASE WHEN ctx IS NULL THEN encode(sha256(convert_to(body,'UTF8')),'hex')
|
|
2096
|
-
ELSE encode(sha256(convert_to(jsonb_build_array(body,ctx)::text,'UTF8')),'hex') END,
|
|
2097
|
-
encode(sha256(convert_to(btrim(body),'UTF8')),'hex'),
|
|
2098
|
-
doc || (metadata - 'occurredAt') || jsonb_strip_nulls(jsonb_build_object('entryId', batch_ids->(ordinal-1)::int, 'context', ctx))
|
|
2099
|
-
FROM entries WHERE NOT closed ORDER BY ordinal
|
|
2100
|
-
ON CONFLICT (runtime_scope, org_id, event_id) DO NOTHING RETURNING event_id
|
|
2101
|
-
) SELECT coalesce(array_agg(inserted.event_id ORDER BY entries.ordinal), ARRAY[]::text[])
|
|
2102
|
-
INTO fresh FROM inserted JOIN entries ON entries.eid = inserted.event_id;
|
|
2103
|
-
-- The INSERT already proves the content of every fresh identity. Only
|
|
2104
|
-
-- retries or a concurrent uniqueness winner need a journal read-back.
|
|
2105
|
-
-- This fast path serves both inline and background origin projection.
|
|
2106
|
-
IF cardinality(fresh) = jsonb_array_length(batch_lines) THEN
|
|
2107
|
-
RETURN fresh;
|
|
2355
|
+
RETURNS text[] LANGUAGE sql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
2356
|
+
SELECT coalesce(array_agg(target.fresh_id ORDER BY target.fresh_ordinal), ARRAY[]::text[])
|
|
2357
|
+
FROM ${schema}.project_origin_log_batch_with_targets(observation, item, run_org, play, compact_logs, canonical_events, at_time, doc, closed) AS target
|
|
2358
|
+
$function$
|
|
2359
|
+
`);
|
|
2360
|
+
await client.query(`DO $install_fresh_log_sequences$ BEGIN
|
|
2361
|
+
${freshLogSequenceAssignmentSql(scope)};
|
|
2362
|
+
REVOKE ALL ON FUNCTION ${schema}.assign_fresh_log_sequences(bigint,bigint,tid[],text[],text,text),
|
|
2363
|
+
${schema}.project_origin_log_batch_with_targets(${schema}.outbox,jsonb,text,text,boolean,boolean,timestamptz,jsonb,boolean)
|
|
2364
|
+
FROM PUBLIC;
|
|
2365
|
+
END; $install_fresh_log_sequences$`);
|
|
2366
|
+
const acquireLogCursorSql = `
|
|
2367
|
+
IF NOT cursor_locked THEN
|
|
2368
|
+
-- One allocation owner supplies final sequences at INSERT time. The
|
|
2369
|
+
-- row fence serializes only this run and survives until the lease fence.
|
|
2370
|
+
INSERT INTO runtime_observability.run_log_cursors(runtime_scope, org_id, run_id, last_seq)
|
|
2371
|
+
VALUES (${quoteLiteral(scope)}, run_org, observation.run_id, 1)
|
|
2372
|
+
ON CONFLICT (runtime_scope, org_id, run_id) DO NOTHING RETURNING last_seq INTO assigned_seq;
|
|
2373
|
+
cursor_created := FOUND;
|
|
2374
|
+
IF cursor_created THEN
|
|
2375
|
+
assigned_seq := 0;
|
|
2376
|
+
ELSE
|
|
2377
|
+
SELECT last_seq INTO assigned_seq FROM runtime_observability.run_log_cursors
|
|
2378
|
+
WHERE runtime_scope = ${quoteLiteral(scope)} AND org_id = run_org
|
|
2379
|
+
AND run_id = observation.run_id FOR UPDATE;
|
|
2108
2380
|
END IF;
|
|
2109
|
-
|
|
2110
|
-
-- Resolve the whole key set once. A per-line LATERAL lookup can choose
|
|
2111
|
-
-- a non-leading event_id index scan when scope/org statistics are skewed,
|
|
2112
|
-
-- re-reading the same history for every ID even though the PK is unique.
|
|
2113
|
-
-- Generic plans may scan the scope/org prefix and test ANY(text[]) for
|
|
2114
|
-
-- every row. Use the indexed run prefix and a once-built JSON key set.
|
|
2115
|
-
SELECT jsonb_object_agg('log-origin:' || observation.run_id || ':' || (id #>> '{}'), true)
|
|
2116
|
-
INTO expected_keys FROM jsonb_array_elements(batch_ids) AS ids(id);
|
|
2117
|
-
SELECT jsonb_object_agg(event.event_id, jsonb_build_object(
|
|
2118
|
-
'runId', event.run_id, 'kind', event.kind, 'digest', event.source_digest))
|
|
2119
|
-
INTO existing_identities
|
|
2120
|
-
FROM runtime_observability.events event
|
|
2121
|
-
WHERE event.runtime_scope = ${quoteLiteral(scope)} AND event.org_id = run_org
|
|
2122
|
-
AND event.run_id = observation.run_id AND expected_keys ? event.event_id;
|
|
2123
|
-
-- JSON key lookups preserve one proof per identity without a join whose
|
|
2124
|
-
-- generic plan can multiply the two variable-size sets. Tombstones keep
|
|
2125
|
-
-- their digest/identity: retry never restores an expired body.
|
|
2126
|
-
-- Stream aligned IDs with their lines: positional text[] subscripting
|
|
2127
|
-
-- walks variable-width elements from the start on every lookup.
|
|
2128
|
-
WITH entries AS MATERIALIZED (
|
|
2129
|
-
SELECT body, 'log-origin:' || observation.run_id || ':' || id AS eid,
|
|
2130
|
-
NULLIF(batch_contexts->(ordinal-1)::int, 'null'::jsonb) AS ctx
|
|
2131
|
-
FROM ROWS FROM (jsonb_array_elements(batch_lines), jsonb_array_elements_text(batch_ids))
|
|
2132
|
-
WITH ORDINALITY AS aligned(body, id, ordinal)
|
|
2133
|
-
)
|
|
2134
|
-
SELECT count(*) INTO mismatches FROM entries
|
|
2135
|
-
WHERE existing_identities->eid->>'runId' IS DISTINCT FROM observation.run_id
|
|
2136
|
-
OR existing_identities->eid->>'kind' IS DISTINCT FROM ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
|
|
2137
|
-
OR existing_identities->eid->>'digest' IS DISTINCT FROM CASE WHEN ctx IS NULL
|
|
2138
|
-
THEN encode(sha256(convert_to(body #>> '{}','UTF8')),'hex')
|
|
2139
|
-
ELSE encode(sha256(convert_to(jsonb_build_array(body #>> '{}',ctx)::text,'UTF8')),'hex') END;
|
|
2140
|
-
IF mismatches > 0 THEN
|
|
2141
|
-
RAISE EXCEPTION 'Origin log identity changed its content or ingress is closed after terminal acceptance';
|
|
2381
|
+
cursor_locked := true;
|
|
2142
2382
|
END IF;
|
|
2143
|
-
|
|
2144
|
-
END $function$
|
|
2145
|
-
`);
|
|
2383
|
+
`;
|
|
2146
2384
|
await client.query(`
|
|
2147
2385
|
CREATE OR REPLACE FUNCTION ${schema}.project_outbox_event(observation ${schema}.outbox) RETURNS void
|
|
2148
2386
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
|
|
@@ -2153,7 +2391,9 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2153
2391
|
log_channel text; log_position bigint; matched_event_id text; matched_doc jsonb;
|
|
2154
2392
|
legacy_position_prefix text; position_event_ids text[];
|
|
2155
2393
|
log_alias jsonb; position_alias jsonb; envelope_attempt jsonb;
|
|
2156
|
-
line_count bigint; fresh_event_ids text[] := ARRAY[]::text[];
|
|
2394
|
+
cursor_created boolean := false; cursor_locked boolean := false; line_count bigint; fresh_event_ids text[] := ARRAY[]::text[];
|
|
2395
|
+
fresh_tids tid[] := ARRAY[]::tid[]; inserted_tid tid;
|
|
2396
|
+
batch_fresh_ids text[]; batch_fresh_tids tid[]; fresh_target_index integer;
|
|
2157
2397
|
recent_log_keys jsonb := '{}'::jsonb; dedupe_key text;
|
|
2158
2398
|
terminal_overlap_counts jsonb := '{}'::jsonb;
|
|
2159
2399
|
recent_log_keys_loaded boolean := false; recent_log_attempt text;
|
|
@@ -2333,6 +2573,9 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2333
2573
|
IF kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended} AND jsonb_typeof(item->'lines') IS DISTINCT FROM 'array' THEN
|
|
2334
2574
|
RAISE EXCEPTION 'Search projection requires log.appended lines';
|
|
2335
2575
|
END IF;
|
|
2576
|
+
IF kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended} AND jsonb_array_length(item->'lines') > 0 THEN
|
|
2577
|
+
${acquireLogCursorSql}
|
|
2578
|
+
END IF;
|
|
2336
2579
|
IF kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended} AND item ? 'lineContexts'
|
|
2337
2580
|
AND (jsonb_typeof(item->'lineContexts') IS DISTINCT FROM 'array'
|
|
2338
2581
|
OR jsonb_array_length(item->'lineContexts') <> jsonb_array_length(item->'lines')) THEN
|
|
@@ -2349,8 +2592,14 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2349
2592
|
END IF;
|
|
2350
2593
|
IF EXISTS (SELECT 1 FROM jsonb_array_elements(item->'lineIds') id WHERE id <> 'null'::jsonb AND (jsonb_typeof(id) <> 'string' OR (id #>> '{}') !~ '^[A-Za-z0-9:_-]{1,128}$')) OR (SELECT count(id) FROM jsonb_array_elements(item->'lineIds') id WHERE id <> 'null'::jsonb) <> (SELECT count(DISTINCT id) FROM jsonb_array_elements(item->'lineIds') id WHERE id <> 'null'::jsonb) THEN RAISE EXCEPTION 'Origin log identities must be distinct bounded strings'; END IF;
|
|
2351
2594
|
IF NOT EXISTS (SELECT 1 FROM jsonb_array_elements(item->'lineIds') id WHERE id = 'null'::jsonb) THEN
|
|
2352
|
-
|
|
2353
|
-
|
|
2595
|
+
SELECT coalesce(array_agg(target.fresh_id ORDER BY target.fresh_ordinal), ARRAY[]::text[]),
|
|
2596
|
+
coalesce(array_agg(target.fresh_tid ORDER BY target.fresh_ordinal), ARRAY[]::tid[])
|
|
2597
|
+
INTO batch_fresh_ids, batch_fresh_tids
|
|
2598
|
+
FROM ${schema}.project_origin_log_batch_with_targets(observation, item, run_org, play, compact_logs, canonical_events, at_time, doc,
|
|
2599
|
+
compact_logs AND observation.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents} AND coalesce((observation.payload_json->>'_runtimeSearchRunClosed')::boolean, run_status IN ('completed','failed','cancelled')),
|
|
2600
|
+
assigned_seq + cardinality(fresh_event_ids)) AS target;
|
|
2601
|
+
fresh_event_ids := fresh_event_ids || batch_fresh_ids;
|
|
2602
|
+
fresh_tids := fresh_tids || batch_fresh_tids;
|
|
2354
2603
|
CONTINUE;
|
|
2355
2604
|
END IF;
|
|
2356
2605
|
END IF;
|
|
@@ -2541,7 +2790,13 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2541
2790
|
UPDATE runtime_observability.events SET search_doc = matched_doc ||
|
|
2542
2791
|
jsonb_build_object('logContentDigest', digest, 'logPositions',
|
|
2543
2792
|
coalesce(matched_doc->'logPositions', '{}'::jsonb) || jsonb_build_object(log_channel, log_position))
|
|
2544
|
-
WHERE runtime_scope = ${quoteLiteral(scope)} AND org_id = run_org AND event_id = matched_event_id
|
|
2793
|
+
WHERE runtime_scope = ${quoteLiteral(scope)} AND org_id = run_org AND event_id = matched_event_id
|
|
2794
|
+
RETURNING ctid INTO inserted_tid;
|
|
2795
|
+
-- Aliasing a fresh line moves its tuple within this transaction.
|
|
2796
|
+
fresh_target_index := array_position(fresh_event_ids, matched_event_id);
|
|
2797
|
+
IF fresh_target_index IS NOT NULL THEN
|
|
2798
|
+
fresh_tids[fresh_target_index] := inserted_tid;
|
|
2799
|
+
END IF;
|
|
2545
2800
|
CONTINUE;
|
|
2546
2801
|
END IF;
|
|
2547
2802
|
line_doc := line_doc || log_alias || jsonb_build_object('logContentDigest', digest);
|
|
@@ -2560,7 +2815,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2560
2815
|
INSERT INTO runtime_observability.events (
|
|
2561
2816
|
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
2562
2817
|
occurred_at, run_id, play_name, kind, level, message, log_body,
|
|
2563
|
-
source_digest, log_text_digest, search_doc
|
|
2818
|
+
source_digest, log_text_digest, search_doc, log_seq
|
|
2564
2819
|
) VALUES (
|
|
2565
2820
|
${quoteLiteral(scope)}, run_org, eid,
|
|
2566
2821
|
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
@@ -2574,9 +2829,11 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2574
2829
|
line_at, observation.run_id, play,
|
|
2575
2830
|
${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, line_metadata->>'level', CASE WHEN compact_logs THEN NULL ELSE left(line #>> '{}', 2048) END,
|
|
2576
2831
|
line #>> '{}', digest,
|
|
2577
|
-
encode(sha256(convert_to(btrim(line #>> '{}'), 'UTF8')), 'hex'), line_doc
|
|
2578
|
-
|
|
2832
|
+
encode(sha256(convert_to(btrim(line #>> '{}'), 'UTF8')), 'hex'), line_doc,
|
|
2833
|
+
assigned_seq + cardinality(fresh_event_ids) + 1
|
|
2834
|
+
) ON CONFLICT DO NOTHING RETURNING ctid INTO inserted_tid;
|
|
2579
2835
|
IF FOUND THEN
|
|
2836
|
+
fresh_tids := array_append(fresh_tids, inserted_tid);
|
|
2580
2837
|
fresh_event_ids := array_append(fresh_event_ids, eid);
|
|
2581
2838
|
ELSE
|
|
2582
2839
|
SELECT source_digest INTO existing_digest FROM runtime_observability.events
|
|
@@ -2714,6 +2971,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2714
2971
|
SELECT value, ordinality
|
|
2715
2972
|
FROM jsonb_array_elements(observation.payload_json->'terminalLogLines') WITH ORDINALITY
|
|
2716
2973
|
LOOP
|
|
2974
|
+
${acquireLogCursorSql}
|
|
2717
2975
|
IF jsonb_typeof(line) IS DISTINCT FROM 'string' THEN
|
|
2718
2976
|
RAISE EXCEPTION 'Search projection requires terminal log strings';
|
|
2719
2977
|
END IF;
|
|
@@ -2765,7 +3023,7 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2765
3023
|
INSERT INTO runtime_observability.events (
|
|
2766
3024
|
runtime_scope, org_id, event_id, event_seq, event_payload,
|
|
2767
3025
|
occurred_at, run_id, play_name, kind, level, message, log_body,
|
|
2768
|
-
source_digest, log_text_digest, search_doc
|
|
3026
|
+
source_digest, log_text_digest, search_doc, log_seq
|
|
2769
3027
|
) VALUES (
|
|
2770
3028
|
${quoteLiteral(scope)}, run_org, observation.event_id || ':log:' || line_ordinal,
|
|
2771
3029
|
CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
|
|
@@ -2783,9 +3041,10 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2783
3041
|
THEN encode(sha256(convert_to(line #>> '{}', 'UTF8')), 'hex')
|
|
2784
3042
|
ELSE encode(sha256(convert_to(jsonb_build_array(line #>> '{}', line_context)::text, 'UTF8')), 'hex') END,
|
|
2785
3043
|
encode(sha256(convert_to(btrim(line #>> '{}'), 'UTF8')), 'hex'),
|
|
2786
|
-
line_doc
|
|
2787
|
-
) ON CONFLICT DO NOTHING;
|
|
3044
|
+
line_doc, assigned_seq + cardinality(fresh_event_ids) + 1
|
|
3045
|
+
) ON CONFLICT DO NOTHING RETURNING ctid INTO inserted_tid;
|
|
2788
3046
|
IF FOUND THEN
|
|
3047
|
+
fresh_tids := array_append(fresh_tids, inserted_tid);
|
|
2789
3048
|
fresh_event_ids := array_append(
|
|
2790
3049
|
fresh_event_ids,
|
|
2791
3050
|
observation.event_id || ':log:' || line_ordinal
|
|
@@ -2796,27 +3055,14 @@ export async function attachRuntimeEventSearchTriggers(
|
|
|
2796
3055
|
END IF;
|
|
2797
3056
|
line_count := cardinality(fresh_event_ids);
|
|
2798
3057
|
IF line_count > 0 THEN
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
-- as tiny, then compare every inserted line against the entire ID list
|
|
2808
|
-
-- in a nested loop or a per-row ANY filter. Fresh IDs came only from
|
|
2809
|
-
-- this transaction's inserts for this scope/org/run, so JSON key
|
|
2810
|
-
-- membership preserves the complete target within the indexed run.
|
|
2811
|
-
SELECT jsonb_object_agg(fresh.event_id, fresh.ordinality)
|
|
2812
|
-
INTO fresh_positions
|
|
2813
|
-
FROM unnest(fresh_event_ids) WITH ORDINALITY AS fresh(event_id, ordinality);
|
|
2814
|
-
UPDATE runtime_observability.events AS event
|
|
2815
|
-
SET log_seq = assigned_seq - line_count + (fresh_positions->>event.event_id)::bigint
|
|
2816
|
-
WHERE event.runtime_scope = ${quoteLiteral(scope)}
|
|
2817
|
-
AND event.org_id = run_org
|
|
2818
|
-
AND event.run_id = observation.run_id
|
|
2819
|
-
AND fresh_positions ? event.event_id;
|
|
3058
|
+
UPDATE runtime_observability.run_log_cursors
|
|
3059
|
+
SET last_seq = assigned_seq + line_count, updated_at = clock_timestamp()
|
|
3060
|
+
WHERE runtime_scope = ${quoteLiteral(scope)} AND org_id = run_org
|
|
3061
|
+
AND run_id = observation.run_id;
|
|
3062
|
+
ELSIF cursor_created THEN
|
|
3063
|
+
DELETE FROM runtime_observability.run_log_cursors
|
|
3064
|
+
WHERE runtime_scope = ${quoteLiteral(scope)} AND org_id = run_org
|
|
3065
|
+
AND run_id = observation.run_id;
|
|
2820
3066
|
END IF;
|
|
2821
3067
|
RETURN;
|
|
2822
3068
|
END
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
reportSetupCompleted,
|
|
14
14
|
resolvePendingAuthClaimForResume,
|
|
15
15
|
statusBannerLine
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-3FF3SPSE.mjs";
|
|
17
|
+
import "./chunk-3DWMYKWU.mjs";
|
|
18
|
+
import "./chunk-TPYRDAU7.mjs";
|
|
19
19
|
import "./chunk-YHPYUZZX.mjs";
|
|
20
20
|
import "./chunk-DLIRSHV6.mjs";
|
|
21
21
|
import "./chunk-NXGCO4JB.mjs";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-EKWJXPIX.mjs";
|
|
23
23
|
import "./chunk-WZYKRD7T.mjs";
|
|
24
24
|
import "./chunk-KRD6H754.mjs";
|
|
25
25
|
export {
|