deepline 0.3.373 → 0.3.375

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 (101) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +1 -53
  3. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +15 -23
  4. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +65 -3
  5. package/dist/cli/auth-B7CLYD7R.js +35 -0
  6. package/dist/cli/{auth-7BMCDCRH.mjs → auth-U3Y7DTIG.mjs} +6 -4
  7. package/dist/cli/{billing-GKI7B4JF.js → billing-P2DNRAND.js} +73 -73
  8. package/dist/cli/{billing-RZEHHMPU.mjs → billing-TKARGBO3.mjs} +4 -4
  9. package/dist/cli/{chunk-IUYKSTWB.mjs → chunk-4GP56LRE.mjs} +12 -12
  10. package/dist/cli/{chunk-JSK6GUY6.js → chunk-4WINCNNH.js} +13 -9
  11. package/dist/cli/{chunk-CLEB5KUZ.js → chunk-ADSZGPYZ.js} +1 -1
  12. package/dist/cli/{chunk-E6BZWURC.mjs → chunk-B2LFDTXZ.mjs} +15 -6
  13. package/dist/cli/{chunk-JP2YA3AV.mjs → chunk-B3TQIJ6L.mjs} +1 -1
  14. package/dist/cli/{chunk-NVZAZGHA.js → chunk-CUIBDSSS.js} +8 -8
  15. package/dist/cli/{chunk-E7MGPPM5.js → chunk-DAG4ZBTJ.js} +1495 -1495
  16. package/dist/cli/{chunk-YPVTQRUM.js → chunk-DHTG5CCI.js} +15 -6
  17. package/dist/cli/{chunk-EYXSAB7O.js → chunk-F5CU5ALX.js} +26 -26
  18. package/dist/cli/{chunk-46JCJ7FT.mjs → chunk-FWF24ULX.mjs} +13 -9
  19. package/dist/cli/{chunk-JHUMUIU6.js → chunk-GN5QM2BB.js} +6 -6
  20. package/dist/cli/{chunk-BM47AKUZ.js → chunk-HEF6UDGA.js} +235 -235
  21. package/dist/cli/{chunk-ORBVXCTL.js → chunk-HP4BE43N.js} +52 -51
  22. package/dist/cli/{chunk-YHHY2YHK.mjs → chunk-HSIVHAZA.mjs} +3 -3
  23. package/dist/cli/{chunk-P5JPGZNI.mjs → chunk-IG3GOO4T.mjs} +1 -1
  24. package/dist/cli/{chunk-ND4BVWWZ.js → chunk-IQTHRG6E.js} +15 -15
  25. package/dist/cli/{chunk-S5OTS6H5.js → chunk-KK4XPUVA.js} +8 -8
  26. package/dist/cli/{chunk-IHQQNWU4.js → chunk-LZUWGLM4.js} +2 -2
  27. package/dist/cli/{chunk-W3H422KP.js → chunk-M2HVNJ47.js} +60 -60
  28. package/dist/cli/{chunk-HNLXC5BP.mjs → chunk-M5QDOHLG.mjs} +1 -1
  29. package/dist/cli/{chunk-XMME4SCK.mjs → chunk-MKS7C3YR.mjs} +3 -2
  30. package/dist/cli/{chunk-ECA6NQYL.mjs → chunk-MPQSBHMA.mjs} +4 -4
  31. package/dist/cli/{chunk-D2CZ4Y5G.js → chunk-MTBQEND4.js} +15 -15
  32. package/dist/cli/{chunk-XJYUQBHY.js → chunk-NVXMPGM4.js} +65 -84
  33. package/dist/cli/{chunk-6JCVL7CE.mjs → chunk-QEEIEHGV.mjs} +2 -2
  34. package/dist/cli/{chunk-TB73PXE3.mjs → chunk-SJVJZOPT.mjs} +2 -2
  35. package/dist/cli/{chunk-QSV76EMO.mjs → chunk-STYH5U6W.mjs} +7 -7
  36. package/dist/cli/{chunk-R6TPSZLN.mjs → chunk-Z4ARN4X3.mjs} +2 -2
  37. package/dist/cli/{chunk-PZ26EKNC.mjs → chunk-ZFWU5V2A.mjs} +13 -32
  38. package/dist/cli/{chunk-Y32GUTEN.mjs → chunk-ZHXPNJQI.mjs} +1467 -1467
  39. package/dist/cli/command-docs.js +2 -2
  40. package/dist/cli/command-docs.mjs +1 -1
  41. package/dist/cli/{csv-3FNCVMWA.mjs → csv-3FTYTEJ3.mjs} +3 -3
  42. package/dist/cli/{csv-NFQYQWWO.js → csv-WAEVGKHF.js} +12 -12
  43. package/dist/cli/{db-VA7HULGR.mjs → db-HUGGFIVS.mjs} +4 -4
  44. package/dist/cli/{db-CTLGRV3X.js → db-UDMDGNKO.js} +29 -29
  45. package/dist/cli/{describe-GXGRMP2M.mjs → describe-EOBOGTB5.mjs} +130 -20
  46. package/dist/cli/{describe-IUHOEPKY.js → describe-SIAKZYRK.js} +144 -34
  47. package/dist/cli/{executions-DSK3HRPU.js → executions-HYYP27MG.js} +6 -6
  48. package/dist/cli/{executions-OZQDZQIK.mjs → executions-O6Z2HSLK.mjs} +4 -4
  49. package/dist/cli/{feedback-KTMLMWKV.js → feedback-3DWSBHKS.js} +7 -7
  50. package/dist/cli/{feedback-ZOY7R4JD.mjs → feedback-RRAMGMQV.mjs} +3 -3
  51. package/dist/cli/{git-FIOI4IA2.mjs → git-KVAK7VOL.mjs} +4 -4
  52. package/dist/cli/git-M36KWCCG.js +23 -0
  53. package/dist/cli/index.js +106 -91
  54. package/dist/cli/index.mjs +63 -48
  55. package/dist/cli/{legacy-noops-KQZWSABN.mjs → legacy-noops-MQNCDMY7.mjs} +4 -4
  56. package/dist/cli/{legacy-noops-WXAYRQMQ.js → legacy-noops-V5GQXXUR.js} +7 -7
  57. package/dist/cli/{monitors-VHNWIF4V.mjs → monitors-6FB63TDM.mjs} +4 -4
  58. package/dist/cli/{monitors-LPUCHRNJ.js → monitors-RFGQILPW.js} +85 -85
  59. package/dist/cli/{org-GWHWBCMD.js → org-L33EPH2E.js} +64 -64
  60. package/dist/cli/{org-UW7NPGEY.mjs → org-TZPQDDVX.mjs} +4 -4
  61. package/dist/cli/{play-FNM2E66T.mjs → play-PU23OETO.mjs} +8 -8
  62. package/dist/cli/play-YR2XQARF.js +155 -0
  63. package/dist/cli/{providers-6D6R3PWX.js → providers-2RHIKO22.js} +7 -7
  64. package/dist/cli/{providers-XAZMYDWS.mjs → providers-QGQR6ZZF.mjs} +4 -4
  65. package/dist/cli/{quickstart-OK65PGZL.mjs → quickstart-24DQYBW7.mjs} +3 -3
  66. package/dist/cli/{quickstart-HBUWBVH6.js → quickstart-BGQVPI7W.js} +5 -5
  67. package/dist/cli/{search-XY6JOTS2.mjs → search-EUN7YFMX.mjs} +5 -5
  68. package/dist/cli/search-OHELMK6E.js +26 -0
  69. package/dist/cli/{secrets-W2LDLU2W.mjs → secrets-OOHN6LNJ.mjs} +4 -4
  70. package/dist/cli/{secrets-6CYCPK7T.js → secrets-XL7GUTQA.js} +13 -13
  71. package/dist/cli/{sessions-GOYEKNTJ.js → sessions-FD5XB7M2.js} +5 -5
  72. package/dist/cli/{sessions-JAX7PGXU.mjs → sessions-SDN4C3YS.mjs} +4 -4
  73. package/dist/cli/{settings-OACUQOCM.mjs → settings-3MXRXYXI.mjs} +7 -7
  74. package/dist/cli/{settings-QMFYFCT7.js → settings-6ZEKVAYL.js} +74 -74
  75. package/dist/cli/{setup-2KA5TZBZ.mjs → setup-DWTR6HNT.mjs} +6 -6
  76. package/dist/cli/{setup-36VOOGVA.js → setup-VKQ5EJZR.js} +41 -41
  77. package/dist/cli/skills-6GFW4GCW.js +28 -0
  78. package/dist/cli/{skills-QY4BX5ZX.mjs → skills-ISG5UUZW.mjs} +4 -4
  79. package/dist/cli/{tools-UCB2QRVC.mjs → tools-JTC3DR6M.mjs} +7 -7
  80. package/dist/cli/tools-LK4L6VCK.js +40 -0
  81. package/dist/cli/{update-327MHUO4.mjs → update-GMTVIHCG.mjs} +6 -6
  82. package/dist/cli/update-PSJLAW4Q.js +28 -0
  83. package/dist/cli/{workflow-G7BU54KW.js → workflow-2765Y3Y5.js} +15 -15
  84. package/dist/cli/{workflow-HJN4GA5A.mjs → workflow-3POT3VFY.mjs} +5 -5
  85. package/dist/index.js +8 -8
  86. package/dist/index.mjs +7 -7
  87. package/dist/install-integrity.json +76 -78
  88. package/dist/release.d.mts +1 -1
  89. package/dist/release.d.ts +1 -1
  90. package/dist/release.js +2 -2
  91. package/dist/release.mjs +1 -1
  92. package/package.json +1 -1
  93. package/dist/cli/auth-BWCNRUG3.js +0 -33
  94. package/dist/cli/client-FO6LMHT4.mjs +0 -20
  95. package/dist/cli/client-KAWK7FJR.js +0 -20
  96. package/dist/cli/git-5P5FLEUC.js +0 -23
  97. package/dist/cli/play-EVBIC7Z5.js +0 -155
  98. package/dist/cli/search-6ZY2XWP2.js +0 -26
  99. package/dist/cli/skills-6GPIEPTP.js +0 -28
  100. package/dist/cli/tools-RTECP4WT.js +0 -40
  101. package/dist/cli/update-WR65EHBN.js +0 -28
@@ -21,7 +21,7 @@ import {
21
21
  RETAINED_PIPELINE_RECEIVERS_HEADER,
22
22
  RUNTIME_ENVIRONMENT_TOKEN_HEADER,
23
23
  resolveConfig
24
- } from "./chunk-IUYKSTWB.mjs";
24
+ } from "./chunk-4GP56LRE.mjs";
25
25
  import {
26
26
  DeeplineError,
27
27
  TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
@@ -29,11 +29,6 @@ import {
29
29
  ToolExecutionError
30
30
  } from "./chunk-NXGCO4JB.mjs";
31
31
 
32
- // ../plays/trigger-config-route.ts
33
- function playTriggerConfigPath(playName) {
34
- return `/api/v2/plays/${encodeURIComponent(playName)}/triggers`;
35
- }
36
-
37
32
  // src/csv-header-matching.ts
38
33
  var CSV_HEADER_MATCHING_NOTE = 'A header matches a column when it equals the column name or its default header, ignoring case, spaces and punctuation: LINKEDIN_URL, linkedin-url and "Linkedin Url" all match linkedin_url. A capital inside a word starts a new word, so "LinkedIn URL" reads as linked_in_url and does not match. Map any other header in the input: {"csv":"<path>","columns":{"<column>":"<your header>"}}.';
39
34
 
@@ -57,31 +52,6 @@ async function* iterateCursorPages(fetchPage) {
57
52
  }
58
53
  }
59
54
 
60
- // src/plays/input-example.ts
61
- function playInputExampleArgs(schema) {
62
- if (!schema) return " --input '@<input.json>'";
63
- if (schema.$ref || schema.allOf) return " --input '@<input.json>'";
64
- const csv = schema.csvInput;
65
- const csvField = typeof csv?.inputField === "string" ? csv.inputField : null;
66
- const required = Array.isArray(schema.required) ? schema.required : [];
67
- const csvOnlyRequired = required.every((key) => key === csvField);
68
- const alternatives = schema.oneOf ?? schema.anyOf;
69
- const hasCsvAlternative = Array.isArray(alternatives) && alternatives.some((branch) => {
70
- if (!branch || typeof branch !== "object") return false;
71
- const keys = branch.required;
72
- return Array.isArray(keys) && keys.includes(csvField) && keys.every((key) => key === csvField);
73
- });
74
- if (csvField && csvOnlyRequired && (!alternatives || hasCsvAlternative)) {
75
- if (csvField === "csv") return " --csv '<contacts.csv>'";
76
- const value = JSON.stringify({ [csvField]: "<contacts.csv>" });
77
- return ` --input '${value.replace(/'/g, "'\\''")}'`;
78
- }
79
- if (Array.isArray(schema.required) && !required.length && !csv && !alternatives) {
80
- return "";
81
- }
82
- return " --input '@<input.json>'";
83
- }
84
-
85
55
  // src/billing-idempotency.ts
86
56
  function requireBillingIdempotencyKey(value) {
87
57
  const normalized = value.trim();
@@ -747,302 +717,166 @@ var PRODUCT_NOTIFICATION_DELIVERY_LEASE_MS = 2 * 6e4;
747
717
  var PRODUCT_NOTIFICATION_SUCCESS_TTL_MS = 15 * 6e4;
748
718
  var PRODUCT_NOTIFICATION_FAILURE_TTL_MS = 24 * 60 * 6e4;
749
719
 
750
- // ../product-notifications/configuration.ts
751
- var PRODUCT_NOTIFICATION_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
752
- function normalizeProductNotificationEmail(value) {
753
- const email = typeof value === "string" ? value.trim().toLowerCase() : "";
754
- return email && email.length <= 320 && PRODUCT_NOTIFICATION_EMAIL_PATTERN.test(email) ? email : null;
755
- }
756
- var DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING = {
757
- mode: "rolling_limit",
758
- windowMs: 36e5
720
+ // ../play-runtime/log-provenance.ts
721
+ var LOG_LEVELS = [
722
+ "debug",
723
+ "info",
724
+ "warn",
725
+ "error"
726
+ ];
727
+ var LOG_LEVEL_RANK = {
728
+ debug: 0,
729
+ info: 1,
730
+ warn: 2,
731
+ error: 3
759
732
  };
760
-
761
- // ../play-runtime/live-events.ts
762
- function projectPlayStepProgressCounters(progress) {
763
- return {
764
- completed: progress.completed,
765
- total: progress.total,
766
- failed: progress.failed,
767
- startedRows: progress.startedRows,
768
- activeRows: progress.activeRows,
769
- waitingRows: progress.waitingRows,
770
- completedRows: progress.completedRows,
771
- supersededRows: progress.supersededRows
772
- };
733
+ var LOG_PROVENANCE_CLASSES = [
734
+ "user",
735
+ "lifecycle",
736
+ "replay",
737
+ "infra",
738
+ "diagnostic",
739
+ "receipt"
740
+ ];
741
+ var LOG_PROVENANCE_POLICY = {
742
+ user: { watch: true, ui: true, debug: true },
743
+ lifecycle: { watch: true, ui: true, debug: true },
744
+ replay: { watch: false, ui: false, debug: true },
745
+ infra: { watch: false, ui: false, debug: true },
746
+ diagnostic: { watch: true, ui: true, debug: true },
747
+ receipt: { watch: false, ui: false, debug: true }
748
+ };
749
+ function logProvenanceReaches(provenance, surface) {
750
+ return LOG_PROVENANCE_POLICY[provenance][surface];
773
751
  }
774
- function resolveTimingWindow(input) {
775
- const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
776
- const completedAt = typeof input.completedAt === "number" && Number.isFinite(input.completedAt) ? input.completedAt : null;
777
- const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : null;
752
+ var PROVENANCE_SENTINEL = "";
753
+ var PROVENANCE_PREFIX = `${PROVENANCE_SENTINEL}prov:`;
754
+ function logLevelReaches(level, minimum) {
755
+ return LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[minimum];
756
+ }
757
+ function parseLogLevel(value) {
758
+ const normalized = value.trim().toLowerCase();
759
+ return LOG_LEVELS.includes(normalized) ? normalized : null;
760
+ }
761
+ function readProvenanceTag(line) {
762
+ if (!line.startsWith(PROVENANCE_PREFIX)) {
763
+ return { provenance: null, line };
764
+ }
765
+ const end = line.indexOf(PROVENANCE_SENTINEL, PROVENANCE_PREFIX.length);
766
+ if (end === -1) {
767
+ return { provenance: null, line };
768
+ }
769
+ const candidate = line.slice(PROVENANCE_PREFIX.length, end);
770
+ const provenance = LOG_PROVENANCE_CLASSES.includes(candidate) ? candidate : null;
771
+ return { provenance, line: line.slice(end + 1) };
772
+ }
773
+ function classifyLegacyLogLine(line) {
774
+ const message = stripLeadingTimestamp(line);
775
+ if (/recovered (?:from checkpoint|response from checkpoint)/i.test(message)) {
776
+ return "replay";
777
+ }
778
+ if (/^\[perf\] runtime receipt\b/i.test(message)) {
779
+ return "receipt";
780
+ }
781
+ if (/^\[perf\]/i.test(message) || /^\[runtime\.(?:phase_summary|logs|log_transport)\]/i.test(message) || /\[worker\] picked up run\b/.test(message) || /\[worker\] heartbeat\b/.test(message) || /\[worker\] progress completedRows=\d+ totalRows=\d+ rowUpdates=\d+/.test(
782
+ message
783
+ ) || /\[worker\] step started\b/.test(message) || /\[worker\] Preparing run files\b/.test(message) || /\[worker\] Run files ready\b/.test(message) || /\[worker\] Runtime ready\b/.test(message) || /\[worker\] Sandbox (?:starting|create start|create done|workspace ready|upload start|runner uploaded)\b/.test(
784
+ message
785
+ ) || /^\[event\] play\.step\.progress\b/.test(message) || /^\[event\] play\.run\.snapshot\b/.test(message) || /^\[event\] play\.sheet\.summary\b/.test(message)) {
786
+ return "infra";
787
+ }
788
+ if (/^\[warn\]/i.test(message) || /^\[error\]/i.test(message) || /^\[runtime\.[a-z_]*(?:failure|error)\]/i.test(message)) {
789
+ return "diagnostic";
790
+ }
791
+ return "user";
792
+ }
793
+ function classifyLogLine(line) {
794
+ const tagged = readProvenanceTag(line);
795
+ const provenance = tagged.provenance ?? classifyLegacyLogLine(tagged.line);
796
+ if (tagged.provenance !== null) {
797
+ return {
798
+ provenance,
799
+ level: inferLogLevel(provenance, tagged.line),
800
+ line: tagged.line
801
+ };
802
+ }
778
803
  return {
779
- startedAt,
780
- completedAt,
781
- updatedAt
804
+ provenance,
805
+ level: inferLogLevel(provenance, tagged.line),
806
+ line: tagged.line
782
807
  };
783
808
  }
784
-
785
- // ../play-runtime/run-failure.ts
786
- var PUBLIC_FAILURE_TEXT_BYTE_LIMIT = 64 * 1024;
787
-
788
- // ../play-runtime/log-context.ts
789
- var utf8 = new TextEncoder();
790
-
791
- // ../play-runtime/run-log-buffer.ts
792
- var RUNNER_LOG_MAX_BYTES = 5 * 1024 * 1024;
793
- var RUNNER_LOG_CONTEXT_MAX_BYTES = 128 * 1024;
794
- var TERMINAL_RESERVE_BYTES = 64 * 1024;
795
- var encoder = new TextEncoder();
796
-
797
- // ../play-runtime/secret-redaction.ts
798
- var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
799
- var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
800
- var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
801
- var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
802
- var EMAIL_LOCAL_CHAR_RE = /[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]/;
803
- var EMAIL_DOMAIN_RE = /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?\.[A-Za-z]{2,63}\b/;
804
- function isEmailLocalPart(value, start, end) {
805
- let localStart = start;
806
- while (localStart > 0 && EMAIL_LOCAL_CHAR_RE.test(value[localStart - 1])) {
807
- localStart -= 1;
809
+ function inferLogLevel(provenance, line) {
810
+ const message = stripLeadingTimestamp(line);
811
+ if (/^\[info\]/i.test(message)) {
812
+ return "info";
808
813
  }
809
- let localEnd = end;
810
- while (localEnd < value.length && EMAIL_LOCAL_CHAR_RE.test(value[localEnd])) {
811
- localEnd += 1;
814
+ if (/^\[debug\]/i.test(message)) {
815
+ return "debug";
812
816
  }
813
- return localStart < localEnd && localEnd - localStart <= 64 && value[localEnd] === "@" && EMAIL_DOMAIN_RE.test(value.slice(localEnd + 1));
817
+ if (/^\[console\.error\]/i.test(message) || /^\[error\]/i.test(message)) {
818
+ return "error";
819
+ }
820
+ if (/^\[console\.warn\]/i.test(message) || /^\[warn\]/i.test(message)) {
821
+ return "warn";
822
+ }
823
+ if (/^\[console\.debug\]/i.test(message)) {
824
+ return "debug";
825
+ }
826
+ if (provenance === "replay" || provenance === "infra" || provenance === "receipt") {
827
+ return "debug";
828
+ }
829
+ if (provenance === "diagnostic") {
830
+ return "warn";
831
+ }
832
+ return "info";
814
833
  }
815
- function redactTokenUnlessEmail(match, offset, source) {
816
- return isEmailLocalPart(source, offset, offset + match.length) ? match : SECRET_REDACTION_PLACEHOLDER;
834
+ function stripLeadingTimestamp(line) {
835
+ const match = line.match(/^\[([^\]]+)\]\s*([\s\S]*)$/);
836
+ if (!match) {
837
+ return line;
838
+ }
839
+ const inner = match[1] ?? "";
840
+ const isTimestamp = !Number.isNaN(new Date(inner).getTime());
841
+ return isTimestamp ? match[2] ?? line : line;
817
842
  }
818
- var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
819
- var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
820
- var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
821
- var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
822
- var URL_SECRET_VALUE_RE = /\b(?:[spr]k|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
823
- var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
824
- var HIGH_ENTROPY_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password|authorization|(?:access|refresh)[_-]?token)\b\s*[:=]\s*["']?[^"',\s]{16,}["']?/gi;
825
- var SENSITIVE_URL_PARAM_RE = /[?#&](?:\w*(?:key|token)|secret|password|authorization|credential|signature|sig)(?:=|$)/i;
826
- var SENSITIVE_FIELD_NAME_RE = new RegExp(
827
- `(^|[._-])(?:${SENSITIVE_FIELD_NAME})$`,
828
- "i"
829
- );
830
- var SENSITIVE_FIELD_NAME_CACHE_MAX_ENTRIES = 4096;
831
- var sensitiveFieldNameCache = /* @__PURE__ */ new Map();
832
- function isSensitiveFieldName(key) {
833
- const cached = sensitiveFieldNameCache.get(key);
834
- if (cached !== void 0) return cached;
835
- const sensitive = SENSITIVE_FIELD_NAME_RE.test(key);
836
- if (sensitiveFieldNameCache.size >= SENSITIVE_FIELD_NAME_CACHE_MAX_ENTRIES) {
837
- sensitiveFieldNameCache.clear();
843
+
844
+ // src/runs/read-errors.ts
845
+ function isTransientPlayRunReadError(error) {
846
+ if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
847
+ return true;
838
848
  }
839
- sensitiveFieldNameCache.set(key, sensitive);
840
- return sensitive;
849
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
850
+ return error.statusCode >= 500 && error.statusCode < 600;
851
+ }
852
+ const text = error instanceof Error ? error.message : String(error);
853
+ return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
854
+ text
855
+ );
841
856
  }
842
- var SERIALIZED_SECRET_FIELD_RE = new RegExp(
843
- `"(${SENSITIVE_FIELD_NAME})"\\s*:\\s*(?:"[^"\\\\]*"|\\{[^{}]*\\})`,
844
- "gi"
845
- );
846
- function escapeRegExp(value) {
847
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
857
+
858
+ // src/runs/log-cursor.ts
859
+ var RUN_LOGS_CURSOR_PREFIX = "before:";
860
+ function encodeRunLogsCursor(beforeSequence) {
861
+ return `${RUN_LOGS_CURSOR_PREFIX}${beforeSequence}`;
848
862
  }
849
- var REDACTION_TRIGGER_RE = new RegExp(
850
- [
851
- // Sensitive field names, in object keys and in `name=`/`"name":` text.
852
- "token",
853
- "secret",
854
- "password",
855
- "authorization",
856
- "cookie",
857
- "credential",
858
- // One `key` branch instead of `api[_-]?key`, `private[_-]?key` and `key_`.
859
- // A regex alternation costs roughly one substring scan per branch, and a
860
- // broader branch can only send more payloads down the exact path.
861
- "key",
862
- // Self-identifying credential prefixes.
863
- "bearer",
864
- "basic",
865
- "-----BEGIN",
866
- "xox[baprs]-",
867
- "[prs]k_",
868
- "pat_",
869
- "ghp_",
870
- // URL credentials: `//user:pass@host` userinfo and sensitive query params.
871
- // The userinfo class excludes `/` exactly as the rule it guards does.
872
- "//[^\\s/]{0,256}@",
873
- "[?#&]sig",
874
- // JWTs carry no keyword at all, so the shape itself is the marker. Anchor
875
- // on the separating dot and drop JWT_RE's leading `{8,}` segment: a dot is
876
- // rare enough for the engine to skip between candidates, which makes this
877
- // gate roughly ten times cheaper than re-running JWT_RE itself, and
878
- // dropping a required segment can only widen what it accepts.
879
- "\\.[A-Za-z0-9_-]{1,16384}\\.[A-Za-z0-9_-]{8}"
880
- ].join("|"),
881
- "i"
882
- );
883
- function mightContainRedactableSecret(value) {
884
- return REDACTION_TRIGGER_RE.test(value);
885
- }
886
- function redactSecretLikeString(value) {
887
- if (!mightContainRedactableSecret(value)) return value;
888
- const serializedRedacted = value.replace(
889
- SERIALIZED_SECRET_FIELD_RE,
890
- `"$1":"${SECRET_REDACTION_PLACEHOLDER}"`
891
- );
892
- const trimmed = serializedRedacted.trim();
893
- if (/^https?:\/\/\S+$/i.test(trimmed)) {
894
- if (/^https?:\/\/[^/?#@]+@/i.test(trimmed) || SENSITIVE_URL_PARAM_RE.test(trimmed)) {
895
- return SECRET_REDACTION_PLACEHOLDER;
896
- }
897
- if (!URL_SECRET_VALUE_RE.test(serializedRedacted)) {
898
- return serializedRedacted;
899
- }
900
- }
901
- return serializedRedacted.replace(PRIVATE_KEY_RE, SECRET_REDACTION_PLACEHOLDER).replace(BEARER_TOKEN_RE, `Bearer ${SECRET_REDACTION_PLACEHOLDER}`).replace(BASIC_CREDENTIAL_RE, `Basic ${SECRET_REDACTION_PLACEHOLDER}`).replace(JWT_RE, redactTokenUnlessEmail).replace(COMMON_SECRET_RE, redactTokenUnlessEmail).replace(GENERIC_PREFIXED_SECRET_RE, redactTokenUnlessEmail).replace(GENERIC_SECRET_ASSIGNMENT_RE, SECRET_REDACTION_PLACEHOLDER).replace(HIGH_ENTROPY_ASSIGNMENT_RE, (match) => {
902
- const separator = match.includes("=") ? "=" : ":";
903
- const [key] = match.split(separator, 1);
904
- return `${key.trim()}${separator}${SECRET_REDACTION_PLACEHOLDER}`;
905
- });
863
+ function isRunLogsCursor(value) {
864
+ return value.startsWith(RUN_LOGS_CURSOR_PREFIX);
906
865
  }
907
- function createSecretRedactionContext(initialValues = []) {
908
- const exactSecrets = /* @__PURE__ */ new Set();
909
- const exactSecretMatchers = /* @__PURE__ */ new Map();
910
- function register(value) {
911
- if (value.length > 0) exactSecrets.add(value);
912
- }
913
- function matchersForSecret(secret) {
914
- const cached = exactSecretMatchers.get(secret);
915
- if (cached) return cached;
916
- const literals = [secret];
917
- try {
918
- const encoded = encodeURIComponent(secret);
919
- if (encoded !== secret) literals.push(encoded);
920
- } catch {
921
- }
922
- const built = literals.map((literal) => ({
923
- literal,
924
- matcher: new RegExp(escapeRegExp(literal), "g")
925
- }));
926
- exactSecretMatchers.set(secret, built);
927
- return built;
928
- }
929
- for (const value of initialValues) register(value);
930
- function containsRegisteredSecret(value, {
931
- includeEncoded = false,
932
- minimumLength = 1
933
- } = {}) {
934
- for (const secret of exactSecrets) {
935
- if (secret.length < minimumLength) continue;
936
- if (value.includes(secret)) return true;
937
- if (includeEncoded) {
938
- try {
939
- const encoded = encodeURIComponent(secret);
940
- if (encoded !== secret && value.includes(encoded)) return true;
941
- } catch {
942
- }
943
- }
944
- }
945
- return false;
946
- }
947
- function matchesRegisteredSecret(value) {
948
- return exactSecrets.has(value);
949
- }
950
- function redactRegisteredSecrets(value) {
951
- if (exactSecrets.size === 0) return value;
952
- let output = value;
953
- for (const secret of exactSecrets) {
954
- for (const { literal, matcher } of matchersForSecret(secret)) {
955
- if (!output.includes(literal)) continue;
956
- matcher.lastIndex = 0;
957
- output = output.replace(matcher, SECRET_REDACTION_PLACEHOLDER);
958
- }
959
- }
960
- return output;
961
- }
962
- function redactString(value) {
963
- const output = redactRegisteredSecrets(value);
964
- return redactSecretLikeString(output);
965
- }
966
- function redact(value) {
967
- if (typeof value === "string") return redactString(value);
968
- if (Array.isArray(value)) {
969
- let changed = false;
970
- const next = new Array(value.length);
971
- for (let index = 0; index < value.length; index += 1) {
972
- const entry = value[index];
973
- const redacted = redact(entry);
974
- next[index] = redacted;
975
- if (redacted !== entry) changed = true;
976
- }
977
- return changed ? next : value;
978
- }
979
- if (value && typeof value === "object") {
980
- const prototype = Object.getPrototypeOf(value);
981
- let changed = prototype !== Object.prototype && prototype !== null;
982
- const source = value;
983
- const keys = Object.keys(source);
984
- const redactedValues = new Array(keys.length);
985
- for (let index = 0; index < keys.length; index += 1) {
986
- const key = keys[index];
987
- const entry = source[key];
988
- const redacted = isSensitiveFieldName(key) ? SECRET_REDACTION_PLACEHOLDER : redact(entry);
989
- redactedValues[index] = redacted;
990
- if (redacted !== entry) changed = true;
991
- }
992
- if (!changed) return value;
993
- return Object.fromEntries(
994
- keys.map((key, index) => [key, redactedValues[index]])
995
- );
996
- }
997
- return value;
998
- }
999
- function redactKnownSecrets(value) {
1000
- if (typeof value === "string") return redactRegisteredSecrets(value);
1001
- if (Array.isArray(value)) {
1002
- return value.map((entry) => redactKnownSecrets(entry));
1003
- }
1004
- if (value && typeof value === "object") {
1005
- return Object.fromEntries(
1006
- Object.entries(value).map(([key, entry]) => [
1007
- key,
1008
- redactKnownSecrets(entry)
1009
- ])
1010
- );
1011
- }
1012
- return value;
866
+ function parseRunLogsCursor(value) {
867
+ const raw = isRunLogsCursor(value) ? value.slice(RUN_LOGS_CURSOR_PREFIX.length) : "";
868
+ const parsed = /^[1-9]\d*$/.test(raw) ? Number(raw) : NaN;
869
+ if (!Number.isSafeInteger(parsed)) {
870
+ throw Object.assign(
871
+ new Error(
872
+ `Invalid run log cursor ${JSON.stringify(value)}. Pass the nextCursor value from a previous deepline runs logs result.`
873
+ ),
874
+ { exitCode: 2 }
875
+ );
1013
876
  }
1014
- return {
1015
- register,
1016
- containsRegisteredSecret,
1017
- matchesRegisteredSecret,
1018
- redactRegisteredSecrets,
1019
- redactString,
1020
- redactKnownSecrets,
1021
- redact
1022
- };
877
+ return parsed;
1023
878
  }
1024
879
 
1025
- // ../play-runtime/output-size-limits.ts
1026
- var TERMINAL_RUN_RESULT_MAX_BYTES = 6 * 1024 * 1024;
1027
- var LEDGER_TERMINAL_RESULT_MAX_BYTES = 256 * 1024;
1028
- var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 5 * 1024 * 1024;
1029
- var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 5 * 1024 * 1024;
1030
- var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
1031
- var RUNTIME_RECEIPT_COMPLETION_BUFFER_MAX_BYTES = 32 * 1024 * 1024;
1032
- var RUNTIME_RECEIPT_CONTROL_BUFFER_MAX_BYTES = 2 * 1024 * 1024;
1033
- var RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES = 16 * 1024 * 1024;
1034
- var RUNTIME_RECEIPT_GATEWAY_BATCH_MAX_BYTES = 12 * 1024 * 1024;
1035
- var RUNTIME_RECEIPT_WRITER_TARGET_BATCH_BYTES = 8 * 1024 * 1024;
1036
- var RUNNER_POST_TERMINAL_DIAGNOSTIC_MAX_BYTES = 64 * 1024;
1037
- var JSON_SIZE_LIMIT_REACHED = Symbol("JSON_SIZE_LIMIT_REACHED");
1038
-
1039
- // ../play-runtime/ledger-safe-payload.ts
1040
- var ledgerIngressRedactor = createSecretRedactionContext();
1041
-
1042
- // ../play-runtime/docflow-node-io.ts
1043
- var utf8Encoder = new TextEncoder();
1044
- var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
1045
-
1046
880
  // ../play-runtime/run-event-catalog.ts
1047
881
  var RUN_EVENT_KIND = {
1048
882
  runCreated: "run.created",
@@ -1458,1254 +1292,1420 @@ var RUN_EVENT_SEARCH_FALLBACK_KIND_SQL_LITERAL = sqlStringLiteral(
1458
1292
  RUN_EVENT_SEARCH_FALLBACK_KIND
1459
1293
  );
1460
1294
 
1461
- // ../play-data-plane/runtime-sheet-orm.ts
1462
- var RUNTIME_SHEET_CELL_META_COLUMN = {
1463
- name: "_cell_meta",
1464
- dataType: "jsonb",
1465
- nullable: "NO",
1466
- defaultSql: "'{}'::jsonb"
1295
+ // ../play-runtime/run-event-query.ts
1296
+ var stringField = { type: "string", nullable: false };
1297
+ var optionalString = { type: "string", nullable: true };
1298
+ var RUN_EVENT_QUERY_FIELD_CATALOG = {
1299
+ eventId: stringField,
1300
+ orgId: stringField,
1301
+ runtimeScope: stringField,
1302
+ runId: stringField,
1303
+ playName: stringField,
1304
+ kind: stringField,
1305
+ source: optionalString,
1306
+ occurredAt: { type: "timestamp", nullable: false },
1307
+ status: optionalString,
1308
+ level: { type: "level", nullable: true },
1309
+ stepId: optionalString,
1310
+ stepKind: optionalString,
1311
+ datasetId: optionalString,
1312
+ phase: optionalString,
1313
+ provider: optionalString,
1314
+ operation: optionalString,
1315
+ producerAttempt: { type: "number", nullable: true }
1467
1316
  };
1468
- var RUNTIME_SHEET_COLUMN_CONTRACTS = [
1469
- {
1470
- name: "_key",
1471
- sqlType: "text",
1472
- dataType: "text",
1473
- nullable: "NO",
1474
- defaultSql: null,
1475
- repairable: false,
1476
- requiresDefault: false
1477
- },
1478
- {
1479
- name: "_status",
1480
- sqlType: "text NOT NULL",
1481
- dataType: "text",
1482
- nullable: "NO",
1483
- defaultSql: null,
1484
- repairable: false,
1485
- requiresDefault: false
1486
- },
1487
- {
1488
- name: "_run_id",
1489
- sqlType: "text",
1490
- dataType: "text",
1491
- nullable: "YES",
1492
- defaultSql: null,
1493
- repairable: true,
1494
- requiresDefault: false
1495
- },
1496
- {
1497
- name: "_error",
1498
- sqlType: "text",
1499
- dataType: "text",
1500
- nullable: "YES",
1501
- defaultSql: null,
1502
- repairable: true,
1503
- requiresDefault: false
1504
- },
1505
- {
1506
- name: "_stage",
1507
- sqlType: "text",
1508
- dataType: "text",
1509
- nullable: "YES",
1510
- defaultSql: null,
1511
- repairable: true,
1512
- requiresDefault: false
1513
- },
1514
- {
1515
- name: "_provider",
1516
- sqlType: "text",
1517
- dataType: "text",
1518
- nullable: "YES",
1519
- defaultSql: null,
1520
- repairable: true,
1521
- requiresDefault: false
1522
- },
1523
- {
1524
- name: "_input_index",
1525
- sqlType: "bigint",
1526
- dataType: "bigint",
1527
- nullable: "YES",
1528
- defaultSql: null,
1529
- repairable: true,
1530
- requiresDefault: false
1531
- },
1532
- {
1533
- name: "_attempt_id",
1534
- sqlType: "text",
1535
- dataType: "text",
1536
- nullable: "YES",
1537
- defaultSql: null,
1538
- repairable: true,
1539
- requiresDefault: false
1540
- },
1541
- {
1542
- name: "_attempt_owner_run_id",
1543
- sqlType: "text",
1544
- dataType: "text",
1545
- nullable: "YES",
1546
- defaultSql: null,
1547
- repairable: true,
1548
- requiresDefault: false
1549
- },
1550
- {
1551
- name: "_attempt_expires_at",
1552
- sqlType: "timestamptz",
1553
- dataType: "timestamp with time zone",
1554
- nullable: "YES",
1555
- defaultSql: null,
1556
- repairable: true,
1557
- requiresDefault: false
1558
- },
1559
- {
1560
- name: "_attempt_seq",
1561
- sqlType: "integer",
1562
- dataType: "integer",
1563
- nullable: "YES",
1564
- defaultSql: null,
1565
- repairable: true,
1566
- requiresDefault: false
1567
- },
1568
- {
1569
- name: "_write_version",
1570
- sqlType: "bigint NOT NULL DEFAULT 0",
1571
- dataType: "bigint",
1572
- nullable: "NO",
1573
- defaultSql: "0",
1574
- repairable: true,
1575
- requiresDefault: true
1576
- },
1577
- {
1578
- name: "_writer_run_id",
1579
- sqlType: "text",
1580
- dataType: "text",
1581
- nullable: "YES",
1582
- defaultSql: null,
1583
- repairable: true,
1584
- requiresDefault: false
1585
- },
1586
- {
1587
- name: "_created_at",
1588
- sqlType: "timestamptz NOT NULL DEFAULT now()",
1589
- dataType: "timestamp with time zone",
1590
- nullable: "NO",
1591
- defaultSql: "now()",
1592
- repairable: true,
1593
- requiresDefault: true
1594
- },
1595
- {
1596
- name: "_updated_at",
1597
- sqlType: "timestamptz NOT NULL DEFAULT now()",
1598
- dataType: "timestamp with time zone",
1599
- nullable: "NO",
1600
- defaultSql: "now()",
1601
- repairable: true,
1602
- requiresDefault: true
1603
- },
1604
- {
1605
- name: "_version",
1606
- sqlType: "bigint NOT NULL",
1607
- dataType: "bigint",
1608
- nullable: "NO",
1609
- defaultSql: null,
1610
- defaultKind: "version_sequence",
1611
- repairable: true,
1612
- requiresDefault: true
1613
- },
1614
- {
1615
- ...RUNTIME_SHEET_CELL_META_COLUMN,
1616
- sqlType: `jsonb NOT NULL DEFAULT ${RUNTIME_SHEET_CELL_META_COLUMN.defaultSql}`,
1617
- repairable: true,
1618
- requiresDefault: true
1619
- }
1620
- ];
1621
- var RUNTIME_SHEET_ATTEMPT_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
1622
- (column) => column.name.startsWith("_attempt_")
1623
- ).map((column) => column.name);
1624
- var RUNTIME_SHEET_WRITE_VERSION_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
1625
- (column) => column.name === "_write_version" || column.name === "_writer_run_id"
1626
- ).map((column) => column.name);
1627
- var RUNTIME_SHEET_TIMESTAMP_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
1628
- (column) => column.name === "_created_at" || column.name === "_updated_at"
1629
- ).map((column) => column.name);
1630
- var RUNTIME_SHEET_SYSTEM_COLUMNS = new Set(
1631
- RUNTIME_SHEET_COLUMN_CONTRACTS.map((column) => column.name)
1632
- );
1633
- var RUNTIME_SHEET_REPAIRABLE_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
1634
- (column) => column.repairable
1635
- );
1636
-
1637
- // ../play-runtime/run-ledger.ts
1638
- var TERMINAL_STATUS_BY_EVENT_TYPE = {
1639
- [RUN_EVENT_KIND.runCompleted]: "completed",
1640
- [RUN_EVENT_KIND.runFailed]: "failed",
1641
- [RUN_EVENT_KIND.runCancelled]: "cancelled"
1642
- };
1643
-
1644
- // ../play-runtime/log-provenance.ts
1645
- var LOG_LEVELS = [
1646
- "debug",
1647
- "info",
1648
- "warn",
1649
- "error"
1650
- ];
1651
- var LOG_LEVEL_RANK = {
1652
- debug: 0,
1653
- info: 1,
1654
- warn: 2,
1655
- error: 3
1656
- };
1657
- var LOG_PROVENANCE_CLASSES = [
1658
- "user",
1659
- "lifecycle",
1660
- "replay",
1661
- "infra",
1662
- "diagnostic",
1663
- "receipt"
1317
+ var RUN_EVENT_QUERY_RETENTION_MS = 7 * 864e5;
1318
+ var RUN_EVENT_QUERY_MAX_LIMIT = 200;
1319
+ var RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT = 10;
1320
+ var RUN_EVENT_QUERY_MAX_CURSOR_BYTES = 48 * 1024;
1321
+ var RUN_EVENT_QUERY_MAX_REQUEST_BYTES = 64 * 1024;
1322
+ var MAX_BYTES = 8192;
1323
+ var MAX_DEPTH = 8;
1324
+ var MAX_PREDICATES = 64;
1325
+ var OPERATORS = [
1326
+ "eq",
1327
+ "ne",
1328
+ "gt",
1329
+ "gte",
1330
+ "lt",
1331
+ "lte",
1332
+ "exists",
1333
+ "in"
1664
1334
  ];
1665
- var LOG_PROVENANCE_POLICY = {
1666
- user: { watch: true, ui: true, debug: true },
1667
- lifecycle: { watch: true, ui: true, debug: true },
1668
- replay: { watch: false, ui: false, debug: true },
1669
- infra: { watch: false, ui: false, debug: true },
1670
- diagnostic: { watch: true, ui: true, debug: true },
1671
- receipt: { watch: false, ui: false, debug: true }
1335
+ var RunEventQueryInputError = class extends TypeError {
1336
+ constructor(message, code = "invalid_run_event_query") {
1337
+ super(message);
1338
+ this.code = code;
1339
+ this.name = "RunEventQueryInputError";
1340
+ }
1672
1341
  };
1673
- function logProvenanceReaches(provenance, surface) {
1674
- return LOG_PROVENANCE_POLICY[provenance][surface];
1675
- }
1676
- var PROVENANCE_SENTINEL = "";
1677
- var PROVENANCE_PREFIX = `${PROVENANCE_SENTINEL}prov:`;
1678
- function logLevelReaches(level, minimum) {
1679
- return LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[minimum];
1680
- }
1681
- function parseLogLevel(value) {
1682
- const normalized = value.trim().toLowerCase();
1683
- return LOG_LEVELS.includes(normalized) ? normalized : null;
1342
+ function fail(message) {
1343
+ throw new RunEventQueryInputError(message);
1684
1344
  }
1685
- function readProvenanceTag(line) {
1686
- if (!line.startsWith(PROVENANCE_PREFIX)) {
1687
- return { provenance: null, line };
1688
- }
1689
- const end = line.indexOf(PROVENANCE_SENTINEL, PROVENANCE_PREFIX.length);
1690
- if (end === -1) {
1691
- return { provenance: null, line };
1692
- }
1693
- const candidate = line.slice(PROVENANCE_PREFIX.length, end);
1694
- const provenance = LOG_PROVENANCE_CLASSES.includes(candidate) ? candidate : null;
1695
- return { provenance, line: line.slice(end + 1) };
1345
+ function object(value) {
1346
+ return value !== null && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
1696
1347
  }
1697
- function classifyLegacyLogLine(line) {
1698
- const message = stripLeadingTimestamp(line);
1699
- if (/recovered (?:from checkpoint|response from checkpoint)/i.test(message)) {
1700
- return "replay";
1701
- }
1702
- if (/^\[perf\] runtime receipt\b/i.test(message)) {
1703
- return "receipt";
1704
- }
1705
- if (/^\[perf\]/i.test(message) || /^\[runtime\.(?:phase_summary|logs|log_transport)\]/i.test(message) || /\[worker\] picked up run\b/.test(message) || /\[worker\] heartbeat\b/.test(message) || /\[worker\] progress completedRows=\d+ totalRows=\d+ rowUpdates=\d+/.test(
1706
- message
1707
- ) || /\[worker\] step started\b/.test(message) || /\[worker\] Preparing run files\b/.test(message) || /\[worker\] Run files ready\b/.test(message) || /\[worker\] Runtime ready\b/.test(message) || /\[worker\] Sandbox (?:starting|create start|create done|workspace ready|upload start|runner uploaded)\b/.test(
1708
- message
1709
- ) || /^\[event\] play\.step\.progress\b/.test(message) || /^\[event\] play\.run\.snapshot\b/.test(message) || /^\[event\] play\.sheet\.summary\b/.test(message)) {
1710
- return "infra";
1711
- }
1712
- if (/^\[warn\]/i.test(message) || /^\[error\]/i.test(message) || /^\[runtime\.[a-z_]*(?:failure|error)\]/i.test(message)) {
1713
- return "diagnostic";
1348
+ function bounded(value, maxBytes = MAX_BYTES) {
1349
+ let encoded;
1350
+ try {
1351
+ encoded = JSON.stringify(value);
1352
+ } catch {
1353
+ fail("Run event query must be bounded JSON without cycles.");
1714
1354
  }
1715
- return "user";
1355
+ if (!encoded || new TextEncoder().encode(encoded).length > maxBytes)
1356
+ fail(`Run event query exceeds ${maxBytes} bytes.`);
1716
1357
  }
1717
- function classifyLogLine(line) {
1718
- const tagged = readProvenanceTag(line);
1719
- const provenance = tagged.provenance ?? classifyLegacyLogLine(tagged.line);
1720
- if (tagged.provenance !== null) {
1721
- return {
1722
- provenance,
1723
- level: inferLogLevel(provenance, tagged.line),
1724
- line: tagged.line
1725
- };
1726
- }
1727
- return {
1728
- provenance,
1729
- level: inferLogLevel(provenance, tagged.line),
1730
- line: tagged.line
1358
+ function validateRunEventField(value) {
1359
+ if (typeof value !== "string") fail("Run event field must be a string.");
1360
+ const field = value.startsWith("$.") ? value.slice(2) : value;
1361
+ if (Object.prototype.hasOwnProperty.call(RUN_EVENT_QUERY_FIELD_CATALOG, field))
1362
+ return field;
1363
+ if (/^context\.[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(field) && !["__proto__", "prototype", "constructor"].includes(field.slice(8)))
1364
+ return field;
1365
+ const alternatives = {
1366
+ step: "stepId",
1367
+ severity: "level",
1368
+ timestamp: "occurredAt",
1369
+ play: "playName",
1370
+ run: "runId",
1371
+ attempt: "producerAttempt"
1731
1372
  };
1373
+ fail(
1374
+ `Unsupported run event field: ${field}. Use ${alternatives[field] ?? "a catalog field or context.<key>"}; call fields() for supported fields.`
1375
+ );
1732
1376
  }
1733
- function inferLogLevel(provenance, line) {
1734
- const message = stripLeadingTimestamp(line);
1735
- if (/^\[info\]/i.test(message)) {
1736
- return "info";
1737
- }
1738
- if (/^\[debug\]/i.test(message)) {
1739
- return "debug";
1740
- }
1741
- if (/^\[console\.error\]/i.test(message) || /^\[error\]/i.test(message)) {
1742
- return "error";
1743
- }
1744
- if (/^\[console\.warn\]/i.test(message) || /^\[warn\]/i.test(message)) {
1745
- return "warn";
1746
- }
1747
- if (/^\[console\.debug\]/i.test(message)) {
1748
- return "debug";
1749
- }
1750
- if (provenance === "replay" || provenance === "infra" || provenance === "receipt") {
1751
- return "debug";
1752
- }
1753
- if (provenance === "diagnostic") {
1754
- return "warn";
1377
+ function scalar(field, value, op) {
1378
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
1379
+ fail(`Run event ${field} requires a JSON scalar.`);
1380
+ if (typeof value === "number" && !Number.isFinite(value))
1381
+ fail(`Run event ${field} requires a finite number.`);
1382
+ if (typeof value === "string" && new TextEncoder().encode(value).length > 1024)
1383
+ fail(`Run event ${field} value exceeds 1024 bytes.`);
1384
+ const descriptor = RUN_EVENT_QUERY_FIELD_CATALOG[field];
1385
+ if (descriptor) {
1386
+ if (value === null) {
1387
+ if (!descriptor.nullable) fail(`Run event ${field} cannot be null.`);
1388
+ } else if (descriptor.type === "number") {
1389
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
1390
+ fail(`Run event ${field} requires a positive integer.`);
1391
+ } else if (typeof value !== "string")
1392
+ fail(`Run event ${field} requires a string.`);
1393
+ else if (descriptor.type === "level" && !RUN_LOG_LEVELS.includes(value))
1394
+ fail("Run event level requires debug, info, warn or error.");
1395
+ else if (descriptor.type === "timestamp") {
1396
+ value = normalizeRunEventQueryTimestamp(value);
1397
+ }
1755
1398
  }
1756
- return "info";
1399
+ if (["gt", "gte", "lt", "lte"].includes(op) && (value === null || typeof value === "boolean"))
1400
+ fail(`Run event ${op} requires a string or number.`);
1401
+ return value;
1757
1402
  }
1758
- function stripLeadingTimestamp(line) {
1759
- const match = line.match(/^\[([^\]]+)\]\s*([\s\S]*)$/);
1760
- if (!match) {
1761
- return line;
1403
+ function comparison(field, op, value) {
1404
+ if (!OPERATORS.includes(op))
1405
+ fail(`Unsupported run event operator: ${op}. Use ${OPERATORS.join(", ")}.`);
1406
+ if (op === "exists") {
1407
+ if (typeof value !== "boolean")
1408
+ fail("Run event exists requires a boolean.");
1409
+ return { field, op, value };
1410
+ }
1411
+ if (op === "in") {
1412
+ if (!Array.isArray(value) || value.length < 1 || value.length > 32)
1413
+ fail("Run event in requires between 1 and 32 scalars.");
1414
+ return { field, op, value: value.map((item) => scalar(field, item, "eq")) };
1762
1415
  }
1763
- const inner = match[1] ?? "";
1764
- const isTimestamp = !Number.isNaN(new Date(inner).getTime());
1765
- return isTimestamp ? match[2] ?? line : line;
1766
- }
1767
-
1768
- // ../play-runtime/run-snapshot-stream.ts
1769
- function isTerminalPlayRunLiveStatus(status) {
1770
- return isTerminalPlayRunLifecycleStatus(status);
1771
- }
1772
- function makeRunStreamEvent(input) {
1773
1416
  return {
1774
- ...input,
1775
- scope: "play",
1776
- at: input.at ?? (/* @__PURE__ */ new Date()).toISOString()
1417
+ field,
1418
+ op,
1419
+ value: scalar(field, value, op)
1777
1420
  };
1778
1421
  }
1779
- var EMPTY_PLAY_RUN_STREAM_DIFF_STATE = {
1780
- runSignature: "",
1781
- snapshotSignature: "",
1782
- stepStatusSignature: "",
1783
- stepProgressSignature: "",
1784
- lastLogSeq: 0
1785
- };
1786
- function getSnapshotCursor(snapshot) {
1787
- return String(snapshot.updatedAt ?? Date.now());
1422
+ function admitObject(input, depth = 0, budget = { count: 0 }) {
1423
+ if (depth > MAX_DEPTH) fail("Run event where exceeds depth 8.");
1424
+ if (!object(input) || !Object.keys(input).length)
1425
+ fail("Run event where requires a nonempty object.");
1426
+ const terms = [];
1427
+ const fieldTerms = (raw, filter) => {
1428
+ const field = validateRunEventField(raw);
1429
+ const operators = object(filter) ? Object.entries(filter) : [["eq", filter]];
1430
+ if (!operators.length)
1431
+ fail(`Run event ${field} has an empty operator object.`);
1432
+ for (const [op, value] of operators) {
1433
+ if (++budget.count > MAX_PREDICATES)
1434
+ fail("Run event where exceeds 64 predicates.");
1435
+ terms.push(comparison(field, op, value));
1436
+ }
1437
+ };
1438
+ for (const [key, filter] of Object.entries(input)) {
1439
+ if (key === "and" || key === "or") {
1440
+ if (!Array.isArray(filter) || filter.length < 1 || filter.length > MAX_PREDICATES)
1441
+ fail(`Run event ${key} requires a nonempty bounded array.`);
1442
+ const children = filter.map(
1443
+ (item) => admitObject(item, depth + 1, budget)
1444
+ );
1445
+ terms.push(key === "and" ? { and: children } : { or: children });
1446
+ } else if (key === "context") {
1447
+ if (!object(filter) || !Object.keys(filter).length)
1448
+ fail("Run event context requires flat scalar fields.");
1449
+ for (const [contextKey, value] of Object.entries(filter))
1450
+ fieldTerms(`context.${contextKey}`, value);
1451
+ } else fieldTerms(key, filter);
1452
+ }
1453
+ return terms.length === 1 ? terms[0] : { and: terms };
1788
1454
  }
1789
- function getRunSignature(snapshot) {
1790
- return [snapshot.runId, snapshot.status, snapshot.updatedAt ?? 0].join(":");
1455
+ function compact(predicate) {
1456
+ if ("and" in predicate) {
1457
+ const merged = {};
1458
+ for (const term of predicate.and) {
1459
+ const child = compact(term);
1460
+ for (const [key, value] of Object.entries(child)) {
1461
+ if (key === "and") return { and: predicate.and.map(compact) };
1462
+ if (key === "context") {
1463
+ const context = merged.context ?? {};
1464
+ for (const [name, filter2] of Object.entries(value)) {
1465
+ if (Object.prototype.hasOwnProperty.call(context, name)) {
1466
+ const combined = combineFilters(context[name], filter2);
1467
+ if (!combined) return { and: predicate.and.map(compact) };
1468
+ context[name] = combined;
1469
+ } else context[name] = filter2;
1470
+ }
1471
+ merged.context = context;
1472
+ } else if (Object.prototype.hasOwnProperty.call(merged, key)) {
1473
+ const combined = key === "or" ? void 0 : combineFilters(merged[key], value);
1474
+ if (!combined) return { and: predicate.and.map(compact) };
1475
+ merged[key] = combined;
1476
+ } else merged[key] = value;
1477
+ }
1478
+ }
1479
+ return merged;
1480
+ }
1481
+ if ("or" in predicate) return { or: predicate.or.map(compact) };
1482
+ const filter = predicate.op === "eq" ? predicate.value : { [predicate.op]: predicate.value };
1483
+ if (predicate.field.startsWith("context."))
1484
+ return { context: { [predicate.field.slice(8)]: filter } };
1485
+ return { [predicate.field]: filter };
1791
1486
  }
1792
- function getStepStatusSignature(snapshot) {
1793
- return snapshot.nodeStates.map(
1794
- (state) => [
1795
- state.nodeId,
1796
- state.status,
1797
- state.artifactTableNamespace ?? "",
1798
- state.startedAt ?? "",
1799
- state.completedAt ?? "",
1800
- state.progress?.startedAt ?? "",
1801
- state.progress?.completedAt ?? "",
1802
- state.updatedAt ?? "",
1803
- state.progress?.updatedAt ?? ""
1804
- ].join(":")
1805
- ).join("|");
1487
+ function combineFilters(left, right) {
1488
+ const first = object(left) ? left : { eq: left };
1489
+ const second = object(right) ? right : { eq: right };
1490
+ if (Object.keys(first).some(
1491
+ (key) => Object.prototype.hasOwnProperty.call(second, key)
1492
+ ))
1493
+ return void 0;
1494
+ return { ...first, ...second };
1806
1495
  }
1807
- function getStepProgressSignature(snapshot) {
1808
- return JSON.stringify(
1809
- snapshot.nodeStates.map((state) => [
1810
- state.nodeId,
1811
- projectPlayStepProgressCounters(state.progress ?? {}),
1812
- state.progress?.artifactTableNamespace ?? "",
1813
- state.progress?.startedAt ?? "",
1814
- state.progress?.completedAt ?? "",
1815
- state.progress?.updatedAt ?? "",
1816
- state.progress?.message ?? ""
1817
- ])
1496
+ function canonical(predicate) {
1497
+ if ("field" in predicate) return predicate;
1498
+ const operator = "and" in predicate ? "and" : "or";
1499
+ const source = "and" in predicate ? predicate.and : predicate.or;
1500
+ const terms = source.map(canonical).flatMap((term) => {
1501
+ if (operator === "and" && "and" in term) return term.and;
1502
+ if (operator === "or" && "or" in term) return term.or;
1503
+ return [term];
1504
+ });
1505
+ terms.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
1506
+ const unique = terms.filter(
1507
+ (term, i) => i === 0 || JSON.stringify(term) !== JSON.stringify(terms[i - 1])
1818
1508
  );
1509
+ if (unique.length === 1) return unique[0];
1510
+ return operator === "and" ? { and: unique } : { or: unique };
1819
1511
  }
1820
- function getSnapshotSignature(snapshot) {
1821
- return JSON.stringify(snapshot);
1822
- }
1823
- function resolvePlayRunLogGap(snapshot, lastLogSeq) {
1824
- if (snapshot.totalLogCount <= lastLogSeq) {
1825
- return null;
1826
- }
1827
- const tailFirstSeq = snapshot.totalLogCount - snapshot.logs.length + 1;
1828
- if (lastLogSeq + 1 >= tailFirstSeq) {
1829
- return null;
1830
- }
1831
- return {
1832
- missingCount: tailFirstSeq - 1 - lastLogSeq,
1833
- tailFirstSeq
1512
+ function parseDsl(input) {
1513
+ let position = 0;
1514
+ let count = 0;
1515
+ const skip = () => {
1516
+ while (/\s/.test(input[position] ?? "") && position < input.length)
1517
+ position++;
1834
1518
  };
1835
- }
1836
- function diffLogLines(input) {
1837
- const { logs, totalLogCount } = input.snapshot;
1838
- if (input.snapshot.logsUnavailable) {
1839
- return {
1840
- lines: [],
1841
- lastLogSeq: input.lastLogSeq,
1842
- firstSeq: null
1843
- };
1844
- }
1845
- if (totalLogCount <= input.lastLogSeq) {
1846
- return { lines: [], lastLogSeq: input.lastLogSeq, firstSeq: null };
1847
- }
1848
- const tailFirstSeq = totalLogCount - logs.length + 1;
1849
- if (input.lastLogSeq + 1 >= tailFirstSeq) {
1850
- return {
1851
- lines: logs.slice(input.lastLogSeq + 1 - tailFirstSeq),
1852
- lastLogSeq: totalLogCount,
1853
- firstSeq: input.lastLogSeq + 1
1854
- };
1855
- }
1856
- const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
1857
- return {
1858
- lines: [
1859
- `[watch] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
1860
- ...logs
1861
- ],
1862
- lastLogSeq: totalLogCount,
1863
- firstSeq: null
1864
- };
1865
- }
1866
- function diffPlayRunStreamEvents(input) {
1867
- const { snapshot, streamId, previous } = input;
1868
- const cursor = getSnapshotCursor(snapshot);
1869
- const logDiff = diffLogLines({
1870
- snapshot,
1871
- lastLogSeq: previous.lastLogSeq
1872
- });
1873
- const next = {
1874
- runSignature: getRunSignature(snapshot),
1875
- stepStatusSignature: getStepStatusSignature(snapshot),
1876
- stepProgressSignature: getStepProgressSignature(snapshot),
1877
- snapshotSignature: getSnapshotSignature(snapshot),
1878
- lastLogSeq: logDiff.lastLogSeq
1519
+ const take = (token) => {
1520
+ skip();
1521
+ if (input.startsWith(token, position)) {
1522
+ position += token.length;
1523
+ return true;
1524
+ }
1525
+ return false;
1879
1526
  };
1880
- const events = [];
1881
- if (next.stepStatusSignature !== previous.stepStatusSignature) {
1882
- for (const state of snapshot.nodeStates) {
1883
- if (state.status === "idle") {
1884
- continue;
1527
+ const json = () => {
1528
+ skip();
1529
+ const start = position;
1530
+ if (input[position] === '"') {
1531
+ position++;
1532
+ let closed = false;
1533
+ while (position < input.length) {
1534
+ const char = input[position++];
1535
+ if (char === "\\") position++;
1536
+ else if (char === '"') {
1537
+ closed = true;
1538
+ break;
1539
+ }
1885
1540
  }
1886
- const persistedStartedAt = state.startedAt ?? state.progress?.startedAt ?? null;
1887
- const persistedCompletedAt = state.completedAt ?? state.progress?.completedAt ?? null;
1888
- events.push(
1889
- makeRunStreamEvent({
1890
- cursor,
1891
- streamId,
1892
- type: "play.step.status",
1893
- payload: {
1894
- runId: snapshot.runId,
1895
- stepId: state.nodeId,
1896
- status: state.status,
1897
- artifactTableNamespace: state.artifactTableNamespace ?? null,
1898
- ...resolveTimingWindow({
1899
- startedAt: persistedStartedAt,
1900
- completedAt: persistedCompletedAt,
1901
- updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1902
- })
1903
- }
1904
- })
1541
+ if (!closed) fail("Run event DSL has an unterminated JSON string.");
1542
+ } else {
1543
+ const match = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(
1544
+ input.slice(position)
1905
1545
  );
1546
+ if (!match)
1547
+ fail("Run event DSL requires a JSON quoted string or scalar.");
1548
+ position += match[0].length;
1906
1549
  }
1907
- }
1908
- if (next.stepProgressSignature !== previous.stepProgressSignature) {
1909
- for (const state of snapshot.nodeStates) {
1910
- if (!state.progress) {
1911
- continue;
1550
+ try {
1551
+ return JSON.parse(input.slice(start, position));
1552
+ } catch {
1553
+ fail("Run event DSL requires valid JSON scalars.");
1554
+ }
1555
+ };
1556
+ const term = (depth) => {
1557
+ if (depth > MAX_DEPTH) fail("Run event where exceeds depth 8.");
1558
+ if (take("(")) {
1559
+ const nested = or(depth + 1);
1560
+ if (!take(")")) fail("Run event DSL is missing a closing parenthesis.");
1561
+ return nested;
1562
+ }
1563
+ skip();
1564
+ const match = /^(?:\$\.)?[A-Za-z_][A-Za-z0-9_.]*/.exec(
1565
+ input.slice(position)
1566
+ );
1567
+ if (!match) fail("Run event DSL requires a supported field.");
1568
+ position += match[0].length;
1569
+ const field = validateRunEventField(match[0]);
1570
+ if (++count > MAX_PREDICATES)
1571
+ fail("Run event where exceeds 64 predicates.");
1572
+ skip();
1573
+ const operation = /^(==|!=|>=|<=|>|<|\b(?:eq|ne|gte|gt|lte|lt|in|exists)\b)/.exec(
1574
+ input.slice(position)
1575
+ );
1576
+ if (!operation) {
1577
+ const token = /^(?:[=!<>~]+|[A-Za-z_]+)/.exec(input.slice(position))?.[0] ?? input[position] ?? "<end>";
1578
+ if (token === "=") {
1579
+ const corrected = `${input.slice(0, position)}==${input.slice(position + 1)}`;
1580
+ fail(
1581
+ `Run event DSL does not support "=" at character ${position + 1}. For equality, replace this "=" with "==": ${JSON.stringify(corrected)}.`
1582
+ );
1912
1583
  }
1913
- events.push(
1914
- makeRunStreamEvent({
1915
- cursor: String(
1916
- state.progress.updatedAt ?? snapshot.updatedAt ?? Date.now()
1917
- ),
1918
- streamId,
1919
- type: "play.step.progress",
1920
- payload: {
1921
- runId: snapshot.runId,
1922
- stepId: state.nodeId,
1923
- ...projectPlayStepProgressCounters(state.progress),
1924
- message: state.progress.message,
1925
- artifactTableNamespace: state.progress.artifactTableNamespace ?? state.artifactTableNamespace ?? null,
1926
- ...resolveTimingWindow({
1927
- startedAt: state.startedAt ?? state.progress.startedAt ?? null,
1928
- completedAt: state.completedAt ?? state.progress.completedAt ?? null,
1929
- updatedAt: state.progress.updatedAt ?? snapshot.updatedAt ?? null
1930
- })
1931
- }
1932
- })
1584
+ fail(
1585
+ `Run event DSL does not support ${JSON.stringify(token)} after ${JSON.stringify(match[0])} at character ${position + 1}. Use ==, !=, >, >=, <, <=, in or exists.`
1933
1586
  );
1934
1587
  }
1935
- }
1936
- if (logDiff.lines.length > 0) {
1937
- events.push(
1938
- makeRunStreamEvent({
1939
- cursor,
1940
- streamId,
1941
- type: "play.run.log",
1942
- payload: {
1943
- runId: snapshot.runId,
1944
- lines: logDiff.lines,
1945
- source: "worker",
1946
- ...logDiff.firstSeq !== null ? { firstSeq: logDiff.firstSeq } : {},
1947
- totalLogCount: snapshot.totalLogCount
1948
- }
1949
- })
1588
+ position += operation[0].length;
1589
+ const symbols = {
1590
+ "==": "eq",
1591
+ "!=": "ne",
1592
+ ">=": "gte",
1593
+ "<=": "lte",
1594
+ ">": "gt",
1595
+ "<": "lt"
1596
+ };
1597
+ const op = symbols[operation[0]] ?? operation[0];
1598
+ if (op === "in") {
1599
+ if (!take("[")) fail("Run event DSL in requires a JSON scalar array.");
1600
+ const values = [];
1601
+ if (!take("]")) {
1602
+ do {
1603
+ values.push(json());
1604
+ if (values.length > 32)
1605
+ fail("Run event in requires between 1 and 32 scalars.");
1606
+ } while (take(","));
1607
+ if (!take("]")) fail("Run event DSL in requires a closing bracket.");
1608
+ }
1609
+ return comparison(field, op, values);
1610
+ }
1611
+ return comparison(field, op, json());
1612
+ };
1613
+ const and = (depth) => {
1614
+ const terms = [term(depth)];
1615
+ while (take("&&")) terms.push(term(depth));
1616
+ return terms.length === 1 ? terms[0] : { and: terms };
1617
+ };
1618
+ const or = (depth) => {
1619
+ const terms = [and(depth)];
1620
+ while (take("||")) terms.push(and(depth));
1621
+ return terms.length === 1 ? terms[0] : { or: terms };
1622
+ };
1623
+ const result = or(0);
1624
+ skip();
1625
+ if (position !== input.length)
1626
+ fail(`Run event DSL has unsupported input at position ${position}.`);
1627
+ return result;
1628
+ }
1629
+ function parseRunEventWhere(input) {
1630
+ bounded(input);
1631
+ const where = compact(
1632
+ canonical(typeof input === "string" ? parseDsl(input) : admitObject(input))
1633
+ );
1634
+ bounded(where);
1635
+ return where;
1636
+ }
1637
+ function normalizeRunEventQueryTimestamp(value) {
1638
+ let extraMicros = "";
1639
+ if (typeof value === "string") {
1640
+ const timestamp = /^[+-]?\d{4,6}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.(\d{1,6}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(
1641
+ value
1950
1642
  );
1643
+ if (!timestamp)
1644
+ fail(
1645
+ "Run event time bounds require an ISO timestamp with a timezone and at most six fractional digits."
1646
+ );
1647
+ extraMicros = (timestamp[1] ?? "").padEnd(6, "0").slice(3);
1951
1648
  }
1952
- if (next.snapshotSignature !== previous.snapshotSignature) {
1953
- const enrichedNodeStates = snapshot.nodeStates.map((state) => {
1954
- const timing = resolveTimingWindow({
1955
- startedAt: state.startedAt ?? state.progress?.startedAt ?? null,
1956
- completedAt: state.completedAt ?? state.progress?.completedAt ?? null,
1957
- updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1958
- });
1959
- return {
1960
- ...state,
1961
- ...timing,
1962
- progress: state.progress ? {
1963
- ...state.progress,
1964
- ...resolveTimingWindow({
1965
- startedAt: state.progress.startedAt ?? state.startedAt ?? null,
1966
- completedAt: state.progress.completedAt ?? state.completedAt ?? null,
1967
- updatedAt: state.progress.updatedAt ?? state.updatedAt ?? snapshot.updatedAt ?? null
1968
- })
1969
- } : state.progress
1649
+ const date = new Date(value);
1650
+ if (!Number.isFinite(date.getTime()))
1651
+ fail("Run event time bound is invalid.");
1652
+ const canonical2 = date.toISOString();
1653
+ return /[1-9]/.test(extraMicros) ? canonical2.replace(/Z$/, `${extraMicros}Z`) : canonical2;
1654
+ }
1655
+ function timestampMicros(value) {
1656
+ const fraction = /\.(\d+)Z$/.exec(value)?.[1] ?? "";
1657
+ return BigInt(Date.parse(value)) * BigInt(1e3) + BigInt(fraction.padEnd(6, "0").slice(3));
1658
+ }
1659
+ function time(value, nowMs, fallback) {
1660
+ if (value === void 0) value = fallback;
1661
+ let timestamp;
1662
+ if (typeof value === "number") timestamp = value;
1663
+ else if (typeof value === "string") {
1664
+ const relative = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
1665
+ if (relative) {
1666
+ const units = {
1667
+ ms: 1,
1668
+ s: 1e3,
1669
+ m: 6e4,
1670
+ h: 36e5,
1671
+ d: 864e5
1970
1672
  };
1971
- });
1972
- events.push(
1973
- makeRunStreamEvent({
1974
- cursor,
1975
- streamId,
1976
- type: "play.run.snapshot",
1977
- payload: { ...snapshot, nodeStates: enrichedNodeStates }
1978
- })
1673
+ timestamp = nowMs - Number(relative[1]) * units[relative[2]];
1674
+ } else {
1675
+ return normalizeRunEventQueryTimestamp(value);
1676
+ }
1677
+ } else fail("Run event time bounds require a timestamp or duration.");
1678
+ return normalizeRunEventQueryTimestamp(timestamp);
1679
+ }
1680
+ function exact(value, name) {
1681
+ if (typeof value !== "string" || !value.trim() || value.length > 256)
1682
+ fail(
1683
+ `Run event ${name} requires a nonempty string of at most 256 characters.`
1979
1684
  );
1685
+ return value;
1686
+ }
1687
+ var CRITERIA_KEYS = [
1688
+ "since",
1689
+ "until",
1690
+ "where",
1691
+ "runId",
1692
+ "stepId",
1693
+ "minLevel",
1694
+ "text",
1695
+ "playName",
1696
+ "kind"
1697
+ ];
1698
+ function normalizeRunEventQuery(criteria, nowMs = Date.now()) {
1699
+ if (!object(criteria)) fail("Run event criteria requires an object.");
1700
+ bounded(criteria, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
1701
+ for (const key of Object.keys(criteria)) {
1702
+ if (!CRITERIA_KEYS.includes(key))
1703
+ fail(`Unsupported run event criterion: ${key}.`);
1980
1704
  }
1981
- if (next.runSignature !== previous.runSignature) {
1982
- events.push(
1983
- makeRunStreamEvent({
1984
- cursor,
1985
- streamId,
1986
- type: "play.run.status",
1987
- payload: {
1988
- runId: snapshot.runId,
1989
- status: snapshot.status,
1990
- updatedAt: snapshot.updatedAt,
1991
- ...snapshot.activity ? { activity: snapshot.activity } : {}
1992
- }
1993
- })
1994
- );
1705
+ const until = time(criteria.until, nowMs, nowMs);
1706
+ const since = time(criteria.since, nowMs, Date.parse(until) - 36e5);
1707
+ if (timestampMicros(since) >= timestampMicros(until) || timestampMicros(until) - timestampMicros(since) > BigInt(RUN_EVENT_QUERY_RETENTION_MS) * BigInt(1e3))
1708
+ fail("Run event query requires a window of at most seven days.");
1709
+ const query = { since, until };
1710
+ if (criteria.where !== void 0)
1711
+ query.where = parseRunEventWhere(criteria.where);
1712
+ for (const name of ["runId", "stepId", "playName", "kind"]) {
1713
+ if (criteria[name] !== void 0) query[name] = exact(criteria[name], name);
1995
1714
  }
1996
- return { events, next };
1997
- }
1998
-
1999
- // src/runs/read-errors.ts
2000
- function isTransientPlayRunReadError(error) {
2001
- if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
2002
- return true;
1715
+ if (criteria.minLevel !== void 0) {
1716
+ if (!RUN_LOG_LEVELS.includes(criteria.minLevel))
1717
+ fail("Run event minLevel requires debug, info, warn or error.");
1718
+ query.minLevel = criteria.minLevel;
2003
1719
  }
2004
- if (error instanceof DeeplineError && typeof error.statusCode === "number") {
2005
- return error.statusCode >= 500 && error.statusCode < 600;
1720
+ if (criteria.text !== void 0) {
1721
+ if (typeof criteria.text !== "string" || !criteria.text.length || new TextEncoder().encode(criteria.text).length > 1024)
1722
+ fail("Run event text requires between 1 and 1024 bytes.");
1723
+ query.text = criteria.text;
2006
1724
  }
2007
- const text = error instanceof Error ? error.message : String(error);
2008
- return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
2009
- text
2010
- );
1725
+ return query;
2011
1726
  }
2012
-
2013
- // src/runs/watch-polling.ts
2014
- var RUN_WATCH_POLL_INTERVAL_MS = 2e3;
2015
- function waitForNextPoll(ms, signal) {
2016
- if (signal?.aborted) {
2017
- return Promise.resolve(false);
1727
+ function normalizeRunEventOperationRequest(input, nowMs = Date.now()) {
1728
+ bounded(input, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
1729
+ if (!object(input)) fail("Run event operation requires an object.");
1730
+ const operation = input.operation;
1731
+ if (!["list", "count", "summarize", "fields"].includes(String(operation)))
1732
+ fail("Run event operation requires list, count, summarize or fields.");
1733
+ if (input.resource !== "logs" && input.resource !== "events")
1734
+ fail("Run event resource requires logs or events.");
1735
+ const allowed = [
1736
+ "operation",
1737
+ "resource",
1738
+ "runId",
1739
+ ...operation === "fields" ? [] : CRITERIA_KEYS,
1740
+ ...operation === "list" ? ["limit", "cursor", "includePayloads"] : operation === "summarize" ? ["groupBy", "measures", "limit", "cursor"] : []
1741
+ ];
1742
+ for (const key of Object.keys(input)) {
1743
+ if (!allowed.includes(key))
1744
+ fail(`Run event ${operation} does not support ${key}.`);
2018
1745
  }
2019
- return new Promise((resolve) => {
2020
- const finish = (elapsed) => {
2021
- clearTimeout(timeout);
2022
- signal?.removeEventListener("abort", onAbort);
2023
- resolve(elapsed);
1746
+ const base = {
1747
+ operation,
1748
+ resource: input.resource,
1749
+ ...input.runId === void 0 ? {} : { runId: exact(input.runId, "runId") }
1750
+ };
1751
+ if (operation === "fields") return base;
1752
+ if (input.resource === "logs" && !base.runId)
1753
+ fail("Run event logs require runId.");
1754
+ if (input.cursor !== void 0 && (typeof input.cursor !== "string" || !input.cursor.length || input.cursor.length > RUN_EVENT_QUERY_MAX_CURSOR_BYTES))
1755
+ fail("Run event cursor is invalid.");
1756
+ const rawCriteria = Object.fromEntries(
1757
+ Object.entries(input).filter(
1758
+ ([key]) => CRITERIA_KEYS.includes(key) && (key !== "runId" || input.resource === "events")
1759
+ )
1760
+ );
1761
+ const query = normalizeRunEventQuery(
1762
+ input.cursor === void 0 ? rawCriteria : { ...rawCriteria, since: nowMs - 36e5, until: nowMs },
1763
+ nowMs
1764
+ );
1765
+ const criteria = { ...query };
1766
+ if (input.cursor !== void 0) {
1767
+ if (input.since === void 0) delete criteria.since;
1768
+ else criteria.since = time(input.since, nowMs, nowMs - 36e5);
1769
+ if (input.until === void 0) delete criteria.until;
1770
+ else criteria.until = time(input.until, nowMs, nowMs);
1771
+ }
1772
+ if (operation === "count")
1773
+ return { ...base, ...criteria };
1774
+ const limit = input.limit ?? (input.includePayloads === true ? 10 : 50);
1775
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > RUN_EVENT_QUERY_MAX_LIMIT)
1776
+ fail("Run event limit must be between 1 and 200.");
1777
+ const page = {
1778
+ limit,
1779
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor }
1780
+ };
1781
+ if (operation === "list") {
1782
+ if (input.includePayloads !== void 0 && typeof input.includePayloads !== "boolean")
1783
+ fail("Run event includePayloads requires a boolean.");
1784
+ if (input.includePayloads === true && limit > RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT)
1785
+ fail("Run event payload pages cannot exceed 10 rows.");
1786
+ return {
1787
+ ...base,
1788
+ ...criteria,
1789
+ ...page,
1790
+ ...input.includePayloads === void 0 ? {} : { includePayloads: input.includePayloads }
2024
1791
  };
2025
- const onAbort = () => finish(false);
2026
- const timeout = setTimeout(() => finish(true), ms);
2027
- signal?.addEventListener("abort", onAbort, { once: true });
1792
+ }
1793
+ if (!Array.isArray(input.groupBy) || input.groupBy.length > 4)
1794
+ fail("Run event groupBy requires at most four fields.");
1795
+ const groupBy = input.groupBy.map(validateRunEventField);
1796
+ if (new Set(groupBy).size !== groupBy.length)
1797
+ fail("Run event groupBy fields must be distinct.");
1798
+ const rawMeasures = input.measures ?? [{ op: "count" }];
1799
+ if (!Array.isArray(rawMeasures) || rawMeasures.length < 1 || rawMeasures.length > 8)
1800
+ fail("Run event measures require between 1 and 8 entries.");
1801
+ const names = /* @__PURE__ */ new Set();
1802
+ const measures = rawMeasures.map((measure) => {
1803
+ if (!object(measure) || measure.op !== "count" && measure.op !== "sum")
1804
+ fail("Run event measures support count and sum.");
1805
+ for (const key of Object.keys(measure)) {
1806
+ if (!["op", "as", ...measure.op === "sum" ? ["field"] : []].includes(key))
1807
+ fail(`Unsupported run event measure option: ${key}.`);
1808
+ }
1809
+ const field = measure.op === "sum" ? validateRunEventField(measure.field) : void 0;
1810
+ if (field && !field.startsWith("context.") && RUN_EVENT_QUERY_FIELD_CATALOG[field].type !== "number")
1811
+ fail(`Run event sum requires a numeric field: ${field}.`);
1812
+ const name = measure.as ?? (field ? `sum_${field.replaceAll(".", "_")}` : "count");
1813
+ if (typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name) || names.has(name))
1814
+ fail("Run event measure names must be distinct identifiers.");
1815
+ names.add(name);
1816
+ return field ? { op: "sum", field, as: name } : { op: "count", as: name };
2028
1817
  });
1818
+ return {
1819
+ ...base,
1820
+ ...criteria,
1821
+ ...page,
1822
+ groupBy,
1823
+ measures
1824
+ };
2029
1825
  }
2030
- function readSnapshotResponse(response, runId) {
2031
- if (!response || typeof response !== "object" || Array.isArray(response)) {
2032
- throw new Error("Run watch returned an invalid snapshot response.");
2033
- }
2034
- const record = response;
2035
- const snapshot = record.snapshot;
2036
- if (record.runId !== runId || !snapshot || typeof snapshot !== "object" || Array.isArray(snapshot) || snapshot.runId !== runId || typeof snapshot.status !== "string" || !Array.isArray(snapshot.logs) || !snapshot.logs.every(
2037
- (line) => typeof line === "string"
2038
- ) || typeof snapshot.totalLogCount !== "number" || !Array.isArray(snapshot.nodeStates)) {
2039
- throw new Error(`Run watch returned an invalid snapshot for ${runId}.`);
2040
- }
2041
- return { runId, snapshot };
1826
+
1827
+ // src/types.ts
1828
+ function isStopAllPlayRunsDryRunResult(result) {
1829
+ return "dryRun" in result && result.dryRun === true;
2042
1830
  }
2043
- async function backfillRotatedLogTail(input) {
2044
- if (!input.previous.runSignature || input.snapshot.logsUnavailable) {
2045
- return input.snapshot;
1831
+
1832
+ // ../plays/trigger-config-route.ts
1833
+ function playTriggerConfigPath(playName) {
1834
+ return `/api/v2/plays/${encodeURIComponent(playName)}/triggers`;
1835
+ }
1836
+
1837
+ // src/plays/input-example.ts
1838
+ function playInputExampleArgs(schema) {
1839
+ if (!schema) return " --input '@<input.json>'";
1840
+ if (schema.$ref || schema.allOf) return " --input '@<input.json>'";
1841
+ const csv = schema.csvInput;
1842
+ const csvField = typeof csv?.inputField === "string" ? csv.inputField : null;
1843
+ const required = Array.isArray(schema.required) ? schema.required : [];
1844
+ const csvOnlyRequired = required.every((key) => key === csvField);
1845
+ const alternatives = schema.oneOf ?? schema.anyOf;
1846
+ const hasCsvAlternative = Array.isArray(alternatives) && alternatives.some((branch) => {
1847
+ if (!branch || typeof branch !== "object") return false;
1848
+ const keys = branch.required;
1849
+ return Array.isArray(keys) && keys.includes(csvField) && keys.every((key) => key === csvField);
1850
+ });
1851
+ if (csvField && csvOnlyRequired && (!alternatives || hasCsvAlternative)) {
1852
+ if (csvField === "csv") return " --csv '<contacts.csv>'";
1853
+ const value = JSON.stringify({ [csvField]: "<contacts.csv>" });
1854
+ return ` --input '${value.replace(/'/g, "'\\''")}'`;
2046
1855
  }
2047
- const gap = resolvePlayRunLogGap(input.snapshot, input.previous.lastLogSeq);
2048
- if (!gap) {
2049
- return input.snapshot;
2050
- }
2051
- const lines = [];
2052
- let cursor = input.previous.lastLogSeq;
2053
- const targetSeq = gap.tailFirstSeq - 1;
2054
- while (cursor < targetSeq) {
2055
- const limit = Math.min(1e3, targetSeq - cursor);
2056
- const page = await input.http.get(
2057
- `/api/v2/runs/${encodeURIComponent(input.runId)}/logs?afterSeq=${cursor}&limit=${limit}`,
2058
- { signal: input.signal }
2059
- );
2060
- if (!page || !Array.isArray(page.entries) || page.entries.length === 0) {
2061
- return input.snapshot;
2062
- }
2063
- for (const entry of page.entries) {
2064
- if (!entry || entry.seq !== cursor + 1 || typeof entry.line !== "string") {
2065
- return input.snapshot;
2066
- }
2067
- lines.push(entry.line);
2068
- cursor = entry.seq;
2069
- }
2070
- }
2071
- if (cursor !== targetSeq) {
2072
- return input.snapshot;
2073
- }
2074
- return { ...input.snapshot, logs: [...lines, ...input.snapshot.logs] };
2075
- }
2076
- async function* watchRunEvents(options) {
2077
- const { http, runId, signal } = options;
2078
- const path = `/api/v2/runs/${encodeURIComponent(runId)}?watchSnapshot=true`;
2079
- const streamId = `play-run-watch:${runId}`;
2080
- const pollIntervalMs = options.pollIntervalMs ?? RUN_WATCH_POLL_INTERVAL_MS;
2081
- let previous = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
2082
- let consecutiveFailures = 0;
2083
- let outageNoticeSent = false;
2084
- while (!signal?.aborted) {
2085
- const pollStartedAt = Date.now();
2086
- let snapshot;
2087
- try {
2088
- const response = await http.get(path, { signal });
2089
- snapshot = await backfillRotatedLogTail({
2090
- http,
2091
- runId,
2092
- snapshot: readSnapshotResponse(response, runId).snapshot,
2093
- previous,
2094
- signal
2095
- });
2096
- } catch (error) {
2097
- if (signal?.aborted) {
2098
- return;
2099
- }
2100
- if (!isTransientPlayRunReadError(error)) {
2101
- throw error;
2102
- }
2103
- consecutiveFailures += 1;
2104
- const reason = error instanceof Error ? error.message : String(error);
2105
- if (!outageNoticeSent) {
2106
- options.onRetry?.({
2107
- attempt: consecutiveFailures,
2108
- delayMs: pollIntervalMs,
2109
- reason
2110
- });
2111
- options.onNotice?.(
2112
- `[watch] run snapshot request failed; retrying every ${pollIntervalMs / 1e3}s (${reason})`
2113
- );
2114
- outageNoticeSent = true;
2115
- }
2116
- const remaining2 = Math.max(
2117
- 0,
2118
- pollIntervalMs - (Date.now() - pollStartedAt)
2119
- );
2120
- if (!await waitForNextPoll(remaining2, signal)) {
2121
- return;
2122
- }
2123
- continue;
2124
- }
2125
- if (outageNoticeSent) {
2126
- options.onNotice?.("[watch] run snapshot polling recovered");
2127
- outageNoticeSent = false;
2128
- }
2129
- consecutiveFailures = 0;
2130
- const diff = diffPlayRunStreamEvents({
2131
- streamId,
2132
- snapshot,
2133
- previous
2134
- });
2135
- previous = diff.next;
2136
- for (const event of diff.events) {
2137
- yield event;
2138
- }
2139
- if (isTerminalPlayRunLiveStatus(snapshot.status)) {
2140
- return;
2141
- }
2142
- const remaining = Math.max(
2143
- 0,
2144
- pollIntervalMs - (Date.now() - pollStartedAt)
2145
- );
2146
- if (!await waitForNextPoll(remaining, signal)) {
2147
- return;
2148
- }
1856
+ if (Array.isArray(schema.required) && !required.length && !csv && !alternatives) {
1857
+ return "";
2149
1858
  }
1859
+ return " --input '@<input.json>'";
2150
1860
  }
2151
1861
 
2152
- // src/runs/log-cursor.ts
2153
- var RUN_LOGS_CURSOR_PREFIX = "before:";
2154
- function encodeRunLogsCursor(beforeSequence) {
2155
- return `${RUN_LOGS_CURSOR_PREFIX}${beforeSequence}`;
1862
+ // ../product-notifications/configuration.ts
1863
+ var PRODUCT_NOTIFICATION_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1864
+ function normalizeProductNotificationEmail(value) {
1865
+ const email = typeof value === "string" ? value.trim().toLowerCase() : "";
1866
+ return email && email.length <= 320 && PRODUCT_NOTIFICATION_EMAIL_PATTERN.test(email) ? email : null;
2156
1867
  }
2157
- function isRunLogsCursor(value) {
2158
- return value.startsWith(RUN_LOGS_CURSOR_PREFIX);
1868
+ var DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING = {
1869
+ mode: "rolling_limit",
1870
+ windowMs: 36e5
1871
+ };
1872
+
1873
+ // ../play-runtime/live-events.ts
1874
+ function projectPlayStepProgressCounters(progress) {
1875
+ return {
1876
+ completed: progress.completed,
1877
+ total: progress.total,
1878
+ failed: progress.failed,
1879
+ startedRows: progress.startedRows,
1880
+ activeRows: progress.activeRows,
1881
+ waitingRows: progress.waitingRows,
1882
+ completedRows: progress.completedRows,
1883
+ supersededRows: progress.supersededRows
1884
+ };
2159
1885
  }
2160
- function parseRunLogsCursor(value) {
2161
- const raw = isRunLogsCursor(value) ? value.slice(RUN_LOGS_CURSOR_PREFIX.length) : "";
2162
- const parsed = /^[1-9]\d*$/.test(raw) ? Number(raw) : NaN;
2163
- if (!Number.isSafeInteger(parsed)) {
2164
- throw Object.assign(
2165
- new Error(
2166
- `Invalid run log cursor ${JSON.stringify(value)}. Pass the nextCursor value from a previous deepline runs logs result.`
2167
- ),
2168
- { exitCode: 2 }
2169
- );
2170
- }
2171
- return parsed;
1886
+ function resolveTimingWindow(input) {
1887
+ const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
1888
+ const completedAt = typeof input.completedAt === "number" && Number.isFinite(input.completedAt) ? input.completedAt : null;
1889
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : null;
1890
+ return {
1891
+ startedAt,
1892
+ completedAt,
1893
+ updatedAt
1894
+ };
2172
1895
  }
2173
1896
 
2174
- // ../play-runtime/run-event-query.ts
2175
- var stringField = { type: "string", nullable: false };
2176
- var optionalString = { type: "string", nullable: true };
2177
- var RUN_EVENT_QUERY_FIELD_CATALOG = {
2178
- eventId: stringField,
2179
- orgId: stringField,
2180
- runtimeScope: stringField,
2181
- runId: stringField,
2182
- playName: stringField,
2183
- kind: stringField,
2184
- source: optionalString,
2185
- occurredAt: { type: "timestamp", nullable: false },
2186
- status: optionalString,
2187
- level: { type: "level", nullable: true },
2188
- stepId: optionalString,
2189
- stepKind: optionalString,
2190
- datasetId: optionalString,
2191
- phase: optionalString,
2192
- provider: optionalString,
2193
- operation: optionalString,
2194
- producerAttempt: { type: "number", nullable: true }
2195
- };
2196
- var RUN_EVENT_QUERY_RETENTION_MS = 7 * 864e5;
2197
- var RUN_EVENT_QUERY_MAX_LIMIT = 200;
2198
- var RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT = 10;
2199
- var RUN_EVENT_QUERY_MAX_CURSOR_BYTES = 48 * 1024;
2200
- var RUN_EVENT_QUERY_MAX_REQUEST_BYTES = 64 * 1024;
2201
- var MAX_BYTES = 8192;
2202
- var MAX_DEPTH = 8;
2203
- var MAX_PREDICATES = 64;
2204
- var OPERATORS = [
2205
- "eq",
2206
- "ne",
2207
- "gt",
2208
- "gte",
2209
- "lt",
2210
- "lte",
2211
- "exists",
2212
- "in"
2213
- ];
2214
- var RunEventQueryInputError = class extends TypeError {
2215
- constructor(message, code = "invalid_run_event_query") {
2216
- super(message);
2217
- this.code = code;
2218
- this.name = "RunEventQueryInputError";
1897
+ // ../play-runtime/run-failure.ts
1898
+ var PUBLIC_FAILURE_TEXT_BYTE_LIMIT = 64 * 1024;
1899
+
1900
+ // ../play-runtime/log-context.ts
1901
+ var utf8 = new TextEncoder();
1902
+
1903
+ // ../play-runtime/run-log-buffer.ts
1904
+ var RUNNER_LOG_MAX_BYTES = 5 * 1024 * 1024;
1905
+ var RUNNER_LOG_CONTEXT_MAX_BYTES = 128 * 1024;
1906
+ var TERMINAL_RESERVE_BYTES = 64 * 1024;
1907
+ var encoder = new TextEncoder();
1908
+
1909
+ // ../play-runtime/secret-redaction.ts
1910
+ var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1911
+ var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1912
+ var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
1913
+ var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
1914
+ var EMAIL_LOCAL_CHAR_RE = /[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]/;
1915
+ var EMAIL_DOMAIN_RE = /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?\.[A-Za-z]{2,63}\b/;
1916
+ function isEmailLocalPart(value, start, end) {
1917
+ let localStart = start;
1918
+ while (localStart > 0 && EMAIL_LOCAL_CHAR_RE.test(value[localStart - 1])) {
1919
+ localStart -= 1;
2219
1920
  }
2220
- };
2221
- function fail(message) {
2222
- throw new RunEventQueryInputError(message);
1921
+ let localEnd = end;
1922
+ while (localEnd < value.length && EMAIL_LOCAL_CHAR_RE.test(value[localEnd])) {
1923
+ localEnd += 1;
1924
+ }
1925
+ return localStart < localEnd && localEnd - localStart <= 64 && value[localEnd] === "@" && EMAIL_DOMAIN_RE.test(value.slice(localEnd + 1));
2223
1926
  }
2224
- function object(value) {
2225
- return value !== null && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
1927
+ function redactTokenUnlessEmail(match, offset, source) {
1928
+ return isEmailLocalPart(source, offset, offset + match.length) ? match : SECRET_REDACTION_PLACEHOLDER;
2226
1929
  }
2227
- function bounded(value, maxBytes = MAX_BYTES) {
2228
- let encoded;
2229
- try {
2230
- encoded = JSON.stringify(value);
2231
- } catch {
2232
- fail("Run event query must be bounded JSON without cycles.");
1930
+ var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1931
+ var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
1932
+ var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
1933
+ var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
1934
+ var URL_SECRET_VALUE_RE = /\b(?:[spr]k|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
1935
+ var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
1936
+ var HIGH_ENTROPY_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password|authorization|(?:access|refresh)[_-]?token)\b\s*[:=]\s*["']?[^"',\s]{16,}["']?/gi;
1937
+ var SENSITIVE_URL_PARAM_RE = /[?#&](?:\w*(?:key|token)|secret|password|authorization|credential|signature|sig)(?:=|$)/i;
1938
+ var SENSITIVE_FIELD_NAME_RE = new RegExp(
1939
+ `(^|[._-])(?:${SENSITIVE_FIELD_NAME})$`,
1940
+ "i"
1941
+ );
1942
+ var SENSITIVE_FIELD_NAME_CACHE_MAX_ENTRIES = 4096;
1943
+ var sensitiveFieldNameCache = /* @__PURE__ */ new Map();
1944
+ function isSensitiveFieldName(key) {
1945
+ const cached = sensitiveFieldNameCache.get(key);
1946
+ if (cached !== void 0) return cached;
1947
+ const sensitive = SENSITIVE_FIELD_NAME_RE.test(key);
1948
+ if (sensitiveFieldNameCache.size >= SENSITIVE_FIELD_NAME_CACHE_MAX_ENTRIES) {
1949
+ sensitiveFieldNameCache.clear();
2233
1950
  }
2234
- if (!encoded || new TextEncoder().encode(encoded).length > maxBytes)
2235
- fail(`Run event query exceeds ${maxBytes} bytes.`);
1951
+ sensitiveFieldNameCache.set(key, sensitive);
1952
+ return sensitive;
2236
1953
  }
2237
- function validateRunEventField(value) {
2238
- if (typeof value !== "string") fail("Run event field must be a string.");
2239
- const field = value.startsWith("$.") ? value.slice(2) : value;
2240
- if (Object.prototype.hasOwnProperty.call(RUN_EVENT_QUERY_FIELD_CATALOG, field))
2241
- return field;
2242
- if (/^context\.[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(field) && !["__proto__", "prototype", "constructor"].includes(field.slice(8)))
2243
- return field;
2244
- const alternatives = {
2245
- step: "stepId",
2246
- severity: "level",
2247
- timestamp: "occurredAt",
2248
- play: "playName",
2249
- run: "runId",
2250
- attempt: "producerAttempt"
2251
- };
2252
- fail(
2253
- `Unsupported run event field: ${field}. Use ${alternatives[field] ?? "a catalog field or context.<key>"}; call fields() for supported fields.`
2254
- );
1954
+ var SERIALIZED_SECRET_FIELD_RE = new RegExp(
1955
+ `"(${SENSITIVE_FIELD_NAME})"\\s*:\\s*(?:"[^"\\\\]*"|\\{[^{}]*\\})`,
1956
+ "gi"
1957
+ );
1958
+ function escapeRegExp(value) {
1959
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2255
1960
  }
2256
- function scalar(field, value, op) {
2257
- if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
2258
- fail(`Run event ${field} requires a JSON scalar.`);
2259
- if (typeof value === "number" && !Number.isFinite(value))
2260
- fail(`Run event ${field} requires a finite number.`);
2261
- if (typeof value === "string" && new TextEncoder().encode(value).length > 1024)
2262
- fail(`Run event ${field} value exceeds 1024 bytes.`);
2263
- const descriptor = RUN_EVENT_QUERY_FIELD_CATALOG[field];
2264
- if (descriptor) {
2265
- if (value === null) {
2266
- if (!descriptor.nullable) fail(`Run event ${field} cannot be null.`);
2267
- } else if (descriptor.type === "number") {
2268
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
2269
- fail(`Run event ${field} requires a positive integer.`);
2270
- } else if (typeof value !== "string")
2271
- fail(`Run event ${field} requires a string.`);
2272
- else if (descriptor.type === "level" && !RUN_LOG_LEVELS.includes(value))
2273
- fail("Run event level requires debug, info, warn or error.");
2274
- else if (descriptor.type === "timestamp") {
2275
- value = normalizeRunEventQueryTimestamp(value);
1961
+ var REDACTION_TRIGGER_RE = new RegExp(
1962
+ [
1963
+ // Sensitive field names, in object keys and in `name=`/`"name":` text.
1964
+ "token",
1965
+ "secret",
1966
+ "password",
1967
+ "authorization",
1968
+ "cookie",
1969
+ "credential",
1970
+ // One `key` branch instead of `api[_-]?key`, `private[_-]?key` and `key_`.
1971
+ // A regex alternation costs roughly one substring scan per branch, and a
1972
+ // broader branch can only send more payloads down the exact path.
1973
+ "key",
1974
+ // Self-identifying credential prefixes.
1975
+ "bearer",
1976
+ "basic",
1977
+ "-----BEGIN",
1978
+ "xox[baprs]-",
1979
+ "[prs]k_",
1980
+ "pat_",
1981
+ "ghp_",
1982
+ // URL credentials: `//user:pass@host` userinfo and sensitive query params.
1983
+ // The userinfo class excludes `/` exactly as the rule it guards does.
1984
+ "//[^\\s/]{0,256}@",
1985
+ "[?#&]sig",
1986
+ // JWTs carry no keyword at all, so the shape itself is the marker. Anchor
1987
+ // on the separating dot and drop JWT_RE's leading `{8,}` segment: a dot is
1988
+ // rare enough for the engine to skip between candidates, which makes this
1989
+ // gate roughly ten times cheaper than re-running JWT_RE itself, and
1990
+ // dropping a required segment can only widen what it accepts.
1991
+ "\\.[A-Za-z0-9_-]{1,16384}\\.[A-Za-z0-9_-]{8}"
1992
+ ].join("|"),
1993
+ "i"
1994
+ );
1995
+ function mightContainRedactableSecret(value) {
1996
+ return REDACTION_TRIGGER_RE.test(value);
1997
+ }
1998
+ function redactSecretLikeString(value) {
1999
+ if (!mightContainRedactableSecret(value)) return value;
2000
+ const serializedRedacted = value.replace(
2001
+ SERIALIZED_SECRET_FIELD_RE,
2002
+ `"$1":"${SECRET_REDACTION_PLACEHOLDER}"`
2003
+ );
2004
+ const trimmed = serializedRedacted.trim();
2005
+ if (/^https?:\/\/\S+$/i.test(trimmed)) {
2006
+ if (/^https?:\/\/[^/?#@]+@/i.test(trimmed) || SENSITIVE_URL_PARAM_RE.test(trimmed)) {
2007
+ return SECRET_REDACTION_PLACEHOLDER;
2008
+ }
2009
+ if (!URL_SECRET_VALUE_RE.test(serializedRedacted)) {
2010
+ return serializedRedacted;
2276
2011
  }
2277
2012
  }
2278
- if (["gt", "gte", "lt", "lte"].includes(op) && (value === null || typeof value === "boolean"))
2279
- fail(`Run event ${op} requires a string or number.`);
2280
- return value;
2013
+ return serializedRedacted.replace(PRIVATE_KEY_RE, SECRET_REDACTION_PLACEHOLDER).replace(BEARER_TOKEN_RE, `Bearer ${SECRET_REDACTION_PLACEHOLDER}`).replace(BASIC_CREDENTIAL_RE, `Basic ${SECRET_REDACTION_PLACEHOLDER}`).replace(JWT_RE, redactTokenUnlessEmail).replace(COMMON_SECRET_RE, redactTokenUnlessEmail).replace(GENERIC_PREFIXED_SECRET_RE, redactTokenUnlessEmail).replace(GENERIC_SECRET_ASSIGNMENT_RE, SECRET_REDACTION_PLACEHOLDER).replace(HIGH_ENTROPY_ASSIGNMENT_RE, (match) => {
2014
+ const separator = match.includes("=") ? "=" : ":";
2015
+ const [key] = match.split(separator, 1);
2016
+ return `${key.trim()}${separator}${SECRET_REDACTION_PLACEHOLDER}`;
2017
+ });
2281
2018
  }
2282
- function comparison(field, op, value) {
2283
- if (!OPERATORS.includes(op))
2284
- fail(`Unsupported run event operator: ${op}. Use ${OPERATORS.join(", ")}.`);
2285
- if (op === "exists") {
2286
- if (typeof value !== "boolean")
2287
- fail("Run event exists requires a boolean.");
2288
- return { field, op, value };
2289
- }
2290
- if (op === "in") {
2291
- if (!Array.isArray(value) || value.length < 1 || value.length > 32)
2292
- fail("Run event in requires between 1 and 32 scalars.");
2293
- return { field, op, value: value.map((item) => scalar(field, item, "eq")) };
2019
+ function createSecretRedactionContext(initialValues = []) {
2020
+ const exactSecrets = /* @__PURE__ */ new Set();
2021
+ const exactSecretMatchers = /* @__PURE__ */ new Map();
2022
+ function register(value) {
2023
+ if (value.length > 0) exactSecrets.add(value);
2294
2024
  }
2295
- return {
2296
- field,
2297
- op,
2298
- value: scalar(field, value, op)
2299
- };
2300
- }
2301
- function admitObject(input, depth = 0, budget = { count: 0 }) {
2302
- if (depth > MAX_DEPTH) fail("Run event where exceeds depth 8.");
2303
- if (!object(input) || !Object.keys(input).length)
2304
- fail("Run event where requires a nonempty object.");
2305
- const terms = [];
2306
- const fieldTerms = (raw, filter) => {
2307
- const field = validateRunEventField(raw);
2308
- const operators = object(filter) ? Object.entries(filter) : [["eq", filter]];
2309
- if (!operators.length)
2310
- fail(`Run event ${field} has an empty operator object.`);
2311
- for (const [op, value] of operators) {
2312
- if (++budget.count > MAX_PREDICATES)
2313
- fail("Run event where exceeds 64 predicates.");
2314
- terms.push(comparison(field, op, value));
2025
+ function matchersForSecret(secret) {
2026
+ const cached = exactSecretMatchers.get(secret);
2027
+ if (cached) return cached;
2028
+ const literals = [secret];
2029
+ try {
2030
+ const encoded = encodeURIComponent(secret);
2031
+ if (encoded !== secret) literals.push(encoded);
2032
+ } catch {
2315
2033
  }
2316
- };
2317
- for (const [key, filter] of Object.entries(input)) {
2318
- if (key === "and" || key === "or") {
2319
- if (!Array.isArray(filter) || filter.length < 1 || filter.length > MAX_PREDICATES)
2320
- fail(`Run event ${key} requires a nonempty bounded array.`);
2321
- const children = filter.map(
2322
- (item) => admitObject(item, depth + 1, budget)
2323
- );
2324
- terms.push(key === "and" ? { and: children } : { or: children });
2325
- } else if (key === "context") {
2326
- if (!object(filter) || !Object.keys(filter).length)
2327
- fail("Run event context requires flat scalar fields.");
2328
- for (const [contextKey, value] of Object.entries(filter))
2329
- fieldTerms(`context.${contextKey}`, value);
2330
- } else fieldTerms(key, filter);
2034
+ const built = literals.map((literal) => ({
2035
+ literal,
2036
+ matcher: new RegExp(escapeRegExp(literal), "g")
2037
+ }));
2038
+ exactSecretMatchers.set(secret, built);
2039
+ return built;
2331
2040
  }
2332
- return terms.length === 1 ? terms[0] : { and: terms };
2333
- }
2334
- function compact(predicate) {
2335
- if ("and" in predicate) {
2336
- const merged = {};
2337
- for (const term of predicate.and) {
2338
- const child = compact(term);
2339
- for (const [key, value] of Object.entries(child)) {
2340
- if (key === "and") return { and: predicate.and.map(compact) };
2341
- if (key === "context") {
2342
- const context = merged.context ?? {};
2343
- for (const [name, filter2] of Object.entries(value)) {
2344
- if (Object.prototype.hasOwnProperty.call(context, name)) {
2345
- const combined = combineFilters(context[name], filter2);
2346
- if (!combined) return { and: predicate.and.map(compact) };
2347
- context[name] = combined;
2348
- } else context[name] = filter2;
2349
- }
2350
- merged.context = context;
2351
- } else if (Object.prototype.hasOwnProperty.call(merged, key)) {
2352
- const combined = key === "or" ? void 0 : combineFilters(merged[key], value);
2353
- if (!combined) return { and: predicate.and.map(compact) };
2354
- merged[key] = combined;
2355
- } else merged[key] = value;
2041
+ for (const value of initialValues) register(value);
2042
+ function containsRegisteredSecret(value, {
2043
+ includeEncoded = false,
2044
+ minimumLength = 1
2045
+ } = {}) {
2046
+ for (const secret of exactSecrets) {
2047
+ if (secret.length < minimumLength) continue;
2048
+ if (value.includes(secret)) return true;
2049
+ if (includeEncoded) {
2050
+ try {
2051
+ const encoded = encodeURIComponent(secret);
2052
+ if (encoded !== secret && value.includes(encoded)) return true;
2053
+ } catch {
2054
+ }
2356
2055
  }
2357
2056
  }
2358
- return merged;
2057
+ return false;
2359
2058
  }
2360
- if ("or" in predicate) return { or: predicate.or.map(compact) };
2361
- const filter = predicate.op === "eq" ? predicate.value : { [predicate.op]: predicate.value };
2362
- if (predicate.field.startsWith("context."))
2363
- return { context: { [predicate.field.slice(8)]: filter } };
2364
- return { [predicate.field]: filter };
2365
- }
2366
- function combineFilters(left, right) {
2367
- const first = object(left) ? left : { eq: left };
2368
- const second = object(right) ? right : { eq: right };
2369
- if (Object.keys(first).some(
2370
- (key) => Object.prototype.hasOwnProperty.call(second, key)
2371
- ))
2372
- return void 0;
2373
- return { ...first, ...second };
2374
- }
2375
- function canonical(predicate) {
2376
- if ("field" in predicate) return predicate;
2377
- const operator = "and" in predicate ? "and" : "or";
2378
- const source = "and" in predicate ? predicate.and : predicate.or;
2379
- const terms = source.map(canonical).flatMap((term) => {
2380
- if (operator === "and" && "and" in term) return term.and;
2381
- if (operator === "or" && "or" in term) return term.or;
2382
- return [term];
2383
- });
2384
- terms.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
2385
- const unique = terms.filter(
2386
- (term, i) => i === 0 || JSON.stringify(term) !== JSON.stringify(terms[i - 1])
2387
- );
2388
- if (unique.length === 1) return unique[0];
2389
- return operator === "and" ? { and: unique } : { or: unique };
2390
- }
2391
- function parseDsl(input) {
2392
- let position = 0;
2393
- let count = 0;
2394
- const skip = () => {
2395
- while (/\s/.test(input[position] ?? "") && position < input.length)
2396
- position++;
2397
- };
2398
- const take = (token) => {
2399
- skip();
2400
- if (input.startsWith(token, position)) {
2401
- position += token.length;
2402
- return true;
2059
+ function matchesRegisteredSecret(value) {
2060
+ return exactSecrets.has(value);
2061
+ }
2062
+ function redactRegisteredSecrets(value) {
2063
+ if (exactSecrets.size === 0) return value;
2064
+ let output = value;
2065
+ for (const secret of exactSecrets) {
2066
+ for (const { literal, matcher } of matchersForSecret(secret)) {
2067
+ if (!output.includes(literal)) continue;
2068
+ matcher.lastIndex = 0;
2069
+ output = output.replace(matcher, SECRET_REDACTION_PLACEHOLDER);
2070
+ }
2403
2071
  }
2404
- return false;
2405
- };
2406
- const json = () => {
2407
- skip();
2408
- const start = position;
2409
- if (input[position] === '"') {
2410
- position++;
2411
- let closed = false;
2412
- while (position < input.length) {
2413
- const char = input[position++];
2414
- if (char === "\\") position++;
2415
- else if (char === '"') {
2416
- closed = true;
2417
- break;
2418
- }
2072
+ return output;
2073
+ }
2074
+ function redactString(value) {
2075
+ const output = redactRegisteredSecrets(value);
2076
+ return redactSecretLikeString(output);
2077
+ }
2078
+ function redact(value) {
2079
+ if (typeof value === "string") return redactString(value);
2080
+ if (Array.isArray(value)) {
2081
+ let changed = false;
2082
+ const next = new Array(value.length);
2083
+ for (let index = 0; index < value.length; index += 1) {
2084
+ const entry = value[index];
2085
+ const redacted = redact(entry);
2086
+ next[index] = redacted;
2087
+ if (redacted !== entry) changed = true;
2419
2088
  }
2420
- if (!closed) fail("Run event DSL has an unterminated JSON string.");
2421
- } else {
2422
- const match = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(
2423
- input.slice(position)
2424
- );
2425
- if (!match)
2426
- fail("Run event DSL requires a JSON quoted string or scalar.");
2427
- position += match[0].length;
2428
- }
2429
- try {
2430
- return JSON.parse(input.slice(start, position));
2431
- } catch {
2432
- fail("Run event DSL requires valid JSON scalars.");
2433
- }
2434
- };
2435
- const term = (depth) => {
2436
- if (depth > MAX_DEPTH) fail("Run event where exceeds depth 8.");
2437
- if (take("(")) {
2438
- const nested = or(depth + 1);
2439
- if (!take(")")) fail("Run event DSL is missing a closing parenthesis.");
2440
- return nested;
2089
+ return changed ? next : value;
2441
2090
  }
2442
- skip();
2443
- const match = /^(?:\$\.)?[A-Za-z_][A-Za-z0-9_.]*/.exec(
2444
- input.slice(position)
2445
- );
2446
- if (!match) fail("Run event DSL requires a supported field.");
2447
- position += match[0].length;
2448
- const field = validateRunEventField(match[0]);
2449
- if (++count > MAX_PREDICATES)
2450
- fail("Run event where exceeds 64 predicates.");
2451
- skip();
2452
- const operation = /^(==|!=|>=|<=|>|<|\b(?:eq|ne|gte|gt|lte|lt|in|exists)\b)/.exec(
2453
- input.slice(position)
2454
- );
2455
- if (!operation) {
2456
- const token = /^(?:[=!<>~]+|[A-Za-z_]+)/.exec(input.slice(position))?.[0] ?? input[position] ?? "<end>";
2457
- if (token === "=") {
2458
- const corrected = `${input.slice(0, position)}==${input.slice(position + 1)}`;
2459
- fail(
2460
- `Run event DSL does not support "=" at character ${position + 1}. For equality, replace this "=" with "==": ${JSON.stringify(corrected)}.`
2461
- );
2091
+ if (value && typeof value === "object") {
2092
+ const prototype = Object.getPrototypeOf(value);
2093
+ let changed = prototype !== Object.prototype && prototype !== null;
2094
+ const source = value;
2095
+ const keys = Object.keys(source);
2096
+ const redactedValues = new Array(keys.length);
2097
+ for (let index = 0; index < keys.length; index += 1) {
2098
+ const key = keys[index];
2099
+ const entry = source[key];
2100
+ const redacted = isSensitiveFieldName(key) ? SECRET_REDACTION_PLACEHOLDER : redact(entry);
2101
+ redactedValues[index] = redacted;
2102
+ if (redacted !== entry) changed = true;
2462
2103
  }
2463
- fail(
2464
- `Run event DSL does not support ${JSON.stringify(token)} after ${JSON.stringify(match[0])} at character ${position + 1}. Use ==, !=, >, >=, <, <=, in or exists.`
2104
+ if (!changed) return value;
2105
+ return Object.fromEntries(
2106
+ keys.map((key, index) => [key, redactedValues[index]])
2465
2107
  );
2466
2108
  }
2467
- position += operation[0].length;
2468
- const symbols = {
2469
- "==": "eq",
2470
- "!=": "ne",
2471
- ">=": "gte",
2472
- "<=": "lte",
2473
- ">": "gt",
2474
- "<": "lt"
2475
- };
2476
- const op = symbols[operation[0]] ?? operation[0];
2477
- if (op === "in") {
2478
- if (!take("[")) fail("Run event DSL in requires a JSON scalar array.");
2479
- const values = [];
2480
- if (!take("]")) {
2481
- do {
2482
- values.push(json());
2483
- if (values.length > 32)
2484
- fail("Run event in requires between 1 and 32 scalars.");
2485
- } while (take(","));
2486
- if (!take("]")) fail("Run event DSL in requires a closing bracket.");
2487
- }
2488
- return comparison(field, op, values);
2109
+ return value;
2110
+ }
2111
+ function redactKnownSecrets(value) {
2112
+ if (typeof value === "string") return redactRegisteredSecrets(value);
2113
+ if (Array.isArray(value)) {
2114
+ return value.map((entry) => redactKnownSecrets(entry));
2489
2115
  }
2490
- return comparison(field, op, json());
2491
- };
2492
- const and = (depth) => {
2493
- const terms = [term(depth)];
2494
- while (take("&&")) terms.push(term(depth));
2495
- return terms.length === 1 ? terms[0] : { and: terms };
2116
+ if (value && typeof value === "object") {
2117
+ return Object.fromEntries(
2118
+ Object.entries(value).map(([key, entry]) => [
2119
+ key,
2120
+ redactKnownSecrets(entry)
2121
+ ])
2122
+ );
2123
+ }
2124
+ return value;
2125
+ }
2126
+ return {
2127
+ register,
2128
+ containsRegisteredSecret,
2129
+ matchesRegisteredSecret,
2130
+ redactRegisteredSecrets,
2131
+ redactString,
2132
+ redactKnownSecrets,
2133
+ redact
2496
2134
  };
2497
- const or = (depth) => {
2498
- const terms = [and(depth)];
2499
- while (take("||")) terms.push(and(depth));
2500
- return terms.length === 1 ? terms[0] : { or: terms };
2135
+ }
2136
+
2137
+ // ../play-runtime/output-size-limits.ts
2138
+ var TERMINAL_RUN_RESULT_MAX_BYTES = 6 * 1024 * 1024;
2139
+ var LEDGER_TERMINAL_RESULT_MAX_BYTES = 256 * 1024;
2140
+ var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 5 * 1024 * 1024;
2141
+ var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 5 * 1024 * 1024;
2142
+ var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
2143
+ var RUNTIME_RECEIPT_COMPLETION_BUFFER_MAX_BYTES = 32 * 1024 * 1024;
2144
+ var RUNTIME_RECEIPT_CONTROL_BUFFER_MAX_BYTES = 2 * 1024 * 1024;
2145
+ var RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES = 16 * 1024 * 1024;
2146
+ var RUNTIME_RECEIPT_GATEWAY_BATCH_MAX_BYTES = 12 * 1024 * 1024;
2147
+ var RUNTIME_RECEIPT_WRITER_TARGET_BATCH_BYTES = 8 * 1024 * 1024;
2148
+ var RUNNER_POST_TERMINAL_DIAGNOSTIC_MAX_BYTES = 64 * 1024;
2149
+ var JSON_SIZE_LIMIT_REACHED = Symbol("JSON_SIZE_LIMIT_REACHED");
2150
+
2151
+ // ../play-runtime/ledger-safe-payload.ts
2152
+ var ledgerIngressRedactor = createSecretRedactionContext();
2153
+
2154
+ // ../play-runtime/docflow-node-io.ts
2155
+ var utf8Encoder = new TextEncoder();
2156
+ var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
2157
+
2158
+ // ../play-data-plane/runtime-sheet-orm.ts
2159
+ var RUNTIME_SHEET_CELL_META_COLUMN = {
2160
+ name: "_cell_meta",
2161
+ dataType: "jsonb",
2162
+ nullable: "NO",
2163
+ defaultSql: "'{}'::jsonb"
2164
+ };
2165
+ var RUNTIME_SHEET_COLUMN_CONTRACTS = [
2166
+ {
2167
+ name: "_key",
2168
+ sqlType: "text",
2169
+ dataType: "text",
2170
+ nullable: "NO",
2171
+ defaultSql: null,
2172
+ repairable: false,
2173
+ requiresDefault: false
2174
+ },
2175
+ {
2176
+ name: "_status",
2177
+ sqlType: "text NOT NULL",
2178
+ dataType: "text",
2179
+ nullable: "NO",
2180
+ defaultSql: null,
2181
+ repairable: false,
2182
+ requiresDefault: false
2183
+ },
2184
+ {
2185
+ name: "_run_id",
2186
+ sqlType: "text",
2187
+ dataType: "text",
2188
+ nullable: "YES",
2189
+ defaultSql: null,
2190
+ repairable: true,
2191
+ requiresDefault: false
2192
+ },
2193
+ {
2194
+ name: "_error",
2195
+ sqlType: "text",
2196
+ dataType: "text",
2197
+ nullable: "YES",
2198
+ defaultSql: null,
2199
+ repairable: true,
2200
+ requiresDefault: false
2201
+ },
2202
+ {
2203
+ name: "_stage",
2204
+ sqlType: "text",
2205
+ dataType: "text",
2206
+ nullable: "YES",
2207
+ defaultSql: null,
2208
+ repairable: true,
2209
+ requiresDefault: false
2210
+ },
2211
+ {
2212
+ name: "_provider",
2213
+ sqlType: "text",
2214
+ dataType: "text",
2215
+ nullable: "YES",
2216
+ defaultSql: null,
2217
+ repairable: true,
2218
+ requiresDefault: false
2219
+ },
2220
+ {
2221
+ name: "_input_index",
2222
+ sqlType: "bigint",
2223
+ dataType: "bigint",
2224
+ nullable: "YES",
2225
+ defaultSql: null,
2226
+ repairable: true,
2227
+ requiresDefault: false
2228
+ },
2229
+ {
2230
+ name: "_attempt_id",
2231
+ sqlType: "text",
2232
+ dataType: "text",
2233
+ nullable: "YES",
2234
+ defaultSql: null,
2235
+ repairable: true,
2236
+ requiresDefault: false
2237
+ },
2238
+ {
2239
+ name: "_attempt_owner_run_id",
2240
+ sqlType: "text",
2241
+ dataType: "text",
2242
+ nullable: "YES",
2243
+ defaultSql: null,
2244
+ repairable: true,
2245
+ requiresDefault: false
2246
+ },
2247
+ {
2248
+ name: "_attempt_expires_at",
2249
+ sqlType: "timestamptz",
2250
+ dataType: "timestamp with time zone",
2251
+ nullable: "YES",
2252
+ defaultSql: null,
2253
+ repairable: true,
2254
+ requiresDefault: false
2255
+ },
2256
+ {
2257
+ name: "_attempt_seq",
2258
+ sqlType: "integer",
2259
+ dataType: "integer",
2260
+ nullable: "YES",
2261
+ defaultSql: null,
2262
+ repairable: true,
2263
+ requiresDefault: false
2264
+ },
2265
+ {
2266
+ name: "_write_version",
2267
+ sqlType: "bigint NOT NULL DEFAULT 0",
2268
+ dataType: "bigint",
2269
+ nullable: "NO",
2270
+ defaultSql: "0",
2271
+ repairable: true,
2272
+ requiresDefault: true
2273
+ },
2274
+ {
2275
+ name: "_writer_run_id",
2276
+ sqlType: "text",
2277
+ dataType: "text",
2278
+ nullable: "YES",
2279
+ defaultSql: null,
2280
+ repairable: true,
2281
+ requiresDefault: false
2282
+ },
2283
+ {
2284
+ name: "_created_at",
2285
+ sqlType: "timestamptz NOT NULL DEFAULT now()",
2286
+ dataType: "timestamp with time zone",
2287
+ nullable: "NO",
2288
+ defaultSql: "now()",
2289
+ repairable: true,
2290
+ requiresDefault: true
2291
+ },
2292
+ {
2293
+ name: "_updated_at",
2294
+ sqlType: "timestamptz NOT NULL DEFAULT now()",
2295
+ dataType: "timestamp with time zone",
2296
+ nullable: "NO",
2297
+ defaultSql: "now()",
2298
+ repairable: true,
2299
+ requiresDefault: true
2300
+ },
2301
+ {
2302
+ name: "_version",
2303
+ sqlType: "bigint NOT NULL",
2304
+ dataType: "bigint",
2305
+ nullable: "NO",
2306
+ defaultSql: null,
2307
+ defaultKind: "version_sequence",
2308
+ repairable: true,
2309
+ requiresDefault: true
2310
+ },
2311
+ {
2312
+ ...RUNTIME_SHEET_CELL_META_COLUMN,
2313
+ sqlType: `jsonb NOT NULL DEFAULT ${RUNTIME_SHEET_CELL_META_COLUMN.defaultSql}`,
2314
+ repairable: true,
2315
+ requiresDefault: true
2316
+ }
2317
+ ];
2318
+ var RUNTIME_SHEET_ATTEMPT_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
2319
+ (column) => column.name.startsWith("_attempt_")
2320
+ ).map((column) => column.name);
2321
+ var RUNTIME_SHEET_WRITE_VERSION_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
2322
+ (column) => column.name === "_write_version" || column.name === "_writer_run_id"
2323
+ ).map((column) => column.name);
2324
+ var RUNTIME_SHEET_TIMESTAMP_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
2325
+ (column) => column.name === "_created_at" || column.name === "_updated_at"
2326
+ ).map((column) => column.name);
2327
+ var RUNTIME_SHEET_SYSTEM_COLUMNS = new Set(
2328
+ RUNTIME_SHEET_COLUMN_CONTRACTS.map((column) => column.name)
2329
+ );
2330
+ var RUNTIME_SHEET_REPAIRABLE_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
2331
+ (column) => column.repairable
2332
+ );
2333
+
2334
+ // ../play-runtime/run-ledger.ts
2335
+ var TERMINAL_STATUS_BY_EVENT_TYPE = {
2336
+ [RUN_EVENT_KIND.runCompleted]: "completed",
2337
+ [RUN_EVENT_KIND.runFailed]: "failed",
2338
+ [RUN_EVENT_KIND.runCancelled]: "cancelled"
2339
+ };
2340
+
2341
+ // ../play-runtime/run-snapshot-stream.ts
2342
+ function isTerminalPlayRunLiveStatus(status) {
2343
+ return isTerminalPlayRunLifecycleStatus(status);
2344
+ }
2345
+ function makeRunStreamEvent(input) {
2346
+ return {
2347
+ ...input,
2348
+ scope: "play",
2349
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString()
2501
2350
  };
2502
- const result = or(0);
2503
- skip();
2504
- if (position !== input.length)
2505
- fail(`Run event DSL has unsupported input at position ${position}.`);
2506
- return result;
2507
2351
  }
2508
- function parseRunEventWhere(input) {
2509
- bounded(input);
2510
- const where = compact(
2511
- canonical(typeof input === "string" ? parseDsl(input) : admitObject(input))
2352
+ var EMPTY_PLAY_RUN_STREAM_DIFF_STATE = {
2353
+ runSignature: "",
2354
+ snapshotSignature: "",
2355
+ stepStatusSignature: "",
2356
+ stepProgressSignature: "",
2357
+ lastLogSeq: 0
2358
+ };
2359
+ function getSnapshotCursor(snapshot) {
2360
+ return String(snapshot.updatedAt ?? Date.now());
2361
+ }
2362
+ function getRunSignature(snapshot) {
2363
+ return [snapshot.runId, snapshot.status, snapshot.updatedAt ?? 0].join(":");
2364
+ }
2365
+ function getStepStatusSignature(snapshot) {
2366
+ return snapshot.nodeStates.map(
2367
+ (state) => [
2368
+ state.nodeId,
2369
+ state.status,
2370
+ state.artifactTableNamespace ?? "",
2371
+ state.startedAt ?? "",
2372
+ state.completedAt ?? "",
2373
+ state.progress?.startedAt ?? "",
2374
+ state.progress?.completedAt ?? "",
2375
+ state.updatedAt ?? "",
2376
+ state.progress?.updatedAt ?? ""
2377
+ ].join(":")
2378
+ ).join("|");
2379
+ }
2380
+ function getStepProgressSignature(snapshot) {
2381
+ return JSON.stringify(
2382
+ snapshot.nodeStates.map((state) => [
2383
+ state.nodeId,
2384
+ projectPlayStepProgressCounters(state.progress ?? {}),
2385
+ state.progress?.artifactTableNamespace ?? "",
2386
+ state.progress?.startedAt ?? "",
2387
+ state.progress?.completedAt ?? "",
2388
+ state.progress?.updatedAt ?? "",
2389
+ state.progress?.message ?? ""
2390
+ ])
2512
2391
  );
2513
- bounded(where);
2514
- return where;
2515
2392
  }
2516
- function normalizeRunEventQueryTimestamp(value) {
2517
- let extraMicros = "";
2518
- if (typeof value === "string") {
2519
- const timestamp = /^[+-]?\d{4,6}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.(\d{1,6}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(
2520
- value
2521
- );
2522
- if (!timestamp)
2523
- fail(
2524
- "Run event time bounds require an ISO timestamp with a timezone and at most six fractional digits."
2525
- );
2526
- extraMicros = (timestamp[1] ?? "").padEnd(6, "0").slice(3);
2393
+ function getSnapshotSignature(snapshot) {
2394
+ return JSON.stringify(snapshot);
2395
+ }
2396
+ function resolvePlayRunLogGap(snapshot, lastLogSeq) {
2397
+ if (snapshot.totalLogCount <= lastLogSeq) {
2398
+ return null;
2527
2399
  }
2528
- const date = new Date(value);
2529
- if (!Number.isFinite(date.getTime()))
2530
- fail("Run event time bound is invalid.");
2531
- const canonical2 = date.toISOString();
2532
- return /[1-9]/.test(extraMicros) ? canonical2.replace(/Z$/, `${extraMicros}Z`) : canonical2;
2400
+ const tailFirstSeq = snapshot.totalLogCount - snapshot.logs.length + 1;
2401
+ if (lastLogSeq + 1 >= tailFirstSeq) {
2402
+ return null;
2403
+ }
2404
+ return {
2405
+ missingCount: tailFirstSeq - 1 - lastLogSeq,
2406
+ tailFirstSeq
2407
+ };
2533
2408
  }
2534
- function timestampMicros(value) {
2535
- const fraction = /\.(\d+)Z$/.exec(value)?.[1] ?? "";
2536
- return BigInt(Date.parse(value)) * BigInt(1e3) + BigInt(fraction.padEnd(6, "0").slice(3));
2409
+ function diffLogLines(input) {
2410
+ const { logs, totalLogCount } = input.snapshot;
2411
+ if (input.snapshot.logsUnavailable) {
2412
+ return {
2413
+ lines: [],
2414
+ lastLogSeq: input.lastLogSeq,
2415
+ firstSeq: null
2416
+ };
2417
+ }
2418
+ if (totalLogCount <= input.lastLogSeq) {
2419
+ return { lines: [], lastLogSeq: input.lastLogSeq, firstSeq: null };
2420
+ }
2421
+ const tailFirstSeq = totalLogCount - logs.length + 1;
2422
+ if (input.lastLogSeq + 1 >= tailFirstSeq) {
2423
+ return {
2424
+ lines: logs.slice(input.lastLogSeq + 1 - tailFirstSeq),
2425
+ lastLogSeq: totalLogCount,
2426
+ firstSeq: input.lastLogSeq + 1
2427
+ };
2428
+ }
2429
+ const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
2430
+ return {
2431
+ lines: [
2432
+ `[watch] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
2433
+ ...logs
2434
+ ],
2435
+ lastLogSeq: totalLogCount,
2436
+ firstSeq: null
2437
+ };
2537
2438
  }
2538
- function time(value, nowMs, fallback) {
2539
- if (value === void 0) value = fallback;
2540
- let timestamp;
2541
- if (typeof value === "number") timestamp = value;
2542
- else if (typeof value === "string") {
2543
- const relative = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
2544
- if (relative) {
2545
- const units = {
2546
- ms: 1,
2547
- s: 1e3,
2548
- m: 6e4,
2549
- h: 36e5,
2550
- d: 864e5
2551
- };
2552
- timestamp = nowMs - Number(relative[1]) * units[relative[2]];
2553
- } else {
2554
- return normalizeRunEventQueryTimestamp(value);
2439
+ function diffPlayRunStreamEvents(input) {
2440
+ const { snapshot, streamId, previous } = input;
2441
+ const cursor = getSnapshotCursor(snapshot);
2442
+ const logDiff = diffLogLines({
2443
+ snapshot,
2444
+ lastLogSeq: previous.lastLogSeq
2445
+ });
2446
+ const next = {
2447
+ runSignature: getRunSignature(snapshot),
2448
+ stepStatusSignature: getStepStatusSignature(snapshot),
2449
+ stepProgressSignature: getStepProgressSignature(snapshot),
2450
+ snapshotSignature: getSnapshotSignature(snapshot),
2451
+ lastLogSeq: logDiff.lastLogSeq
2452
+ };
2453
+ const events = [];
2454
+ if (next.stepStatusSignature !== previous.stepStatusSignature) {
2455
+ for (const state of snapshot.nodeStates) {
2456
+ if (state.status === "idle") {
2457
+ continue;
2458
+ }
2459
+ const persistedStartedAt = state.startedAt ?? state.progress?.startedAt ?? null;
2460
+ const persistedCompletedAt = state.completedAt ?? state.progress?.completedAt ?? null;
2461
+ events.push(
2462
+ makeRunStreamEvent({
2463
+ cursor,
2464
+ streamId,
2465
+ type: "play.step.status",
2466
+ payload: {
2467
+ runId: snapshot.runId,
2468
+ stepId: state.nodeId,
2469
+ status: state.status,
2470
+ artifactTableNamespace: state.artifactTableNamespace ?? null,
2471
+ ...resolveTimingWindow({
2472
+ startedAt: persistedStartedAt,
2473
+ completedAt: persistedCompletedAt,
2474
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
2475
+ })
2476
+ }
2477
+ })
2478
+ );
2555
2479
  }
2556
- } else fail("Run event time bounds require a timestamp or duration.");
2557
- return normalizeRunEventQueryTimestamp(timestamp);
2558
- }
2559
- function exact(value, name) {
2560
- if (typeof value !== "string" || !value.trim() || value.length > 256)
2561
- fail(
2562
- `Run event ${name} requires a nonempty string of at most 256 characters.`
2480
+ }
2481
+ if (next.stepProgressSignature !== previous.stepProgressSignature) {
2482
+ for (const state of snapshot.nodeStates) {
2483
+ if (!state.progress) {
2484
+ continue;
2485
+ }
2486
+ events.push(
2487
+ makeRunStreamEvent({
2488
+ cursor: String(
2489
+ state.progress.updatedAt ?? snapshot.updatedAt ?? Date.now()
2490
+ ),
2491
+ streamId,
2492
+ type: "play.step.progress",
2493
+ payload: {
2494
+ runId: snapshot.runId,
2495
+ stepId: state.nodeId,
2496
+ ...projectPlayStepProgressCounters(state.progress),
2497
+ message: state.progress.message,
2498
+ artifactTableNamespace: state.progress.artifactTableNamespace ?? state.artifactTableNamespace ?? null,
2499
+ ...resolveTimingWindow({
2500
+ startedAt: state.startedAt ?? state.progress.startedAt ?? null,
2501
+ completedAt: state.completedAt ?? state.progress.completedAt ?? null,
2502
+ updatedAt: state.progress.updatedAt ?? snapshot.updatedAt ?? null
2503
+ })
2504
+ }
2505
+ })
2506
+ );
2507
+ }
2508
+ }
2509
+ if (logDiff.lines.length > 0) {
2510
+ events.push(
2511
+ makeRunStreamEvent({
2512
+ cursor,
2513
+ streamId,
2514
+ type: "play.run.log",
2515
+ payload: {
2516
+ runId: snapshot.runId,
2517
+ lines: logDiff.lines,
2518
+ source: "worker",
2519
+ ...logDiff.firstSeq !== null ? { firstSeq: logDiff.firstSeq } : {},
2520
+ totalLogCount: snapshot.totalLogCount
2521
+ }
2522
+ })
2563
2523
  );
2564
- return value;
2565
- }
2566
- var CRITERIA_KEYS = [
2567
- "since",
2568
- "until",
2569
- "where",
2570
- "runId",
2571
- "stepId",
2572
- "minLevel",
2573
- "text",
2574
- "playName",
2575
- "kind"
2576
- ];
2577
- function normalizeRunEventQuery(criteria, nowMs = Date.now()) {
2578
- if (!object(criteria)) fail("Run event criteria requires an object.");
2579
- bounded(criteria, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
2580
- for (const key of Object.keys(criteria)) {
2581
- if (!CRITERIA_KEYS.includes(key))
2582
- fail(`Unsupported run event criterion: ${key}.`);
2583
2524
  }
2584
- const until = time(criteria.until, nowMs, nowMs);
2585
- const since = time(criteria.since, nowMs, Date.parse(until) - 36e5);
2586
- if (timestampMicros(since) >= timestampMicros(until) || timestampMicros(until) - timestampMicros(since) > BigInt(RUN_EVENT_QUERY_RETENTION_MS) * BigInt(1e3))
2587
- fail("Run event query requires a window of at most seven days.");
2588
- const query = { since, until };
2589
- if (criteria.where !== void 0)
2590
- query.where = parseRunEventWhere(criteria.where);
2591
- for (const name of ["runId", "stepId", "playName", "kind"]) {
2592
- if (criteria[name] !== void 0) query[name] = exact(criteria[name], name);
2525
+ if (next.snapshotSignature !== previous.snapshotSignature) {
2526
+ const enrichedNodeStates = snapshot.nodeStates.map((state) => {
2527
+ const timing = resolveTimingWindow({
2528
+ startedAt: state.startedAt ?? state.progress?.startedAt ?? null,
2529
+ completedAt: state.completedAt ?? state.progress?.completedAt ?? null,
2530
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
2531
+ });
2532
+ return {
2533
+ ...state,
2534
+ ...timing,
2535
+ progress: state.progress ? {
2536
+ ...state.progress,
2537
+ ...resolveTimingWindow({
2538
+ startedAt: state.progress.startedAt ?? state.startedAt ?? null,
2539
+ completedAt: state.progress.completedAt ?? state.completedAt ?? null,
2540
+ updatedAt: state.progress.updatedAt ?? state.updatedAt ?? snapshot.updatedAt ?? null
2541
+ })
2542
+ } : state.progress
2543
+ };
2544
+ });
2545
+ events.push(
2546
+ makeRunStreamEvent({
2547
+ cursor,
2548
+ streamId,
2549
+ type: "play.run.snapshot",
2550
+ payload: { ...snapshot, nodeStates: enrichedNodeStates }
2551
+ })
2552
+ );
2593
2553
  }
2594
- if (criteria.minLevel !== void 0) {
2595
- if (!RUN_LOG_LEVELS.includes(criteria.minLevel))
2596
- fail("Run event minLevel requires debug, info, warn or error.");
2597
- query.minLevel = criteria.minLevel;
2554
+ if (next.runSignature !== previous.runSignature) {
2555
+ events.push(
2556
+ makeRunStreamEvent({
2557
+ cursor,
2558
+ streamId,
2559
+ type: "play.run.status",
2560
+ payload: {
2561
+ runId: snapshot.runId,
2562
+ status: snapshot.status,
2563
+ updatedAt: snapshot.updatedAt,
2564
+ ...snapshot.activity ? { activity: snapshot.activity } : {}
2565
+ }
2566
+ })
2567
+ );
2598
2568
  }
2599
- if (criteria.text !== void 0) {
2600
- if (typeof criteria.text !== "string" || !criteria.text.length || new TextEncoder().encode(criteria.text).length > 1024)
2601
- fail("Run event text requires between 1 and 1024 bytes.");
2602
- query.text = criteria.text;
2569
+ return { events, next };
2570
+ }
2571
+
2572
+ // src/runs/watch-polling.ts
2573
+ var RUN_WATCH_POLL_INTERVAL_MS = 2e3;
2574
+ function waitForNextPoll(ms, signal) {
2575
+ if (signal?.aborted) {
2576
+ return Promise.resolve(false);
2603
2577
  }
2604
- return query;
2578
+ return new Promise((resolve) => {
2579
+ const finish = (elapsed) => {
2580
+ clearTimeout(timeout);
2581
+ signal?.removeEventListener("abort", onAbort);
2582
+ resolve(elapsed);
2583
+ };
2584
+ const onAbort = () => finish(false);
2585
+ const timeout = setTimeout(() => finish(true), ms);
2586
+ signal?.addEventListener("abort", onAbort, { once: true });
2587
+ });
2605
2588
  }
2606
- function normalizeRunEventOperationRequest(input, nowMs = Date.now()) {
2607
- bounded(input, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
2608
- if (!object(input)) fail("Run event operation requires an object.");
2609
- const operation = input.operation;
2610
- if (!["list", "count", "summarize", "fields"].includes(String(operation)))
2611
- fail("Run event operation requires list, count, summarize or fields.");
2612
- if (input.resource !== "logs" && input.resource !== "events")
2613
- fail("Run event resource requires logs or events.");
2614
- const allowed = [
2615
- "operation",
2616
- "resource",
2617
- "runId",
2618
- ...operation === "fields" ? [] : CRITERIA_KEYS,
2619
- ...operation === "list" ? ["limit", "cursor", "includePayloads"] : operation === "summarize" ? ["groupBy", "measures", "limit", "cursor"] : []
2620
- ];
2621
- for (const key of Object.keys(input)) {
2622
- if (!allowed.includes(key))
2623
- fail(`Run event ${operation} does not support ${key}.`);
2589
+ function readSnapshotResponse(response, runId) {
2590
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
2591
+ throw new Error("Run watch returned an invalid snapshot response.");
2624
2592
  }
2625
- const base = {
2626
- operation,
2627
- resource: input.resource,
2628
- ...input.runId === void 0 ? {} : { runId: exact(input.runId, "runId") }
2629
- };
2630
- if (operation === "fields") return base;
2631
- if (input.resource === "logs" && !base.runId)
2632
- fail("Run event logs require runId.");
2633
- if (input.cursor !== void 0 && (typeof input.cursor !== "string" || !input.cursor.length || input.cursor.length > RUN_EVENT_QUERY_MAX_CURSOR_BYTES))
2634
- fail("Run event cursor is invalid.");
2635
- const rawCriteria = Object.fromEntries(
2636
- Object.entries(input).filter(
2637
- ([key]) => CRITERIA_KEYS.includes(key) && (key !== "runId" || input.resource === "events")
2638
- )
2639
- );
2640
- const query = normalizeRunEventQuery(
2641
- input.cursor === void 0 ? rawCriteria : { ...rawCriteria, since: nowMs - 36e5, until: nowMs },
2642
- nowMs
2643
- );
2644
- const criteria = { ...query };
2645
- if (input.cursor !== void 0) {
2646
- if (input.since === void 0) delete criteria.since;
2647
- else criteria.since = time(input.since, nowMs, nowMs - 36e5);
2648
- if (input.until === void 0) delete criteria.until;
2649
- else criteria.until = time(input.until, nowMs, nowMs);
2593
+ const record = response;
2594
+ const snapshot = record.snapshot;
2595
+ if (record.runId !== runId || !snapshot || typeof snapshot !== "object" || Array.isArray(snapshot) || snapshot.runId !== runId || typeof snapshot.status !== "string" || !Array.isArray(snapshot.logs) || !snapshot.logs.every(
2596
+ (line) => typeof line === "string"
2597
+ ) || typeof snapshot.totalLogCount !== "number" || !Array.isArray(snapshot.nodeStates)) {
2598
+ throw new Error(`Run watch returned an invalid snapshot for ${runId}.`);
2650
2599
  }
2651
- if (operation === "count")
2652
- return { ...base, ...criteria };
2653
- const limit = input.limit ?? (input.includePayloads === true ? 10 : 50);
2654
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > RUN_EVENT_QUERY_MAX_LIMIT)
2655
- fail("Run event limit must be between 1 and 200.");
2656
- const page = {
2657
- limit,
2658
- ...input.cursor === void 0 ? {} : { cursor: input.cursor }
2659
- };
2660
- if (operation === "list") {
2661
- if (input.includePayloads !== void 0 && typeof input.includePayloads !== "boolean")
2662
- fail("Run event includePayloads requires a boolean.");
2663
- if (input.includePayloads === true && limit > RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT)
2664
- fail("Run event payload pages cannot exceed 10 rows.");
2665
- return {
2666
- ...base,
2667
- ...criteria,
2668
- ...page,
2669
- ...input.includePayloads === void 0 ? {} : { includePayloads: input.includePayloads }
2670
- };
2600
+ return { runId, snapshot };
2601
+ }
2602
+ async function backfillRotatedLogTail(input) {
2603
+ if (!input.previous.runSignature || input.snapshot.logsUnavailable) {
2604
+ return input.snapshot;
2671
2605
  }
2672
- if (!Array.isArray(input.groupBy) || input.groupBy.length > 4)
2673
- fail("Run event groupBy requires at most four fields.");
2674
- const groupBy = input.groupBy.map(validateRunEventField);
2675
- if (new Set(groupBy).size !== groupBy.length)
2676
- fail("Run event groupBy fields must be distinct.");
2677
- const rawMeasures = input.measures ?? [{ op: "count" }];
2678
- if (!Array.isArray(rawMeasures) || rawMeasures.length < 1 || rawMeasures.length > 8)
2679
- fail("Run event measures require between 1 and 8 entries.");
2680
- const names = /* @__PURE__ */ new Set();
2681
- const measures = rawMeasures.map((measure) => {
2682
- if (!object(measure) || measure.op !== "count" && measure.op !== "sum")
2683
- fail("Run event measures support count and sum.");
2684
- for (const key of Object.keys(measure)) {
2685
- if (!["op", "as", ...measure.op === "sum" ? ["field"] : []].includes(key))
2686
- fail(`Unsupported run event measure option: ${key}.`);
2606
+ const gap = resolvePlayRunLogGap(input.snapshot, input.previous.lastLogSeq);
2607
+ if (!gap) {
2608
+ return input.snapshot;
2609
+ }
2610
+ const lines = [];
2611
+ let cursor = input.previous.lastLogSeq;
2612
+ const targetSeq = gap.tailFirstSeq - 1;
2613
+ while (cursor < targetSeq) {
2614
+ const limit = Math.min(1e3, targetSeq - cursor);
2615
+ const page = await input.http.get(
2616
+ `/api/v2/runs/${encodeURIComponent(input.runId)}/logs?afterSeq=${cursor}&limit=${limit}`,
2617
+ { signal: input.signal }
2618
+ );
2619
+ if (!page || !Array.isArray(page.entries) || page.entries.length === 0) {
2620
+ return input.snapshot;
2687
2621
  }
2688
- const field = measure.op === "sum" ? validateRunEventField(measure.field) : void 0;
2689
- if (field && !field.startsWith("context.") && RUN_EVENT_QUERY_FIELD_CATALOG[field].type !== "number")
2690
- fail(`Run event sum requires a numeric field: ${field}.`);
2691
- const name = measure.as ?? (field ? `sum_${field.replaceAll(".", "_")}` : "count");
2692
- if (typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name) || names.has(name))
2693
- fail("Run event measure names must be distinct identifiers.");
2694
- names.add(name);
2695
- return field ? { op: "sum", field, as: name } : { op: "count", as: name };
2696
- });
2697
- return {
2698
- ...base,
2699
- ...criteria,
2700
- ...page,
2701
- groupBy,
2702
- measures
2703
- };
2622
+ for (const entry of page.entries) {
2623
+ if (!entry || entry.seq !== cursor + 1 || typeof entry.line !== "string") {
2624
+ return input.snapshot;
2625
+ }
2626
+ lines.push(entry.line);
2627
+ cursor = entry.seq;
2628
+ }
2629
+ }
2630
+ if (cursor !== targetSeq) {
2631
+ return input.snapshot;
2632
+ }
2633
+ return { ...input.snapshot, logs: [...lines, ...input.snapshot.logs] };
2704
2634
  }
2705
-
2706
- // src/types.ts
2707
- function isStopAllPlayRunsDryRunResult(result) {
2708
- return "dryRun" in result && result.dryRun === true;
2635
+ async function* watchRunEvents(options) {
2636
+ const { http, runId, signal } = options;
2637
+ const path = `/api/v2/runs/${encodeURIComponent(runId)}?watchSnapshot=true`;
2638
+ const streamId = `play-run-watch:${runId}`;
2639
+ const pollIntervalMs = options.pollIntervalMs ?? RUN_WATCH_POLL_INTERVAL_MS;
2640
+ let previous = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
2641
+ let consecutiveFailures = 0;
2642
+ let outageNoticeSent = false;
2643
+ while (!signal?.aborted) {
2644
+ const pollStartedAt = Date.now();
2645
+ let snapshot;
2646
+ try {
2647
+ const response = await http.get(path, { signal });
2648
+ snapshot = await backfillRotatedLogTail({
2649
+ http,
2650
+ runId,
2651
+ snapshot: readSnapshotResponse(response, runId).snapshot,
2652
+ previous,
2653
+ signal
2654
+ });
2655
+ } catch (error) {
2656
+ if (signal?.aborted) {
2657
+ return;
2658
+ }
2659
+ if (!isTransientPlayRunReadError(error)) {
2660
+ throw error;
2661
+ }
2662
+ consecutiveFailures += 1;
2663
+ const reason = error instanceof Error ? error.message : String(error);
2664
+ if (!outageNoticeSent) {
2665
+ options.onRetry?.({
2666
+ attempt: consecutiveFailures,
2667
+ delayMs: pollIntervalMs,
2668
+ reason
2669
+ });
2670
+ options.onNotice?.(
2671
+ `[watch] run snapshot request failed; retrying every ${pollIntervalMs / 1e3}s (${reason})`
2672
+ );
2673
+ outageNoticeSent = true;
2674
+ }
2675
+ const remaining2 = Math.max(
2676
+ 0,
2677
+ pollIntervalMs - (Date.now() - pollStartedAt)
2678
+ );
2679
+ if (!await waitForNextPoll(remaining2, signal)) {
2680
+ return;
2681
+ }
2682
+ continue;
2683
+ }
2684
+ if (outageNoticeSent) {
2685
+ options.onNotice?.("[watch] run snapshot polling recovered");
2686
+ outageNoticeSent = false;
2687
+ }
2688
+ consecutiveFailures = 0;
2689
+ const diff = diffPlayRunStreamEvents({
2690
+ streamId,
2691
+ snapshot,
2692
+ previous
2693
+ });
2694
+ previous = diff.next;
2695
+ for (const event of diff.events) {
2696
+ yield event;
2697
+ }
2698
+ if (isTerminalPlayRunLiveStatus(snapshot.status)) {
2699
+ return;
2700
+ }
2701
+ const remaining = Math.max(
2702
+ 0,
2703
+ pollIntervalMs - (Date.now() - pollStartedAt)
2704
+ );
2705
+ if (!await waitForNextPoll(remaining, signal)) {
2706
+ return;
2707
+ }
2708
+ }
2709
2709
  }
2710
2710
 
2711
2711
  // ../play-runtime/runtime-environment.ts