@glubean/runner 0.7.0 → 0.8.0

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 (71) hide show
  1. package/dist/engine-bridge.d.ts +4 -0
  2. package/dist/engine-bridge.d.ts.map +1 -1
  3. package/dist/engine-bridge.js +10 -1
  4. package/dist/engine-bridge.js.map +1 -1
  5. package/dist/executor.d.ts +2 -2
  6. package/dist/executor.d.ts.map +1 -1
  7. package/dist/executor.js +9 -226
  8. package/dist/executor.js.map +1 -1
  9. package/dist/harness.js +3 -79
  10. package/dist/harness.js.map +1 -1
  11. package/dist/index.d.ts +16 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +17 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/load/continuation-pool.d.ts +82 -0
  16. package/dist/load/continuation-pool.d.ts.map +1 -0
  17. package/dist/load/continuation-pool.js +154 -0
  18. package/dist/load/continuation-pool.js.map +1 -0
  19. package/dist/load/execute-iteration.d.ts +126 -0
  20. package/dist/load/execute-iteration.d.ts.map +1 -0
  21. package/dist/load/execute-iteration.js +367 -0
  22. package/dist/load/execute-iteration.js.map +1 -0
  23. package/dist/load/histogram.d.ts +63 -0
  24. package/dist/load/histogram.d.ts.map +1 -0
  25. package/dist/load/histogram.js +149 -0
  26. package/dist/load/histogram.js.map +1 -0
  27. package/dist/load/orchestrator.d.ts +55 -0
  28. package/dist/load/orchestrator.d.ts.map +1 -0
  29. package/dist/load/orchestrator.js +571 -0
  30. package/dist/load/orchestrator.js.map +1 -0
  31. package/dist/load/reducer.d.ts +109 -0
  32. package/dist/load/reducer.d.ts.map +1 -0
  33. package/dist/load/reducer.js +718 -0
  34. package/dist/load/reducer.js.map +1 -0
  35. package/dist/load/route-key.d.ts +38 -0
  36. package/dist/load/route-key.d.ts.map +1 -0
  37. package/dist/load/route-key.js +107 -0
  38. package/dist/load/route-key.js.map +1 -0
  39. package/dist/load/samples.d.ts +83 -0
  40. package/dist/load/samples.d.ts.map +1 -0
  41. package/dist/load/samples.js +269 -0
  42. package/dist/load/samples.js.map +1 -0
  43. package/dist/load/sink.d.ts +127 -0
  44. package/dist/load/sink.d.ts.map +1 -0
  45. package/dist/load/sink.js +351 -0
  46. package/dist/load/sink.js.map +1 -0
  47. package/dist/load/subprocess.d.ts +83 -0
  48. package/dist/load/subprocess.d.ts.map +1 -0
  49. package/dist/load/subprocess.js +229 -0
  50. package/dist/load/subprocess.js.map +1 -0
  51. package/dist/load/threshold.d.ts +44 -0
  52. package/dist/load/threshold.d.ts.map +1 -0
  53. package/dist/load/threshold.js +197 -0
  54. package/dist/load/threshold.js.map +1 -0
  55. package/dist/load/timeline.d.ts +36 -0
  56. package/dist/load/timeline.d.ts.map +1 -0
  57. package/dist/load/timeline.js +158 -0
  58. package/dist/load/timeline.js.map +1 -0
  59. package/dist/load-harness.d.ts +2 -0
  60. package/dist/load-harness.d.ts.map +1 -0
  61. package/dist/load-harness.js +105 -0
  62. package/dist/load-harness.js.map +1 -0
  63. package/dist/runner-resolve.d.ts +53 -0
  64. package/dist/runner-resolve.d.ts.map +1 -0
  65. package/dist/runner-resolve.js +264 -0
  66. package/dist/runner-resolve.js.map +1 -0
  67. package/dist/workflow/event-timeline.d.ts +3 -0
  68. package/dist/workflow/event-timeline.d.ts.map +1 -0
  69. package/dist/workflow/event-timeline.js +72 -0
  70. package/dist/workflow/event-timeline.js.map +1 -0
  71. package/package.json +4 -4
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Streaming over-time series for the load reducer.
3
+ *
4
+ * Buckets the run into fixed-width windows (from run start) so a consumer can draw the
5
+ * classic load curves: RPS, error-rate, latency percentiles, and concurrency vs time.
6
+ * Memory is bounded: windows start at `baseWindowMs` and, once they would exceed
7
+ * `maxWindows`, the width is DOUBLED and adjacent windows merged pairwise — so an
8
+ * arbitrarily long run keeps at most `maxWindows` windows. Each window keeps a bounded
9
+ * `LoadHistogram` for its latency percentiles (mergeable, so coarsening folds cleanly).
10
+ */
11
+ import { LoadHistogram } from "./histogram.js";
12
+ const ZERO_PCT = { p50: 0, p90: 0, p95: 0, p99: 0, max: 0 };
13
+ function newWindow() {
14
+ return { requests: 0, errors: 0, latency: new LoadHistogram(), starts: 0, iterations: 0, peakSample: 0 };
15
+ }
16
+ export class LoadTimeline {
17
+ baseWindowMs;
18
+ maxWindows;
19
+ windowMs;
20
+ // Sparse: only windows with activity exist; finalize() fills the gaps with zeros so the
21
+ // emitted series is dense (a contiguous x-axis).
22
+ windows = new Map();
23
+ /** @param baseWindowMs initial window width (ms). @param maxWindows coarsening cap. */
24
+ constructor(baseWindowMs = 250, maxWindows = 600) {
25
+ this.baseWindowMs = baseWindowMs;
26
+ this.maxWindows = maxWindows;
27
+ this.windowMs = baseWindowMs;
28
+ }
29
+ /** The window index covering `offsetMs` (ms from run start), coarsening first if the
30
+ * index would exceed the cap (so the series stays ≤ maxWindows). */
31
+ indexFor(offsetMs) {
32
+ let idx = Math.floor(Math.max(0, offsetMs) / this.windowMs);
33
+ while (idx >= this.maxWindows) {
34
+ this.coarsen();
35
+ idx = Math.floor(Math.max(0, offsetMs) / this.windowMs);
36
+ }
37
+ return idx;
38
+ }
39
+ /** The LAST window covered by a run of length `runEndMs` (the run spans [0, runEndMs)), or
40
+ * -1 for a non-positive length. A run ending exactly on a boundary (runEndMs = k·windowMs)
41
+ * covers windows 0..k-1, NOT a window that starts at the run end (codex). Coarsens so the
42
+ * index fits the cap. */
43
+ runEndIndex(runEndMs) {
44
+ if (runEndMs <= 0)
45
+ return -1;
46
+ while (Math.ceil(runEndMs / this.windowMs) - 1 >= this.maxWindows)
47
+ this.coarsen();
48
+ return Math.ceil(runEndMs / this.windowMs) - 1;
49
+ }
50
+ /** The window covering `offsetMs`, creating it if absent. */
51
+ windowFor(offsetMs) {
52
+ const idx = this.indexFor(offsetMs);
53
+ let w = this.windows.get(idx);
54
+ if (w === undefined) {
55
+ w = newWindow();
56
+ this.windows.set(idx, w);
57
+ }
58
+ return w;
59
+ }
60
+ /** Double the window width and merge each adjacent pair (2k, 2k+1 → k). All folds are
61
+ * commutative (sum / max / histogram merge), so Map iteration order is irrelevant. */
62
+ coarsen() {
63
+ const next = new Map();
64
+ for (const [i, w] of this.windows) {
65
+ const ni = Math.floor(i / 2);
66
+ const existing = next.get(ni);
67
+ if (existing === undefined) {
68
+ next.set(ni, w);
69
+ }
70
+ else {
71
+ existing.requests += w.requests;
72
+ existing.errors += w.errors;
73
+ existing.starts += w.starts;
74
+ existing.iterations += w.iterations;
75
+ if (w.peakSample > existing.peakSample)
76
+ existing.peakSample = w.peakSample;
77
+ existing.latency.merge(w.latency);
78
+ }
79
+ }
80
+ this.windows = next;
81
+ this.windowMs *= 2;
82
+ }
83
+ /** Record one request observation at `offsetMs`. `inFlight` is the live iteration count
84
+ * at that moment — sampled so a window busy with requests (e.g. a poll) shows concurrency. */
85
+ recordRequest(offsetMs, durationMs, ok, inFlight) {
86
+ const w = this.windowFor(offsetMs);
87
+ w.requests += 1;
88
+ if (!ok)
89
+ w.errors += 1;
90
+ w.latency.record(durationMs);
91
+ if (inFlight > w.peakSample)
92
+ w.peakSample = inFlight;
93
+ }
94
+ /** Record one started iteration at `offsetMs`. `inFlight` is the live count just after the
95
+ * start (its local peak), sampled so even a same-window start+end shows its concurrency. */
96
+ recordIterationStart(offsetMs, inFlight) {
97
+ const w = this.windowFor(offsetMs);
98
+ w.starts += 1;
99
+ if (inFlight > w.peakSample)
100
+ w.peakSample = inFlight;
101
+ }
102
+ /** Record one completed iteration (iteration:end) at `offsetMs`. */
103
+ recordIterationEnd(offsetMs) {
104
+ this.windowFor(offsetMs).iterations += 1;
105
+ }
106
+ /** Emit the dense series: every window from 0..last, idle windows zero-filled. `runEndMs`
107
+ * (the run's `load:end` offset) extends the series to the actual run end, so a trailing
108
+ * idle / sustained-in-flight period after the last recorded event (an abort, drain timeout,
109
+ * or hung iteration) is still present instead of being truncated (codex). */
110
+ finalize(runEndMs = 0) {
111
+ if (this.windows.size === 0)
112
+ return { windowMs: this.windowMs, windows: [] };
113
+ // Extend to the last window the run actually covered (may coarsen existing windows).
114
+ let maxIdx = this.runEndIndex(runEndMs);
115
+ for (const i of this.windows.keys())
116
+ if (i > maxIdx)
117
+ maxIdx = i;
118
+ const windowSec = this.windowMs / 1000;
119
+ const windows = [];
120
+ // `carriedIn` = in-flight iterations ENTERING each window = Σ(starts − ends) through the
121
+ // PREVIOUS windows. The window's reported concurrency is max(its sampled peak, carriedIn):
122
+ // - the sampled peak catches intra-window concurrency the net would cancel (a short
123
+ // iteration that starts and ends in one window — even after coarsening);
124
+ // - carriedIn catches a long iteration spanning quiet/idle windows (sustained, not zero).
125
+ let carriedIn = 0;
126
+ for (let i = 0; i <= maxIdx; i++) {
127
+ const w = this.windows.get(i);
128
+ const peakInFlight = Math.max(0, w?.peakSample ?? 0, carriedIn);
129
+ if (w === undefined) {
130
+ windows.push({
131
+ offsetMs: i * this.windowMs,
132
+ requests: 0,
133
+ errors: 0,
134
+ errorRate: 0,
135
+ throughputPerSec: 0,
136
+ latency: ZERO_PCT,
137
+ iterations: 0,
138
+ peakInFlight,
139
+ });
140
+ }
141
+ else {
142
+ windows.push({
143
+ offsetMs: i * this.windowMs,
144
+ requests: w.requests,
145
+ errors: w.errors,
146
+ errorRate: w.requests > 0 ? w.errors / w.requests : 0,
147
+ throughputPerSec: windowSec > 0 ? w.requests / windowSec : 0,
148
+ latency: w.latency.count > 0 ? w.latency.percentiles() : ZERO_PCT,
149
+ iterations: w.iterations,
150
+ peakInFlight,
151
+ });
152
+ }
153
+ carriedIn += (w?.starts ?? 0) - (w?.iterations ?? 0);
154
+ }
155
+ return { windowMs: this.windowMs, windows };
156
+ }
157
+ }
158
+ //# sourceMappingURL=timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../../src/load/timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,MAAM,QAAQ,GAAgB,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAczE,SAAS,SAAS;IAChB,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,aAAa,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;AAC3G,CAAC;AAED,MAAM,OAAO,YAAY;IAQJ;IACA;IARX,QAAQ,CAAS;IACzB,wFAAwF;IACxF,iDAAiD;IACzC,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE5C,uFAAuF;IACvF,YACmB,eAAe,GAAG,EAClB,aAAa,GAAG;QADhB,iBAAY,GAAZ,YAAY,CAAM;QAClB,eAAU,GAAV,UAAU,CAAM;QAEjC,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC;IAC/B,CAAC;IAED;yEACqE;IAC7D,QAAQ,CAAC,QAAgB;QAC/B,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5D,OAAO,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;8BAG0B;IAClB,WAAW,CAAC,QAAgB;QAClC,IAAI,QAAQ,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAClF,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,6DAA6D;IACrD,SAAS,CAAC,QAAgB;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,CAAC,GAAG,SAAS,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;2FACuF;IAC/E,OAAO;QACb,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC;gBAChC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;gBAC5B,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;gBAC5B,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC;gBACpC,IAAI,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU;oBAAE,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;gBAC3E,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IACrB,CAAC;IAED;mGAC+F;IAC/F,aAAa,CAAC,QAAgB,EAAE,UAAkB,EAAE,EAAW,EAAE,QAAgB;QAC/E,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC,EAAE;YAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,QAAQ,GAAG,CAAC,CAAC,UAAU;YAAE,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC;IACvD,CAAC;IAED;iGAC6F;IAC7F,oBAAoB,CAAC,QAAgB,EAAE,QAAgB;QACrD,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;QACd,IAAI,QAAQ,GAAG,CAAC,CAAC,UAAU;YAAE,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC;IACvD,CAAC;IAED,oEAAoE;IACpE,kBAAkB,CAAC,QAAgB;QACjC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED;;;kFAG8E;IAC9E,QAAQ,CAAC,QAAQ,GAAG,CAAC;QACnB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC7E,qFAAqF;QACrF,IAAI,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,MAAM;gBAAE,MAAM,GAAG,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvC,MAAM,OAAO,GAAyB,EAAE,CAAC;QACzC,yFAAyF;QACzF,2FAA2F;QAC3F,qFAAqF;QACrF,4EAA4E;QAC5E,2FAA2F;QAC3F,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;YAChE,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ;oBAC3B,QAAQ,EAAE,CAAC;oBACX,MAAM,EAAE,CAAC;oBACT,SAAS,EAAE,CAAC;oBACZ,gBAAgB,EAAE,CAAC;oBACnB,OAAO,EAAE,QAAQ;oBACjB,UAAU,EAAE,CAAC;oBACb,YAAY;iBACb,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ;oBAC3B,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,SAAS,EAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;oBACrD,gBAAgB,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;oBAC5D,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ;oBACjE,UAAU,EAAE,CAAC,CAAC,UAAU;oBACxB,YAAY;iBACb,CAAC,CAAC;YACL,CAAC;YACD,SAAS,IAAI,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC9C,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=load-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-harness.d.ts","sourceRoot":"","sources":["../src/load-harness.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Load harness — runs INSIDE the Node.js subprocess (spawned via tsx by
3
+ * `runLoadFileInSubprocess`). The load-execution counterpart to `harness.ts`.
4
+ *
5
+ * Because this harness and the user `.load.ts` file are loaded in the SAME
6
+ * process — and resolved through the SAME project-local runner — both halves
7
+ * co-resolve one `@glubean/sdk`, so `runLoad`'s engine carrier and the scenario's
8
+ * runtime carrier are identical (no split-brain).
9
+ *
10
+ * Protocol:
11
+ * stdin ← JSON `{ vars, secrets }` (raw; env fallback applied here)
12
+ * argv ← `--file=<absolute path to the .load.ts>`
13
+ * stdout → `WIRE_PREFIX`-tagged NDJSON `LoadHarnessMessage` lines: one
14
+ * `artifact` per completed plan, one `error` per import / plan-run
15
+ * failure, a terminal `done`. The prefix lets the parent tell protocol
16
+ * from ordinary user `console.log` (forwarded as-is); writes go through
17
+ * `writeSync(1, …)` so they flush before exit. A crash (no `done`) is
18
+ * surfaced by the parent from stderr.
19
+ */
20
+ import { parseArgs } from "node:util";
21
+ import { writeSync } from "node:fs";
22
+ import { pathToFileURL } from "node:url";
23
+ import { bootstrap } from "./bootstrap.js";
24
+ import { runLoad } from "./load/orchestrator.js";
25
+ import { collectLoadPlans, withProcessEnvFallback, WIRE_PREFIX, } from "./load/subprocess.js";
26
+ /** Write one protocol message synchronously to stdout (fd 1, flush-safe). The
27
+ * LEADING newline guarantees the prefix begins a fresh line even when user /
28
+ * plugin code wrote to stdout WITHOUT a trailing newline (otherwise the message
29
+ * would be appended to that line and no longer start with the prefix). */
30
+ function emit(msg) {
31
+ writeSync(1, "\n" + WIRE_PREFIX + JSON.stringify(msg) + "\n");
32
+ }
33
+ /** Mark the harness done (terminal sentinel) and exit cleanly. */
34
+ function finishClean() {
35
+ emit({ type: "done" });
36
+ process.exit(0);
37
+ }
38
+ // A crash (uncaught error) is reported to stderr and exits nonzero WITHOUT a
39
+ // `done` sentinel — the parent detects the missing sentinel and surfaces the
40
+ // stderr text as a "did not complete" error, so nothing is silently lost.
41
+ function crash(message) {
42
+ process.stderr.write(message + "\n");
43
+ process.exit(1);
44
+ }
45
+ process.on("uncaughtException", (error) => {
46
+ crash(`load harness crashed: ${error?.stack ?? error?.message ?? String(error)}`);
47
+ });
48
+ process.on("unhandledRejection", (reason) => {
49
+ crash(`load harness crashed: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`);
50
+ });
51
+ const { values: args } = parseArgs({
52
+ args: process.argv.slice(2),
53
+ options: { file: { type: "string" } },
54
+ strict: false,
55
+ });
56
+ const file = args.file;
57
+ if (!file)
58
+ crash("load harness: missing required --file argument");
59
+ /** Read the full stdin payload (the `{ vars, secrets }` JSON). */
60
+ async function readStdin() {
61
+ const chunks = [];
62
+ for await (const chunk of process.stdin)
63
+ chunks.push(chunk);
64
+ return Buffer.concat(chunks).toString("utf-8");
65
+ }
66
+ const stdinRaw = await readStdin();
67
+ const { vars = {}, secrets = {} } = stdinRaw
68
+ ? JSON.parse(stdinRaw)
69
+ : {};
70
+ // Register project plugins (matchers / protocol adapters) before importing the
71
+ // load file, which may use them — same as the test/contract paths.
72
+ await bootstrap(process.cwd());
73
+ let ns;
74
+ try {
75
+ // tsx (the spawning runtime) transforms full TypeScript; Node resolves the
76
+ // file's bare imports (`@glubean/sdk/load`) relative to the file's location.
77
+ ns = (await import(pathToFileURL(file).href));
78
+ }
79
+ catch (e) {
80
+ // A handled per-file failure (not a crash): emit the error + the done sentinel.
81
+ emit({
82
+ type: "error",
83
+ message: `failed to import load file ${file}: ${e instanceof Error ? e.message : String(e)} (ensure @glubean/sdk is resolvable from the file)`,
84
+ });
85
+ finishClean();
86
+ }
87
+ const envVars = withProcessEnvFallback(vars);
88
+ const envSecrets = withProcessEnvFallback(secrets);
89
+ for (const plan of collectLoadPlans(ns)) {
90
+ try {
91
+ // runLoad can throw for an invalid plan (traffic-mix, no termination bound,
92
+ // bad bounds) — report it per-plan and keep going so other plans' completed
93
+ // artifacts still emit.
94
+ const artifact = await runLoad(plan, { vars: envVars, secrets: envSecrets });
95
+ emit({ type: "artifact", runnerId: plan.id, artifact });
96
+ }
97
+ catch (e) {
98
+ emit({
99
+ type: "error",
100
+ message: `load plan "${plan.id}" (${file}) failed: ${e instanceof Error ? e.message : String(e)}`,
101
+ });
102
+ }
103
+ }
104
+ finishClean();
105
+ //# sourceMappingURL=load-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-harness.js","sourceRoot":"","sources":["../src/load-harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /** Diagnostic warning emitted by the executor's runner resolver. */
2
+ export interface RunnerWarning {
3
+ /** Human-readable message. */
4
+ message: string;
5
+ /** Stable code for filtering / consumer dedupe. */
6
+ code: "runner_fallback_no_project" | "runner_fallback_no_dist" | "runner_protocol_old" | "runner_pkg_root_not_found";
7
+ }
8
+ export interface ResolvedRunner {
9
+ distDir: string;
10
+ pkgRoot: string;
11
+ source: "project" | "bundled";
12
+ resolvedFrom?: string;
13
+ version?: string;
14
+ pendingWarnings: RunnerWarning[];
15
+ }
16
+ /**
17
+ * Resolve the runner dist/ directory + package root.
18
+ *
19
+ * Preference: project-local `@glubean/runner` (found from `projectCwd`'s
20
+ * `node_modules` chain). Fallback: bundled (the consumer's own runner copy).
21
+ *
22
+ * Returns warnings to be yielded as `{ type: "warning", ... }` events at
23
+ * the start of every `run()` call. Always emits a warning when falling
24
+ * back to bundled (so users see why "configure() values..." errors may
25
+ * appear in a misconfigured project).
26
+ */
27
+ export declare function resolveRunnerRoot(projectCwd: string, bundledDistDir: string, bundledPkgRoot: string): ResolvedRunner;
28
+ /** Resolve the tsx CLI entry used to transform the user's TypeScript in the
29
+ * spawned harness subprocess. Cached after the first lookup. */
30
+ export declare function resolveTsxPath(): string;
31
+ /** A computed zero-project plan: extra tsx `--import` args, env redirects, and a
32
+ * cleanup that undoes any temp package.json. */
33
+ export interface ZeroProjectSetup {
34
+ /** tsx args to prepend (`--import <zero-project-register.mjs>`), or `[]`. */
35
+ tsxArgs: string[];
36
+ /** Env additions (`GLUBEAN_VENDORED_ROOT`), or `{}`. */
37
+ env: Record<string, string>;
38
+ /** Restore the working dir's package.json to its original state. Idempotent. */
39
+ cleanup(): void;
40
+ }
41
+ /**
42
+ * Set up zero-project (scratch) mode for a spawn rooted at `cwd`: when the cwd
43
+ * has no `@glubean/sdk` in node_modules, redirect the user file's `@glubean/*`
44
+ * imports to the resolved runner's vendored copy (via the register hook +
45
+ * `GLUBEAN_VENDORED_ROOT`) and ensure a `"type":"module"` package.json so the
46
+ * `.ts` file loads as ESM. When the cwd already has `@glubean/sdk`, this is a
47
+ * no-op (empty args/env, no-op cleanup).
48
+ *
49
+ * Pure w.r.t. instance state — both `TestExecutor` and the load spawn call it
50
+ * and own the returned `tsxArgs`/`env`/`cleanup`.
51
+ */
52
+ export declare function prepareZeroProject(cwd: string, runnerDistDir: string, runnerPkgRoot: string): ZeroProjectSetup;
53
+ //# sourceMappingURL=runner-resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-resolve.d.ts","sourceRoot":"","sources":["../src/runner-resolve.ts"],"names":[],"mappings":"AAqEA,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,IAAI,EAAE,4BAA4B,GAAG,yBAAyB,GAAG,qBAAqB,GAAG,2BAA2B,CAAC;CACtH;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,aAAa,EAAE,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GACrB,cAAc,CAuGhB;AAQD;iEACiE;AACjE,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAID;iDACiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,gFAAgF;IAChF,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,GACpB,gBAAgB,CA2DlB"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Shared runner-resolution internals — the delicate "where does the runner
3
+ * harness live + how do we spawn it" code, factored out of `executor.ts` so the
4
+ * test harness (`TestExecutor`) AND the load harness (`runLoadFileInSubprocess`)
5
+ * resolve the SAME project-local runner the SAME way.
6
+ *
7
+ * Three reused seams:
8
+ * - `resolveRunnerRoot` — the **dual-package hazard fix**: prefer the project's
9
+ * own `@glubean/runner` dist (so the spawned harness + the user file co-resolve
10
+ * the project's `@glubean/sdk`), fall back to the bundled copy with a warning.
11
+ * **Most delicate code in the package — change with care.**
12
+ * - `resolveTsxPath` — locate the tsx CLI to transform the user's TypeScript.
13
+ * - `prepareZeroProject` — scratch-mode (no node_modules) `--import` register +
14
+ * `GLUBEAN_VENDORED_ROOT` redirect + temp package.json, with a cleanup closure.
15
+ *
16
+ * Internal to `@glubean/runner` — not part of the public `index.ts` surface.
17
+ */
18
+ import { existsSync, unlinkSync, readFileSync, writeFileSync, realpathSync } from "node:fs";
19
+ import { dirname, resolve, join } from "node:path";
20
+ import { createRequire } from "node:module";
21
+ // ── Project-local runner resolution (Plan 1) ────────────────────────────────
22
+ /**
23
+ * Walk up from a starting file path looking for the first package.json
24
+ * whose `name` field matches `expectedName`. Layout-independent — works
25
+ * regardless of dist/ depth.
26
+ *
27
+ * Returns the realpath of the package root, or undefined if not found.
28
+ */
29
+ function findPackageRoot(startFile, expectedName) {
30
+ let dir = dirname(startFile);
31
+ for (let i = 0; i < 16; i++) {
32
+ const candidate = resolve(dir, "package.json");
33
+ if (existsSync(candidate)) {
34
+ try {
35
+ const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
36
+ if (pkg?.name === expectedName) {
37
+ try {
38
+ return realpathSync(dir);
39
+ }
40
+ catch {
41
+ return dir;
42
+ }
43
+ }
44
+ }
45
+ catch {
46
+ // corrupt / non-json; keep walking
47
+ }
48
+ }
49
+ const parent = dirname(dir);
50
+ if (parent === dir)
51
+ return undefined;
52
+ dir = parent;
53
+ }
54
+ return undefined;
55
+ }
56
+ /**
57
+ * Compare two version strings (numeric major.minor.patch). Returns true
58
+ * if `a < b`. Tolerant of missing fields and non-numeric tails.
59
+ */
60
+ function semverLt(a, b) {
61
+ const parse = (s) => s.split(/[.\-+]/).slice(0, 3).map((p) => parseInt(p, 10) || 0);
62
+ const [aMajor, aMinor, aPatch] = parse(a);
63
+ const [bMajor, bMinor, bPatch] = parse(b);
64
+ if (aMajor !== bMajor)
65
+ return aMajor < bMajor;
66
+ if (aMinor !== bMinor)
67
+ return aMinor < bMinor;
68
+ return aPatch < bPatch;
69
+ }
70
+ /**
71
+ * Resolve the runner dist/ directory + package root.
72
+ *
73
+ * Preference: project-local `@glubean/runner` (found from `projectCwd`'s
74
+ * `node_modules` chain). Fallback: bundled (the consumer's own runner copy).
75
+ *
76
+ * Returns warnings to be yielded as `{ type: "warning", ... }` events at
77
+ * the start of every `run()` call. Always emits a warning when falling
78
+ * back to bundled (so users see why "configure() values..." errors may
79
+ * appear in a misconfigured project).
80
+ */
81
+ export function resolveRunnerRoot(projectCwd, bundledDistDir, bundledPkgRoot) {
82
+ const warnings = [];
83
+ try {
84
+ // Root `createRequire` at a stub path INSIDE the project cwd — not
85
+ // at `import.meta.url` (the workspace executor file). If we use the
86
+ // workspace URL, Node's resolver returns the workspace's own
87
+ // `@glubean/runner` via package self-reference (the `exports` field
88
+ // points back at itself), regardless of `paths: [projectCwd]`. With
89
+ // a cwd-rooted stub there's no self-reference and Node walks the
90
+ // project's node_modules chain correctly.
91
+ const req = createRequire(resolve(projectCwd, "__glubean_resolve_stub__.js"));
92
+ const mainEntry = req.resolve("@glubean/runner");
93
+ const pkgRoot = findPackageRoot(mainEntry, "@glubean/runner");
94
+ if (!pkgRoot) {
95
+ warnings.push({
96
+ code: "runner_pkg_root_not_found",
97
+ message: `Could not locate @glubean/runner's package.json above ${mainEntry}; using bundled runner.`,
98
+ });
99
+ return {
100
+ distDir: bundledDistDir,
101
+ pkgRoot: bundledPkgRoot,
102
+ source: "bundled",
103
+ pendingWarnings: warnings,
104
+ };
105
+ }
106
+ const projectDistDir = dirname(mainEntry);
107
+ if (!existsSync(resolve(projectDistDir, "harness.js"))) {
108
+ warnings.push({
109
+ code: "runner_fallback_no_dist",
110
+ message: `Project @glubean/runner at ${pkgRoot} has no built harness.js (looked in ${projectDistDir}); using bundled runner.`,
111
+ });
112
+ return {
113
+ distDir: bundledDistDir,
114
+ pkgRoot: bundledPkgRoot,
115
+ source: "bundled",
116
+ pendingWarnings: warnings,
117
+ };
118
+ }
119
+ let version;
120
+ let projectProtocol;
121
+ try {
122
+ const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf-8"));
123
+ version = pkg.version;
124
+ projectProtocol = pkg.glubeanRunnerProtocol;
125
+ }
126
+ catch {
127
+ // best effort
128
+ }
129
+ // Min-protocol check: emit a warning if project's protocol is older
130
+ // than what this consumer needs, but DON'T fall back. Using project-
131
+ // local runner with an old protocol just means some new env channels
132
+ // may be silently ignored. Falling back to bundled when the project
133
+ // has its own SDK would re-introduce the dual-package hazard the
134
+ // whole fix exists to prevent. Project-local wins; missing features
135
+ // are surfaced via warning so the user knows to upgrade.
136
+ try {
137
+ const bundledPkg = JSON.parse(readFileSync(resolve(bundledPkgRoot, "package.json"), "utf-8"));
138
+ const bundledMin = bundledPkg.glubeanRunnerProtocolMinimum;
139
+ if (bundledMin && (!projectProtocol || semverLt(projectProtocol, bundledMin))) {
140
+ warnings.push({
141
+ code: "runner_protocol_old",
142
+ message: `Project @glubean/runner ${version ?? "<unknown>"} declares glubeanRunnerProtocol=${projectProtocol ?? "<unset>"}, ` +
143
+ `but this consumer was built for >= ${bundledMin}. ` +
144
+ `Using project-local runner anyway; some newer features may be silently unavailable. ` +
145
+ `Run \`npm i -D @glubean/runner@latest\` to silence.`,
146
+ });
147
+ }
148
+ }
149
+ catch {
150
+ // bundled pkg.json unreadable — skip the check; happens in test fixtures
151
+ }
152
+ return {
153
+ distDir: projectDistDir,
154
+ pkgRoot,
155
+ source: "project",
156
+ resolvedFrom: mainEntry,
157
+ version,
158
+ pendingWarnings: warnings,
159
+ };
160
+ }
161
+ catch {
162
+ // No project-local runner installed. Plan 1 AC4: emit a warning whenever
163
+ // bundled is used so users see why version-skew "configure() values..."
164
+ // errors may appear.
165
+ warnings.push({
166
+ code: "runner_fallback_no_project",
167
+ message: `No project-local @glubean/runner found at ${projectCwd}; using consumer's bundled runner. ` +
168
+ `If your tests import @glubean/sdk from this project, install runner to keep module identity ` +
169
+ `consistent: \`npm i -D @glubean/runner\`.`,
170
+ });
171
+ return {
172
+ distDir: bundledDistDir,
173
+ pkgRoot: bundledPkgRoot,
174
+ source: "bundled",
175
+ pendingWarnings: warnings,
176
+ };
177
+ }
178
+ }
179
+ // ── End of project-local resolution ─────────────────────────────────────────
180
+ // ── tsx path resolution ─────────────────────────────────────────────────────
181
+ let _tsxPath;
182
+ /** Resolve the tsx CLI entry used to transform the user's TypeScript in the
183
+ * spawned harness subprocess. Cached after the first lookup. */
184
+ export function resolveTsxPath() {
185
+ if (_tsxPath)
186
+ return _tsxPath;
187
+ const req = createRequire(import.meta.url);
188
+ _tsxPath = resolve(dirname(req.resolve("tsx/package.json")), "dist/cli.mjs");
189
+ return _tsxPath;
190
+ }
191
+ /**
192
+ * Set up zero-project (scratch) mode for a spawn rooted at `cwd`: when the cwd
193
+ * has no `@glubean/sdk` in node_modules, redirect the user file's `@glubean/*`
194
+ * imports to the resolved runner's vendored copy (via the register hook +
195
+ * `GLUBEAN_VENDORED_ROOT`) and ensure a `"type":"module"` package.json so the
196
+ * `.ts` file loads as ESM. When the cwd already has `@glubean/sdk`, this is a
197
+ * no-op (empty args/env, no-op cleanup).
198
+ *
199
+ * Pure w.r.t. instance state — both `TestExecutor` and the load spawn call it
200
+ * and own the returned `tsxArgs`/`env`/`cleanup`.
201
+ */
202
+ export function prepareZeroProject(cwd, runnerDistDir, runnerPkgRoot) {
203
+ if (existsSync(join(cwd, "node_modules", "@glubean", "sdk"))) {
204
+ return { tsxArgs: [], env: {}, cleanup: () => { } };
205
+ }
206
+ // Plan 1: relocate zero-project subpaths with the harness — if harness moved
207
+ // to project-local runner, register + vendored root must move too, otherwise
208
+ // scratch mode mixes project harness with bundled @glubean/* resolution.
209
+ const registerPath = resolve(runnerDistDir, "zero-project-register.mjs");
210
+ const tsxArgs = ["--import", registerPath];
211
+ // Use the runner package root as the synthetic parent. From there, Node's
212
+ // normal package resolution finds sibling @glubean/* packages in both
213
+ // workspace installs (packages/runner/node_modules) and published installs
214
+ // (ancestor node_modules).
215
+ const env = { GLUBEAN_VENDORED_ROOT: runnerPkgRoot };
216
+ const pkgPath = join(cwd, "package.json");
217
+ let tempPackageJson = false;
218
+ let originalPackageJson;
219
+ if (!existsSync(pkgPath)) {
220
+ try {
221
+ writeFileSync(pkgPath, '{"type":"module"}\n');
222
+ tempPackageJson = "created";
223
+ }
224
+ catch {
225
+ // Non-critical
226
+ }
227
+ }
228
+ else {
229
+ try {
230
+ const original = readFileSync(pkgPath, "utf-8");
231
+ const pkg = JSON.parse(original);
232
+ if (pkg.type !== "module") {
233
+ originalPackageJson = original;
234
+ pkg.type = "module";
235
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
236
+ tempPackageJson = "patched";
237
+ }
238
+ }
239
+ catch {
240
+ // Non-critical
241
+ }
242
+ }
243
+ return {
244
+ tsxArgs,
245
+ env,
246
+ cleanup() {
247
+ if (!tempPackageJson)
248
+ return;
249
+ try {
250
+ if (tempPackageJson === "created") {
251
+ unlinkSync(pkgPath);
252
+ }
253
+ else if (tempPackageJson === "patched" && originalPackageJson) {
254
+ writeFileSync(pkgPath, originalPackageJson);
255
+ }
256
+ }
257
+ catch {
258
+ // Non-critical
259
+ }
260
+ tempPackageJson = false;
261
+ },
262
+ };
263
+ }
264
+ //# sourceMappingURL=runner-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-resolve.js","sourceRoot":"","sources":["../src/runner-resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { GlubeanEvent } from "@glubean/sdk";
2
+ export declare function workflowEventToTimeline(ev: GlubeanEvent): Record<string, unknown> | null;
3
+ //# sourceMappingURL=event-timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-timeline.d.ts","sourceRoot":"","sources":["../../src/workflow/event-timeline.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAYjD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAqExF"}