@glubean/runner 0.5.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 (83) hide show
  1. package/dist/engine-bridge.d.ts +55 -0
  2. package/dist/engine-bridge.d.ts.map +1 -0
  3. package/dist/engine-bridge.js +219 -0
  4. package/dist/engine-bridge.js.map +1 -0
  5. package/dist/executor.d.ts +70 -2
  6. package/dist/executor.d.ts.map +1 -1
  7. package/dist/executor.js +21 -226
  8. package/dist/executor.js.map +1 -1
  9. package/dist/generate_summary.d.ts +15 -0
  10. package/dist/generate_summary.d.ts.map +1 -1
  11. package/dist/generate_summary.js +52 -1
  12. package/dist/generate_summary.js.map +1 -1
  13. package/dist/harness.js +257 -21
  14. package/dist/harness.js.map +1 -1
  15. package/dist/index.d.ts +24 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +24 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/load/continuation-pool.d.ts +82 -0
  20. package/dist/load/continuation-pool.d.ts.map +1 -0
  21. package/dist/load/continuation-pool.js +154 -0
  22. package/dist/load/continuation-pool.js.map +1 -0
  23. package/dist/load/execute-iteration.d.ts +126 -0
  24. package/dist/load/execute-iteration.d.ts.map +1 -0
  25. package/dist/load/execute-iteration.js +367 -0
  26. package/dist/load/execute-iteration.js.map +1 -0
  27. package/dist/load/histogram.d.ts +63 -0
  28. package/dist/load/histogram.d.ts.map +1 -0
  29. package/dist/load/histogram.js +149 -0
  30. package/dist/load/histogram.js.map +1 -0
  31. package/dist/load/orchestrator.d.ts +55 -0
  32. package/dist/load/orchestrator.d.ts.map +1 -0
  33. package/dist/load/orchestrator.js +571 -0
  34. package/dist/load/orchestrator.js.map +1 -0
  35. package/dist/load/reducer.d.ts +109 -0
  36. package/dist/load/reducer.d.ts.map +1 -0
  37. package/dist/load/reducer.js +718 -0
  38. package/dist/load/reducer.js.map +1 -0
  39. package/dist/load/route-key.d.ts +38 -0
  40. package/dist/load/route-key.d.ts.map +1 -0
  41. package/dist/load/route-key.js +107 -0
  42. package/dist/load/route-key.js.map +1 -0
  43. package/dist/load/samples.d.ts +83 -0
  44. package/dist/load/samples.d.ts.map +1 -0
  45. package/dist/load/samples.js +269 -0
  46. package/dist/load/samples.js.map +1 -0
  47. package/dist/load/sink.d.ts +127 -0
  48. package/dist/load/sink.d.ts.map +1 -0
  49. package/dist/load/sink.js +351 -0
  50. package/dist/load/sink.js.map +1 -0
  51. package/dist/load/subprocess.d.ts +83 -0
  52. package/dist/load/subprocess.d.ts.map +1 -0
  53. package/dist/load/subprocess.js +229 -0
  54. package/dist/load/subprocess.js.map +1 -0
  55. package/dist/load/threshold.d.ts +44 -0
  56. package/dist/load/threshold.d.ts.map +1 -0
  57. package/dist/load/threshold.js +197 -0
  58. package/dist/load/threshold.js.map +1 -0
  59. package/dist/load/timeline.d.ts +36 -0
  60. package/dist/load/timeline.d.ts.map +1 -0
  61. package/dist/load/timeline.js +158 -0
  62. package/dist/load/timeline.js.map +1 -0
  63. package/dist/load-harness.d.ts +2 -0
  64. package/dist/load-harness.d.ts.map +1 -0
  65. package/dist/load-harness.js +105 -0
  66. package/dist/load-harness.js.map +1 -0
  67. package/dist/resolve.d.ts +10 -11
  68. package/dist/resolve.d.ts.map +1 -1
  69. package/dist/resolve.js +28 -9
  70. package/dist/resolve.js.map +1 -1
  71. package/dist/runner-resolve.d.ts +53 -0
  72. package/dist/runner-resolve.d.ts.map +1 -0
  73. package/dist/runner-resolve.js +264 -0
  74. package/dist/runner-resolve.js.map +1 -0
  75. package/dist/workflow/event-timeline.d.ts +3 -0
  76. package/dist/workflow/event-timeline.d.ts.map +1 -0
  77. package/dist/workflow/event-timeline.js +72 -0
  78. package/dist/workflow/event-timeline.js.map +1 -0
  79. package/dist/workflow/execute.d.ts +267 -0
  80. package/dist/workflow/execute.d.ts.map +1 -0
  81. package/dist/workflow/execute.js +1475 -0
  82. package/dist/workflow/execute.js.map +1 -0
  83. package/package.json +8 -4
@@ -0,0 +1,127 @@
1
+ /**
2
+ * LoadSink — translates the engine's per-run wire events into the load
3
+ * `LoadEvent` fact stream and feeds the `LoadReducer`.
4
+ *
5
+ * One sink + one engine core serve every concurrent iteration: each iteration
6
+ * runs an engine TestDef whose `meta.id` is the unique iterationId, so wire
7
+ * events arrive tagged with `testId = iterationId` and the sink demuxes them
8
+ * back to the right iteration's attribution (no per-iteration core needed).
9
+ *
10
+ * Success requests feed only the reducer's aggregates; the sink stays bounded
11
+ * (per-iteration step-name maps are dropped at iteration end). Failure-trace
12
+ * sampling and producer-release events land in later milestones.
13
+ */
14
+ import type { LoadEndReason, LoadErrorKind, LoadEvent, LoadReducer, LoadResolvedConfig } from "@glubean/sdk/load";
15
+ /** Per-iteration attribution the sink stamps onto translated events. */
16
+ export interface LoadIterationEnvelope {
17
+ scenarioId: string;
18
+ scenarioRefId?: string;
19
+ producerSlotId: string;
20
+ iterationId: string;
21
+ }
22
+ /** Distributive Omit — applied per union member so each LoadEvent variant keeps
23
+ * its own discriminant-specific fields (a plain Omit over the union would keep
24
+ * only the common envelope keys). */
25
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
26
+ type LoadEventBody = DistributiveOmit<LoadEvent, "ts" | "seq" | "runId" | "runnerId">;
27
+ export declare class LoadSink {
28
+ private readonly reducer;
29
+ private readonly runId;
30
+ private readonly runnerId;
31
+ private readonly now;
32
+ private seq;
33
+ private sealed;
34
+ private readonly envelopes;
35
+ private readonly stepNames;
36
+ private readonly phases;
37
+ private readonly iterSpan;
38
+ private sawUnreleasedTailPoll;
39
+ constructor(reducer: LoadReducer, runId: string, runnerId: string, now?: () => number);
40
+ /** Stamp envelope basics (ts / seq / runId / runnerId) and apply to the reducer.
41
+ * A no-op once sealed (post-finalize), so abandoned continuations can't mutate
42
+ * the already-built artifact. */
43
+ emit(body: LoadEventBody): void;
44
+ /** Stop forwarding events to the reducer (called once the run is finalized). */
45
+ seal(): void;
46
+ /** Whether any iteration ran a post-primary TAIL `.poll()` without requesting producer
47
+ * release (a request before it, none after, and no release asked). Gates the
48
+ * long-poll advisory — the closed-scheduling case it nudges toward release. */
49
+ get unreleasedTailPollRan(): boolean;
50
+ /** Flag the advisory if `span` shows a poll that HELD the slot: a request preceded it
51
+ * (minReq < maxUnreleasedPoll) and no primary-phase request followed it
52
+ * (maxPrimaryReq <= maxUnreleasedPoll — excludes a pre-primary readiness wait while
53
+ * ignoring post-boundary continuation requests). The sentinels make a missing held poll
54
+ * / request fail the first test. Idempotent — safe to call at release time AND at
55
+ * endIteration (the spans are frozen once the primary boundary / release is reached). */
56
+ private markUnreleasedTailPoll;
57
+ /** Register an iteration's attribution before its engine run starts. */
58
+ beginIteration(env: LoadIterationEnvelope): void;
59
+ /** Drop an iteration's per-iteration buffers once it has finished. */
60
+ endIteration(iterationId: string): void;
61
+ /** The shared per-iteration attribution every translated event carries. Phase
62
+ * starts "primary" and flips to "continuation" at the iteration's first
63
+ * `primaryComplete` boundary (see `emitPrimaryCompleted`). */
64
+ private baseOf;
65
+ /** Emit `iteration:start` (the executor calls this right after beginIteration). */
66
+ emitIterationStart(env: LoadIterationEnvelope, opts?: {
67
+ inputKey?: string;
68
+ feederKeys?: Record<string, string>;
69
+ }): void;
70
+ /** Emit `iteration:end` (the executor calls this once the engine run resolves). */
71
+ emitIterationEnd(env: LoadIterationEnvelope, result: {
72
+ ok: boolean;
73
+ durationMs: number;
74
+ errorKind?: LoadErrorKind;
75
+ }): void;
76
+ /** A per-scenario-stable, leaf-unique step id. The engine's leaf `index` is
77
+ * assigned in deterministic registry order (taken or skipped), so the same
78
+ * logical step always gets the same index — folding it in disambiguates two
79
+ * leaves that share a display name (the reducer keys aggregates by stepId). */
80
+ private stepIdOf;
81
+ /** Emit `report:checkpoint` (backs `ctx.report.checkpoint`). */
82
+ emitCheckpoint(env: LoadIterationEnvelope, checkpointId: string, data?: Record<string, unknown>): void;
83
+ /** Emit `producer:primaryCompleted` (backs the first `ctx.report.primaryComplete`)
84
+ * and FLIP this iteration into its continuation phase immediately, so every
85
+ * request / checkpoint the sink translates afterwards carries
86
+ * `phase: "continuation"` (the API's contract). The boundary event itself is
87
+ * stamped with the pre-flip ("primary") phase. A step that spans the boundary
88
+ * still keeps a single phase because step:end uses its captured START phase
89
+ * (see the `step_end` case). M5 records the measurement boundary only —
90
+ * `releaseRequested` is forwarded for M6 scheduling, it does not release a slot. */
91
+ emitPrimaryCompleted(env: LoadIterationEnvelope, info: {
92
+ primaryId: string;
93
+ primaryDurationMs: number;
94
+ releaseRequested: boolean;
95
+ }): void;
96
+ /** Emit `producer:released` (M6): the producer slot was freed at this iteration's
97
+ * primaryComplete boundary so a new primary can start; the rest of this iteration
98
+ * runs as a continuation. `backpressureMs` is the wait the release incurred when
99
+ * the continuation backlog was full. */
100
+ emitProducerReleased(env: LoadIterationEnvelope, info: {
101
+ releaseId: string;
102
+ primaryDurationMs: number;
103
+ continuationBacklog: number;
104
+ backpressureMs: number;
105
+ }): void;
106
+ /** Emit `producer:releaseRejected` (M6): a release was requested but not granted —
107
+ * backlog full under `fail-iteration`, a duplicate, a parked release whose drain
108
+ * bound expired (`drainTimeout`), or one cancelled by the run deadline. */
109
+ emitProducerReleaseRejected(env: LoadIterationEnvelope, info: {
110
+ releaseId: string;
111
+ reason: "continuationBacklogFull" | "duplicateRelease" | "drainTimeout" | "runDeadlineReached";
112
+ waitMs: number;
113
+ continuationBacklog: number;
114
+ }): void;
115
+ /** Emit `load:start` with the resolved config (orchestrator run boundary). */
116
+ emitLoadStart(config: LoadResolvedConfig): void;
117
+ /** Emit `load:end` with why the run stopped. */
118
+ emitLoadEnd(reason: LoadEndReason): void;
119
+ /** Emit `producerSlot:start` when a producer slot begins generating load. */
120
+ emitProducerSlotStart(producerSlotIndex: number): void;
121
+ /** Emit `producerSlot:end` with how many primary iterations the slot ran. */
122
+ emitProducerSlotEnd(producerSlotIndex: number, primaryIterations: number): void;
123
+ /** The engine-core sink callback: translate one wire event to LoadEvent(s). */
124
+ readonly handleWire: (wire: Record<string, unknown>) => void;
125
+ }
126
+ export {};
127
+ //# sourceMappingURL=sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/load/sink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,351 @@
1
+ import { resolveRouteKey } from "./route-key.js";
2
+ export class LoadSink {
3
+ reducer;
4
+ runId;
5
+ runnerId;
6
+ now;
7
+ seq = 0;
8
+ // Once the run is finalized, events from continuations the drain timeout abandoned
9
+ // must not mutate reducer state (they'd land after the artifact was built).
10
+ sealed = false;
11
+ envelopes = new Map();
12
+ stepNames = new Map();
13
+ // Per-iteration phase: "primary" until that iteration's first primaryComplete
14
+ // boundary, "continuation" immediately after. Keyed by iterationId so concurrent
15
+ // iterations (one shared sink) flip independently. Every translated event carries
16
+ // this live phase, so post-boundary work (requests / assertions) is "continuation"
17
+ // — matching the public contract that `primaryComplete` switches the iteration
18
+ // into continuation. (Step aggregates stay coherent because the reducer keys steps
19
+ // by stepId, not by phase, so a step that spans the boundary is one row.)
20
+ phases = new Map();
21
+ // Per-iteration state for spotting a poll that held the producer slot without (timely)
22
+ // release — the long-poll advisory's target. All step indices are STEP-SCOPED; a poll's
23
+ // own attempt requests share its index, and unscoped setup/teardown requests (no
24
+ // stepIndex) don't enter the span. Two boundaries gate the spans separately:
25
+ // - `primaryCompleted` (a bare OR releasing `primaryComplete`): after it, requests are
26
+ // continuation-phase, so `maxPrimaryReq` (the latest PRIMARY-phase request) stops
27
+ // advancing — a post-boundary request must not disqualify a held poll;
28
+ // - `releaseRequested` (a releasing `primaryComplete`): after it the slot is FREED, so
29
+ // `maxUnreleasedPoll` (the latest poll that ran while the slot was held) stops.
30
+ // Plus `minReq`, the earliest request. The advisory fires iff a request preceded the held
31
+ // poll (minReq < maxUnreleasedPoll) AND no primary-phase request followed it
32
+ // (maxPrimaryReq <= maxUnreleasedPoll) — the latter excludes a pre-primary readiness wait
33
+ // whose load comes later, while a post-boundary continuation request doesn't disqualify.
34
+ iterSpan = new Map();
35
+ // Whether any iteration ran a TAIL `.poll()` WITHOUT requesting producer release — the
36
+ // closed-scheduling case the long-poll advisory targets. Per-iteration (not run-wide):
37
+ // a row that releases doesn't mute the advisory for a sibling row that doesn't.
38
+ sawUnreleasedTailPoll = false;
39
+ constructor(reducer, runId, runnerId, now = () => Date.now()) {
40
+ this.reducer = reducer;
41
+ this.runId = runId;
42
+ this.runnerId = runnerId;
43
+ this.now = now;
44
+ }
45
+ /** Stamp envelope basics (ts / seq / runId / runnerId) and apply to the reducer.
46
+ * A no-op once sealed (post-finalize), so abandoned continuations can't mutate
47
+ * the already-built artifact. */
48
+ emit(body) {
49
+ if (this.sealed)
50
+ return;
51
+ this.reducer.apply({
52
+ ts: this.now(),
53
+ seq: this.seq++,
54
+ runId: this.runId,
55
+ runnerId: this.runnerId,
56
+ ...body,
57
+ });
58
+ }
59
+ /** Stop forwarding events to the reducer (called once the run is finalized). */
60
+ seal() {
61
+ this.sealed = true;
62
+ }
63
+ /** Whether any iteration ran a post-primary TAIL `.poll()` without requesting producer
64
+ * release (a request before it, none after, and no release asked). Gates the
65
+ * long-poll advisory — the closed-scheduling case it nudges toward release. */
66
+ get unreleasedTailPollRan() {
67
+ return this.sawUnreleasedTailPoll;
68
+ }
69
+ /** Flag the advisory if `span` shows a poll that HELD the slot: a request preceded it
70
+ * (minReq < maxUnreleasedPoll) and no primary-phase request followed it
71
+ * (maxPrimaryReq <= maxUnreleasedPoll — excludes a pre-primary readiness wait while
72
+ * ignoring post-boundary continuation requests). The sentinels make a missing held poll
73
+ * / request fail the first test. Idempotent — safe to call at release time AND at
74
+ * endIteration (the spans are frozen once the primary boundary / release is reached). */
75
+ markUnreleasedTailPoll(span) {
76
+ if (span.minReq < span.maxUnreleasedPoll && span.maxPrimaryReq <= span.maxUnreleasedPoll) {
77
+ this.sawUnreleasedTailPoll = true;
78
+ }
79
+ }
80
+ /** Register an iteration's attribution before its engine run starts. */
81
+ beginIteration(env) {
82
+ this.envelopes.set(env.iterationId, env);
83
+ this.stepNames.set(env.iterationId, new Map());
84
+ this.phases.set(env.iterationId, "primary");
85
+ // Sentinels: no request (min +∞) and no primary request / held poll (max −∞) until seen.
86
+ this.iterSpan.set(env.iterationId, {
87
+ minReq: Infinity, maxPrimaryReq: -Infinity, maxUnreleasedPoll: -Infinity, primaryCompleted: false, releaseRequested: false,
88
+ });
89
+ }
90
+ /** Drop an iteration's per-iteration buffers once it has finished. */
91
+ endIteration(iterationId) {
92
+ // Catch the no-release case (the iteration ran to completion without ever asking for
93
+ // release, so its whole span is now final). Released iterations are evaluated earlier,
94
+ // at release time, so a later drain-abandoned continuation can't lose the advisory.
95
+ const span = this.iterSpan.get(iterationId);
96
+ if (span !== undefined)
97
+ this.markUnreleasedTailPoll(span);
98
+ this.envelopes.delete(iterationId);
99
+ this.stepNames.delete(iterationId);
100
+ this.phases.delete(iterationId);
101
+ this.iterSpan.delete(iterationId);
102
+ }
103
+ /** The shared per-iteration attribution every translated event carries. Phase
104
+ * starts "primary" and flips to "continuation" at the iteration's first
105
+ * `primaryComplete` boundary (see `emitPrimaryCompleted`). */
106
+ baseOf(env) {
107
+ return {
108
+ scenarioId: env.scenarioId,
109
+ ...(env.scenarioRefId !== undefined ? { scenarioRefId: env.scenarioRefId } : {}),
110
+ producerSlotId: env.producerSlotId,
111
+ iterationId: env.iterationId,
112
+ phase: this.phases.get(env.iterationId) ?? "primary",
113
+ };
114
+ }
115
+ /** Emit `iteration:start` (the executor calls this right after beginIteration). */
116
+ emitIterationStart(env, opts = {}) {
117
+ this.emit({
118
+ type: "iteration:start",
119
+ ...this.baseOf(env),
120
+ ...(opts.inputKey !== undefined ? { inputKey: opts.inputKey } : {}),
121
+ ...(opts.feederKeys !== undefined ? { feederKeys: opts.feederKeys } : {}),
122
+ });
123
+ }
124
+ /** Emit `iteration:end` (the executor calls this once the engine run resolves). */
125
+ emitIterationEnd(env, result) {
126
+ this.emit({
127
+ type: "iteration:end",
128
+ ...this.baseOf(env),
129
+ ok: result.ok,
130
+ durationMs: result.durationMs,
131
+ ...(result.errorKind !== undefined ? { errorKind: result.errorKind } : {}),
132
+ });
133
+ }
134
+ /** A per-scenario-stable, leaf-unique step id. The engine's leaf `index` is
135
+ * assigned in deterministic registry order (taken or skipped), so the same
136
+ * logical step always gets the same index — folding it in disambiguates two
137
+ * leaves that share a display name (the reducer keys aggregates by stepId). */
138
+ stepIdOf(index, name) {
139
+ return `${index}:${name}`;
140
+ }
141
+ /** Emit `report:checkpoint` (backs `ctx.report.checkpoint`). */
142
+ emitCheckpoint(env, checkpointId, data) {
143
+ this.emit({
144
+ type: "report:checkpoint",
145
+ ...this.baseOf(env),
146
+ checkpointId,
147
+ ...(data !== undefined ? { data } : {}),
148
+ });
149
+ }
150
+ /** Emit `producer:primaryCompleted` (backs the first `ctx.report.primaryComplete`)
151
+ * and FLIP this iteration into its continuation phase immediately, so every
152
+ * request / checkpoint the sink translates afterwards carries
153
+ * `phase: "continuation"` (the API's contract). The boundary event itself is
154
+ * stamped with the pre-flip ("primary") phase. A step that spans the boundary
155
+ * still keeps a single phase because step:end uses its captured START phase
156
+ * (see the `step_end` case). M5 records the measurement boundary only —
157
+ * `releaseRequested` is forwarded for M6 scheduling, it does not release a slot. */
158
+ emitPrimaryCompleted(env, info) {
159
+ this.emit({
160
+ type: "producer:primaryCompleted",
161
+ ...this.baseOf(env),
162
+ primaryId: info.primaryId,
163
+ primaryDurationMs: info.primaryDurationMs,
164
+ releaseRequested: info.releaseRequested,
165
+ });
166
+ // This is the PRIMARY BOUNDARY (bare or releasing): from here on, requests are
167
+ // continuation-phase and stop advancing `maxPrimaryReq`. A RELEASING boundary also
168
+ // frees the slot, so later polls stop extending `maxUnreleasedPoll`; but any poll that
169
+ // ALREADY ran still held the slot — a late release can't free it in hindsight. Since
170
+ // the spans are now frozen, evaluate the advisory at release time — the continuation
171
+ // may be abandoned by the drain timeout before endIteration runs, which would otherwise
172
+ // lose this misordered case. (A requested-but-rejected release still counts as "asked".)
173
+ const span = this.iterSpan.get(env.iterationId);
174
+ if (span !== undefined) {
175
+ span.primaryCompleted = true;
176
+ if (info.releaseRequested) {
177
+ span.releaseRequested = true;
178
+ this.markUnreleasedTailPoll(span);
179
+ }
180
+ }
181
+ this.phases.set(env.iterationId, "continuation");
182
+ }
183
+ /** Emit `producer:released` (M6): the producer slot was freed at this iteration's
184
+ * primaryComplete boundary so a new primary can start; the rest of this iteration
185
+ * runs as a continuation. `backpressureMs` is the wait the release incurred when
186
+ * the continuation backlog was full. */
187
+ emitProducerReleased(env, info) {
188
+ this.emit({
189
+ type: "producer:released",
190
+ ...this.baseOf(env),
191
+ releaseId: info.releaseId,
192
+ primaryDurationMs: info.primaryDurationMs,
193
+ continuationBacklog: info.continuationBacklog,
194
+ ...(info.backpressureMs > 0 ? { continuationBackpressure: true } : {}),
195
+ backpressureMs: info.backpressureMs,
196
+ });
197
+ }
198
+ /** Emit `producer:releaseRejected` (M6): a release was requested but not granted —
199
+ * backlog full under `fail-iteration`, a duplicate, a parked release whose drain
200
+ * bound expired (`drainTimeout`), or one cancelled by the run deadline. */
201
+ emitProducerReleaseRejected(env, info) {
202
+ this.emit({
203
+ type: "producer:releaseRejected",
204
+ ...this.baseOf(env),
205
+ releaseId: info.releaseId,
206
+ reason: info.reason,
207
+ waitMs: info.waitMs,
208
+ continuationBacklog: info.continuationBacklog,
209
+ });
210
+ }
211
+ /** Emit `load:start` with the resolved config (orchestrator run boundary). */
212
+ emitLoadStart(config) {
213
+ this.emit({ type: "load:start", config });
214
+ }
215
+ /** Emit `load:end` with why the run stopped. */
216
+ emitLoadEnd(reason) {
217
+ this.emit({ type: "load:end", reason });
218
+ }
219
+ /** Emit `producerSlot:start` when a producer slot begins generating load. */
220
+ emitProducerSlotStart(producerSlotIndex) {
221
+ this.emit({ type: "producerSlot:start", producerSlotIndex });
222
+ }
223
+ /** Emit `producerSlot:end` with how many primary iterations the slot ran. */
224
+ emitProducerSlotEnd(producerSlotIndex, primaryIterations) {
225
+ this.emit({ type: "producerSlot:end", producerSlotIndex, primaryIterations });
226
+ }
227
+ /** The engine-core sink callback: translate one wire event to LoadEvent(s). */
228
+ handleWire = (wire) => {
229
+ const iterationId = wire.testId;
230
+ if (!iterationId)
231
+ return;
232
+ const env = this.envelopes.get(iterationId);
233
+ if (!env)
234
+ return; // not a tracked load iteration
235
+ const base = this.baseOf(env);
236
+ switch (wire.type) {
237
+ case "step_start": {
238
+ const index = wire.index;
239
+ const name = wire.name;
240
+ const group = wire.group;
241
+ this.stepNames.get(iterationId)?.set(index, name);
242
+ this.emit({
243
+ type: "step:start",
244
+ ...base,
245
+ stepId: this.stepIdOf(index, name),
246
+ stepName: name,
247
+ ...(group !== undefined ? { groupId: group } : {}),
248
+ });
249
+ break;
250
+ }
251
+ case "step_end": {
252
+ const index = wire.index;
253
+ const name = wire.name;
254
+ const status = wire.status;
255
+ const group = wire.group;
256
+ this.emit({
257
+ type: "step:end",
258
+ ...base,
259
+ stepId: this.stepIdOf(index, name),
260
+ stepName: name,
261
+ ok: status === "passed",
262
+ durationMs: wire.durationMs ?? 0,
263
+ ...(status === "skipped" ? { skipped: true } : {}),
264
+ assertionFailures: wire.failedAssertions ?? 0,
265
+ ...(wire.error !== undefined ? { errorKind: "stepError" } : {}),
266
+ // Carries group for SKIPPED leaves (no step:start emitted for them).
267
+ ...(group !== undefined ? { groupId: group } : {}),
268
+ });
269
+ break;
270
+ }
271
+ case "trace": {
272
+ const t = (wire.data ?? {});
273
+ const stepIndex = wire.stepIndex;
274
+ // Widen this iteration's request step-index span — ONLY for step-scoped requests.
275
+ // An unscoped setup/teardown HTTP call (no stepIndex) must not count as a request
276
+ // before a step, or a pre-primary readiness poll would look like a tail.
277
+ if (stepIndex !== undefined) {
278
+ const span = this.iterSpan.get(iterationId);
279
+ if (span !== undefined) {
280
+ if (stepIndex < span.minReq)
281
+ span.minReq = stepIndex;
282
+ // Only PRIMARY-PHASE requests (before the boundary) bound the tail: a post-
283
+ // boundary continuation request mustn't make an earlier held poll look pre-primary.
284
+ if (!span.primaryCompleted && stepIndex > span.maxPrimaryReq)
285
+ span.maxPrimaryReq = stepIndex;
286
+ }
287
+ }
288
+ const stepName = stepIndex !== undefined ? this.stepNames.get(iterationId)?.get(stepIndex) : undefined;
289
+ // Resolve the routeKey. An EXACT route template from the trace (M8 — a
290
+ // `contract.http()` client / user `X-Glubean-Route` header, e.g. "GET /runs/:runId")
291
+ // wins and is exact (`contract-metadata`, not heuristic). Otherwise fall back to
292
+ // method + heuristic URL normalization (id-like segments → ":id", M3-e): the method
293
+ // is recovered from the trace `target` when explicit `method` is absent, so a
294
+ // target-only POST/PUT isn't mislabelled GET.
295
+ const exact = t.routeKey !== undefined && t.routeKey !== "";
296
+ const { method, routeKey } = resolveRouteKey(t.method, t.url, t.target, t.protocol, t.routeKey);
297
+ this.emit({
298
+ type: "request:observed",
299
+ ...base,
300
+ ...(stepName !== undefined ? { stepId: this.stepIdOf(stepIndex, stepName) } : {}),
301
+ method,
302
+ url: t.url ?? "",
303
+ routeKey,
304
+ routeKeySource: exact ? "contract-metadata" : "normalized-url",
305
+ routeKeyHeuristic: !exact,
306
+ ...(t.status !== undefined ? { status: t.status } : {}),
307
+ ok: t.ok ?? false,
308
+ durationMs: t.durationMs ?? 0,
309
+ });
310
+ break;
311
+ }
312
+ case "assertion": {
313
+ const stepIndex = wire.stepIndex;
314
+ const stepName = stepIndex !== undefined ? this.stepNames.get(iterationId)?.get(stepIndex) : undefined;
315
+ this.emit({
316
+ type: "assertion:observed",
317
+ ...base,
318
+ ...(stepName !== undefined ? { stepId: this.stepIdOf(stepIndex, stepName) } : {}),
319
+ passed: wire.passed,
320
+ ...(wire.message !== undefined ? { message: wire.message } : {}),
321
+ // Diagnostic operands so a failure sample can show what value failed (the reducer
322
+ // previews/bounds them — only retained for FAILED assertions). Presence checks, not
323
+ // `!== undefined`, so a deliberately-`undefined` operand is preserved (codex).
324
+ ...("actual" in wire ? { actual: wire.actual } : {}),
325
+ ...("expected" in wire ? { expected: wire.expected } : {}),
326
+ });
327
+ break;
328
+ }
329
+ case "poll": {
330
+ // A poll step ran (the event fires after its attempt loop). Record its step
331
+ // index; whether it's a post-primary TAIL is decided in endIteration once the
332
+ // full request span is known (a later load request would disqualify it). No
333
+ // reducer mutation, so seal is irrelevant.
334
+ const pollIndex = wire.index;
335
+ const span = this.iterSpan.get(iterationId);
336
+ // Only a poll that ran while the slot was still HELD (no release yet) counts — a
337
+ // poll after release ran on a freed slot. Track the highest such index, so a later
338
+ // released poll can't erase an earlier held one.
339
+ if (span !== undefined && !span.releaseRequested && pollIndex > span.maxUnreleasedPoll) {
340
+ span.maxUnreleasedPoll = pollIndex;
341
+ }
342
+ break;
343
+ }
344
+ // log / metric / action / event / warning / schema / branch / start / status /
345
+ // timeout: not part of the M3 closed-model aggregates.
346
+ default:
347
+ break;
348
+ }
349
+ };
350
+ }
351
+ //# sourceMappingURL=sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.js","sourceRoot":"","sources":["../../src/load/sink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import type { LoadArtifact, LoadPlan } from "@glubean/sdk/load";
2
+ /**
3
+ * Marker prefixed to every harness protocol line on stdout. The user's load file
4
+ * + plugins share stdout, whose `console.log` may itself print JSON; gating on
5
+ * this token stops a stray `{"type":"error",...}` log from spoofing an outcome or
6
+ * a failure. It leads with the ASCII Unit Separator (0x1F) — a control byte that
7
+ * ordinary text logs never emit — so collision is negligible. Lines without it
8
+ * are forwarded to the parent's stdout as ordinary user output.
9
+ *
10
+ * (stdout, not a dedicated fd: tsx re-spawns an inner node process and forwards
11
+ * only fd 0/1/2, so an extra protocol fd wouldn't reach the harness.)
12
+ */
13
+ export declare const WIRE_PREFIX = "\u001Fglubean-load-wire\u001F";
14
+ /**
15
+ * One NDJSON line (after `WIRE_PREFIX`) emitted by `load-harness`. `artifact`
16
+ * carries a completed plan's finalized `LoadArtifact`; `error` carries a per-file
17
+ * import failure or a per-plan run failure; `done` is the terminal sentinel
18
+ * proving the harness finished its own logic cleanly (its absence at child exit
19
+ * means the child crashed before completing).
20
+ */
21
+ export type LoadHarnessMessage = {
22
+ type: "artifact";
23
+ runnerId: string;
24
+ artifact: LoadArtifact;
25
+ } | {
26
+ type: "error";
27
+ message: string;
28
+ } | {
29
+ type: "done";
30
+ };
31
+ /**
32
+ * Collect every LoadPlan exported by a module namespace (flattening `.each()`
33
+ * arrays of plans).
34
+ */
35
+ export declare function collectLoadPlans(ns: Record<string, unknown>): LoadPlan[];
36
+ /**
37
+ * Wrap an env map so a missing key falls back to `process.env` — the same
38
+ * shell/CI env semantics the test harness gives `ctx.vars` / `ctx.secrets`, so
39
+ * `BASE_URL=... glubean load ...` resolves even without a `.env` entry.
40
+ *
41
+ * Applied INSIDE the child (which inherits the parent's `process.env`): the
42
+ * parent ships the raw resolved `{ vars, secrets }` over stdin — a Proxy can't
43
+ * survive JSON serialization, so the fallback must be re-established where
44
+ * `process.env` is actually present. Spreading the proxy (for `ctx.vars.all()`)
45
+ * still yields only the explicit map's own keys, not the whole environment.
46
+ */
47
+ export declare function withProcessEnvFallback(map: Record<string, string>): Record<string, string>;
48
+ /** One plan's completed run, parsed from the child's `artifact` message. */
49
+ export interface LoadSubprocessOutcome {
50
+ runnerId: string;
51
+ artifact: LoadArtifact;
52
+ }
53
+ /** A per-file or per-plan failure, parsed from the child's `error` message. */
54
+ export interface LoadSubprocessError {
55
+ message: string;
56
+ }
57
+ /** Result of running one `.load.ts` file in a child process. */
58
+ export interface RunLoadFileResult {
59
+ outcomes: LoadSubprocessOutcome[];
60
+ errors: LoadSubprocessError[];
61
+ }
62
+ /** Options for a single-file subprocess load run. */
63
+ export interface RunLoadFileOptions {
64
+ /** Resolved environment vars (raw — env fallback is applied in the child). */
65
+ vars: Record<string, string>;
66
+ /** Resolved secrets (raw — env fallback is applied in the child). */
67
+ secrets: Record<string, string>;
68
+ /** Working dir for the child (the project root) — drives runner resolution. */
69
+ cwd: string;
70
+ }
71
+ /**
72
+ * Run one `.load.ts` file's plans in a child process and collect their
73
+ * artifacts. Spawns `load-harness.js` (project-local when the project ships a
74
+ * built runner with it, else the CLI's bundled copy) through tsx so the harness
75
+ * and the user file resolve the same `@glubean/sdk`. The raw `{ vars, secrets }`
76
+ * are written to the child's stdin; the child applies the process.env fallback.
77
+ *
78
+ * Mirrors the in-process contract: a file's import failure or a plan's run
79
+ * failure becomes an `errors[]` entry (never throws), so other files/plans still
80
+ * produce results.
81
+ */
82
+ export declare function runLoadFileInSubprocess(file: string, opts: RunLoadFileOptions): Promise<RunLoadFileResult>;
83
+ //# sourceMappingURL=subprocess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../src/load/subprocess.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAKhE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,kCAA8B,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAarB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,EAAE,CAUxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB1F;AAID,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gEAAgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CA+F5B"}