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.
Files changed (92) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +4 -4
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +4 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +114 -50
  6. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +6 -2
  7. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +379 -133
  8. package/dist/cli/{auth-AHF355KV.mjs → auth-D4GXO4IN.mjs} +4 -4
  9. package/dist/cli/auth-IJKAKAHG.js +40 -0
  10. package/dist/cli/{billing-LSZECOCV.mjs → billing-NVV5VSUJ.mjs} +4 -4
  11. package/dist/cli/{billing-5L5X2OAC.js → billing-TQ4UM4D4.js} +73 -73
  12. package/dist/cli/{chunk-VNO3VAJT.mjs → chunk-3DWMYKWU.mjs} +1 -1
  13. package/dist/cli/{chunk-CC3QT7Q5.mjs → chunk-3FF3SPSE.mjs} +2 -2
  14. package/dist/cli/{chunk-JZ7TJL7G.js → chunk-3MVBOBV4.js} +2 -2
  15. package/dist/cli/{chunk-RXVJYS76.mjs → chunk-4ONG2MWB.mjs} +1 -1
  16. package/dist/cli/{chunk-57GMG2DV.js → chunk-6HXTHMHZ.js} +26 -26
  17. package/dist/cli/{chunk-SGPGIC3M.js → chunk-AAIV3ACM.js} +50 -50
  18. package/dist/cli/{chunk-QN55SQYI.mjs → chunk-B6ME2XIO.mjs} +5 -5
  19. package/dist/cli/{chunk-FQL75GML.js → chunk-BZBV2GY2.js} +8 -8
  20. package/dist/cli/{chunk-V4PLPRWQ.js → chunk-CB6AJNHI.js} +4 -4
  21. package/dist/cli/{chunk-JHNYOTWR.mjs → chunk-EKWJXPIX.mjs} +1 -1
  22. package/dist/cli/{chunk-GJECLPJP.mjs → chunk-EVLCUV64.mjs} +4 -4
  23. package/dist/cli/{chunk-C27DGNII.mjs → chunk-HG5PMETP.mjs} +5 -5
  24. package/dist/cli/{chunk-DJSPH5T2.js → chunk-HYAA3F6U.js} +10 -10
  25. package/dist/cli/{chunk-4BWZA57I.js → chunk-JPNBAZBR.js} +6 -6
  26. package/dist/cli/{chunk-77FZLY5L.js → chunk-LM2JHRZM.js} +1 -1
  27. package/dist/cli/{chunk-KTBMDDEQ.mjs → chunk-MPXPTSGJ.mjs} +3 -3
  28. package/dist/cli/{chunk-J3OZIYU4.js → chunk-MZ4LBCV7.js} +15 -15
  29. package/dist/cli/{chunk-MJ4OWXFM.js → chunk-NAJEB3N4.js} +44 -44
  30. package/dist/cli/{chunk-5SGQF4QF.js → chunk-NLT6GCWZ.js} +8 -8
  31. package/dist/cli/{chunk-HPEIY4UR.mjs → chunk-TECEX3K5.mjs} +3 -3
  32. package/dist/cli/{chunk-BYBRENKM.mjs → chunk-TPYRDAU7.mjs} +1 -1
  33. package/dist/cli/{chunk-VYDYDELF.js → chunk-WGX7SIGL.js} +65 -65
  34. package/dist/cli/{chunk-B4Y2CZKS.mjs → chunk-YRZIKG47.mjs} +2 -2
  35. package/dist/cli/{chunk-MQWZYCQJ.mjs → chunk-YX7JSSJ2.mjs} +2 -2
  36. package/dist/cli/{csv-IO2BKRNX.js → csv-5AASYDRF.js} +12 -12
  37. package/dist/cli/{csv-J6ZPUWE4.mjs → csv-H7LD6IQK.mjs} +3 -3
  38. package/dist/cli/{db-QICV6JWY.mjs → db-CG3UQUA7.mjs} +4 -4
  39. package/dist/cli/{db-ZVRBVSPU.js → db-MJBZBDWZ.js} +25 -25
  40. package/dist/cli/{describe-WX6VWGH6.js → describe-2A7GARI2.js} +10 -10
  41. package/dist/cli/{describe-YA35XS55.mjs → describe-INFC5ACQ.mjs} +6 -6
  42. package/dist/cli/{executions-YQAUBJLS.js → executions-AUDQ5HOJ.js} +6 -6
  43. package/dist/cli/{executions-X3PDLFKO.mjs → executions-DO47P6MX.mjs} +4 -4
  44. package/dist/cli/{feedback-M2WMOY5B.js → feedback-MNWNDZVU.js} +7 -7
  45. package/dist/cli/{feedback-HSERSHDV.mjs → feedback-XGZSJM2N.mjs} +3 -3
  46. package/dist/cli/{git-3JMX455D.mjs → git-2ECFQKAA.mjs} +4 -4
  47. package/dist/cli/git-6S64UBWC.js +24 -0
  48. package/dist/cli/index.js +89 -89
  49. package/dist/cli/index.mjs +32 -32
  50. package/dist/cli/{legacy-noops-EMUCJ5KW.js → legacy-noops-3UZTD4DS.js} +7 -7
  51. package/dist/cli/{legacy-noops-OKC6VDD3.mjs → legacy-noops-534TKEMO.mjs} +4 -4
  52. package/dist/cli/{monitors-RH3FIA63.mjs → monitors-5ARELE2S.mjs} +4 -4
  53. package/dist/cli/{monitors-M6DU2JXC.js → monitors-G4S54JSX.js} +84 -84
  54. package/dist/cli/{org-KK3C5J6E.mjs → org-BAMOOXQW.mjs} +4 -4
  55. package/dist/cli/{org-BZDZO32O.js → org-RIWF2TLI.js} +64 -64
  56. package/dist/cli/{play-ZKYHO2KC.js → play-32XSQMFK.js} +231 -231
  57. package/dist/cli/{play-QXBNDI6C.mjs → play-WV4UNGPR.mjs} +7 -7
  58. package/dist/cli/{providers-JRN5FNXI.mjs → providers-SMSRO23R.mjs} +4 -4
  59. package/dist/cli/{providers-VCT7HFYW.js → providers-XKY2CNKH.js} +7 -7
  60. package/dist/cli/{quickstart-Y33XAQDY.mjs → quickstart-BFYYEVSS.mjs} +3 -3
  61. package/dist/cli/{quickstart-X2IC2U6U.js → quickstart-Q2TA72HD.js} +5 -5
  62. package/dist/cli/search-GK7P3AZY.js +30 -0
  63. package/dist/cli/{search-R443WBN7.mjs → search-I2TVB2P5.mjs} +5 -5
  64. package/dist/cli/{secrets-U6V4M7R3.js → secrets-B4NYTSL7.js} +13 -13
  65. package/dist/cli/{secrets-EUKR5NG2.mjs → secrets-BHKICIE3.mjs} +4 -4
  66. package/dist/cli/{sessions-RZVHJSVB.mjs → sessions-IOR2ZNNH.mjs} +4 -4
  67. package/dist/cli/{sessions-IPKYJT4M.js → sessions-XY2UREJ7.js} +5 -5
  68. package/dist/cli/{settings-W4YFY5JJ.js → settings-ASGROQDE.js} +74 -74
  69. package/dist/cli/{settings-FKU7KJ24.mjs → settings-W6S7B4NH.mjs} +7 -7
  70. package/dist/cli/{setup-6WIECL4V.mjs → setup-UUWGI3IN.mjs} +6 -6
  71. package/dist/cli/{setup-BEIN6536.js → setup-YIYQ73OV.js} +41 -41
  72. package/dist/cli/{skills-CV72BOP7.mjs → skills-CHA4QCGI.mjs} +4 -4
  73. package/dist/cli/skills-LKNKHB2I.js +29 -0
  74. package/dist/cli/{tools-2ADAYBNB.js → tools-43ZFJWAQ.js} +7 -7
  75. package/dist/cli/{tools-2SH4RIFD.mjs → tools-PMZDYKTT.mjs} +6 -6
  76. package/dist/cli/{update-YTR4JUPG.mjs → update-6VSEGTSG.mjs} +6 -6
  77. package/dist/cli/update-R7FODDIW.js +29 -0
  78. package/dist/cli/{workflow-446CNNRV.js → workflow-26EOXZ7M.js} +15 -15
  79. package/dist/cli/{workflow-BBSL2QKK.mjs → workflow-SVMVLQOF.mjs} +5 -5
  80. package/dist/index.js +6 -6
  81. package/dist/index.mjs +3 -3
  82. package/dist/install-integrity.json +70 -70
  83. package/dist/release.d.mts +1 -1
  84. package/dist/release.d.ts +1 -1
  85. package/dist/release.js +2 -2
  86. package/dist/release.mjs +1 -1
  87. package/package.json +1 -1
  88. package/dist/cli/auth-O7A7JS2M.js +0 -40
  89. package/dist/cli/git-IUDHAE5O.js +0 -24
  90. package/dist/cli/search-7257KVSQ.js +0 -30
  91. package/dist/cli/skills-A7ATFQEL.js +0 -29
  92. package/dist/cli/update-AUYYCJUT.js +0 -29
@@ -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 plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
2043
- DECLARE fresh text[]; mismatches bigint; expected_keys jsonb; existing_identities jsonb;
2044
- batch_lines jsonb := item->'lines';
2045
- batch_ids jsonb := item->'lineIds';
2046
- batch_contexts jsonb := item->'lineContexts';
2047
- batch_metadata jsonb := item->'lineMetadata';
2048
- batch_event jsonb := item - 'lines' - 'lineContexts' - 'lineIds' - 'lineMetadata';
2049
- BEGIN
2050
- IF jsonb_typeof(batch_ids) IS DISTINCT FROM 'array'
2051
- OR jsonb_array_length(batch_ids) <> jsonb_array_length(batch_lines)
2052
- OR EXISTS (SELECT 1 FROM jsonb_array_elements(batch_ids) id WHERE jsonb_typeof(id) <> 'string' OR (id #>> '{}') !~ '^[A-Za-z0-9:_-]{1,128}$')
2053
- OR (SELECT count(DISTINCT id) FROM jsonb_array_elements(batch_ids) id) <> jsonb_array_length(batch_ids) THEN
2054
- RAISE EXCEPTION 'Origin log identities must be distinct bounded strings aligned with lines';
2055
- END IF;
2056
- IF EXISTS (SELECT 1 FROM jsonb_array_elements(batch_lines) line WHERE jsonb_typeof(line) <> 'string') THEN
2057
- RAISE EXCEPTION 'Search projection requires log line strings';
2058
- END IF;
2059
- IF item ? 'lineContexts' AND EXISTS (
2060
- SELECT 1 FROM jsonb_array_elements(batch_contexts) ctx
2061
- WHERE jsonb_typeof(ctx) NOT IN ('object', 'null')
2062
- ) THEN RAISE EXCEPTION 'Search projection log context must be an object'; END IF;
2063
- IF item ? 'lineContexts' AND EXISTS (
2064
- SELECT 1 FROM jsonb_array_elements(batch_contexts) ctx
2065
- WHERE jsonb_typeof(ctx) = 'object' AND (
2066
- (SELECT count(*) FROM jsonb_object_keys(ctx)) > 8 OR octet_length(ctx::text) > 2064
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
- -- A new READ COMMITTED statement sees a concurrent uniqueness winner.
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
- RETURN fresh;
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[]; fresh_positions jsonb;
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
- fresh_event_ids := fresh_event_ids || ${schema}.project_origin_log_batch(observation, item, run_org, play, compact_logs, canonical_events, at_time, doc,
2353
- 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')));
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
- ) ON CONFLICT DO NOTHING;
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
- INSERT INTO runtime_observability.run_log_cursors (
2800
- runtime_scope, org_id, run_id, last_seq
2801
- ) VALUES (${quoteLiteral(scope)}, run_org, observation.run_id, line_count)
2802
- ON CONFLICT (runtime_scope, org_id, run_id)
2803
- DO UPDATE SET last_seq = runtime_observability.run_log_cursors.last_seq + EXCLUDED.last_seq,
2804
- updated_at = clock_timestamp()
2805
- RETURNING last_seq INTO assigned_seq;
2806
- -- Build ordinal lookup once. Generic plans estimate a run and unnest
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-CC3QT7Q5.mjs";
17
- import "./chunk-VNO3VAJT.mjs";
18
- import "./chunk-BYBRENKM.mjs";
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-JHNYOTWR.mjs";
22
+ import "./chunk-EKWJXPIX.mjs";
23
23
  import "./chunk-WZYKRD7T.mjs";
24
24
  import "./chunk-KRD6H754.mjs";
25
25
  export {