@loadstrike/loadstrike-sdk 1.0.30201 → 1.0.31001

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.
@@ -14,6 +14,12 @@ const correlation_js_1 = require("./correlation.js");
14
14
  const transports_js_1 = require("./transports.js");
15
15
  const reporting_js_1 = require("./reporting.js");
16
16
  const sinks_js_1 = require("./sinks.js");
17
+ const load_engine_v2_js_1 = require("./load-engine-v2.js");
18
+ const iteration_observations_js_1 = require("./iteration-observations.js");
19
+ const iteration_observation_diagnostics_js_1 = require("./iteration-observation-diagnostics.js");
20
+ const sink_retry_policy_js_1 = require("./sink-retry-policy.js");
21
+ const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
22
+ const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
17
23
  exports.LoadStrikeNodeType = {
18
24
  SingleNode: "SingleNode",
19
25
  Coordinator: "Coordinator",
@@ -147,12 +153,20 @@ class LoadStrikePluginData {
147
153
  }
148
154
  exports.LoadStrikePluginData = LoadStrikePluginData;
149
155
  class MeasurementAccumulator {
150
- constructor() {
156
+ constructor(useHistogram = false) {
157
+ this.useHistogram = useHistogram;
151
158
  this.count = 0;
152
159
  this.allBytes = 0;
153
160
  this.latenciesMs = [];
154
161
  this.sizesBytes = [];
162
+ this.latencyLessOrEq800 = 0;
163
+ this.latencyMore800Less1200 = 0;
164
+ this.latencyMoreOrEq1200 = 0;
155
165
  this.statusCodes = new Map();
166
+ if (useHistogram) {
167
+ this.latencyHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
168
+ this.sizeHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
169
+ }
156
170
  }
157
171
  get Count() {
158
172
  return this.count;
@@ -169,8 +183,20 @@ class MeasurementAccumulator {
169
183
  const key = `${statusCode}|${message}|${reply.isSuccess ? "ok" : "fail"}`;
170
184
  this.count += 1;
171
185
  this.allBytes += sizeBytes;
172
- this.latenciesMs.push(latencyMs);
173
- this.sizesBytes.push(sizeBytes);
186
+ if (this.useHistogram) {
187
+ this.latencyHistogram.record(normalizeLatencyMicroseconds(latencyMs));
188
+ this.sizeHistogram.record(normalizeHistogramInteger(sizeBytes, "Response size"));
189
+ }
190
+ else {
191
+ this.latenciesMs.push(latencyMs);
192
+ this.sizesBytes.push(sizeBytes);
193
+ }
194
+ if (latencyMs <= 800)
195
+ this.latencyLessOrEq800 += 1;
196
+ else if (latencyMs < 1200)
197
+ this.latencyMore800Less1200 += 1;
198
+ else
199
+ this.latencyMoreOrEq1200 += 1;
174
200
  const existing = this.statusCodes.get(key);
175
201
  if (existing) {
176
202
  existing.count += 1;
@@ -188,6 +214,9 @@ class MeasurementAccumulator {
188
214
  * Use this when all builder inputs are ready to be materialized.
189
215
  */
190
216
  build(allRequestCount, durationMs) {
217
+ if (this.useHistogram) {
218
+ return buildHistogramMeasurement(this.histogramSnapshot(), allRequestCount, durationMs);
219
+ }
191
220
  const count = this.count;
192
221
  const totalDurationMs = Math.max(durationMs, 0);
193
222
  const latencyValues = [...this.latenciesMs];
@@ -236,14 +265,274 @@ class MeasurementAccumulator {
236
265
  statusCodes
237
266
  };
238
267
  }
268
+ buildCombined(other, allRequestCount, durationMs) {
269
+ if (!this.useHistogram || !other.useHistogram) {
270
+ throw new Error("Combined measurements require Load Engine V2 histograms.");
271
+ }
272
+ const left = this.histogramSnapshot();
273
+ const right = other.histogramSnapshot();
274
+ left.latency.merge(right.latency);
275
+ left.size.merge(right.size);
276
+ for (const [key, value] of right.statusCodes) {
277
+ const existing = left.statusCodes.get(key);
278
+ if (existing)
279
+ existing.count += value.count;
280
+ else
281
+ left.statusCodes.set(key, { ...value });
282
+ }
283
+ return buildHistogramMeasurement({
284
+ count: left.count + right.count,
285
+ allBytes: left.allBytes + right.allBytes,
286
+ latency: left.latency,
287
+ size: left.size,
288
+ statusCodes: left.statusCodes,
289
+ lessOrEq800: left.lessOrEq800 + right.lessOrEq800,
290
+ more800Less1200: left.more800Less1200 + right.more800Less1200,
291
+ moreOrEq1200: left.moreOrEq1200 + right.moreOrEq1200
292
+ }, allRequestCount, durationMs);
293
+ }
294
+ histogramSnapshot() {
295
+ return {
296
+ count: this.count,
297
+ allBytes: this.allBytes,
298
+ latency: this.latencyHistogram.clone(),
299
+ size: this.sizeHistogram.clone(),
300
+ statusCodes: new Map(Array.from(this.statusCodes, ([key, value]) => [key, { ...value }])),
301
+ lessOrEq800: this.latencyLessOrEq800,
302
+ more800Less1200: this.latencyMore800Less1200,
303
+ moreOrEq1200: this.latencyMoreOrEq1200
304
+ };
305
+ }
306
+ }
307
+ function buildHistogramMeasurement(snapshot, allRequestCount, durationMs) {
308
+ const count = snapshot.count;
309
+ const totalDurationMs = Math.max(durationMs, 0);
310
+ const latency = snapshot.latency;
311
+ const size = snapshot.size;
312
+ return {
313
+ count64: latency.count.toString(),
314
+ distributionMode: latency.mode === "quantized-v1" || size.mode === "quantized-v1"
315
+ ? "quantized-v1"
316
+ : "exact-normalized",
317
+ maxRelativeError: Math.max(latency.maxRelativeError, size.maxRelativeError),
318
+ histogramSidecar: {
319
+ latency: latency.toSidecar(),
320
+ size: size.toSidecar(),
321
+ allBytes64: size.exactTotal.toString(),
322
+ lessOrEq80064: snapshot.lessOrEq800.toString(),
323
+ more800Less120064: snapshot.more800Less1200.toString(),
324
+ moreOrEq120064: snapshot.moreOrEq1200.toString()
325
+ },
326
+ request: {
327
+ count,
328
+ percent: allRequestCount <= 0 ? 0 : Math.round((100 * count) / allRequestCount),
329
+ rps: totalDurationMs <= 0 ? 0 : count / (totalDurationMs / 1000)
330
+ },
331
+ dataTransfer: {
332
+ allBytes: snapshot.allBytes,
333
+ allBytes64: size.exactTotal.toString(),
334
+ minBytes: Number(size.minimum),
335
+ maxBytes: Number(size.maximum),
336
+ meanBytes: Math.round(size.mean),
337
+ percent50: Number(size.percentile(0.5)),
338
+ percent75: Number(size.percentile(0.75)),
339
+ percent95: Number(size.percentile(0.95)),
340
+ percent99: Number(size.percentile(0.99)),
341
+ percent100: Number(size.percentile(1)),
342
+ stdDev: size.populationStandardDeviation
343
+ },
344
+ latency: {
345
+ latencyCount: {
346
+ lessOrEq800: snapshot.lessOrEq800,
347
+ more800Less1200: snapshot.more800Less1200,
348
+ moreOrEq1200: snapshot.moreOrEq1200
349
+ },
350
+ minMs: Number(latency.minimum) / 1000,
351
+ maxMs: Number(latency.maximum) / 1000,
352
+ meanMs: latency.mean / 1000,
353
+ percent50: Number(latency.percentile(0.5)) / 1000,
354
+ percent75: Number(latency.percentile(0.75)) / 1000,
355
+ percent95: Number(latency.percentile(0.95)) / 1000,
356
+ percent99: Number(latency.percentile(0.99)) / 1000,
357
+ percent100: Number(latency.percentile(1)) / 1000,
358
+ stdDev: latency.populationStandardDeviation / 1000
359
+ },
360
+ statusCodes: Array.from(snapshot.statusCodes.values())
361
+ .sort((left, right) => right.count - left.count)
362
+ .map((value) => ({
363
+ count: value.count,
364
+ isError: value.isError,
365
+ message: value.message,
366
+ percent: count <= 0 ? 0 : Math.round((100 * value.count) / count),
367
+ statusCode: value.statusCode
368
+ }))
369
+ };
370
+ }
371
+ function normalizeLatencyMicroseconds(latencyMs) {
372
+ return normalizeHistogramInteger(Math.max(latencyMs, 0) * 1000, "Latency");
373
+ }
374
+ function normalizeRawObservationLatencyMicroseconds(latencyMs) {
375
+ const maximum = 9223372036854775807n;
376
+ if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
377
+ return 0n;
378
+ }
379
+ const microseconds = latencyMs * 1000;
380
+ if (!Number.isFinite(microseconds) || microseconds >= Number(maximum)) {
381
+ return maximum;
382
+ }
383
+ return BigInt(Math.max(0, Math.round(microseconds)));
384
+ }
385
+ function normalizeHistogramInteger(value, name) {
386
+ if (!Number.isFinite(value) || value > Number(9223372036854775807n)) {
387
+ throw new RangeError(`${name} is outside the supported histogram range.`);
388
+ }
389
+ return BigInt(Math.max(0, Math.round(value)));
390
+ }
391
+ class LoadEngineV2Telemetry {
392
+ constructor(budget) {
393
+ this.budget = budget;
394
+ this.mutableSegments = [];
395
+ this.warnings = new Map();
396
+ }
397
+ createSegment(scenarioName, scenarioIndex, simulationIndex, kind, shardIndex, shardCount) {
398
+ const segment = {
399
+ scenarioName,
400
+ scenarioIndex,
401
+ simulationIndex,
402
+ kind,
403
+ shardIndex,
404
+ shardCount,
405
+ planned: 0n,
406
+ due: 0n,
407
+ started: 0n,
408
+ completed: 0n,
409
+ dropped: 0n,
410
+ unreached: 0n,
411
+ requestedWorkers: 0n,
412
+ startedWorkers: 0n,
413
+ unavailableWorkers: 0n,
414
+ dropReasons: new Map(),
415
+ unavailableWorkerReasons: new Map(),
416
+ decisionLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
417
+ startLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
418
+ accountingComplete: false
419
+ };
420
+ this.mutableSegments.push(segment);
421
+ return segment;
422
+ }
423
+ recordWarning(code, segment, count) {
424
+ if (count <= 0n)
425
+ return;
426
+ const key = `${code}\n${segment.scenarioIndex}\n${segment.simulationIndex}`;
427
+ const nowNs = BigInt(Date.now()) * 1000000n;
428
+ const existing = this.warnings.get(key);
429
+ if (existing) {
430
+ existing.count += count;
431
+ existing.lastObservedUtcNs = nowNs;
432
+ }
433
+ else {
434
+ this.warnings.set(key, {
435
+ code,
436
+ scenarioName: segment.scenarioName,
437
+ scenarioIndex: segment.scenarioIndex,
438
+ simulationIndex: segment.simulationIndex,
439
+ simulationKind: segment.kind,
440
+ count,
441
+ firstObservedUtcNs: nowNs,
442
+ lastObservedUtcNs: nowNs
443
+ });
444
+ }
445
+ }
446
+ recordDecisionLag(segment, lagNs) {
447
+ segment.decisionLag.record(maxBigInt(lagNs, 0n) / 1000n);
448
+ }
449
+ recordStartLag(segment, lagNs) {
450
+ segment.startLag.record(maxBigInt(lagNs, 0n) / 1000n);
451
+ }
452
+ buildSchedulerDistributions() {
453
+ return this.mutableSegments.flatMap((segment) => {
454
+ const scenarioIndex64 = segment.scenarioIndex.toString();
455
+ const simulationIndex64 = segment.simulationIndex.toString();
456
+ return [
457
+ ["scheduler-decision-lag", "decision", segment.decisionLag],
458
+ ["scheduler-start-lag", "start", segment.startLag]
459
+ ].map(([seriesKind, identityKind, histogram]) => ({
460
+ seriesKind,
461
+ scenarioIndex64,
462
+ scenarioName: segment.scenarioName,
463
+ identityKeyHex: (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(identityKind, scenarioIndex64, simulationIndex64).toString("hex"),
464
+ outcome: "none",
465
+ unit: "microseconds",
466
+ histogram: histogram.toSidecar(),
467
+ exactTotalDecimalOrEmpty: histogram.toSidecar().exactTotal64
468
+ }));
469
+ });
470
+ }
471
+ buildWarnings() {
472
+ return Array.from(this.warnings.values())
473
+ .sort((left, right) => left.code.localeCompare(right.code)
474
+ || left.scenarioName.localeCompare(right.scenarioName)
475
+ || left.simulationIndex - right.simulationIndex)
476
+ .map((value) => ({
477
+ code: value.code,
478
+ scenarioName: value.scenarioName,
479
+ scenarioIndex: value.scenarioIndex,
480
+ simulationIndex: value.simulationIndex,
481
+ simulationKind: value.simulationKind,
482
+ count64: value.count.toString(),
483
+ message: value.code,
484
+ firstObservedUtcNs: value.firstObservedUtcNs.toString(),
485
+ lastObservedUtcNs: value.lastObservedUtcNs.toString()
486
+ }));
487
+ }
488
+ buildSegments() {
489
+ return this.mutableSegments.map((segment) => ({
490
+ scenarioName: segment.scenarioName,
491
+ scenarioIndex: segment.scenarioIndex,
492
+ simulationIndex: segment.simulationIndex,
493
+ kind: segment.kind,
494
+ shardIndex: segment.shardIndex,
495
+ shardCount: segment.shardCount,
496
+ plannedIterations64: segment.planned.toString(),
497
+ dueIterations64: segment.due.toString(),
498
+ startedIterations64: segment.started.toString(),
499
+ completedIterations64: segment.completed.toString(),
500
+ droppedIterations64: segment.dropped.toString(),
501
+ unreachedIterations64: segment.unreached.toString(),
502
+ requestedWorkerSlots64: segment.requestedWorkers.toString(),
503
+ startedWorkerSlots64: segment.startedWorkers.toString(),
504
+ unavailableWorkerSlots64: segment.unavailableWorkers.toString(),
505
+ dropReasons: Object.fromEntries(Array.from(segment.dropReasons, ([key, count]) => [key, count.toString()])),
506
+ unavailableWorkerReasons: Object.fromEntries(Array.from(segment.unavailableWorkerReasons, ([key, count]) => [key, count.toString()])),
507
+ deliveryPercent: segment.due === 0n ? 100 : Number(segment.started * 10000n / segment.due) / 100,
508
+ accountingComplete: segment.accountingComplete
509
+ }));
510
+ }
511
+ buildStats() {
512
+ return {
513
+ configuredMaxInFlight: this.budget.maxInFlight,
514
+ maxInFlightObserved: this.budget.highWater,
515
+ currentInFlight: this.budget.current,
516
+ segments: this.buildSegments()
517
+ };
518
+ }
519
+ }
520
+ function incrementReason(reasons, code, count = 1n) {
521
+ reasons.set(code, (reasons.get(code) ?? 0n) + count);
522
+ }
523
+ function ownedV2OrdinalCount(total, shardIndex, shardCount) {
524
+ if (total <= BigInt(shardIndex))
525
+ return 0n;
526
+ return (total - 1n - BigInt(shardIndex)) / BigInt(shardCount) + 1n;
239
527
  }
240
528
  class StepStatsAccumulator {
241
- constructor(scenarioName, stepName, sortIndex) {
529
+ constructor(scenarioName, stepName, sortIndex, useHistogram = false) {
242
530
  this.scenarioName = scenarioName;
243
531
  this.stepName = stepName;
244
532
  this.sortIndex = sortIndex;
245
- this.ok = new MeasurementAccumulator();
246
- this.fail = new MeasurementAccumulator();
533
+ this.useHistogram = useHistogram;
534
+ this.ok = new MeasurementAccumulator(useHistogram);
535
+ this.fail = new MeasurementAccumulator(useHistogram);
247
536
  }
248
537
  /**
249
538
  * Exposes the public record operation.
@@ -282,6 +571,7 @@ class StepStatsAccumulator {
282
571
  minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
283
572
  maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
284
573
  statusCodes,
574
+ allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, requestCount, durationMs) : undefined,
285
575
  ok,
286
576
  fail,
287
577
  sortIndex: this.sortIndex
@@ -289,15 +579,16 @@ class StepStatsAccumulator {
289
579
  }
290
580
  }
291
581
  class ScenarioStatsAccumulator {
292
- constructor(scenarioName, sortIndex) {
582
+ constructor(scenarioName, sortIndex, useHistogram = false) {
293
583
  this.scenarioName = scenarioName;
294
584
  this.sortIndex = sortIndex;
295
- this.ok = new MeasurementAccumulator();
296
- this.fail = new MeasurementAccumulator();
585
+ this.useHistogram = useHistogram;
297
586
  this.steps = new Map();
298
587
  this.nextStepSortIndex = 0;
299
588
  this.loadSimulationStats = { simulationName: "", value: 0 };
300
589
  this.currentOperation = "None";
590
+ this.ok = new MeasurementAccumulator(useHistogram);
591
+ this.fail = new MeasurementAccumulator(useHistogram);
301
592
  }
302
593
  /**
303
594
  * Exposes the public setLoadSimulation operation.
@@ -338,13 +629,18 @@ class ScenarioStatsAccumulator {
338
629
  * Exposes the public recordStep operation.
339
630
  * Use this when the surrounding wrapper type makes this operation the clearest way to express your intent.
340
631
  */
341
- recordStep(stepName, reply, observedLatencyMs) {
632
+ recordStep(stepName, reply, observedLatencyMs, sortIndex) {
342
633
  const existing = this.steps.get(stepName);
343
- const step = existing ?? new StepStatsAccumulator(this.scenarioName, stepName, this.nextStepSortIndex += 1);
634
+ const resolvedSortIndex = sortIndex === undefined
635
+ ? this.nextStepSortIndex + 1
636
+ : Math.max(Math.trunc(sortIndex), 0);
637
+ const step = existing ?? new StepStatsAccumulator(this.scenarioName, stepName, resolvedSortIndex, this.useHistogram);
344
638
  if (!existing) {
639
+ this.nextStepSortIndex = Math.max(this.nextStepSortIndex, resolvedSortIndex);
345
640
  this.steps.set(stepName, step);
346
641
  }
347
642
  step.record(reply, observedLatencyMs);
643
+ return step.sortIndex;
348
644
  }
349
645
  /**
350
646
  * Builds the configured payload or helper object.
@@ -373,6 +669,7 @@ class ScenarioStatsAccumulator {
373
669
  minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
374
670
  maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
375
671
  statusCodes,
672
+ allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, totalRequests, durationMs) : undefined,
376
673
  allBytes,
377
674
  currentOperation: this.currentOperation,
378
675
  durationMs: Math.max(durationMs, 0),
@@ -468,7 +765,8 @@ class LoadStrikeStep {
468
765
  const internal = context;
469
766
  await internal.invokeBeforeStep(stepName);
470
767
  let reply;
471
- const startedAt = Date.now();
768
+ const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
769
+ const startedAtNs = process.hrtime.bigint();
472
770
  try {
473
771
  reply = normalizeReply(await run());
474
772
  }
@@ -476,8 +774,21 @@ class LoadStrikeStep {
476
774
  reply = LoadStrikeResponse.fail("step_exception", resolveRuntimeErrorMessage(error, "step failed"), 0);
477
775
  }
478
776
  reply = attachReplyProjection(reply);
479
- const observedLatencyMs = Math.max(Date.now() - startedAt, 0);
480
- internal.recordStep(stepName, reply, observedLatencyMs);
777
+ const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
778
+ const completedUtcNs = startedUtcNs + observedLatencyNs;
779
+ const observedLatencyMs = Number(observedLatencyNs) / 1000000;
780
+ const recordedSortIndex = internal.recordStep(stepName, reply, observedLatencyMs);
781
+ internal.recordStepObservation?.((0, iteration_observations_js_1.createIterationStepObservation)({
782
+ stepName,
783
+ sortIndex: typeof recordedSortIndex === "number" ? recordedSortIndex : 0,
784
+ startedUtcNs,
785
+ completedUtcNs,
786
+ observedLatencyUs64: observedLatencyNs / 1000n,
787
+ reportedLatencyUs64: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
788
+ isSuccess: reply.isSuccess,
789
+ statusCode: normalizeStatusCode(reply.statusCode, reply.isSuccess),
790
+ sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(reply.sizeBytes), 0), "Step response bytes")
791
+ }));
481
792
  await internal.invokeAfterStep(stepName, reply);
482
793
  return reply;
483
794
  }
@@ -1016,10 +1327,14 @@ class LoadStrikeContext {
1016
1327
  const normalizedValues = normalizeRunContextCollectionShapes(this.values);
1017
1328
  return {
1018
1329
  displayConsoleMetrics: normalizedValues.ConsoleMetricsEnabled,
1330
+ loadEngineContractVersion: normalizedValues.LoadEngineContractVersion,
1331
+ maxInFlight: normalizedValues.MaxInFlight,
1019
1332
  nodeType: normalizedValues.NodeType,
1020
1333
  localDevClusterEnabled: normalizedValues.LocalDevClusterEnabled,
1021
1334
  agentGroup: normalizedValues.AgentGroup,
1022
1335
  agentsCount: normalizedValues.AgentsCount,
1336
+ agentId: normalizedValues.AgentId,
1337
+ expectedAgentIds: normalizedValues.ExpectedAgentIds,
1023
1338
  targetScenarios: normalizedValues.TargetScenarios,
1024
1339
  agentTargetScenarios: normalizedValues.AgentTargetScenarios,
1025
1340
  coordinatorTargetScenarios: normalizedValues.CoordinatorTargetScenarios,
@@ -1038,6 +1353,13 @@ class LoadStrikeContext {
1038
1353
  reportFolderPath: normalizedValues.ReportFolderPath,
1039
1354
  reportFormats: normalizedValues.ReportFormats,
1040
1355
  reportingIntervalSeconds: normalizedValues.ReportingIntervalSeconds,
1356
+ iterationObservationFlushIntervalSeconds: normalizedValues.IterationObservationFlushIntervalSeconds,
1357
+ maxIterationObservationBufferBytes: normalizedValues.MaxIterationObservationBufferBytes,
1358
+ maxIterationObservationsPerBatch: normalizedValues.MaxIterationObservationsPerBatch,
1359
+ maxIterationObservationBatchBytes: normalizedValues.MaxIterationObservationBatchBytes,
1360
+ iterationObservationSinkQueueDepth: normalizedValues.IterationObservationSinkQueueDepth,
1361
+ iterationObservationSinkParallelism: normalizedValues.IterationObservationSinkParallelism,
1362
+ iterationObservationDrainTimeoutSeconds: normalizedValues.IterationObservationDrainTimeoutSeconds,
1041
1363
  minimumLogLevel: normalizedValues.MinimumLogLevel,
1042
1364
  loggerConfig: normalizedValues.LoggerConfig,
1043
1365
  reportingSinks: normalizedValues.ReportingSinks,
@@ -1051,6 +1373,8 @@ class LoadStrikeContext {
1051
1373
  workerPlugins: normalizedValues.WorkerPlugins,
1052
1374
  customSettings: normalizedValues.CustomSettings,
1053
1375
  globalCustomSettings: normalizedValues.GlobalCustomSettings,
1376
+ clusterShardIndex: normalizedValues.ClusterShardIndex,
1377
+ clusterShardCount: normalizedValues.ClusterShardCount,
1054
1378
  runArgs: this.runArgs.length ? [...this.runArgs] : undefined
1055
1379
  };
1056
1380
  }
@@ -1112,6 +1436,19 @@ class LoadStrikeContext {
1112
1436
  DisplayConsoleMetrics(enable) {
1113
1437
  return this.mergeValues({ ConsoleMetricsEnabled: Boolean(enable) });
1114
1438
  }
1439
+ useLoadEngineV2() {
1440
+ return this.UseLoadEngineV2();
1441
+ }
1442
+ UseLoadEngineV2() {
1443
+ return this.mergeValues({ LoadEngineContractVersion: 2 });
1444
+ }
1445
+ withMaxInFlight(maxInFlight) {
1446
+ return this.WithMaxInFlight(maxInFlight);
1447
+ }
1448
+ WithMaxInFlight(maxInFlight) {
1449
+ validateV2MaxInFlight(this.values.LoadEngineContractVersion, maxInFlight);
1450
+ return this.mergeValues({ MaxInFlight: maxInFlight });
1451
+ }
1115
1452
  /**
1116
1453
  * Toggles local development cluster mode.
1117
1454
  * Use this when you want to simulate coordinator and agent behavior on a single machine.
@@ -1178,6 +1515,26 @@ class LoadStrikeContext {
1178
1515
  WithAgentGroup(agentGroup) {
1179
1516
  return this.mergeValues({ AgentGroup: requireNonEmpty(agentGroup, "Agent group must be provided.") });
1180
1517
  }
1518
+ /** Sets the stable identity required by a remote Load Engine V2 agent. */
1519
+ withAgentId(agentId) {
1520
+ return this.WithAgentId(agentId);
1521
+ }
1522
+ /** Sets the stable identity required by a remote Load Engine V2 agent. */
1523
+ WithAgentId(agentId) {
1524
+ return this.mergeValues({ AgentId: requireNonEmpty(agentId, "Agent id must be provided.") });
1525
+ }
1526
+ /** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
1527
+ withExpectedAgentIds(...agentIds) {
1528
+ return this.WithExpectedAgentIds(...agentIds);
1529
+ }
1530
+ /** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
1531
+ WithExpectedAgentIds(...agentIds) {
1532
+ const normalized = validateScenarioNames(agentIds);
1533
+ if (new Set(normalized).size !== normalized.length) {
1534
+ throw new Error("Expected agent ids must be unique.");
1535
+ }
1536
+ return this.mergeValues({ ExpectedAgentIds: normalized });
1537
+ }
1181
1538
  /**
1182
1539
  * Sets the requested agent count.
1183
1540
  * Use this when a coordinator should fan work out across a specific number of agents.
@@ -2022,7 +2379,7 @@ function firstWebVitalViolation(...values) {
2022
2379
  return "";
2023
2380
  }
2024
2381
  class LoadStrikeScenario {
2025
- constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
2382
+ constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = [], declaredStepNames = []) {
2026
2383
  this.name = name;
2027
2384
  this.runHandler = runHandler;
2028
2385
  this.initHandler = initHandler;
@@ -2036,6 +2393,7 @@ class LoadStrikeScenario {
2036
2393
  this.weight = weight;
2037
2394
  this.restartIterationOnFail = restartIterationOnFail;
2038
2395
  this.internalLicenseFeatures = normalizeStringArray(internalLicenseFeatures);
2396
+ this.declaredStepNames = normalizeDeclaredStepNames(declaredStepNames);
2039
2397
  }
2040
2398
  static create(name, runHandler) {
2041
2399
  const scenarioName = requireNonEmpty(name, "Scenario name must be provided.");
@@ -2078,7 +2436,7 @@ class LoadStrikeScenario {
2078
2436
  if (typeof handler !== "function") {
2079
2437
  throw new TypeError("Init handler must be provided.");
2080
2438
  }
2081
- return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2439
+ return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2082
2440
  }
2083
2441
  /**
2084
2442
  * Configures init async for this SDK object.
@@ -2095,7 +2453,7 @@ class LoadStrikeScenario {
2095
2453
  if (typeof handler !== "function") {
2096
2454
  throw new TypeError("Clean handler must be provided.");
2097
2455
  }
2098
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2456
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2099
2457
  }
2100
2458
  /**
2101
2459
  * Configures clean async for this SDK object.
@@ -2112,14 +2470,14 @@ class LoadStrikeScenario {
2112
2470
  if (!Number.isFinite(maxFailCount)) {
2113
2471
  throw new RangeError("maxFailCount should be a finite number.");
2114
2472
  }
2115
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2473
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2116
2474
  }
2117
2475
  /**
2118
2476
  * Configures out warm up for this SDK object.
2119
2477
  * Use this when out warm up should be set explicitly before the run starts.
2120
2478
  */
2121
2479
  withoutWarmUp() {
2122
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2480
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2123
2481
  }
2124
2482
  /**
2125
2483
  * Configures warm up duration for this SDK object.
@@ -2129,7 +2487,7 @@ class LoadStrikeScenario {
2129
2487
  if (!Number.isFinite(durationSeconds)) {
2130
2488
  throw new RangeError("Warmup duration should be a finite number.");
2131
2489
  }
2132
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2490
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2133
2491
  }
2134
2492
  /**
2135
2493
  * Configures weight for this SDK object.
@@ -2139,14 +2497,14 @@ class LoadStrikeScenario {
2139
2497
  if (!Number.isFinite(weight)) {
2140
2498
  throw new RangeError("Weight should be a finite number.");
2141
2499
  }
2142
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail, this.internalLicenseFeatures);
2500
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2143
2501
  }
2144
2502
  /**
2145
2503
  * Configures restart iteration on fail for this SDK object.
2146
2504
  * Use this when restart iteration on fail should be set explicitly before the run starts.
2147
2505
  */
2148
2506
  withRestartIterationOnFail(shouldRestart) {
2149
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart), this.internalLicenseFeatures);
2507
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart), this.internalLicenseFeatures, this.declaredStepNames);
2150
2508
  }
2151
2509
  /**
2152
2510
  * Configures cross platform tracking for this SDK object.
@@ -2171,7 +2529,7 @@ class LoadStrikeScenario {
2171
2529
  if (isCorrelateExistingTrafficTracking(copied) && this.loadSimulations.length > 0) {
2172
2530
  throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
2173
2531
  }
2174
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2532
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2175
2533
  }
2176
2534
  /**
2177
2535
  * Configures load simulations for this SDK object.
@@ -2184,7 +2542,7 @@ class LoadStrikeScenario {
2184
2542
  if (isCorrelateExistingTrafficTracking(this.trackingConfiguration)) {
2185
2543
  throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
2186
2544
  }
2187
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2545
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2188
2546
  }
2189
2547
  /**
2190
2548
  * Configures thresholds for this SDK object.
@@ -2194,7 +2552,7 @@ class LoadStrikeScenario {
2194
2552
  if (!thresholds.length) {
2195
2553
  throw new Error("At least one threshold should be provided.");
2196
2554
  }
2197
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
2555
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
2198
2556
  }
2199
2557
  /**
2200
2558
  * Returns simulations.
@@ -2255,6 +2613,26 @@ class LoadStrikeScenario {
2255
2613
  __loadStrikeInternalLicenseFeatures() {
2256
2614
  return [...this.internalLicenseFeatures];
2257
2615
  }
2616
+ /** Freezes the named step identities that may be reported by this scenario. */
2617
+ withDeclaredSteps(...stepNames) {
2618
+ if (!stepNames.length) {
2619
+ throw new Error("At least one declared step name should be provided.");
2620
+ }
2621
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, stepNames);
2622
+ }
2623
+ /** Returns the immutable declared-step names in declaration order. */
2624
+ getDeclaredSteps() {
2625
+ return [...this.declaredStepNames];
2626
+ }
2627
+ __loadStrikeSetTrafficMixV2Metadata(metadata) {
2628
+ this.trafficMixV2Metadata = cloneLoadEngineV2TrafficMixMetadata(metadata);
2629
+ return this;
2630
+ }
2631
+ __loadStrikeTrafficMixV2Metadata() {
2632
+ return this.trafficMixV2Metadata
2633
+ ? cloneLoadEngineV2TrafficMixMetadata(this.trafficMixV2Metadata)
2634
+ : undefined;
2635
+ }
2258
2636
  __loadStrikeScenarioSourceAnalysis() {
2259
2637
  const source = this.runHandler.toString();
2260
2638
  const lines = source
@@ -2277,7 +2655,7 @@ class LoadStrikeScenario {
2277
2655
  }
2278
2656
  __loadStrikeWithInternalLicenseFeatures(...features) {
2279
2657
  const merged = Array.from(new Set([...this.internalLicenseFeatures, ...normalizeStringArray(features)]));
2280
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, merged);
2658
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, merged, this.declaredStepNames);
2281
2659
  }
2282
2660
  async invokeInit(context) {
2283
2661
  if (this.initHandler) {
@@ -2298,6 +2676,9 @@ class LoadStrikeScenario {
2298
2676
  return normalizeReply(result);
2299
2677
  }
2300
2678
  catch (error) {
2679
+ if (error instanceof RuntimePolicyCallbackError) {
2680
+ throw error;
2681
+ }
2301
2682
  return LoadStrikeResponse.fail("exception", resolveRuntimeErrorMessage(error, "scenario failed"), 0);
2302
2683
  }
2303
2684
  }
@@ -2343,6 +2724,10 @@ class LoadStrikeScenario {
2343
2724
  WithLoadSimulations(...simulations) {
2344
2725
  return this.withLoadSimulations(...simulations);
2345
2726
  }
2727
+ /** Freezes the named step identities that may be reported by this scenario. */
2728
+ WithDeclaredSteps(...stepNames) {
2729
+ return this.withDeclaredSteps(...stepNames);
2730
+ }
2346
2731
  /**
2347
2732
  * Configures max fail count for this SDK object.
2348
2733
  * Use this when max fail count should be set explicitly before the run starts.
@@ -2473,7 +2858,7 @@ class LoadStrikeTrafficMix {
2473
2858
  return this.withScenarioMix(...scenarioMix);
2474
2859
  }
2475
2860
  expandScenarios() {
2476
- return expandTrafficMixScenarios(this);
2861
+ return expandTrafficMixScenarios(this, 0);
2477
2862
  }
2478
2863
  ExpandScenarios() {
2479
2864
  return this.expandScenarios();
@@ -2487,10 +2872,11 @@ class LoadStrikeTrafficMix {
2487
2872
  }
2488
2873
  exports.LoadStrikeTrafficMix = LoadStrikeTrafficMix;
2489
2874
  class LoadStrikeRunner {
2490
- constructor(scenarios, options, contextConfigurators = []) {
2875
+ constructor(scenarios, options, contextConfigurators = [], internalOptions = {}) {
2491
2876
  this.scenarios = scenarios;
2492
2877
  this.options = normalizeRunnerOptionCollectionShapes(options);
2493
2878
  this.contextConfigurators = [...contextConfigurators];
2879
+ this.internalOptions = internalOptions;
2494
2880
  }
2495
2881
  /**
2496
2882
  * Creates a new instance of this public SDK type.
@@ -2526,7 +2912,7 @@ class LoadStrikeRunner {
2526
2912
  * Use this when one total load profile should be split across weighted scenario lanes.
2527
2913
  */
2528
2914
  static registerTrafficMix(trafficMix) {
2529
- return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix));
2915
+ return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix, 0));
2530
2916
  }
2531
2917
  /**
2532
2918
  * Registers a traffic mix on a fresh runnable context.
@@ -2542,6 +2928,12 @@ class LoadStrikeRunner {
2542
2928
  static DisplayConsoleMetrics(context, enable) {
2543
2929
  return context.DisplayConsoleMetrics(enable);
2544
2930
  }
2931
+ static UseLoadEngineV2(context) {
2932
+ return context.UseLoadEngineV2();
2933
+ }
2934
+ static WithMaxInFlight(context, maxInFlight) {
2935
+ return context.WithMaxInFlight(maxInFlight);
2936
+ }
2545
2937
  /**
2546
2938
  * Toggles local development cluster mode.
2547
2939
  * Use this when you want to simulate coordinator and agent behavior on a single machine.
@@ -2577,6 +2969,12 @@ class LoadStrikeRunner {
2577
2969
  static WithAgentGroup(context, agentGroup) {
2578
2970
  return context.WithAgentGroup(agentGroup);
2579
2971
  }
2972
+ static WithAgentId(context, agentId) {
2973
+ return context.WithAgentId(agentId);
2974
+ }
2975
+ static WithExpectedAgentIds(context, ...agentIds) {
2976
+ return context.WithExpectedAgentIds(...agentIds);
2977
+ }
2580
2978
  /**
2581
2979
  * Sets the requested agent count.
2582
2980
  * Use this when a coordinator should fan work out across a specific number of agents.
@@ -2794,7 +3192,10 @@ class LoadStrikeRunner {
2794
3192
  * Use this when one total load profile should be split across weighted scenario lanes.
2795
3193
  */
2796
3194
  addTrafficMix(trafficMix) {
2797
- this.scenarios = [...this.scenarios, ...expandTrafficMixScenarios(trafficMix)];
3195
+ this.scenarios = [
3196
+ ...this.scenarios,
3197
+ ...expandTrafficMixScenarios(trafficMix, nextTrafficMixDeclarationIndex(this.scenarios))
3198
+ ];
2798
3199
  return this;
2799
3200
  }
2800
3201
  /**
@@ -2927,6 +3328,19 @@ class LoadStrikeRunner {
2927
3328
  WithReportingInterval(intervalSeconds) {
2928
3329
  return this.withReportingInterval(intervalSeconds);
2929
3330
  }
3331
+ useLoadEngineV2() {
3332
+ return this.configure({ loadEngineContractVersion: 2 });
3333
+ }
3334
+ UseLoadEngineV2() {
3335
+ return this.useLoadEngineV2();
3336
+ }
3337
+ withMaxInFlight(maxInFlight) {
3338
+ validateV2MaxInFlight(this.options.loadEngineContractVersion, maxInFlight);
3339
+ return this.configure({ maxInFlight });
3340
+ }
3341
+ WithMaxInFlight(maxInFlight) {
3342
+ return this.withMaxInFlight(maxInFlight);
3343
+ }
2930
3344
  withReportingSinks(...sinks) {
2931
3345
  if (!sinks.length) {
2932
3346
  throw new Error("At least one reporting sink should be provided.");
@@ -3025,7 +3439,7 @@ class LoadStrikeRunner {
3025
3439
  }
3026
3440
  async run(args = []) {
3027
3441
  if (this.contextConfigurators.length) {
3028
- return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).run(args);
3442
+ return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
3029
3443
  }
3030
3444
  if (args.length) {
3031
3445
  return this.buildContext().run(args);
@@ -3043,8 +3457,8 @@ class LoadStrikeRunner {
3043
3457
  }));
3044
3458
  const sinkErrors = [];
3045
3459
  const policyErrors = [];
3046
- const sinkRetryCount = Math.max(this.options.sinkRetryCount ?? 2, 0);
3047
- const sinkRetryBackoffMs = Math.max(this.options.sinkRetryBackoffMs ?? 25, 0);
3460
+ const sinkRetryCount = (0, sink_retry_policy_js_1.normalizeSinkRetryCount)(this.options.sinkRetryCount);
3461
+ const sinkRetryBackoffMs = (0, sink_retry_policy_js_1.normalizeSinkRetryBackoffMs)(this.options.sinkRetryBackoffMs);
3048
3462
  const policies = this.options.runtimePolicies ?? [];
3049
3463
  const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
3050
3464
  const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
@@ -3073,10 +3487,15 @@ class LoadStrikeRunner {
3073
3487
  let licenseClient = null;
3074
3488
  let licensePayload = null;
3075
3489
  let licenseSession = null;
3490
+ let iterationObservationsFinalized = false;
3076
3491
  const clusterMode = resolveClusterExecutionMode(this.options);
3077
3492
  const selectedScenarios = clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
3078
3493
  ? await this.filterScenariosWithPolicies(this.scenarios, policies, policyErrors, runtimePolicyErrorMode)
3079
3494
  : await this.selectScenarios(policies, policyErrors, runtimePolicyErrorMode);
3495
+ if (this.options.loadEngineContractVersion === 2
3496
+ && (clusterMode === "nats-coordinator" || clusterMode === "nats-agent")) {
3497
+ validateLoadEngineV2ScenarioFeatures(selectedScenarios);
3498
+ }
3080
3499
  if (clusterMode === "nats-agent") {
3081
3500
  return this.runAgentWithNats(createdUtc, testInfo, nodeInfo);
3082
3501
  }
@@ -3133,14 +3552,49 @@ class LoadStrikeRunner {
3133
3552
  await init(baseContext, this.options.infraConfig ?? {});
3134
3553
  }
3135
3554
  }
3136
- await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3555
+ await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3137
3556
  for (const plugin of plugins) {
3138
3557
  const start = resolveWorkerPluginStart(plugin);
3139
3558
  if (start) {
3140
3559
  await start(sessionInfo);
3141
3560
  }
3142
3561
  }
3143
- await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3562
+ await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3563
+ const iterationObservationRunId = String(this.internalOptions.iterationObservationRunId
3564
+ ?? sessionInfo.portalReportingRunId
3565
+ ?? sessionInfo.PortalReportingRunId
3566
+ ?? testInfo.sessionId);
3567
+ const iterationObservationResultOwnerId = String(nodeInfo.nodeType).toLowerCase() === "agent"
3568
+ ? String(this.options.agentCommandId
3569
+ ?? `${nodeInfo.machineName}:${Math.max(Math.trunc(this.options.clusterShardIndex ?? 0), 0)}`)
3570
+ : "";
3571
+ const iterationObservationProcessGroup = 0;
3572
+ const iterationObservationExpectedResultOwnerCount64 = Math.max(Math.trunc(this.options.clusterShardCount ?? 1), 1).toString();
3573
+ const initializedIterationObservationSinks = sinkStates
3574
+ .filter((state) => !state.disabled)
3575
+ .map((state) => ({
3576
+ name: state.name,
3577
+ iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
3578
+ iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
3579
+ saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
3580
+ completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
3581
+ }));
3582
+ const reporterIterationObservationSinks = this.internalOptions.iterationObservationSinks
3583
+ ?? (clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
3584
+ ? []
3585
+ : initializedIterationObservationSinks);
3586
+ const iterationObservationReporter = new iteration_observations_js_1.IterationObservationReporter({
3587
+ runId: iterationObservationRunId,
3588
+ sessionId: testInfo.sessionId,
3589
+ resultOwnerId: iterationObservationResultOwnerId,
3590
+ expectedResultOwnerCount64: iterationObservationExpectedResultOwnerCount64,
3591
+ processGroup: iterationObservationProcessGroup,
3592
+ settings: resolveIterationObservationSettings(this.options),
3593
+ sinks: reporterIterationObservationSinks,
3594
+ sinkRetryCount,
3595
+ sinkRetryBackoffMs,
3596
+ logger: runLogger
3597
+ });
3144
3598
  const emitRealtimeSnapshot = async () => {
3145
3599
  if (realtimeInFlight) {
3146
3600
  return;
@@ -3151,7 +3605,7 @@ class LoadStrikeRunner {
3151
3605
  .map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? Math.max(Date.now() - started.getTime(), 0)))
3152
3606
  .sort((left, right) => left.sortIndex - right.sortIndex);
3153
3607
  const metricsSnapshot = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
3154
- await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3608
+ await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3155
3609
  if (toBoolean(this.options.displayConsoleMetrics, true)) {
3156
3610
  const requestCount = snapshot.reduce((sum, value) => sum + value.allRequestCount, 0);
3157
3611
  const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
@@ -3207,21 +3661,30 @@ class LoadStrikeRunner {
3207
3661
  let result;
3208
3662
  let metricStats;
3209
3663
  if (clusterMode === "local-coordinator") {
3210
- const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
3664
+ const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
3211
3665
  metricStats = aggregated.metrics;
3212
3666
  result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
3213
3667
  }
3214
3668
  else if (clusterMode === "nats-coordinator") {
3215
- const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
3669
+ const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
3216
3670
  metricStats = aggregated.metrics;
3217
3671
  result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
3218
3672
  }
3219
3673
  else {
3220
3674
  const testAbortController = new AbortController();
3221
3675
  const stopTestState = { value: false, reason: undefined };
3222
- await Promise.all(selectedScenarios.map((scenario, scenarioIndex) => executeScenarioRuntime({
3676
+ const loadEngineV2Budget = this.options.loadEngineContractVersion === 2
3677
+ ? (this.options.loadEngineV2BudgetOverride
3678
+ ?? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000))
3679
+ : undefined;
3680
+ const loadEngineV2Telemetry = loadEngineV2Budget
3681
+ ? new LoadEngineV2Telemetry(loadEngineV2Budget)
3682
+ : undefined;
3683
+ const executeSelectedScenario = (scenario, selectedScenarioIndex) => executeScenarioRuntime({
3223
3684
  scenario,
3224
- scenarioIndex,
3685
+ scenarioIndex: this.options.loadEngineContractVersion === 2
3686
+ ? this.scenarios.indexOf(scenario)
3687
+ : selectedScenarioIndex,
3225
3688
  scenarioCount: selectedScenarios.length,
3226
3689
  options: this.options,
3227
3690
  logger: runLogger,
@@ -3236,12 +3699,26 @@ class LoadStrikeRunner {
3236
3699
  scenarioDurationsMs,
3237
3700
  stopTestState,
3238
3701
  testAbortController,
3702
+ loadEngineV2Budget,
3703
+ loadEngineV2Telemetry,
3704
+ iterationObservationReporter,
3705
+ iterationObservationRunId,
3706
+ iterationObservationResultOwnerId,
3707
+ iterationObservationProcessGroup,
3239
3708
  executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
3240
3709
  invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
3241
3710
  invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
3242
3711
  invokeBeforeStep: (runtimePolicies, scenarioName, stepName) => this.invokeBeforeStep(runtimePolicies, scenarioName, stepName, policyErrors, runtimePolicyErrorMode),
3243
3712
  invokeAfterStep: (runtimePolicies, scenarioName, stepName, reply) => this.invokeAfterStep(runtimePolicies, scenarioName, stepName, reply, policyErrors, runtimePolicyErrorMode)
3244
- })));
3713
+ });
3714
+ if (this.options.loadEngineV2SegmentLifecycleOverride) {
3715
+ for (let scenarioIndex = 0; scenarioIndex < selectedScenarios.length; scenarioIndex += 1) {
3716
+ await executeSelectedScenario(selectedScenarios[scenarioIndex], scenarioIndex);
3717
+ }
3718
+ }
3719
+ else {
3720
+ await Promise.all(selectedScenarios.map(executeSelectedScenario));
3721
+ }
3245
3722
  nodeInfo.currentOperation = stopTestState.value ? "Stop" : "Complete";
3246
3723
  const scenarioStatList = Array.from(scenarioAccumulators.values())
3247
3724
  .map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? 0))
@@ -3281,17 +3758,46 @@ class LoadStrikeRunner {
3281
3758
  reportFiles: [],
3282
3759
  logFiles: [...loggerSetup.logFiles],
3283
3760
  correlationRows: buildDetailedCorrelationRows(),
3284
- failedCorrelationRows: buildDetailedFailedCorrelationRows()
3761
+ failedCorrelationRows: buildDetailedFailedCorrelationRows(),
3762
+ ...(loadEngineV2Telemetry
3763
+ ? {
3764
+ generatorWarnings: loadEngineV2Telemetry.buildWarnings(),
3765
+ schedulerSegments: loadEngineV2Telemetry.buildSegments(),
3766
+ schedulerStats: loadEngineV2Telemetry.buildStats(),
3767
+ [LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: loadEngineV2Telemetry.buildSchedulerDistributions()
3768
+ }
3769
+ : {})
3285
3770
  };
3286
3771
  }
3287
3772
  await stopRealtimeReporting();
3773
+ const observationDelivery = await iterationObservationReporter.sealAndDrain();
3774
+ iterationObservationsFinalized = true;
3775
+ if (clusterMode !== "local-coordinator"
3776
+ && clusterMode !== "nats-coordinator") {
3777
+ result.observationDeliveryStats = {
3778
+ lastBatchSequence64: observationDelivery.lastBatchSequence64,
3779
+ capturedCount64: observationDelivery.capturedCount64,
3780
+ deliveredCount64: observationDelivery.deliveredCount64,
3781
+ droppedBufferCount64: observationDelivery.droppedBufferCount64,
3782
+ droppedSinkCount64: observationDelivery.droppedSinkCount64
3783
+ };
3784
+ result.reportingComplete = observationDelivery.reportingComplete;
3785
+ }
3786
+ else {
3787
+ result.observationDeliveryStats ?? (result.observationDeliveryStats = emptyObservationDeliveryStats());
3788
+ result.reportingComplete ?? (result.reportingComplete = observationDelivery.reportingComplete);
3789
+ }
3790
+ result.generatorWarnings = [
3791
+ ...(result.generatorWarnings ?? []),
3792
+ ...iterationObservationReporter.buildWarnings()
3793
+ ];
3288
3794
  result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
3289
3795
  const finalizedResult = attachRunResultAliases(result);
3290
3796
  finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
3291
3797
  finalizedResult.reportFiles = this.writeReports(finalizedResult);
3292
3798
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
3293
- await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3294
- await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3799
+ await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3800
+ await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3295
3801
  sinksStopped = true;
3296
3802
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
3297
3803
  finalizedResult.sinkErrors = sinkErrors
@@ -3300,8 +3806,11 @@ class LoadStrikeRunner {
3300
3806
  }
3301
3807
  finally {
3302
3808
  await stopRealtimeReporting();
3809
+ if (!iterationObservationsFinalized) {
3810
+ await iterationObservationReporter.sealAndDrain().catch(() => { });
3811
+ }
3303
3812
  if (!sinksStopped) {
3304
- await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3813
+ await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3305
3814
  }
3306
3815
  if (!pluginsStopped) {
3307
3816
  await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
@@ -3336,7 +3845,7 @@ class LoadStrikeRunner {
3336
3845
  }
3337
3846
  return filtered;
3338
3847
  }
3339
- async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}) {
3848
+ async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}, internalOptions = {}) {
3340
3849
  if (!targetScenarios.length) {
3341
3850
  return buildEmptyNodeStats({
3342
3851
  startedUtc: new Date().toISOString(),
@@ -3364,7 +3873,7 @@ class LoadStrikeRunner {
3364
3873
  displayConsoleMetrics: false,
3365
3874
  reportingSinks: includeWorkerExtensions ? this.options.reportingSinks : [],
3366
3875
  workerPlugins: includeWorkerExtensions ? this.options.workerPlugins : []
3367
- });
3876
+ }, [], internalOptions);
3368
3877
  const childResult = await childRunner.run();
3369
3878
  const childStats = detailedToNodeStats(childResult);
3370
3879
  return {
@@ -3377,70 +3886,127 @@ class LoadStrikeRunner {
3377
3886
  logFiles: [...(childResult.logFiles ?? [])]
3378
3887
  };
3379
3888
  }
3380
- async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
3889
+ async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
3381
3890
  if (!licenseClient) {
3382
3891
  throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
3383
3892
  }
3384
- const controllerRunToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
3893
+ const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
3385
3894
  if (!controllerRunToken) {
3386
3895
  throw new Error("Coordinator agent execution authorization requires an active controller run token.");
3387
3896
  }
3388
- const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
3897
+ const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
3898
+ const sharedV2Budget = this.options.loadEngineContractVersion === 2
3899
+ ? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000)
3900
+ : undefined;
3389
3901
  const nodeResults = await Promise.all(assignments.map(async (targetScenarios, index) => {
3390
3902
  const commandId = (0, node_crypto_1.randomBytes)(16).toString("hex");
3391
- const agentExecutionToken = await licenseClient.createAgentExecutionToken(controllerRunToken, testInfo.sessionId, commandId, index, assignments.length, targetScenarios);
3903
+ const agentExecutionToken = await licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, commandId, index, assignments.length, targetScenarios);
3392
3904
  return this.runClusterChildNode(targetScenarios, "Agent", `local-agent-${index + 1}`, false, {
3393
3905
  sessionId: testInfo.sessionId,
3394
3906
  testSuite: testInfo.testSuite,
3395
3907
  testName: testInfo.testName,
3396
3908
  agentCommandId: commandId,
3397
- agentExecutionToken
3909
+ agentExecutionToken,
3910
+ clusterShardIndex: index,
3911
+ clusterShardCount: assignments.length,
3912
+ loadEngineV2BudgetOverride: sharedV2Budget
3913
+ }, {
3914
+ iterationObservationRunId,
3915
+ iterationObservationSinks
3398
3916
  });
3399
3917
  }));
3400
3918
  const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
3401
3919
  if (coordinatorTargets.length) {
3402
- nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false));
3920
+ nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
3921
+ iterationObservationRunId,
3922
+ iterationObservationSinks
3923
+ }));
3403
3924
  }
3404
- return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults);
3925
+ return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults, this.options.loadEngineContractVersion === 2);
3405
3926
  }
3406
- async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
3927
+ async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
3407
3928
  if (!licenseClient) {
3408
3929
  throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
3409
3930
  }
3410
- const controllerRunToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
3931
+ const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
3411
3932
  if (!controllerRunToken) {
3412
3933
  throw new Error("Coordinator agent execution authorization requires an active controller run token.");
3413
3934
  }
3414
- const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
3935
+ const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
3936
+ const expectedAgentIds = this.options.loadEngineContractVersion === 2
3937
+ ? normalizeRequiredV2AgentIds(this.options.expectedAgentIds, assignments.length)
3938
+ : undefined;
3415
3939
  const coordinator = new cluster_js_1.DistributedClusterCoordinator({
3416
3940
  clusterId: this.options.clusterId ?? "local",
3417
3941
  sessionId: testInfo.sessionId,
3418
3942
  testSuite: testInfo.testSuite,
3419
3943
  testName: testInfo.testName,
3420
3944
  expectedAgentResults: assignments.length,
3945
+ expectedAgentIds,
3946
+ loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
3421
3947
  agentGroup: this.options.agentGroup,
3422
3948
  commandTimeoutMs: Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1),
3423
3949
  nats: this.options.natsServerUrl
3424
3950
  ? { ServerUrl: this.options.natsServerUrl }
3425
3951
  : undefined
3426
3952
  });
3427
- const dispatch = await coordinator.dispatch(assignments, (command) => licenseClient.createAgentExecutionToken(controllerRunToken, testInfo.sessionId, command.commandId, command.agentIndex, command.agentCount, command.targetScenarios));
3428
- const nodes = dispatch.nodeResults.map((value) => clusterNodeResultToNodeStats(value, testInfo, { ...nodeInfo, nodeType: "Agent" }));
3953
+ const tokenFactory = (command) => licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, command.commandId, command.agentIndex, command.agentCount, command.targetScenarios);
3954
+ const dispatch = this.options.loadEngineContractVersion === 2
3955
+ ? await coordinator.dispatchV2(assignments, buildRuntimeLoadEngineV2Plan(scenarios, this.options, testInfo, expectedAgentIds), tokenFactory)
3956
+ : await coordinator.dispatch(assignments, tokenFactory);
3957
+ const nodes = dispatch.nodeResults.map((value) => clusterNodeResultToNodeStats(value, testInfo, { ...nodeInfo, nodeType: "Agent" }, this.options.loadEngineContractVersion === 2));
3429
3958
  const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
3430
3959
  if (coordinatorTargets.length) {
3431
- nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false));
3960
+ nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
3961
+ iterationObservationRunId,
3962
+ iterationObservationSinks
3963
+ }));
3432
3964
  }
3433
- let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes);
3965
+ let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes, this.options.loadEngineContractVersion === 2);
3434
3966
  if (dispatch.missingNodes > 0) {
3435
3967
  aggregated = appendClusterPluginHint(aggregated, `Timed out waiting for ${dispatch.missingNodes} agent node result(s).`);
3968
+ aggregated = attachNodeStatsAliases({
3969
+ ...aggregated,
3970
+ reportingComplete: false,
3971
+ schedulerSegments: [
3972
+ ...(aggregated.schedulerSegments ?? []),
3973
+ ...(dispatch.ownerLoss?.schedulerSegments ?? [])
3974
+ ],
3975
+ generatorWarnings: [
3976
+ ...aggregated.generatorWarnings,
3977
+ ...(dispatch.ownerLoss?.generatorWarnings ?? []).map((warning) => ({
3978
+ code: warning.code,
3979
+ scenarioName: "cluster",
3980
+ simulationIndex: -1,
3981
+ count64: warning.count64,
3982
+ message: `Result owner ${warning.agentId} was lost after assignment; application failures were not fabricated.`,
3983
+ firstObservedUtcNs: "0",
3984
+ lastObservedUtcNs: "0"
3985
+ }))
3986
+ ]
3987
+ });
3436
3988
  }
3437
3989
  return aggregated;
3438
3990
  }
3439
3991
  async runAgentWithNats(startedUtc, testInfo, nodeInfo) {
3992
+ const v2AgentId = this.options.loadEngineContractVersion === 2
3993
+ ? requireNonEmpty(this.options.agentId ?? "", "Remote Load Engine V2 agents require an explicit stable AgentId.")
3994
+ : `${nodeInfo.machineName}-${generateRuntimeSessionId()}`;
3440
3995
  const agent = new cluster_js_1.DistributedClusterAgent({
3441
3996
  clusterId: this.options.clusterId ?? "local",
3442
3997
  sessionId: testInfo.sessionId,
3443
- agentId: `${nodeInfo.machineName}-${generateRuntimeSessionId()}`,
3998
+ agentId: v2AgentId,
3999
+ loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
4000
+ validateV2Plan: this.options.loadEngineContractVersion === 2
4001
+ ? (received) => {
4002
+ const local = buildRuntimeLoadEngineV2Plan(this.scenarios, this.options, { ...testInfo, sessionId: received.sessionId }, received.expectedAgentIds);
4003
+ local.runId = received.runId;
4004
+ local.registrationNonce = received.registrationNonce;
4005
+ if ((0, cluster_js_1.buildLoadEngineV2Plan)(local).hash !== (0, cluster_js_1.buildLoadEngineV2Plan)(received).hash) {
4006
+ throw new Error("Load Engine V2 signed plan descriptors do not match immutable local scenario declarations.");
4007
+ }
4008
+ }
4009
+ : undefined,
3444
4010
  agentGroup: this.options.agentGroup,
3445
4011
  nats: this.options.natsServerUrl
3446
4012
  ? { ServerUrl: this.options.natsServerUrl }
@@ -3450,13 +4016,16 @@ class LoadStrikeRunner {
3450
4016
  const deadline = Date.now() + Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1);
3451
4017
  while (Date.now() < deadline) {
3452
4018
  let handledStats = null;
3453
- const handled = await agent.pollAndExecuteOnce(async (dispatch) => {
4019
+ const execute = async (dispatch) => {
3454
4020
  handledStats = await this.runClusterChildNode(dispatch.scenarioNames, "Agent", nodeInfo.machineName, true, {
3455
4021
  sessionId: testInfo.sessionId,
3456
4022
  testSuite: testInfo.testSuite,
3457
4023
  testName: testInfo.testName,
3458
4024
  agentCommandId: dispatch.commandId,
3459
- agentExecutionToken: dispatch.agentRunToken
4025
+ agentExecutionToken: dispatch.agentRunToken,
4026
+ clusterShardIndex: dispatch.agentIndex ?? 0,
4027
+ clusterShardCount: dispatch.agentCount ?? 1,
4028
+ loadEngineV2SegmentLifecycleOverride: dispatch.segmentLifecycle
3460
4029
  });
3461
4030
  return {
3462
4031
  nodeId: handledStats.nodeInfo.machineName,
@@ -3464,9 +4033,12 @@ class LoadStrikeRunner {
3464
4033
  allRequestCount: handledStats.allRequestCount,
3465
4034
  allOkCount: handledStats.allOkCount,
3466
4035
  allFailCount: handledStats.allFailCount,
3467
- stats: nodeStatsToClusterPayload(handledStats)
4036
+ stats: nodeStatsToClusterPayload(handledStats, this.scenarios.filter((scenario) => dispatch.scenarioNames.includes(scenario.name)), this.options.loadEngineContractVersion === 2)
3468
4037
  };
3469
- });
4038
+ };
4039
+ const handled = this.options.loadEngineContractVersion === 2
4040
+ ? await agent.pollAndExecuteV2Once(execute)
4041
+ : await agent.pollAndExecuteOnce(execute);
3470
4042
  if (handled && handledStats) {
3471
4043
  return toDetailedRunResultFromNodeStats(handledStats, startedUtc, [], []);
3472
4044
  }
@@ -3546,9 +4118,9 @@ class LoadStrikeRunner {
3546
4118
  }
3547
4119
  return filtered;
3548
4120
  }
3549
- async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
4121
+ async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
3550
4122
  for (const state of sinkStates) {
3551
- await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4123
+ await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
3552
4124
  const init = resolveSinkInit(state.sink);
3553
4125
  if (init) {
3554
4126
  await init(context, infraConfig);
@@ -3556,9 +4128,9 @@ class LoadStrikeRunner {
3556
4128
  });
3557
4129
  }
3558
4130
  }
3559
- async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors) {
4131
+ async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
3560
4132
  for (const state of sinkStates) {
3561
- await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4133
+ await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
3562
4134
  const start = resolveSinkStart(state.sink);
3563
4135
  if (start) {
3564
4136
  await start(session);
@@ -3566,9 +4138,9 @@ class LoadStrikeRunner {
3566
4138
  });
3567
4139
  }
3568
4140
  }
3569
- async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors) {
4141
+ async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
3570
4142
  for (const state of sinkStates) {
3571
- await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4143
+ await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
3572
4144
  const saveRealtimeStats = resolveSinkSaveRealtimeStats(state.sink);
3573
4145
  if (saveRealtimeStats) {
3574
4146
  await saveRealtimeStats(scenarioStats);
@@ -3580,10 +4152,9 @@ class LoadStrikeRunner {
3580
4152
  });
3581
4153
  }
3582
4154
  }
3583
- async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
3584
- const shutdownRetryCount = 0;
4155
+ async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
3585
4156
  for (const state of sinkStates) {
3586
- await this.invokeSinkAction(state, "stop", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
4157
+ await this.invokeSinkAction(state, "stop", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
3587
4158
  const stop = resolveSinkStop(state.sink);
3588
4159
  if (stop) {
3589
4160
  await stop();
@@ -3591,15 +4162,15 @@ class LoadStrikeRunner {
3591
4162
  });
3592
4163
  const dispose = resolveSinkDispose(state.sink);
3593
4164
  if (dispose) {
3594
- await this.invokeSinkAction(state, "dispose", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
4165
+ await this.invokeSinkAction(state, "dispose", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
3595
4166
  await dispose();
3596
4167
  });
3597
4168
  }
3598
4169
  }
3599
4170
  }
3600
- async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
4171
+ async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
3601
4172
  for (const state of sinkStates) {
3602
- await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
4173
+ await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, false, async () => {
3603
4174
  const saveRunResult = resolveSinkSaveRunResult(state.sink);
3604
4175
  if (saveRunResult) {
3605
4176
  await saveRunResult(result);
@@ -3607,23 +4178,49 @@ class LoadStrikeRunner {
3607
4178
  });
3608
4179
  }
3609
4180
  }
3610
- async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, ignoreDisabled, disableOnFailure, action) {
4181
+ async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, runId, logger, ignoreDisabled, disableOnFailure, action) {
3611
4182
  if (state.disabled && !ignoreDisabled) {
3612
4183
  return;
3613
4184
  }
3614
- let attempts = 0;
3615
- while (attempts <= retryCount) {
3616
- attempts += 1;
4185
+ const maximumAttempts = (0, sink_retry_policy_js_1.normalizeSinkRetryCount)(retryCount) + 1;
4186
+ const backoffMs = (0, sink_retry_policy_js_1.normalizeSinkRetryBackoffMs)(retryBackoffMs);
4187
+ for (let attempts = 1; attempts <= maximumAttempts; attempts += 1) {
3617
4188
  try {
3618
4189
  await action();
4190
+ if (attempts > 1) {
4191
+ (0, iteration_observation_diagnostics_js_1.logIterationObservationRecovery)(logger, {
4192
+ sinkName: state.name,
4193
+ operation: "reporting-sink-action",
4194
+ phase,
4195
+ runId,
4196
+ resultOwnerId: "",
4197
+ observationCount: 0,
4198
+ attempt: attempts,
4199
+ maximumAttempts,
4200
+ nextDelayMs: 0
4201
+ });
4202
+ }
3619
4203
  return;
3620
4204
  }
3621
4205
  catch (error) {
3622
- if (attempts > retryCount) {
4206
+ const exhausted = attempts >= maximumAttempts;
4207
+ const nextDelayMs = exhausted ? 0 : (0, sink_retry_policy_js_1.sinkRetryDelayMs)(backoffMs, attempts);
4208
+ (0, iteration_observation_diagnostics_js_1.logIterationObservationFailure)(logger, exhausted ? "error" : "warn", {
4209
+ sinkName: state.name,
4210
+ operation: "reporting-sink-action",
4211
+ phase,
4212
+ runId,
4213
+ resultOwnerId: "",
4214
+ observationCount: 0,
4215
+ attempt: attempts,
4216
+ maximumAttempts,
4217
+ nextDelayMs
4218
+ }, error);
4219
+ if (exhausted) {
3623
4220
  sinkErrors.push({
3624
4221
  sinkName: state.name,
3625
4222
  phase,
3626
- message: String(error ?? "sink action failed"),
4223
+ message: "The reporting sink action failed after retries.",
3627
4224
  attempts
3628
4225
  });
3629
4226
  if (disableOnFailure) {
@@ -3631,9 +4228,7 @@ class LoadStrikeRunner {
3631
4228
  }
3632
4229
  return;
3633
4230
  }
3634
- if (retryBackoffMs > 0) {
3635
- await sleep(retryBackoffMs * attempts);
3636
- }
4231
+ await (0, sink_retry_policy_js_1.waitForSinkRetryDelay)(nextDelayMs);
3637
4232
  }
3638
4233
  }
3639
4234
  }
@@ -3746,13 +4341,148 @@ function hasPluginRows(value) {
3746
4341
  }
3747
4342
  return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
3748
4343
  }
4344
+ async function executeV2FixedArrivals(args) {
4345
+ const { rate, intervalNs, totalArrivals, budget, cancellationToken, shouldStopNow, nextInstanceInfo, runBombingInvocation, logger, scenarioName, deadlineOffsetsNs, tolerancesNs, ownedOrdinals, shardIndex = 0, shardCount = 1, telemetry, segment } = args;
4346
+ const segmentStartNs = process.hrtime.bigint();
4347
+ const toleranceNs = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(rate, intervalNs);
4348
+ const active = new Set();
4349
+ let schedulerLate = 0n;
4350
+ let maxInFlight = 0n;
4351
+ let executionError;
4352
+ const normalizedShardCount = Math.max(Math.trunc(shardCount), 1);
4353
+ const normalizedShardIndex = Math.min(Math.max(Math.trunc(shardIndex), 0), normalizedShardCount - 1);
4354
+ const ordinals = ownedOrdinals
4355
+ ? [...ownedOrdinals]
4356
+ : (() => {
4357
+ const values = [];
4358
+ for (let ordinal = BigInt(normalizedShardIndex); ordinal < totalArrivals; ordinal += BigInt(normalizedShardCount)) {
4359
+ values.push(ordinal);
4360
+ }
4361
+ return values;
4362
+ })();
4363
+ if (ordinals.some((ordinal, index) => ordinal < 0n || ordinal >= totalArrivals
4364
+ || (index > 0 && ordinal <= ordinals[index - 1]))) {
4365
+ throw new Error("Load Engine V2 owned arrival ordinals must be sorted, unique, and in range.");
4366
+ }
4367
+ if (segment) {
4368
+ segment.planned = BigInt(ordinals.length);
4369
+ }
4370
+ for (const ordinal of ordinals) {
4371
+ if (shouldStopNow())
4372
+ break;
4373
+ const index = Number(ordinal);
4374
+ const deadlineNs = segmentStartNs
4375
+ + (deadlineOffsetsNs?.[index] ?? (0, load_engine_v2_js_1.loadEngineV2FixedDeadlineNs)(ordinal, rate, intervalNs));
4376
+ await delayUntilMonotonicDeadline(deadlineNs, cancellationToken);
4377
+ if (shouldStopNow()) {
4378
+ break;
4379
+ }
4380
+ const nowNs = process.hrtime.bigint();
4381
+ if (segment)
4382
+ segment.due += 1n;
4383
+ if (segment)
4384
+ telemetry?.recordDecisionLag(segment, nowNs - deadlineNs);
4385
+ if ((0, load_engine_v2_js_1.classifyLoadEngineV2Arrival)(nowNs, deadlineNs, tolerancesNs?.[index] ?? toleranceNs, true) === "scheduler_late") {
4386
+ schedulerLate += 1n;
4387
+ if (segment) {
4388
+ segment.dropped += 1n;
4389
+ incrementReason(segment.dropReasons, "scheduler_late");
4390
+ }
4391
+ continue;
4392
+ }
4393
+ const release = budget.tryAcquire();
4394
+ if (!release) {
4395
+ maxInFlight += 1n;
4396
+ if (segment) {
4397
+ segment.dropped += 1n;
4398
+ incrementReason(segment.dropReasons, "max_in_flight");
4399
+ }
4400
+ continue;
4401
+ }
4402
+ if (segment)
4403
+ segment.started += 1n;
4404
+ const instanceInfo = nextInstanceInfo();
4405
+ let task;
4406
+ task = (async () => {
4407
+ if (segment)
4408
+ telemetry?.recordStartLag(segment, process.hrtime.bigint() - deadlineNs);
4409
+ await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, ordinal);
4410
+ })().catch((error) => {
4411
+ executionError ?? (executionError = error);
4412
+ }).finally(() => {
4413
+ if (segment)
4414
+ segment.completed += 1n;
4415
+ release();
4416
+ active.delete(task);
4417
+ });
4418
+ active.add(task);
4419
+ }
4420
+ while (active.size > 0) {
4421
+ await Promise.race(active);
4422
+ }
4423
+ if (segment) {
4424
+ segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
4425
+ segment.accountingComplete = segment.due + segment.unreached === segment.planned
4426
+ && segment.started + segment.dropped === segment.due
4427
+ && segment.completed === segment.started;
4428
+ }
4429
+ if (telemetry && segment) {
4430
+ telemetry.recordWarning("scheduler_late", segment, schedulerLate);
4431
+ telemetry.recordWarning("max_in_flight", segment, maxInFlight);
4432
+ }
4433
+ if (schedulerLate > 0n) {
4434
+ logger.warn(`Load Engine V2 warning scheduler_late: dropped ${schedulerLate.toString()} overdue arrivals for scenario ${scenarioName}.`);
4435
+ }
4436
+ if (maxInFlight > 0n) {
4437
+ logger.warn(`Load Engine V2 warning max_in_flight: dropped ${maxInFlight.toString()} arrivals for scenario ${scenarioName}; the process limit is ${budget.maxInFlight}.`);
4438
+ }
4439
+ if (executionError !== undefined) {
4440
+ throw executionError;
4441
+ }
4442
+ }
4443
+ async function delayUntilMonotonicDeadline(deadlineNs, cancellationToken) {
4444
+ while (!cancellationToken.aborted) {
4445
+ const remainingNs = deadlineNs - process.hrtime.bigint();
4446
+ if (remainingNs <= 0n) {
4447
+ return;
4448
+ }
4449
+ const remainingMs = Number((remainingNs + 999999n) / 1000000n);
4450
+ await delayWithAbort(Math.max(1, Math.min(remainingMs, 50)), cancellationToken);
4451
+ }
4452
+ }
4453
+ function secondsToNanoseconds(seconds, name) {
4454
+ if (!Number.isFinite(seconds) || seconds <= 0) {
4455
+ throw new RangeError(`${name} must be greater than zero.`);
4456
+ }
4457
+ const nanoseconds = Math.trunc(seconds * 1000000000);
4458
+ if (!Number.isSafeInteger(nanoseconds) || nanoseconds <= 0) {
4459
+ throw new RangeError(`${name} is outside the supported nanosecond range.`);
4460
+ }
4461
+ return BigInt(nanoseconds);
4462
+ }
4463
+ function buildV2RampTolerances(offsets, durationNs) {
4464
+ return offsets.map((offset, index) => {
4465
+ const quantum = offsets.length === 1
4466
+ ? durationNs
4467
+ : index === 0
4468
+ ? offsets[1] - offset
4469
+ : offset - offsets[index - 1];
4470
+ return minBigInt(100000000n, maxBigInt(2000000n, maxBigInt(1n, quantum) * 4n));
4471
+ });
4472
+ }
4473
+ function minBigInt(left, right) {
4474
+ return left < right ? left : right;
4475
+ }
4476
+ function maxBigInt(left, right) {
4477
+ return left > right ? left : right;
4478
+ }
3749
4479
  async function executeScenarioRuntime(args) {
3750
- const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
4480
+ const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, loadEngineV2Budget, loadEngineV2Telemetry, iterationObservationReporter, iterationObservationRunId = testInfo.sessionId, iterationObservationResultOwnerId = "", iterationObservationProcessGroup = 0, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
3751
4481
  const scenarioStartedMs = Date.now();
3752
4482
  const scenarioContextData = {};
3753
4483
  const registeredMetrics = [];
3754
4484
  const runtime = ensureScenarioRuntime(scenarioRuntimes, scenario.name);
3755
- const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex);
4485
+ const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex, options.loadEngineContractVersion === 2);
3756
4486
  scenarioAccumulators.set(scenario.name, accumulator);
3757
4487
  const scenarioAbortController = new AbortController();
3758
4488
  const scenarioCancellationToken = combineAbortSignals(testAbortController.signal, scenarioAbortController.signal);
@@ -3761,12 +4491,25 @@ async function executeScenarioRuntime(args) {
3761
4491
  ? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
3762
4492
  : Number.POSITIVE_INFINITY;
3763
4493
  const scenarioPartition = attachScenarioPartitionAliases({
3764
- number: 0,
3765
- count: 1
4494
+ number: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardIndex ?? 0), 0) : 0,
4495
+ count: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardCount ?? 1), 1) : 1
3766
4496
  });
4497
+ const trafficMixV2 = options.loadEngineContractVersion === 2
4498
+ ? scenario.__loadStrikeTrafficMixV2Metadata()
4499
+ : undefined;
4500
+ if (trafficMixV2) {
4501
+ const expectedSeedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(trafficMixV2.declarationIndex, trafficMixV2.name);
4502
+ if (trafficMixV2.seedId !== expectedSeedId) {
4503
+ throw new Error(`Load Engine V2 traffic-mix seed metadata differs for scenario ${scenario.name}.`);
4504
+ }
4505
+ }
3767
4506
  let stopScenario = false;
3768
4507
  let invocationNumber = 0;
3769
4508
  let instanceCounter = 0;
4509
+ let nextV1ObservationOrdinal = 0n;
4510
+ let nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
4511
+ let nextObservationStepSortIndex = 0;
4512
+ const observationStepSortIndexes = new Map();
3770
4513
  const shouldStopNow = () => stopTestState.value
3771
4514
  || stopScenario
3772
4515
  || scenarioCancellationToken.aborted
@@ -3800,7 +4543,7 @@ async function executeScenarioRuntime(args) {
3800
4543
  runtime.maxLatencyMs = Math.max(runtime.maxLatencyMs, latencyMs);
3801
4544
  accumulator.recordScenario(reply, observedLatencyMs);
3802
4545
  };
3803
- const recordStepReply = (stepName, reply, observedLatencyMs) => {
4546
+ const recordStepReply = (stepName, reply, observedLatencyMs, sortIndex) => {
3804
4547
  const key = `${scenario.name}::${stepName}`;
3805
4548
  const stepRuntime = ensureStepRuntime(stepRuntimes, key, scenario.name, stepName);
3806
4549
  if (reply.isSuccess) {
@@ -3823,11 +4566,35 @@ async function executeScenarioRuntime(args) {
3823
4566
  stepRuntime.maxLatencyMs = Math.max(stepRuntime.maxLatencyMs, latencyMs);
3824
4567
  const statusCode = normalizeStatusCode(reply.statusCode, reply.isSuccess);
3825
4568
  stepRuntime.statusCodes[statusCode] = (stepRuntime.statusCodes[statusCode] ?? 0) + 1;
3826
- accumulator.recordStep(stepName, reply, observedLatencyMs);
4569
+ return accumulator.recordStep(stepName, reply, observedLatencyMs, sortIndex);
4570
+ };
4571
+ const resolveObservationStepSortIndex = (stepName) => {
4572
+ const existing = observationStepSortIndexes.get(stepName);
4573
+ if (existing !== undefined) {
4574
+ return existing;
4575
+ }
4576
+ nextObservationStepSortIndex += 1;
4577
+ observationStepSortIndexes.set(stepName, nextObservationStepSortIndex);
4578
+ return nextObservationStepSortIndex;
4579
+ };
4580
+ const nextObservationOrdinal = (explicit) => {
4581
+ if (explicit !== undefined) {
4582
+ return explicit;
4583
+ }
4584
+ if (options.loadEngineContractVersion === 2) {
4585
+ const ordinal = nextV2FallbackOrdinal;
4586
+ nextV2FallbackOrdinal += BigInt(scenarioPartition.count);
4587
+ return ordinal;
4588
+ }
4589
+ const ordinal = nextV1ObservationOrdinal;
4590
+ nextV1ObservationOrdinal += 1n;
4591
+ return ordinal;
3827
4592
  };
3828
4593
  const runSingleInvocation = async (operation, instanceData, instanceNumber, instanceId, recordScenarioResult) => {
3829
4594
  invocationNumber += 1;
3830
4595
  const runtimeRandom = createRuntimeRandom();
4596
+ const attemptSteps = [];
4597
+ const recordedSteps = [];
3831
4598
  const context = {
3832
4599
  scenarioName: scenario.name,
3833
4600
  data: scenarioContextData,
@@ -3856,7 +4623,12 @@ async function executeScenarioRuntime(args) {
3856
4623
  testAbortController.abort(stopTestState.reason);
3857
4624
  },
3858
4625
  recordStep: (stepName, reply, observedLatencyMs) => {
3859
- recordStepReply(stepName, reply, observedLatencyMs);
4626
+ const sortIndex = resolveObservationStepSortIndex(stepName);
4627
+ recordedSteps.push({ stepName, reply, observedLatencyMs, sortIndex });
4628
+ return sortIndex;
4629
+ },
4630
+ recordStepObservation: (observation) => {
4631
+ attemptSteps.push(observation);
3860
4632
  },
3861
4633
  shouldStopScenario: () => stopScenario || scenarioCancellationToken.aborted,
3862
4634
  shouldStopTest: () => stopTestState.value || scenarioCancellationToken.aborted,
@@ -3864,28 +4636,100 @@ async function executeScenarioRuntime(args) {
3864
4636
  invokeAfterStep: async (stepName, reply) => invokeAfterStep(policies, scenario.name, stepName, reply)
3865
4637
  };
3866
4638
  attachScenarioContextAliases(context);
3867
- const startedAt = Date.now();
3868
- const reply = await executeScenarioInvocation(scenario, context, operation);
3869
- const observedLatencyMs = Math.max(Date.now() - startedAt, 0);
4639
+ const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
4640
+ const startedAtNs = process.hrtime.bigint();
4641
+ let policyFailure;
4642
+ let reply;
4643
+ try {
4644
+ reply = await executeScenarioInvocation(scenario, context, operation);
4645
+ }
4646
+ catch (error) {
4647
+ if (!(error instanceof RuntimePolicyCallbackError)) {
4648
+ throw error;
4649
+ }
4650
+ policyFailure = error;
4651
+ reply = LoadStrikeResponse.fail("runtime_policy_error", "", 0);
4652
+ }
4653
+ const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
4654
+ const completedUtcNs = startedUtcNs + observedLatencyNs;
4655
+ const observedLatencyMs = Number(observedLatencyNs) / 1000000;
3870
4656
  if (recordScenarioResult) {
3871
4657
  recordScenarioReply(reply, observedLatencyMs);
3872
4658
  }
3873
- return { reply, observedLatencyMs };
4659
+ return {
4660
+ reply,
4661
+ observedLatencyMs,
4662
+ startedUtcNs,
4663
+ completedUtcNs,
4664
+ observedLatencyUs: observedLatencyNs / 1000n,
4665
+ reportedLatencyUs: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
4666
+ steps: attemptSteps,
4667
+ recordedSteps,
4668
+ ...(policyFailure ? { policyFailure } : {})
4669
+ };
4670
+ };
4671
+ const captureAttemptObservation = (operation, globalOrdinal, attemptIndex, isFinalAttempt, attempt, simulationIndex, simulationKind, iterationId, globalSecondaryOrdinal = 0n) => {
4672
+ if (!iterationObservationReporter?.enabled) {
4673
+ return;
4674
+ }
4675
+ iterationObservationReporter.capture((0, iteration_observations_js_1.createIterationObservation)({
4676
+ runId: iterationObservationRunId,
4677
+ sessionId: testInfo.sessionId,
4678
+ resultOwnerId: iterationObservationResultOwnerId,
4679
+ processGroup: iterationObservationProcessGroup,
4680
+ scenarioName: scenario.name,
4681
+ scenarioIndex,
4682
+ simulationIndex,
4683
+ simulationKind,
4684
+ phase: operation === "WarmUp" ? "warmup" : "bombing",
4685
+ globalOrdinal64: globalOrdinal,
4686
+ globalSecondaryOrdinal64: globalSecondaryOrdinal,
4687
+ ...(iterationId ? { iterationId } : {}),
4688
+ shardIndex: scenarioPartition.number,
4689
+ shardCount: scenarioPartition.count,
4690
+ attemptIndex,
4691
+ isFinalAttempt,
4692
+ startedUtcNs: attempt.startedUtcNs,
4693
+ completedUtcNs: attempt.completedUtcNs,
4694
+ observedLatencyUs64: attempt.observedLatencyUs,
4695
+ reportedLatencyUs64: attempt.reportedLatencyUs,
4696
+ isSuccess: attempt.reply.isSuccess,
4697
+ statusCode: normalizeStatusCode(attempt.reply.statusCode, attempt.reply.isSuccess),
4698
+ sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(attempt.reply.sizeBytes), 0), "Scenario response bytes"),
4699
+ steps: attempt.steps
4700
+ }));
3874
4701
  };
3875
- const runBombingInvocation = async (instanceData, instanceNumber, instanceId) => {
4702
+ const runBombingInvocation = async (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex = -1, simulationKind = "SingleInvocation", explicitSecondaryOrdinal = 0n) => {
3876
4703
  if (shouldStopNow()) {
3877
4704
  return;
3878
4705
  }
4706
+ const globalOrdinal = nextObservationOrdinal(explicitGlobalOrdinal);
4707
+ const identityKind = canonicalLoadEngineV2InvocationIdentityKind(simulationKind);
4708
+ const iterationId = options.loadEngineContractVersion === 2
4709
+ && explicitGlobalOrdinal !== undefined
4710
+ && identityKind
4711
+ ? (0, cluster_js_1.buildLoadEngineV2GlobalInvocationId)(iterationObservationRunId, scenarioIndex, simulationIndex, identityKind, globalOrdinal, explicitSecondaryOrdinal)
4712
+ : undefined;
3879
4713
  let attempts = 0;
3880
4714
  const maxAttempts = 1 + (scenario.shouldRestartIterationOnFail() ? restartIterationMaxAttempts : 0);
3881
4715
  while (attempts < maxAttempts && !shouldStopNow()) {
3882
4716
  attempts += 1;
3883
4717
  const attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
3884
- const shouldRetry = !attempt.reply.isSuccess
4718
+ const shouldRetry = !attempt.policyFailure
4719
+ && !attempt.reply.isSuccess
3885
4720
  && scenario.shouldRestartIterationOnFail()
3886
4721
  && attempts < maxAttempts
3887
4722
  && !shouldStopNow();
4723
+ captureAttemptObservation("Bombing", globalOrdinal, attempts - 1, !shouldRetry, attempt, simulationIndex, simulationKind, iterationId, explicitSecondaryOrdinal);
4724
+ if (attempt.policyFailure) {
4725
+ stopScenario = true;
4726
+ scenarioAbortController.abort(attempt.policyFailure);
4727
+ throw attempt.policyFailure;
4728
+ }
3888
4729
  if (!shouldRetry) {
4730
+ for (const step of attempt.recordedSteps) {
4731
+ recordStepReply(step.stepName, step.reply, step.observedLatencyMs, step.sortIndex);
4732
+ }
3889
4733
  recordScenarioReply(attempt.reply, attempt.observedLatencyMs);
3890
4734
  if (scenario.getMaxFailCount() > 0 && runtime.allFailCount >= scenario.getMaxFailCount()) {
3891
4735
  stopScenario = true;
@@ -3905,30 +4749,287 @@ async function executeScenarioRuntime(args) {
3905
4749
  const endMs = Date.now() + Math.trunc(warmUpDurationSeconds * 1000);
3906
4750
  const instanceInfo = nextInstanceInfo();
3907
4751
  while (Date.now() < endMs && !shouldStopNow()) {
3908
- await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
4752
+ const globalOrdinal = nextObservationOrdinal();
4753
+ const attempt = await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
4754
+ captureAttemptObservation("WarmUp", globalOrdinal, 0, true, attempt, -1, "SingleInvocation");
4755
+ if (attempt.policyFailure) {
4756
+ stopScenario = true;
4757
+ scenarioAbortController.abort(attempt.policyFailure);
4758
+ throw attempt.policyFailure;
4759
+ }
3909
4760
  }
3910
4761
  };
3911
- const executeSimulationAsync = async (simulation) => {
3912
- const kind = String(simulation.Kind ?? "");
3913
- const weight = Math.max(scenario.getWeight(), 1);
3914
- const rate = applyScenarioWeight(toInt(simulation.Rate), weight);
3915
- const minRate = applyScenarioWeight(toInt(simulation.MinRate), weight);
3916
- const maxRate = applyScenarioWeight(toInt(simulation.MaxRate), weight);
3917
- const copies = Math.max(applyScenarioWeight(Math.max(toInt(simulation.Copies), 1), weight), 1);
3918
- const iterations = applyScenarioWeight(Math.max(toInt(simulation.Iterations), 0), weight);
3919
- const intervalMs = Math.max(Math.trunc(Math.max(toNumber(simulation.IntervalSeconds), 0) * 1000), 0);
3920
- const duringMs = Math.max(Math.trunc(Math.max(toNumber(simulation.DuringSeconds), 0) * 1000), 0);
3921
- accumulator.setLoadSimulation(kind, resolveLoadSimulationValue(simulation, weight));
4762
+ const executeV2TimedConstant = async (copies, durationNs, ramping, runSimulationInvocation, segment) => {
4763
+ if (!loadEngineV2Budget) {
4764
+ return;
4765
+ }
4766
+ const offsets = ramping ? (0, load_engine_v2_js_1.planRampingConstantDeadlines)(copies, durationNs) : Array(copies).fill(0n);
4767
+ const ownedWorkerSlots = trafficMixV2
4768
+ ? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count)
4769
+ : offsets.flatMap((_offset, workerSlot) => workerSlot % scenarioPartition.count === scenarioPartition.number
4770
+ ? [{ laneOrdinal: BigInt(workerSlot), globalRank: BigInt(workerSlot) }]
4771
+ : []);
4772
+ const quantum = ramping ? maxBigInt(1n, durationNs / BigInt(copies)) : 1n;
4773
+ const toleranceNs = minBigInt(100000000n, maxBigInt(2000000n, quantum * 4n));
4774
+ const startNs = process.hrtime.bigint();
4775
+ const endNs = startNs + durationNs;
4776
+ const active = new Set();
4777
+ let schedulerLate = 0;
4778
+ let unavailable = 0;
4779
+ let executionError;
4780
+ if (segment) {
4781
+ segment.requestedWorkers = BigInt(ownedWorkerSlots.length);
4782
+ }
4783
+ for (const unit of ownedWorkerSlots) {
4784
+ const offsetNs = offsets[Number(unit.globalRank)];
4785
+ await delayUntilMonotonicDeadline(startNs + offsetNs, scenarioCancellationToken);
4786
+ if (shouldStopNow()) {
4787
+ break;
4788
+ }
4789
+ const decisionNowNs = process.hrtime.bigint();
4790
+ if (segment)
4791
+ loadEngineV2Telemetry?.recordDecisionLag(segment, decisionNowNs - (startNs + offsetNs));
4792
+ if (decisionNowNs - (startNs + offsetNs) > toleranceNs) {
4793
+ schedulerLate += 1;
4794
+ if (segment) {
4795
+ segment.unavailableWorkers += 1n;
4796
+ incrementReason(segment.unavailableWorkerReasons, "scheduler_late");
4797
+ }
4798
+ continue;
4799
+ }
4800
+ const release = loadEngineV2Budget.tryAcquire();
4801
+ if (!release) {
4802
+ unavailable += 1;
4803
+ if (segment) {
4804
+ segment.unavailableWorkers += 1n;
4805
+ incrementReason(segment.unavailableWorkerReasons, "max_in_flight");
4806
+ }
4807
+ continue;
4808
+ }
4809
+ if (segment)
4810
+ segment.startedWorkers += 1n;
4811
+ const instanceInfo = nextInstanceInfo();
4812
+ let task;
4813
+ task = (async () => {
4814
+ if (segment) {
4815
+ loadEngineV2Telemetry?.recordStartLag(segment, process.hrtime.bigint() - (startNs + offsetNs));
4816
+ }
4817
+ let completedSinceYield = 0;
4818
+ let workerIterationIndex = 0n;
4819
+ while (process.hrtime.bigint() < endNs && !shouldStopNow()) {
4820
+ if (segment) {
4821
+ segment.planned += 1n;
4822
+ segment.due += 1n;
4823
+ segment.started += 1n;
4824
+ }
4825
+ await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, workerIterationIndex);
4826
+ workerIterationIndex += 1n;
4827
+ if (segment)
4828
+ segment.completed += 1n;
4829
+ completedSinceYield += 1;
4830
+ if (completedSinceYield >= 64) {
4831
+ completedSinceYield = 0;
4832
+ await new Promise((resolve) => setImmediate(resolve));
4833
+ }
4834
+ }
4835
+ })().catch((error) => {
4836
+ executionError ?? (executionError = error);
4837
+ }).finally(() => {
4838
+ release();
4839
+ active.delete(task);
4840
+ });
4841
+ active.add(task);
4842
+ }
4843
+ while (active.size > 0) {
4844
+ await Promise.race(active);
4845
+ }
4846
+ if (segment) {
4847
+ segment.accountingComplete = segment.completed === segment.started
4848
+ && segment.started === segment.due
4849
+ && segment.due === segment.planned
4850
+ && segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
4851
+ loadEngineV2Telemetry?.recordWarning("scheduler_late", segment, BigInt(schedulerLate));
4852
+ loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, BigInt(unavailable));
4853
+ }
4854
+ if (schedulerLate > 0) {
4855
+ logger.warn(`Load Engine V2 warning scheduler_late: ${schedulerLate} constant worker slots were unavailable for scenario ${scenario.name}.`);
4856
+ }
4857
+ if (unavailable > 0) {
4858
+ logger.warn(`Load Engine V2 warning max_in_flight: ${unavailable} constant worker slots were unavailable for scenario ${scenario.name}.`);
4859
+ }
4860
+ if (executionError !== undefined) {
4861
+ throw executionError;
4862
+ }
4863
+ };
4864
+ const executeV2IterationsConstant = async (copies, iterations, runSimulationInvocation, segment) => {
4865
+ if (!loadEngineV2Budget || iterations <= 0) {
4866
+ return;
4867
+ }
4868
+ let activeShardCount;
4869
+ let ownedWorkerSlots;
4870
+ let ownedIterations;
4871
+ if (trafficMixV2) {
4872
+ const laneCopies = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
4873
+ const laneIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
4874
+ if (laneIterations === 0n) {
4875
+ if (segment) {
4876
+ segment.requestedWorkers = 0n;
4877
+ segment.accountingComplete = true;
4878
+ }
4879
+ return;
4880
+ }
4881
+ if (laneCopies === 0n) {
4882
+ throw new Error("Load Engine V2 traffic-mix lane owns iterations but no constant worker slot.");
4883
+ }
4884
+ activeShardCount = Math.min(scenarioPartition.count, Number(laneCopies), Number(laneIterations));
4885
+ if (scenarioPartition.number >= activeShardCount) {
4886
+ if (segment) {
4887
+ segment.requestedWorkers = 0n;
4888
+ segment.accountingComplete = true;
4889
+ }
4890
+ return;
4891
+ }
4892
+ ownedWorkerSlots = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
4893
+ ownedIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
4894
+ }
4895
+ else {
4896
+ activeShardCount = Math.min(scenarioPartition.count, copies, iterations);
4897
+ if (scenarioPartition.number >= activeShardCount) {
4898
+ return;
4899
+ }
4900
+ ownedWorkerSlots = Array.from({ length: copies }, (_value, slot) => slot)
4901
+ .filter((slot) => slot % activeShardCount === scenarioPartition.number)
4902
+ .map((slot) => ({ laneOrdinal: BigInt(slot), globalRank: BigInt(slot) }));
4903
+ ownedIterations = Array.from({ length: iterations }, (_value, ordinal) => ordinal)
4904
+ .filter((ordinal) => ordinal % activeShardCount === scenarioPartition.number)
4905
+ .map((ordinal) => ({ laneOrdinal: BigInt(ordinal), globalRank: BigInt(ordinal) }));
4906
+ }
4907
+ if (scenarioPartition.number >= activeShardCount) {
4908
+ return;
4909
+ }
4910
+ const targetWorkers = Math.min(ownedWorkerSlots.length, ownedIterations.length);
4911
+ if (targetWorkers <= 0) {
4912
+ if (segment) {
4913
+ segment.requestedWorkers = 0n;
4914
+ segment.accountingComplete = true;
4915
+ }
4916
+ return;
4917
+ }
4918
+ if (segment) {
4919
+ segment.planned = BigInt(ownedIterations.length);
4920
+ segment.requestedWorkers = BigInt(targetWorkers);
4921
+ }
4922
+ const releases = [];
4923
+ let firstRelease;
4924
+ while (!firstRelease && !shouldStopNow()) {
4925
+ firstRelease = loadEngineV2Budget.tryAcquire();
4926
+ if (!firstRelease) {
4927
+ await delayWithAbort(1, scenarioCancellationToken);
4928
+ }
4929
+ }
4930
+ if (!firstRelease) {
4931
+ if (segment) {
4932
+ segment.unreached = segment.planned;
4933
+ segment.unavailableWorkers = segment.requestedWorkers;
4934
+ if (segment.unavailableWorkers > 0n) {
4935
+ incrementReason(segment.unavailableWorkerReasons, "cancelled", segment.unavailableWorkers);
4936
+ }
4937
+ segment.accountingComplete = true;
4938
+ }
4939
+ return;
4940
+ }
4941
+ releases.push(firstRelease);
4942
+ for (let worker = 1; worker < targetWorkers; worker += 1) {
4943
+ const release = loadEngineV2Budget.tryAcquire();
4944
+ if (release) {
4945
+ releases.push(release);
4946
+ }
4947
+ }
4948
+ if (releases.length < targetWorkers) {
4949
+ logger.warn(`Load Engine V2 warning max_in_flight: requested ${targetWorkers} constant workers but started ${releases.length} for scenario ${scenario.name}; all iterations will still run.`);
4950
+ }
4951
+ if (segment) {
4952
+ segment.startedWorkers = BigInt(releases.length);
4953
+ segment.unavailableWorkers = BigInt(targetWorkers - releases.length);
4954
+ if (segment.unavailableWorkers > 0n) {
4955
+ incrementReason(segment.unavailableWorkerReasons, "max_in_flight", segment.unavailableWorkers);
4956
+ loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, segment.unavailableWorkers);
4957
+ }
4958
+ }
4959
+ let nextIterationIndex = 0;
4960
+ const tasks = releases.map(async (release) => {
4961
+ const instanceInfo = nextInstanceInfo();
4962
+ try {
4963
+ while (!shouldStopNow()) {
4964
+ const unit = ownedIterations[nextIterationIndex];
4965
+ nextIterationIndex += 1;
4966
+ if (!unit) {
4967
+ break;
4968
+ }
4969
+ if (segment) {
4970
+ segment.due += 1n;
4971
+ segment.started += 1n;
4972
+ }
4973
+ await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, 0n);
4974
+ if (segment)
4975
+ segment.completed += 1n;
4976
+ }
4977
+ }
4978
+ finally {
4979
+ release();
4980
+ }
4981
+ });
4982
+ await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
4983
+ if (segment) {
4984
+ segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
4985
+ segment.accountingComplete = segment.due + segment.unreached === segment.planned
4986
+ && segment.started === segment.due
4987
+ && segment.completed === segment.started
4988
+ && segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
4989
+ }
4990
+ };
4991
+ const executeSimulationAsync = async (simulation, simulationIndex) => {
4992
+ const descriptor = trafficMixV2
4993
+ ? trafficMixV2.originalSimulations[simulationIndex]
4994
+ : simulation;
4995
+ if (!descriptor) {
4996
+ throw new Error(`Load Engine V2 traffic-mix phase ${simulationIndex} is missing its global descriptor.`);
4997
+ }
4998
+ const kind = String(descriptor.Kind ?? "");
4999
+ const simulationKind = kind || "SingleInvocation";
5000
+ const runSimulationInvocation = (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, explicitSecondaryOrdinal = 0n) => runBombingInvocation(instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex, simulationKind, explicitSecondaryOrdinal);
5001
+ if (options.loadEngineContractVersion === 2) {
5002
+ nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
5003
+ }
5004
+ const weight = trafficMixV2 ? 1 : Math.max(scenario.getWeight(), 1);
5005
+ const rate = applyScenarioWeight(toInt(descriptor.Rate), weight);
5006
+ const minRate = applyScenarioWeight(toInt(descriptor.MinRate), weight);
5007
+ const maxRate = applyScenarioWeight(toInt(descriptor.MaxRate), weight);
5008
+ const copies = Math.max(applyScenarioWeight(Math.max(toInt(descriptor.Copies), 1), weight), 1);
5009
+ const iterations = applyScenarioWeight(Math.max(toInt(descriptor.Iterations), 0), weight);
5010
+ const intervalMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.IntervalSeconds), 0) * 1000), 0);
5011
+ const duringMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.DuringSeconds), 0) * 1000), 0);
5012
+ accumulator.setLoadSimulation(kind, resolveLoadSimulationValue(descriptor, weight));
3922
5013
  accumulator.setCurrentOperation("Bombing");
5014
+ const schedulerSegment = options.loadEngineContractVersion === 2
5015
+ ? loadEngineV2Telemetry?.createSegment(scenario.name, scenarioIndex, simulationIndex, kind, scenarioPartition.number, scenarioPartition.count)
5016
+ : undefined;
5017
+ const trafficMixOwnedOrdinals = (total) => trafficMixV2
5018
+ ? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(total, trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count).map((unit) => unit.globalRank)
5019
+ : undefined;
3923
5020
  if (kind === "KeepConstant") {
3924
5021
  if (duringMs <= 0) {
3925
5022
  return;
3926
5023
  }
5024
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5025
+ await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "KeepConstant duration"), false, runSimulationInvocation, schedulerSegment);
5026
+ return;
5027
+ }
3927
5028
  const endMs = Date.now() + duringMs;
3928
5029
  const tasks = Array.from({ length: copies }, async () => {
3929
5030
  const instanceInfo = nextInstanceInfo();
3930
5031
  while (Date.now() < endMs && !shouldStopNow()) {
3931
- await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
5032
+ await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
3932
5033
  }
3933
5034
  });
3934
5035
  await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
@@ -3938,6 +5039,10 @@ async function executeScenarioRuntime(args) {
3938
5039
  if (duringMs <= 0) {
3939
5040
  return;
3940
5041
  }
5042
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5043
+ await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingConstant duration"), true, runSimulationInvocation, schedulerSegment);
5044
+ return;
5045
+ }
3941
5046
  const tasks = [];
3942
5047
  const endMs = Date.now() + duringMs;
3943
5048
  const startIntervalMs = copies <= 1 ? 0 : Math.max(Math.trunc(duringMs / copies), 0);
@@ -3945,7 +5050,7 @@ async function executeScenarioRuntime(args) {
3945
5050
  const instanceInfo = nextInstanceInfo();
3946
5051
  tasks.push((async () => {
3947
5052
  while (Date.now() < endMs && !shouldStopNow()) {
3948
- await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
5053
+ await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
3949
5054
  }
3950
5055
  })());
3951
5056
  if (copy < copies && startIntervalMs > 0) {
@@ -3959,12 +5064,34 @@ async function executeScenarioRuntime(args) {
3959
5064
  if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
3960
5065
  return;
3961
5066
  }
5067
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5068
+ const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "Inject interval");
5069
+ const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Inject duration");
5070
+ await executeV2FixedArrivals({
5071
+ rate,
5072
+ intervalNs,
5073
+ totalArrivals: (0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, durationNs),
5074
+ budget: loadEngineV2Budget,
5075
+ cancellationToken: scenarioCancellationToken,
5076
+ shouldStopNow,
5077
+ nextInstanceInfo,
5078
+ runBombingInvocation: runSimulationInvocation,
5079
+ logger,
5080
+ scenarioName: scenario.name,
5081
+ shardIndex: scenarioPartition.number,
5082
+ shardCount: scenarioPartition.count,
5083
+ ownedOrdinals: trafficMixOwnedOrdinals((0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, durationNs)),
5084
+ telemetry: loadEngineV2Telemetry,
5085
+ segment: schedulerSegment
5086
+ });
5087
+ return;
5088
+ }
3962
5089
  const pending = [];
3963
5090
  const endMs = Date.now() + duringMs;
3964
5091
  while (Date.now() < endMs && !shouldStopNow()) {
3965
5092
  for (let index = 0; index < rate; index += 1) {
3966
5093
  const instanceInfo = nextInstanceInfo();
3967
- pending.push(runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
5094
+ pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
3968
5095
  }
3969
5096
  await delayWithAbort(intervalMs, scenarioCancellationToken);
3970
5097
  }
@@ -3975,6 +5102,31 @@ async function executeScenarioRuntime(args) {
3975
5102
  if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
3976
5103
  return;
3977
5104
  }
5105
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5106
+ const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "RampingInject interval");
5107
+ const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingInject duration");
5108
+ const offsets = (0, load_engine_v2_js_1.planRampingInjectionDeadlines)(rate, intervalNs, durationNs);
5109
+ await executeV2FixedArrivals({
5110
+ rate,
5111
+ intervalNs,
5112
+ totalArrivals: BigInt(offsets.length),
5113
+ deadlineOffsetsNs: offsets,
5114
+ tolerancesNs: buildV2RampTolerances(offsets, durationNs),
5115
+ budget: loadEngineV2Budget,
5116
+ cancellationToken: scenarioCancellationToken,
5117
+ shouldStopNow,
5118
+ nextInstanceInfo,
5119
+ runBombingInvocation: runSimulationInvocation,
5120
+ logger,
5121
+ scenarioName: scenario.name,
5122
+ shardIndex: scenarioPartition.number,
5123
+ shardCount: scenarioPartition.count,
5124
+ ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
5125
+ telemetry: loadEngineV2Telemetry,
5126
+ segment: schedulerSegment
5127
+ });
5128
+ return;
5129
+ }
3978
5130
  const pending = [];
3979
5131
  const startedAtMs = Date.now();
3980
5132
  const endMs = startedAtMs + duringMs;
@@ -3984,7 +5136,7 @@ async function executeScenarioRuntime(args) {
3984
5136
  const currentRate = Math.max(1, Math.ceil(rate * progress));
3985
5137
  for (let index = 0; index < currentRate; index += 1) {
3986
5138
  const instanceInfo = nextInstanceInfo();
3987
- pending.push(runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
5139
+ pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
3988
5140
  }
3989
5141
  await delayWithAbort(intervalMs, scenarioCancellationToken);
3990
5142
  }
@@ -3995,6 +5147,37 @@ async function executeScenarioRuntime(args) {
3995
5147
  if (duringMs <= 0 || maxRate <= 0 || intervalMs <= 0) {
3996
5148
  return;
3997
5149
  }
5150
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5151
+ const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "InjectRandom interval");
5152
+ const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "InjectRandom duration");
5153
+ const descriptorSeed = (0, load_engine_v2_js_1.fnv1a32)(trafficMixV2
5154
+ ? `traffic-mix\n${trafficMixV2.seedId}\n${simulationIndex}`
5155
+ : `${scenario.name}\n${simulationIndex}`);
5156
+ const rotated = ((descriptorSeed << 13) | (descriptorSeed >>> 19)) >>> 0;
5157
+ const seed = ((0, load_engine_v2_js_1.fnv1a32)(testInfo.sessionId) ^ rotated) >>> 0;
5158
+ const offsets = (0, load_engine_v2_js_1.planRandomInjectionDeadlines)(Math.max(0, minRate), maxRate, intervalNs, durationNs, seed);
5159
+ const tolerance = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(Math.max(1, maxRate), intervalNs);
5160
+ await executeV2FixedArrivals({
5161
+ rate: Math.max(1, maxRate),
5162
+ intervalNs,
5163
+ totalArrivals: BigInt(offsets.length),
5164
+ deadlineOffsetsNs: offsets,
5165
+ tolerancesNs: offsets.map(() => tolerance),
5166
+ budget: loadEngineV2Budget,
5167
+ cancellationToken: scenarioCancellationToken,
5168
+ shouldStopNow,
5169
+ nextInstanceInfo,
5170
+ runBombingInvocation: runSimulationInvocation,
5171
+ logger,
5172
+ scenarioName: scenario.name,
5173
+ shardIndex: scenarioPartition.number,
5174
+ shardCount: scenarioPartition.count,
5175
+ ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
5176
+ telemetry: loadEngineV2Telemetry,
5177
+ segment: schedulerSegment
5178
+ });
5179
+ return;
5180
+ }
3998
5181
  const pending = [];
3999
5182
  const normalizedMinRate = Math.max(1, minRate);
4000
5183
  const normalizedMaxRate = Math.max(normalizedMinRate, maxRate);
@@ -4003,7 +5186,7 @@ async function executeScenarioRuntime(args) {
4003
5186
  const currentRate = randomIntInclusive(normalizedMinRate, normalizedMaxRate);
4004
5187
  for (let index = 0; index < currentRate; index += 1) {
4005
5188
  const instanceInfo = nextInstanceInfo();
4006
- pending.push(runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
5189
+ pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
4007
5190
  }
4008
5191
  await delayWithAbort(intervalMs, scenarioCancellationToken);
4009
5192
  }
@@ -4014,6 +5197,10 @@ async function executeScenarioRuntime(args) {
4014
5197
  if (iterations <= 0) {
4015
5198
  return;
4016
5199
  }
5200
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5201
+ await executeV2IterationsConstant(copies, iterations, runSimulationInvocation, schedulerSegment);
5202
+ return;
5203
+ }
4017
5204
  let remaining = iterations;
4018
5205
  const tasks = Array.from({ length: copies }, async () => {
4019
5206
  const instanceInfo = nextInstanceInfo();
@@ -4022,7 +5209,7 @@ async function executeScenarioRuntime(args) {
4022
5209
  if (remaining < 0) {
4023
5210
  break;
4024
5211
  }
4025
- await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
5212
+ await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
4026
5213
  }
4027
5214
  });
4028
5215
  await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
@@ -4032,13 +5219,33 @@ async function executeScenarioRuntime(args) {
4032
5219
  if (iterations <= 0 || rate <= 0 || intervalMs <= 0) {
4033
5220
  return;
4034
5221
  }
5222
+ if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
5223
+ await executeV2FixedArrivals({
5224
+ rate,
5225
+ intervalNs: secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "IterationsForInject interval"),
5226
+ totalArrivals: BigInt(iterations),
5227
+ budget: loadEngineV2Budget,
5228
+ cancellationToken: scenarioCancellationToken,
5229
+ shouldStopNow,
5230
+ nextInstanceInfo,
5231
+ runBombingInvocation: runSimulationInvocation,
5232
+ logger,
5233
+ scenarioName: scenario.name,
5234
+ shardIndex: scenarioPartition.number,
5235
+ shardCount: scenarioPartition.count,
5236
+ ownedOrdinals: trafficMixOwnedOrdinals(BigInt(iterations)),
5237
+ telemetry: loadEngineV2Telemetry,
5238
+ segment: schedulerSegment
5239
+ });
5240
+ return;
5241
+ }
4035
5242
  const pending = [];
4036
5243
  let remaining = iterations;
4037
5244
  while (remaining > 0 && !shouldStopNow()) {
4038
5245
  const count = Math.min(rate, remaining);
4039
5246
  for (let index = 0; index < count; index += 1) {
4040
5247
  const instanceInfo = nextInstanceInfo();
4041
- pending.push(runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
5248
+ pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
4042
5249
  }
4043
5250
  remaining -= count;
4044
5251
  if (remaining > 0) {
@@ -4050,10 +5257,19 @@ async function executeScenarioRuntime(args) {
4050
5257
  }
4051
5258
  if (kind === "Pause") {
4052
5259
  if (duringMs > 0) {
4053
- await delayWithAbort(duringMs, scenarioCancellationToken);
5260
+ if (options.loadEngineContractVersion === 2) {
5261
+ await delayUntilMonotonicDeadline(process.hrtime.bigint() + secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Pause duration"), scenarioCancellationToken);
5262
+ }
5263
+ else {
5264
+ await delayWithAbort(duringMs, scenarioCancellationToken);
5265
+ }
4054
5266
  }
5267
+ if (schedulerSegment)
5268
+ schedulerSegment.accountingComplete = true;
4055
5269
  return;
4056
5270
  }
5271
+ if (schedulerSegment)
5272
+ schedulerSegment.accountingComplete = true;
4057
5273
  };
4058
5274
  const initContext = {
4059
5275
  customSettings: { ...(options.customSettings ?? {}) },
@@ -4085,15 +5301,33 @@ async function executeScenarioRuntime(args) {
4085
5301
  accumulator.setCurrentOperation("Bombing");
4086
5302
  const simulations = scenario.getSimulations();
4087
5303
  if (!simulations.length) {
4088
- const instanceInfo = nextInstanceInfo();
4089
- await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
5304
+ if (options.loadEngineContractVersion === 2) {
5305
+ await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, 0);
5306
+ try {
5307
+ await executeSimulationAsync(LoadStrikeSimulation.iterationsForConstant(1, 1), 0);
5308
+ }
5309
+ finally {
5310
+ await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, 0);
5311
+ }
5312
+ }
5313
+ else {
5314
+ const instanceInfo = nextInstanceInfo();
5315
+ await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
5316
+ }
4090
5317
  }
4091
5318
  else {
4092
- for (const simulation of simulations) {
5319
+ for (let simulationIndex = 0; simulationIndex < simulations.length; simulationIndex += 1) {
5320
+ const simulation = simulations[simulationIndex];
4093
5321
  if (shouldStopNow()) {
4094
5322
  break;
4095
5323
  }
4096
- await executeSimulationAsync(simulation);
5324
+ await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, simulationIndex);
5325
+ try {
5326
+ await executeSimulationAsync(simulation, simulationIndex);
5327
+ }
5328
+ finally {
5329
+ await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, simulationIndex);
5330
+ }
4097
5331
  }
4098
5332
  }
4099
5333
  accumulator.setCurrentOperation(stopScenario || stopTestState.value ? "Stop" : "Complete");
@@ -4182,18 +5416,36 @@ async function waitForScenarioTasks(tasks, scenarioName, timeoutSeconds, logger,
4182
5416
  if (!tasks.length) {
4183
5417
  return;
4184
5418
  }
4185
- const all = Promise.allSettled(tasks).then(() => { });
4186
- if (timeoutSeconds <= 0) {
4187
- await all;
4188
- return;
5419
+ const settled = Promise.allSettled(tasks);
5420
+ const throwPolicyFailure = (results) => {
5421
+ const failure = results.find((result) => result.status === "rejected" && result.reason instanceof RuntimePolicyCallbackError);
5422
+ if (failure) {
5423
+ throw failure.reason;
5424
+ }
5425
+ };
5426
+ if (timeoutSeconds <= 0) {
5427
+ throwPolicyFailure(await settled);
5428
+ return;
5429
+ }
5430
+ const timeoutController = new AbortController();
5431
+ let completed;
5432
+ try {
5433
+ completed = await Promise.race([
5434
+ settled.then(() => true),
5435
+ delayWithAbort(Math.trunc(timeoutSeconds * 1000), combineAbortSignals(signal, timeoutController.signal)).then(() => false)
5436
+ ]);
5437
+ }
5438
+ finally {
5439
+ timeoutController.abort();
4189
5440
  }
4190
- const completed = await Promise.race([
4191
- all.then(() => true),
4192
- delayWithAbort(Math.trunc(timeoutSeconds * 1000), signal).then(() => false)
4193
- ]);
4194
5441
  if (!completed) {
5442
+ if (signal.reason instanceof RuntimePolicyCallbackError) {
5443
+ throw signal.reason;
5444
+ }
4195
5445
  logger.warn(`Scenario ${scenarioName} timed out while waiting for completion (${timeoutSeconds}s).`);
5446
+ return;
4196
5447
  }
5448
+ throwPolicyFailure(await settled);
4197
5449
  }
4198
5450
  function randomIntInclusive(minValue, maxValue) {
4199
5451
  const min = Math.trunc(Math.min(minValue, maxValue));
@@ -4420,6 +5672,9 @@ function normalizeDataTransferStatsValue(value) {
4420
5672
  const source = asAliasRecord(value);
4421
5673
  return {
4422
5674
  allBytes: pickAliasNumber(source, "allBytes", "AllBytes"),
5675
+ ...(hasAliasValue(source, "allBytes64", "AllBytes64")
5676
+ ? { allBytes64: pickAliasString(source, "allBytes64", "AllBytes64") }
5677
+ : {}),
4423
5678
  maxBytes: pickAliasNumber(source, "maxBytes", "MaxBytes"),
4424
5679
  meanBytes: pickAliasNumber(source, "meanBytes", "MeanBytes"),
4425
5680
  minBytes: pickAliasNumber(source, "minBytes", "MinBytes"),
@@ -4427,6 +5682,9 @@ function normalizeDataTransferStatsValue(value) {
4427
5682
  percent75: pickAliasNumber(source, "percent75", "Percent75"),
4428
5683
  percent95: pickAliasNumber(source, "percent95", "Percent95"),
4429
5684
  percent99: pickAliasNumber(source, "percent99", "Percent99"),
5685
+ ...(hasAliasValue(source, "percent100", "Percent100")
5686
+ ? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
5687
+ : {}),
4430
5688
  stdDev: pickAliasNumber(source, "stdDev", "StdDev")
4431
5689
  };
4432
5690
  }
@@ -4449,6 +5707,9 @@ function normalizeLatencyStatsValue(value) {
4449
5707
  percent75: pickAliasNumber(source, "percent75", "Percent75"),
4450
5708
  percent95: pickAliasNumber(source, "percent95", "Percent95"),
4451
5709
  percent99: pickAliasNumber(source, "percent99", "Percent99"),
5710
+ ...(hasAliasValue(source, "percent100", "Percent100")
5711
+ ? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
5712
+ : {}),
4452
5713
  stdDev: pickAliasNumber(source, "stdDev", "StdDev")
4453
5714
  };
4454
5715
  }
@@ -4464,13 +5725,42 @@ function normalizeStatusCodeStatsValue(value) {
4464
5725
  }
4465
5726
  function normalizeMeasurementStatsValue(value) {
4466
5727
  const source = asAliasRecord(value);
4467
- return {
5728
+ const projected = {
5729
+ ...(hasAliasValue(source, "count64", "Count64")
5730
+ ? { count64: pickAliasString(source, "count64", "Count64") }
5731
+ : {}),
5732
+ ...(hasAliasValue(source, "distributionMode", "DistributionMode")
5733
+ ? { distributionMode: pickAliasString(source, "distributionMode", "DistributionMode") }
5734
+ : {}),
5735
+ ...(hasAliasValue(source, "maxRelativeError", "MaxRelativeError")
5736
+ ? { maxRelativeError: pickAliasNumber(source, "maxRelativeError", "MaxRelativeError") }
5737
+ : {}),
4468
5738
  dataTransfer: normalizeDataTransferStatsValue(pickAliasValue(source, "dataTransfer", "DataTransfer")),
4469
5739
  latency: normalizeLatencyStatsValue(pickAliasValue(source, "latency", "Latency")),
4470
5740
  request: normalizeRequestStatsValue(pickAliasValue(source, "request", "Request")),
4471
5741
  statusCodes: pickAliasArray(source, "statusCodes", "StatusCodes")
4472
5742
  .map((entry) => normalizeStatusCodeStatsValue(entry))
4473
5743
  };
5744
+ const sidecarValue = pickAliasValue(source, "histogramSidecar", "HistogramSidecar");
5745
+ if (sidecarValue && typeof sidecarValue === "object" && !Array.isArray(sidecarValue)) {
5746
+ const sidecar = sidecarValue;
5747
+ if (sidecar?.latency && sidecar.size) {
5748
+ Object.defineProperty(projected, "histogramSidecar", {
5749
+ value: {
5750
+ latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.latency).toSidecar(),
5751
+ size: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.size).toSidecar(),
5752
+ allBytes64: String(sidecar.allBytes64 ?? "0"),
5753
+ lessOrEq80064: String(sidecar.lessOrEq80064 ?? "0"),
5754
+ more800Less120064: String(sidecar.more800Less120064 ?? "0"),
5755
+ moreOrEq120064: String(sidecar.moreOrEq120064 ?? "0")
5756
+ },
5757
+ enumerable: false,
5758
+ configurable: false,
5759
+ writable: false
5760
+ });
5761
+ }
5762
+ }
5763
+ return projected;
4474
5764
  }
4475
5765
  function normalizeLoadSimulationStatsValue(value) {
4476
5766
  const source = asAliasRecord(value);
@@ -4570,6 +5860,9 @@ function normalizeStepStatsValue(value, index = 0) {
4570
5860
  statusCodes: normalizeAliasNumberRecord(pickAliasValue(source, "statusCodes", "StatusCodes")),
4571
5861
  ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
4572
5862
  fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
5863
+ ...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
5864
+ ? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
5865
+ : {}),
4573
5866
  sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
4574
5867
  ? pickAliasNumber(source, "sortIndex", "SortIndex")
4575
5868
  : index
@@ -4609,6 +5902,9 @@ function normalizeScenarioStatsValue(value, index = 0) {
4609
5902
  durationMs: pickAliasNumber(source, "durationMs", "DurationMs", "Duration"),
4610
5903
  ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
4611
5904
  fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
5905
+ ...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
5906
+ ? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
5907
+ : {}),
4612
5908
  loadSimulationStats: normalizeLoadSimulationStatsValue(pickAliasValue(source, "loadSimulationStats", "LoadSimulationStats")),
4613
5909
  sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
4614
5910
  ? pickAliasNumber(source, "sortIndex", "SortIndex")
@@ -4695,7 +5991,7 @@ function attachSessionStartInfoAliases(session) {
4695
5991
  return session;
4696
5992
  }
4697
5993
  function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
4698
- const runToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
5994
+ const runToken = currentLicenseSessionRunToken(licenseSession);
4699
5995
  if (!runToken || !licenseClient) {
4700
5996
  return;
4701
5997
  }
@@ -4705,9 +6001,17 @@ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, l
4705
6001
  sinkSession.portalReportingIngestUrl = ingestUrl;
4706
6002
  sinkSession.portalReportingRunId = runId;
4707
6003
  sessionInfo.runToken = runToken;
6004
+ Object.defineProperty(sessionInfo, PORTAL_RUN_TOKEN_PROVIDER, {
6005
+ configurable: true,
6006
+ enumerable: false,
6007
+ value: () => currentLicenseSessionRunToken(licenseSession)
6008
+ });
4708
6009
  sessionInfo.portalReportingIngestUrl = ingestUrl;
4709
6010
  sessionInfo.portalReportingRunId = runId;
4710
6011
  }
6012
+ function currentLicenseSessionRunToken(licenseSession) {
6013
+ return stringValueOrDefault(licenseSession?.runToken, "").trim();
6014
+ }
4711
6015
  function buildPortalReportingRunId(sessionId) {
4712
6016
  const sessionPart = String(sessionId ?? "")
4713
6017
  .replace(/[^A-Za-z0-9._-]+/g, "-")
@@ -4792,6 +6096,7 @@ function attachDataTransferStatsAliases(stats) {
4792
6096
  const projected = normalizeDataTransferStatsValue(stats);
4793
6097
  return attachAliasMap(projected, {
4794
6098
  AllBytes: "allBytes",
6099
+ AllBytes64: "allBytes64",
4795
6100
  MaxBytes: "maxBytes",
4796
6101
  MeanBytes: "meanBytes",
4797
6102
  MinBytes: "minBytes",
@@ -4799,6 +6104,7 @@ function attachDataTransferStatsAliases(stats) {
4799
6104
  Percent75: "percent75",
4800
6105
  Percent95: "percent95",
4801
6106
  Percent99: "percent99",
6107
+ Percent100: "percent100",
4802
6108
  StdDev: "stdDev"
4803
6109
  });
4804
6110
  }
@@ -4822,6 +6128,7 @@ function attachLatencyStatsAliases(stats) {
4822
6128
  Percent75: "percent75",
4823
6129
  Percent95: "percent95",
4824
6130
  Percent99: "percent99",
6131
+ Percent100: "percent100",
4825
6132
  StdDev: "stdDev"
4826
6133
  });
4827
6134
  return projected;
@@ -4843,6 +6150,9 @@ function attachMeasurementStatsAliases(stats) {
4843
6150
  projected.latency = attachLatencyStatsAliases(projected.latency);
4844
6151
  projected.statusCodes = projected.statusCodes.map((value) => attachStatusCodeStatsAliases(value));
4845
6152
  attachAliasMap(projected, {
6153
+ Count64: "count64",
6154
+ DistributionMode: "distributionMode",
6155
+ MaxRelativeError: "maxRelativeError",
4846
6156
  Request: "request",
4847
6157
  DataTransfer: "dataTransfer",
4848
6158
  Latency: "latency",
@@ -4904,6 +6214,8 @@ function attachStepStatsAliases(step) {
4904
6214
  const projected = normalizeStepStatsValue(step);
4905
6215
  projected.ok = attachMeasurementStatsAliases(projected.ok);
4906
6216
  projected.fail = attachMeasurementStatsAliases(projected.fail);
6217
+ if (projected.allMeasurement)
6218
+ projected.allMeasurement = attachMeasurementStatsAliases(projected.allMeasurement);
4907
6219
  attachAliasMap(projected, {
4908
6220
  ScenarioName: "scenarioName",
4909
6221
  StepName: "stepName",
@@ -4918,6 +6230,7 @@ function attachStepStatsAliases(step) {
4918
6230
  StatusCodes: "statusCodes",
4919
6231
  Ok: "ok",
4920
6232
  Fail: "fail",
6233
+ AllMeasurement: "allMeasurement",
4921
6234
  SortIndex: "sortIndex"
4922
6235
  });
4923
6236
  return projected;
@@ -4956,7 +6269,20 @@ function attachLoadSimulationProjection(simulation) {
4956
6269
  });
4957
6270
  return simulation;
4958
6271
  }
4959
- function expandTrafficMixScenarios(trafficMix) {
6272
+ function cloneLoadEngineV2TrafficMixMetadata(metadata) {
6273
+ return {
6274
+ ...metadata,
6275
+ shareWeights: [...metadata.shareWeights],
6276
+ originalSimulations: metadata.originalSimulations.map((simulation) => attachLoadSimulationProjection({ ...simulation }))
6277
+ };
6278
+ }
6279
+ function nextTrafficMixDeclarationIndex(scenarios) {
6280
+ return scenarios.reduce((next, scenario) => {
6281
+ const metadata = scenario.__loadStrikeTrafficMixV2Metadata();
6282
+ return metadata ? Math.max(next, metadata.declarationIndex + 1) : next;
6283
+ }, 0);
6284
+ }
6285
+ function expandTrafficMixScenarios(trafficMix, declarationIndex = 0) {
4960
6286
  if (!(trafficMix instanceof LoadStrikeTrafficMix)) {
4961
6287
  throw new TypeError("Traffic mix must be provided.");
4962
6288
  }
@@ -4969,16 +6295,30 @@ function expandTrafficMixScenarios(trafficMix) {
4969
6295
  throw new Error("Traffic mix scenario shares must be configured before registration.");
4970
6296
  }
4971
6297
  const weights = scenarioMix.map((share) => share.weight);
6298
+ const seedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(declarationIndex, trafficMix.name);
4972
6299
  return scenarioMix.map((share, index) => {
4973
- const splitSimulations = totalLoad
4974
- .map((simulation) => splitTrafficSimulation(simulation, weights, index))
4975
- .filter((simulation) => simulation != null);
4976
- const scenario = !splitSimulations.length
4977
- ? share.scenario.withLoadSimulations(LoadStrikeSimulation.pause(0))
4978
- : share.scenario.withLoadSimulations(...splitSimulations);
4979
- return scenario.__loadStrikeWithInternalLicenseFeatures(TRAFFIC_MIX_FEATURE);
6300
+ const splitSimulations = totalLoad.map((simulation) => splitTrafficSimulation(simulation, weights, index)
6301
+ ?? trafficMixNoWorkSimulation(simulation));
6302
+ return share.scenario
6303
+ .withLoadSimulations(...splitSimulations)
6304
+ .__loadStrikeWithInternalLicenseFeatures(TRAFFIC_MIX_FEATURE)
6305
+ .__loadStrikeSetTrafficMixV2Metadata({
6306
+ declarationIndex,
6307
+ name: trafficMix.name,
6308
+ laneIndex: index,
6309
+ shareWeight: share.weight,
6310
+ shareWeights: weights,
6311
+ seedId,
6312
+ originalSimulations: totalLoad
6313
+ });
4980
6314
  });
4981
6315
  }
6316
+ function trafficMixNoWorkSimulation(simulation) {
6317
+ const kind = String(simulation.Kind ?? "");
6318
+ return kind === "IterationsForInject" || kind === "IterationsForConstant"
6319
+ ? LoadStrikeSimulation.pause(0)
6320
+ : LoadStrikeSimulation.pause(Math.max(readFiniteSimulationNumber(simulation, "DuringSeconds"), 0));
6321
+ }
4982
6322
  function splitTrafficSimulation(simulation, weights, index) {
4983
6323
  const kind = String(simulation.Kind ?? "");
4984
6324
  const duringSeconds = readFiniteSimulationNumber(simulation, "DuringSeconds");
@@ -5065,6 +6405,8 @@ function attachScenarioStatsAliases(scenario) {
5065
6405
  const normalized = normalizeScenarioStatsValue(scenario);
5066
6406
  normalized.ok = attachMeasurementStatsAliases(normalized.ok);
5067
6407
  normalized.fail = attachMeasurementStatsAliases(normalized.fail);
6408
+ if (normalized.allMeasurement)
6409
+ normalized.allMeasurement = attachMeasurementStatsAliases(normalized.allMeasurement);
5068
6410
  normalized.loadSimulationStats = attachLoadSimulationStatsAliases(normalized.loadSimulationStats);
5069
6411
  normalized.stepStats = normalized.stepStats.map((value) => attachStepStatsAliases(value));
5070
6412
  const findStepStats = scenario.findStepStats ?? ((stepName) => normalized.stepStats.find((value) => value.stepName === stepName));
@@ -5098,6 +6440,7 @@ function attachScenarioStatsAliases(scenario) {
5098
6440
  DurationMs: "durationMs",
5099
6441
  Ok: "ok",
5100
6442
  Fail: "fail",
6443
+ AllMeasurement: "allMeasurement",
5101
6444
  LoadSimulationStats: "loadSimulationStats",
5102
6445
  SortIndex: "sortIndex",
5103
6446
  StepStats: "stepStats"
@@ -5118,6 +6461,10 @@ function attachNodeStatsAliases(stats) {
5118
6461
  : stats.scenarioStats.flatMap((value) => value.stepStats)).map((value) => attachStepStatsAliases(value));
5119
6462
  stats.pluginsData = stats.pluginsData.map((value) => normalizePluginData(value.pluginName ?? value.PluginName ?? "", value));
5120
6463
  stats.sinkErrors = stats.sinkErrors.map((value) => attachSinkErrorAliases(value));
6464
+ stats.generatorWarnings = (stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases);
6465
+ stats.schedulerSegments = (stats.schedulerSegments ?? []).map((value) => normalizeSchedulerSegment(value));
6466
+ stats.observationDeliveryStats = normalizeObservationDeliveryStats(stats.observationDeliveryStats ?? emptyObservationDeliveryStats());
6467
+ stats.reportingComplete ?? (stats.reportingComplete = false);
5121
6468
  const findScenarioStats = stats.findScenarioStats ?? ((scenarioName) => stats.scenarioStats.find((value) => value.scenarioName === scenarioName));
5122
6469
  const getScenarioStats = stats.getScenarioStats ?? ((scenarioName) => {
5123
6470
  const value = findScenarioStats(scenarioName);
@@ -5152,7 +6499,11 @@ function attachNodeStatsAliases(stats) {
5152
6499
  DisabledSinks: "disabledSinks",
5153
6500
  SinkErrors: "sinkErrors",
5154
6501
  ReportFiles: "reportFiles",
5155
- LogFiles: "logFiles"
6502
+ LogFiles: "logFiles",
6503
+ GeneratorWarnings: "generatorWarnings",
6504
+ SchedulerSegments: "schedulerSegments",
6505
+ ObservationDeliveryStats: "observationDeliveryStats",
6506
+ ReportingComplete: "reportingComplete"
5156
6507
  });
5157
6508
  defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(stats.startedUtc));
5158
6509
  defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(stats.completedUtc));
@@ -5208,11 +6559,43 @@ function attachRunResultAliases(result) {
5208
6559
  .map((value) => ({ ...asAliasRecord(value) })),
5209
6560
  failedCorrelationRows: pickAliasArray(source, "failedCorrelationRows", "FailedCorrelationRows")
5210
6561
  .map((value) => ({ ...asAliasRecord(value) })),
6562
+ ...(hasAliasValue(source, "generatorWarnings", "GeneratorWarnings")
6563
+ ? {
6564
+ generatorWarnings: pickAliasArray(source, "generatorWarnings", "GeneratorWarnings")
6565
+ .map(attachGeneratorWarningAliases)
6566
+ }
6567
+ : {}),
6568
+ ...(hasAliasValue(source, "schedulerSegments", "SchedulerSegments")
6569
+ ? {
6570
+ schedulerSegments: pickAliasArray(source, "schedulerSegments", "SchedulerSegments")
6571
+ .map(normalizeSchedulerSegment)
6572
+ }
6573
+ : {}),
6574
+ ...(hasAliasValue(source, "schedulerStats", "SchedulerStats")
6575
+ ? { schedulerStats: normalizeSchedulerStats(pickAliasValue(source, "schedulerStats", "SchedulerStats")) }
6576
+ : {}),
6577
+ ...(hasAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats")
6578
+ ? {
6579
+ observationDeliveryStats: normalizeObservationDeliveryStats(pickAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats"))
6580
+ }
6581
+ : {}),
6582
+ ...(hasAliasValue(source, "reportingComplete", "ReportingComplete")
6583
+ ? { reportingComplete: pickAliasBoolean(source, "reportingComplete", "ReportingComplete") }
6584
+ : {}),
5211
6585
  findScenarioStats,
5212
6586
  getScenarioStats,
5213
6587
  FindScenarioStats: findScenarioStats,
5214
6588
  GetScenarioStats: getScenarioStats
5215
6589
  };
6590
+ const schedulerDistributions = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS];
6591
+ if (schedulerDistributions) {
6592
+ Object.defineProperty(projected, LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS, {
6593
+ value: schedulerDistributions.map(cloneLoadEngineV2DistributionRecord),
6594
+ enumerable: false,
6595
+ configurable: false,
6596
+ writable: false
6597
+ });
6598
+ }
5216
6599
  attachAliasMap(projected, {
5217
6600
  AllBytes: "allBytes",
5218
6601
  AllRequestCount: "allRequestCount",
@@ -5236,7 +6619,12 @@ function attachRunResultAliases(result) {
5236
6619
  ReportFiles: "reportFiles",
5237
6620
  LogFiles: "logFiles",
5238
6621
  CorrelationRows: "correlationRows",
5239
- FailedCorrelationRows: "failedCorrelationRows"
6622
+ FailedCorrelationRows: "failedCorrelationRows",
6623
+ GeneratorWarnings: "generatorWarnings",
6624
+ SchedulerSegments: "schedulerSegments",
6625
+ SchedulerStats: "schedulerStats",
6626
+ ObservationDeliveryStats: "observationDeliveryStats",
6627
+ ReportingComplete: "reportingComplete"
5240
6628
  });
5241
6629
  defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(projected.startedUtc));
5242
6630
  defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(projected.completedUtc));
@@ -5844,6 +7232,14 @@ function detailedToNodeStats(result, metricStats) {
5844
7232
  sinkErrors: (result.sinkErrors ?? []).map((sinkError) => ({ ...sinkError })),
5845
7233
  reportFiles: [...(result.reportFiles ?? [])],
5846
7234
  logFiles: [...(result.logFiles ?? [])],
7235
+ generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
7236
+ schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
7237
+ schedulerStats: result.schedulerStats
7238
+ ? normalizeSchedulerStats(result.schedulerStats)
7239
+ : undefined,
7240
+ [LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
7241
+ observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
7242
+ reportingComplete: result.reportingComplete ?? true,
5847
7243
  findScenarioStats: (scenarioName) => scenarioStats.find((scenario) => scenario.scenarioName === scenarioName),
5848
7244
  getScenarioStats: (scenarioName) => {
5849
7245
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -5911,22 +7307,35 @@ function buildEmptyNodeStats(args) {
5911
7307
  sinkErrors: [],
5912
7308
  reportFiles: [],
5913
7309
  logFiles: [],
7310
+ generatorWarnings: [],
7311
+ observationDeliveryStats: emptyObservationDeliveryStats(),
7312
+ reportingComplete: true,
5914
7313
  findScenarioStats: (scenarioName) => undefined,
5915
7314
  getScenarioStats: (scenarioName) => {
5916
7315
  throw new Error(`Scenario stats not found: ${scenarioName}`);
5917
7316
  }
5918
7317
  });
5919
7318
  }
5920
- function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios) {
7319
+ function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios, globalV2 = false, coordinatorTargetScenarios = []) {
5921
7320
  const resolvedAgentCount = Math.max(agentsCount, 1);
5922
- const selectedScenarios = targetScenarios.length
7321
+ let selectedScenarios = targetScenarios.length
5923
7322
  ? scenarios.filter((scenario) => targetScenarios.includes(scenario.name))
5924
7323
  : [...scenarios];
7324
+ if (globalV2 && coordinatorTargetScenarios.length) {
7325
+ const coordinatorNames = new Set(coordinatorTargetScenarios);
7326
+ selectedScenarios = selectedScenarios.filter((scenario) => !coordinatorNames.has(scenario.name));
7327
+ }
5925
7328
  if (!selectedScenarios.length) {
5926
7329
  return Array.from({ length: resolvedAgentCount }, () => []);
5927
7330
  }
5928
7331
  if (agentTargetScenarios.length) {
5929
- return Array.from({ length: resolvedAgentCount }, () => [...agentTargetScenarios]);
7332
+ const coordinatorNames = new Set(coordinatorTargetScenarios);
7333
+ const names = agentTargetScenarios.filter((name) => !coordinatorNames.has(name));
7334
+ return Array.from({ length: resolvedAgentCount }, () => [...names]);
7335
+ }
7336
+ if (globalV2) {
7337
+ const scenarioNames = selectedScenarios.map((scenario) => scenario.name);
7338
+ return Array.from({ length: resolvedAgentCount }, () => [...scenarioNames]);
5930
7339
  }
5931
7340
  const weightedNames = [];
5932
7341
  for (const scenario of selectedScenarios) {
@@ -5946,7 +7355,320 @@ function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios,
5946
7355
  }
5947
7356
  return assignments.map((entry) => [...entry]);
5948
7357
  }
5949
- function nodeStatsToClusterPayload(result) {
7358
+ function buildRuntimeLoadEngineV2Plan(scenarios, options, testInfo, expectedAgentIds) {
7359
+ validateLoadEngineV2ScenarioFeatures(scenarios);
7360
+ const kindToken = (value) => {
7361
+ const normalized = String(value ?? "").replace(/[^a-z0-9]/gi, "").toLowerCase();
7362
+ const tokens = {
7363
+ inject: "inject", injectrandom: "inject-random", rampinginject: "ramping-inject",
7364
+ keepconstant: "keep-constant", rampingconstant: "ramping-constant",
7365
+ iterationsforinject: "iterations-for-inject", iterationsforconstant: "iterations-for-constant",
7366
+ pause: "pause"
7367
+ };
7368
+ const token = tokens[normalized];
7369
+ if (!token)
7370
+ throw new Error(`Load Engine V2 simulation kind is unsupported: ${String(value ?? "")}`);
7371
+ return token;
7372
+ };
7373
+ const integer = (value) => Math.max(Math.trunc(toNumber(value)), 0).toString();
7374
+ const ns = (value) => Math.max(Math.round(toNumber(value) * 1000000000), 0).toString();
7375
+ const coordinatorNames = new Set(options.coordinatorTargetScenarios ?? []);
7376
+ const explicitAgentNames = new Set(options.agentTargetScenarios ?? []);
7377
+ const selectedNames = new Set(options.targetScenarios ?? []);
7378
+ const plannedScenarios = scenarios
7379
+ .map((scenario, declarationIndex) => ({ scenario, declarationIndex }))
7380
+ .filter(({ scenario }) => (selectedNames.size === 0 || selectedNames.has(scenario.name))
7381
+ && (explicitAgentNames.size > 0
7382
+ ? explicitAgentNames.has(scenario.name)
7383
+ : !coordinatorNames.has(scenario.name)));
7384
+ const implicitSingleInvocation = {
7385
+ simulationIndex64: "0",
7386
+ kind: "iterations-for-constant",
7387
+ rate64: "0",
7388
+ minRate64: "0",
7389
+ maxRate64: "0",
7390
+ copies64: "1",
7391
+ iterations64: "1",
7392
+ intervalNs64: "0",
7393
+ durationNs64: "0"
7394
+ };
7395
+ return {
7396
+ runId: testInfo.sessionId,
7397
+ sessionId: testInfo.sessionId,
7398
+ registrationNonce: (0, node_crypto_1.randomBytes)(16).toString("hex"),
7399
+ maxInFlight64: Math.max(options.maxInFlight ?? 10000, 1).toString(),
7400
+ reportingIntervalNs64: ns(options.reportingIntervalSeconds ?? 1),
7401
+ schedulerVisibleProcessorCount64: Math.max(node_os_1.default.cpus().length, 1).toString(),
7402
+ expectedAgentIds: [...expectedAgentIds],
7403
+ scenarios: plannedScenarios.map(({ scenario, declarationIndex }) => ({
7404
+ scenarioIndex64: declarationIndex.toString(),
7405
+ scenarioName: scenario.name,
7406
+ target: "agent",
7407
+ callbackExecutionMode: "async",
7408
+ declaredStepNames: scenario.getDeclaredSteps(),
7409
+ simulations: scenario.getSimulations().length
7410
+ ? scenario.getSimulations().map((simulation, simulationIndex) => ({
7411
+ simulationIndex64: simulationIndex.toString(),
7412
+ kind: kindToken(simulation.Kind ?? simulation.kind),
7413
+ rate64: integer(simulation.Rate ?? simulation.rate),
7414
+ minRate64: integer(simulation.MinRate ?? simulation.minRate),
7415
+ maxRate64: integer(simulation.MaxRate ?? simulation.maxRate),
7416
+ copies64: integer(simulation.Copies ?? simulation.copies),
7417
+ iterations64: integer(simulation.Iterations ?? simulation.iterations),
7418
+ intervalNs64: ns(simulation.IntervalSeconds ?? simulation.intervalSeconds),
7419
+ durationNs64: ns(simulation.DuringSeconds ?? simulation.duringSeconds)
7420
+ }))
7421
+ : [{ ...implicitSingleInvocation }]
7422
+ }))
7423
+ };
7424
+ }
7425
+ function normalizeRequiredV2AgentIds(values, expectedCount) {
7426
+ const ids = normalizeOptionalStringArray(values) ?? [];
7427
+ if (ids.length !== expectedCount || new Set(ids).size !== ids.length) {
7428
+ throw new Error("Remote Load Engine V2 coordinators require an exact unique ExpectedAgentIds set matching AgentsCount.");
7429
+ }
7430
+ return ids;
7431
+ }
7432
+ function validateLoadEngineV2ScenarioFeatures(scenarios) {
7433
+ for (const scenario of scenarios) {
7434
+ if (scenario.getTrackingConfiguration()) {
7435
+ throw new Error(`Load Engine V2 correlation is not available in the supported non-correlation profile. Scenario=${scenario.name}.`);
7436
+ }
7437
+ }
7438
+ }
7439
+ function canonicalLoadEngineV2InvocationIdentityKind(simulationKind) {
7440
+ const normalized = simulationKind.replace(/[^a-z0-9]/gi, "").toLowerCase();
7441
+ if (["inject", "injectrandom", "rampinginject", "iterationsforinject"].includes(normalized)) {
7442
+ return "arrival";
7443
+ }
7444
+ if (["keepconstant", "rampingconstant"].includes(normalized)) {
7445
+ return "worker-iteration";
7446
+ }
7447
+ if (normalized === "iterationsforconstant") {
7448
+ return "constant-iteration";
7449
+ }
7450
+ return "";
7451
+ }
7452
+ function buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations) {
7453
+ const emptyHistogram = () => new load_engine_v2_js_1.LoadStrikeHistogramV1().toSidecar();
7454
+ const distributions = [];
7455
+ const measurementSummaries = [];
7456
+ const statusBody = (measurement, outcome) => {
7457
+ const named = measurement.statusCodes
7458
+ .filter((row) => Boolean(row.statusCode || row.message))
7459
+ .map((row) => {
7460
+ const key = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(row.statusCode, row.message);
7461
+ if (!key)
7462
+ throw new Error("Load Engine V2 status identity unexpectedly resolved empty.");
7463
+ return {
7464
+ statusIdentityKeyHex: key.identity.toString("hex"),
7465
+ display: key.display,
7466
+ count64: Math.max(Math.trunc(row.count), 0).toString(),
7467
+ aggregatedObservationCount64: "0",
7468
+ hasAggregatedIdentities: false
7469
+ };
7470
+ })
7471
+ .sort((left, right) => Buffer.compare(Buffer.from(left.statusIdentityKeyHex, "hex"), Buffer.from(right.statusIdentityKeyHex, "hex")));
7472
+ let statuses = named;
7473
+ if (named.length > 64) {
7474
+ const retained = named.slice(0, 63);
7475
+ const aggregated = named.slice(63).reduce((sum, row) => sum + BigInt(row.count64), 0n);
7476
+ retained.push({
7477
+ statusIdentityKeyHex: Buffer.concat([Buffer.from("LS-ID1\n", "ascii"), Buffer.from([0x0d])]).toString("hex"),
7478
+ display: "<other>", count64: aggregated.toString(),
7479
+ aggregatedObservationCount64: aggregated.toString(), hasAggregatedIdentities: aggregated > 0n
7480
+ });
7481
+ statuses = retained;
7482
+ }
7483
+ return {
7484
+ outcome,
7485
+ statusObservationCount64: statuses.reduce((sum, row) => sum + BigInt(row.count64), 0n).toString(),
7486
+ statuses
7487
+ };
7488
+ };
7489
+ const appendMeasurement = (seriesKind, scenarioIndex64, scenarioName, identity, display, ok, fail, all, reservedOther) => {
7490
+ const emptyMeasurement = () => ({
7491
+ count64: "0",
7492
+ histogramSidecar: {
7493
+ latency: emptyHistogram(), size: emptyHistogram(), allBytes64: "0",
7494
+ lessOrEq80064: "0", more800Less120064: "0", moreOrEq120064: "0"
7495
+ },
7496
+ request: { count: 0, percent: 0, rps: 0 },
7497
+ dataTransfer: { allBytes: 0, minBytes: 0, maxBytes: 0, meanBytes: 0, percent50: 0,
7498
+ percent75: 0, percent95: 0, percent99: 0, percent100: 0, stdDev: 0 },
7499
+ latency: { latencyCount: { lessOrEq800: 0, more800Less1200: 0, moreOrEq1200: 0 },
7500
+ minMs: 0, maxMs: 0, meanMs: 0, percent50: 0, percent75: 0, percent95: 0,
7501
+ percent99: 0, stdDev: 0 },
7502
+ statusCodes: []
7503
+ });
7504
+ const okValue = ok ?? emptyMeasurement();
7505
+ const failValue = fail ?? emptyMeasurement();
7506
+ const allValue = all ?? emptyMeasurement();
7507
+ for (const [outcome, measurement] of [
7508
+ ["ok", okValue], ["fail", failValue], ["all", allValue]
7509
+ ]) {
7510
+ const sidecar = measurement.histogramSidecar;
7511
+ if (!sidecar)
7512
+ throw new Error("Load Engine V2 assigned measurement is missing histogram state.");
7513
+ distributions.push({
7514
+ seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
7515
+ outcome, unit: "microseconds", histogram: sidecar.latency,
7516
+ exactTotalDecimalOrEmpty: sidecar.latency.exactTotal64,
7517
+ bands: [
7518
+ { bandId64: "0", count64: sidecar.lessOrEq80064 },
7519
+ { bandId64: "1", count64: sidecar.more800Less120064 },
7520
+ { bandId64: "2", count64: sidecar.moreOrEq120064 }
7521
+ ]
7522
+ });
7523
+ distributions.push({
7524
+ seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
7525
+ outcome, unit: "bytes", histogram: sidecar.size,
7526
+ exactTotalDecimalOrEmpty: sidecar.size.exactTotal64
7527
+ });
7528
+ }
7529
+ const observation = BigInt(allValue.count64 ?? allValue.histogramSidecar?.latency.count64 ?? "0");
7530
+ const success = BigInt(okValue.count64 ?? okValue.histogramSidecar?.latency.count64 ?? "0");
7531
+ const failure = BigInt(failValue.count64 ?? failValue.histogramSidecar?.latency.count64 ?? "0");
7532
+ measurementSummaries.push({
7533
+ seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"), display,
7534
+ observationCount64: observation.toString(), successCount64: success.toString(),
7535
+ failureCount64: failure.toString(),
7536
+ aggregatedObservationCount64: reservedOther ? observation.toString() : "0",
7537
+ hasAggregatedIdentities: reservedOther && observation > 0n,
7538
+ outcomes: [statusBody(okValue, "ok"), statusBody(failValue, "fail")]
7539
+ });
7540
+ };
7541
+ for (const scenario of result.scenarioStats) {
7542
+ const scenarioIndex64 = Math.max(Math.trunc(scenario.sortIndex), 0).toString();
7543
+ appendMeasurement("scenario", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(scenarioIndex64), scenario.scenarioName, scenario.ok, scenario.fail, scenario.allMeasurement, false);
7544
+ const declaration = scenarioDeclarations?.find((value) => value.name === scenario.scenarioName);
7545
+ const declaredNames = declaration?.getDeclaredSteps()
7546
+ ?? (scenarioDeclarations ? [] : undefined);
7547
+ if (declaredNames === undefined) {
7548
+ for (const step of scenario.stepStats) {
7549
+ const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName);
7550
+ appendMeasurement("step", scenarioIndex64, scenario.scenarioName, key.identity, key.display, step.ok, step.fail, step.allMeasurement, false);
7551
+ }
7552
+ }
7553
+ else {
7554
+ const requiredStepAllMeasurement = (step) => {
7555
+ if (!step.allMeasurement) {
7556
+ throw new Error("Load Engine V2 assigned step is missing its all-outcome histogram state.");
7557
+ }
7558
+ return step.allMeasurement;
7559
+ };
7560
+ const groups = new Map();
7561
+ for (const declaredName of declaredNames) {
7562
+ const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, declaredName);
7563
+ groups.set(key.identity.toString("hex"), {
7564
+ identity: key.identity, display: key.display, routedToOther: false, steps: []
7565
+ });
7566
+ }
7567
+ for (const step of scenario.stepStats) {
7568
+ const resolved = (0, cluster_js_1.resolveLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName, declaredNames);
7569
+ const key = resolved.identity.toString("hex");
7570
+ const group = groups.get(key) ?? {
7571
+ identity: resolved.identity,
7572
+ display: resolved.display,
7573
+ routedToOther: resolved.routedToOther,
7574
+ steps: []
7575
+ };
7576
+ group.steps.push(step);
7577
+ groups.set(key, group);
7578
+ }
7579
+ const reservedHex = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64).toString("hex");
7580
+ const declaredGroups = [...groups.entries()]
7581
+ .filter(([key]) => key !== reservedHex)
7582
+ .map(([, value]) => value)
7583
+ .sort((left, right) => Buffer.compare(left.identity, right.identity));
7584
+ for (const group of declaredGroups) {
7585
+ const ok = group.steps.length
7586
+ ? aggregateMeasurementStats(group.steps.map((step) => step.ok), scenario.allRequestCount, scenario.durationMs, true)
7587
+ : undefined;
7588
+ const fail = group.steps.length
7589
+ ? aggregateMeasurementStats(group.steps.map((step) => step.fail), scenario.allRequestCount, scenario.durationMs, true)
7590
+ : undefined;
7591
+ const all = group.steps.length
7592
+ ? aggregateMeasurementStats(group.steps.map(requiredStepAllMeasurement), scenario.allRequestCount, scenario.durationMs, true)
7593
+ : undefined;
7594
+ appendMeasurement("step", scenarioIndex64, scenario.scenarioName, group.identity, group.display, ok, fail, all, false);
7595
+ }
7596
+ const other = groups.get(reservedHex);
7597
+ if (other?.steps.length) {
7598
+ appendMeasurement("step", scenarioIndex64, scenario.scenarioName, other.identity, "<other>", aggregateMeasurementStats(other.steps.map((step) => step.ok), scenario.allRequestCount, scenario.durationMs, true), aggregateMeasurementStats(other.steps.map((step) => step.fail), scenario.allRequestCount, scenario.durationMs, true), aggregateMeasurementStats(other.steps.map(requiredStepAllMeasurement), scenario.allRequestCount, scenario.durationMs, true), true);
7599
+ continue;
7600
+ }
7601
+ }
7602
+ appendMeasurement("step", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64), "<other>", undefined, undefined, undefined, true);
7603
+ }
7604
+ for (const segment of result.schedulerSegments ?? []) {
7605
+ const scenarioIndex64 = segment.scenarioIndex.toString();
7606
+ const simulationIndex64 = segment.simulationIndex.toString();
7607
+ for (const kind of ["decision", "start"]) {
7608
+ const seriesKind = kind === "decision" ? "scheduler-decision-lag" : "scheduler-start-lag";
7609
+ const identityKeyHex = (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(kind, scenarioIndex64, simulationIndex64).toString("hex");
7610
+ const signed = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.find((record) => record.seriesKind === seriesKind
7611
+ && record.scenarioIndex64 === scenarioIndex64
7612
+ && record.identityKeyHex === identityKeyHex
7613
+ && record.outcome === "none"
7614
+ && record.unit === "microseconds");
7615
+ if (!distributions.some((record) => record.seriesKind === seriesKind
7616
+ && record.scenarioIndex64 === scenarioIndex64
7617
+ && record.identityKeyHex === identityKeyHex)) {
7618
+ distributions.push(signed ? cloneLoadEngineV2DistributionRecord(signed) : {
7619
+ seriesKind,
7620
+ scenarioIndex64, scenarioName: segment.scenarioName,
7621
+ identityKeyHex,
7622
+ outcome: "none", unit: "microseconds", histogram: emptyHistogram(),
7623
+ exactTotalDecimalOrEmpty: "0"
7624
+ });
7625
+ }
7626
+ }
7627
+ }
7628
+ return (0, load_engine_v2_js_1.serializeLoadEngineV2HistogramArtifact)({ distributions, measurementSummaries });
7629
+ }
7630
+ function cloneLoadEngineV2DistributionRecord(record) {
7631
+ return {
7632
+ ...record,
7633
+ histogram: {
7634
+ ...record.histogram,
7635
+ exactSamples64: [...record.histogram.exactSamples64],
7636
+ buckets: record.histogram.buckets.map((bucket) => ({ ...bucket }))
7637
+ },
7638
+ bands: record.bands?.map((band) => ({ ...band }))
7639
+ };
7640
+ }
7641
+ function mergeLoadEngineV2SchedulerDistributions(records) {
7642
+ const merged = new Map();
7643
+ for (const input of records) {
7644
+ if (input.seriesKind !== "scheduler-decision-lag" && input.seriesKind !== "scheduler-start-lag") {
7645
+ continue;
7646
+ }
7647
+ const key = [input.seriesKind, input.scenarioIndex64, input.identityKeyHex, input.outcome, input.unit].join("\0");
7648
+ const current = merged.get(key);
7649
+ if (!current) {
7650
+ merged.set(key, cloneLoadEngineV2DistributionRecord(input));
7651
+ continue;
7652
+ }
7653
+ if (current.scenarioName !== input.scenarioName) {
7654
+ throw new Error("Load Engine V2 scheduler histogram identities disagree across agents.");
7655
+ }
7656
+ const histogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(current.histogram);
7657
+ histogram.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(input.histogram));
7658
+ current.histogram = histogram.toSidecar();
7659
+ current.exactTotalDecimalOrEmpty = histogram.toSidecar().exactTotal64;
7660
+ const bands = new Map();
7661
+ for (const band of [...(current.bands ?? []), ...(input.bands ?? [])]) {
7662
+ bands.set(band.bandId64, (bands.get(band.bandId64) ?? 0n) + BigInt(band.count64));
7663
+ }
7664
+ current.bands = Array.from(bands, ([bandId64, count]) => ({ bandId64, count64: count.toString() }));
7665
+ }
7666
+ return Array.from(merged.values());
7667
+ }
7668
+ function nodeStatsToClusterPayload(result, scenarioDeclarations, requireLoadEngineV2Histogram = false) {
7669
+ const histogramArtifactBase64 = requireLoadEngineV2Histogram
7670
+ ? buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations).toString("base64")
7671
+ : undefined;
5950
7672
  return {
5951
7673
  allBytes: result.allBytes,
5952
7674
  allRequestCount: result.allRequestCount,
@@ -5959,7 +7681,13 @@ function nodeStatsToClusterPayload(result) {
5959
7681
  pluginsData: result.pluginsData,
5960
7682
  nodeInfo: result.nodeInfo,
5961
7683
  testInfo: result.testInfo,
5962
- logFiles: [...(result.logFiles ?? [])]
7684
+ logFiles: [...(result.logFiles ?? [])],
7685
+ generatorWarnings: result.generatorWarnings,
7686
+ observationDeliveryStats: result.observationDeliveryStats,
7687
+ schedulerSegments: result.schedulerSegments,
7688
+ schedulerStats: result.schedulerStats,
7689
+ ...(histogramArtifactBase64 ? { histogramArtifactBase64 } : {}),
7690
+ reportingComplete: result.reportingComplete
5963
7691
  };
5964
7692
  }
5965
7693
  function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policyErrors = []) {
@@ -5987,6 +7715,14 @@ function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policy
5987
7715
  policyErrors: policyErrors.map((value) => attachRuntimePolicyErrorAliases({ ...value })),
5988
7716
  reportFiles: [...result.reportFiles],
5989
7717
  logFiles: [...(result.logFiles ?? [])],
7718
+ generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
7719
+ schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
7720
+ schedulerStats: result.schedulerStats
7721
+ ? normalizeSchedulerStats(result.schedulerStats)
7722
+ : undefined,
7723
+ [LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
7724
+ observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
7725
+ reportingComplete: result.reportingComplete ?? false,
5990
7726
  correlationRows: buildDetailedCorrelationRows(),
5991
7727
  failedCorrelationRows: buildDetailedFailedCorrelationRows()
5992
7728
  });
@@ -6009,7 +7745,198 @@ function flattenMetricValues(metricStats) {
6009
7745
  }))
6010
7746
  ];
6011
7747
  }
6012
- function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
7748
+ function projectLoadEngineV2MeasurementsFromArtifact(sourceScenarios, artifact) {
7749
+ const distributions = new Map();
7750
+ for (const distribution of artifact.distributions) {
7751
+ const key = loadEngineV2DistributionProjectionKey(distribution.seriesKind, distribution.scenarioIndex64, distribution.identityKeyHex, distribution.outcome, distribution.unit);
7752
+ if (distributions.has(key)) {
7753
+ throw new Error("Load Engine V2 histogram artifact contains a duplicate distribution identity.");
7754
+ }
7755
+ distributions.set(key, distribution);
7756
+ }
7757
+ const scenarioSummaries = artifact.measurementSummaries
7758
+ .filter((summary) => summary.seriesKind === "scenario")
7759
+ .sort((left, right) => compareLoadEngineV2Decimal(left.scenarioIndex64, right.scenarioIndex64));
7760
+ if (scenarioSummaries.length !== sourceScenarios.length) {
7761
+ throw new Error("Load Engine V2 histogram artifact scenario summaries do not reconcile with the scheduler snapshot.");
7762
+ }
7763
+ const sourceByName = new Map();
7764
+ for (const scenario of sourceScenarios) {
7765
+ if (!scenario.scenarioName || sourceByName.has(scenario.scenarioName)) {
7766
+ throw new Error("Load Engine V2 scheduler snapshot scenario identities are empty or duplicated.");
7767
+ }
7768
+ sourceByName.set(scenario.scenarioName, scenario);
7769
+ }
7770
+ return scenarioSummaries.map((summary) => {
7771
+ const source = sourceByName.get(summary.scenarioName);
7772
+ if (!source) {
7773
+ throw new Error("Load Engine V2 histogram artifact scenario identity is absent from the scheduler snapshot.");
7774
+ }
7775
+ const expectedIdentity = (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(summary.scenarioIndex64).toString("hex");
7776
+ if (summary.identityKeyHex !== expectedIdentity) {
7777
+ throw new Error("Load Engine V2 histogram artifact scenario identity is not canonical.");
7778
+ }
7779
+ const observationCount = loadEngineV2SafeNumber(summary.observationCount64, "scenario observation count");
7780
+ const successCount = loadEngineV2SafeNumber(summary.successCount64, "scenario success count");
7781
+ const failureCount = loadEngineV2SafeNumber(summary.failureCount64, "scenario failure count");
7782
+ if (source.allRequestCount !== observationCount
7783
+ || source.allOkCount !== successCount
7784
+ || source.allFailCount !== failureCount) {
7785
+ throw new Error("Load Engine V2 histogram artifact scenario counts do not reconcile with the scheduler snapshot.");
7786
+ }
7787
+ const ok = projectLoadEngineV2Measurement(summary, "ok", distributions, observationCount, source.durationMs, [source.ok]);
7788
+ const fail = projectLoadEngineV2Measurement(summary, "fail", distributions, observationCount, source.durationMs, [source.fail]);
7789
+ const allMeasurement = projectLoadEngineV2Measurement(summary, "all", distributions, observationCount, source.durationMs, [source.ok, source.fail]);
7790
+ const stepSummaries = artifact.measurementSummaries
7791
+ .filter((candidate) => candidate.seriesKind === "step"
7792
+ && candidate.scenarioIndex64 === summary.scenarioIndex64
7793
+ && candidate.scenarioName === summary.scenarioName)
7794
+ .sort((left, right) => Buffer.compare(Buffer.from(left.identityKeyHex, "hex"), Buffer.from(right.identityKeyHex, "hex")));
7795
+ const stepIdentitySet = new Set(stepSummaries.map((candidate) => candidate.identityKeyHex));
7796
+ const reservedOtherIdentity = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(summary.scenarioIndex64).toString("hex");
7797
+ const stepStats = stepSummaries.map((stepSummary, sortIndex) => {
7798
+ const matchingSourceSteps = source.stepStats.filter((step) => {
7799
+ const observedIdentity = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(summary.scenarioIndex64, step.stepName).identity.toString("hex");
7800
+ return stepSummary.identityKeyHex === reservedOtherIdentity
7801
+ ? !stepIdentitySet.has(observedIdentity)
7802
+ : observedIdentity === stepSummary.identityKeyHex;
7803
+ });
7804
+ const stepObservationCount = loadEngineV2SafeNumber(stepSummary.observationCount64, "step observation count");
7805
+ const stepOk = projectLoadEngineV2Measurement(stepSummary, "ok", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.ok));
7806
+ const stepFail = projectLoadEngineV2Measurement(stepSummary, "fail", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.fail));
7807
+ const stepAll = projectLoadEngineV2Measurement(stepSummary, "all", distributions, observationCount, source.durationMs, matchingSourceSteps.flatMap((step) => [step.ok, step.fail]));
7808
+ const totalLatencyMs = stepAll.latency.meanMs * stepObservationCount;
7809
+ return {
7810
+ scenarioName: summary.scenarioName,
7811
+ stepName: stepSummary.display,
7812
+ okCount: stepOk.request.count,
7813
+ failCount: stepFail.request.count,
7814
+ requestCount: stepObservationCount,
7815
+ totalBytes: stepAll.dataTransfer.allBytes,
7816
+ totalLatencyMs,
7817
+ avgLatencyMs: stepObservationCount > 0 ? totalLatencyMs / stepObservationCount : 0,
7818
+ minLatencyMs: stepAll.latency.minMs,
7819
+ maxLatencyMs: stepAll.latency.maxMs,
7820
+ statusCodes: aggregateStatusCodeCounts(stepOk.statusCodes, stepFail.statusCodes),
7821
+ ok: stepOk,
7822
+ fail: stepFail,
7823
+ allMeasurement: stepAll,
7824
+ sortIndex
7825
+ };
7826
+ });
7827
+ const totalLatencyMs = allMeasurement.latency.meanMs * observationCount;
7828
+ const projected = {
7829
+ scenarioName: summary.scenarioName,
7830
+ allRequestCount: observationCount,
7831
+ allOkCount: successCount,
7832
+ allFailCount: failureCount,
7833
+ totalBytes: allMeasurement.dataTransfer.allBytes,
7834
+ totalLatencyMs,
7835
+ avgLatencyMs: observationCount > 0 ? totalLatencyMs / observationCount : 0,
7836
+ minLatencyMs: allMeasurement.latency.minMs,
7837
+ maxLatencyMs: allMeasurement.latency.maxMs,
7838
+ statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
7839
+ allMeasurement,
7840
+ allBytes: allMeasurement.dataTransfer.allBytes,
7841
+ currentOperation: source.currentOperation,
7842
+ durationMs: source.durationMs,
7843
+ ok,
7844
+ fail,
7845
+ loadSimulationStats: { ...source.loadSimulationStats },
7846
+ sortIndex: loadEngineV2SafeNumber(summary.scenarioIndex64, "scenario index"),
7847
+ stepStats,
7848
+ findStepStats: (stepName) => stepStats.find((step) => step.stepName === stepName),
7849
+ getStepStats: (stepName) => {
7850
+ const step = stepStats.find((candidate) => candidate.stepName === stepName);
7851
+ if (!step)
7852
+ throw new Error(`Step stats not found: ${stepName}`);
7853
+ return step;
7854
+ }
7855
+ };
7856
+ return attachScenarioStatsAliases(projected);
7857
+ });
7858
+ }
7859
+ function projectLoadEngineV2Measurement(summary, outcome, distributions, allRequestCount, durationMs, sourceMeasurements) {
7860
+ const expectedCount64 = outcome === "ok"
7861
+ ? summary.successCount64
7862
+ : outcome === "fail"
7863
+ ? summary.failureCount64
7864
+ : summary.observationCount64;
7865
+ const latency = requireLoadEngineV2MeasurementDistribution(summary, outcome, "microseconds", distributions);
7866
+ const size = requireLoadEngineV2MeasurementDistribution(summary, outcome, "bytes", distributions);
7867
+ if (latency.histogram.count64 !== expectedCount64 || size.histogram.count64 !== expectedCount64) {
7868
+ throw new Error("Load Engine V2 histogram distribution counts do not reconcile with its measurement summary.");
7869
+ }
7870
+ const bands = new Map();
7871
+ for (const band of latency.bands ?? []) {
7872
+ if (bands.has(band.bandId64)) {
7873
+ throw new Error("Load Engine V2 latency distribution contains a duplicate band.");
7874
+ }
7875
+ bands.set(band.bandId64, BigInt(band.count64));
7876
+ }
7877
+ if (bands.size !== 3 || !bands.has("0") || !bands.has("1") || !bands.has("2")
7878
+ || [...bands.values()].reduce((sum, value) => sum + value, 0n) !== BigInt(expectedCount64)) {
7879
+ throw new Error("Load Engine V2 latency distribution bands do not reconcile with its count.");
7880
+ }
7881
+ const sourceStatuses = new Map();
7882
+ for (const measurement of sourceMeasurements) {
7883
+ for (const status of measurement.statusCodes) {
7884
+ const identity = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(status.statusCode, status.message);
7885
+ if (identity)
7886
+ sourceStatuses.set(identity.identity.toString("hex"), status);
7887
+ }
7888
+ }
7889
+ const statusCodes = new Map();
7890
+ const outcomeSummaries = outcome === "all"
7891
+ ? summary.outcomes
7892
+ : summary.outcomes.filter((candidate) => candidate.outcome === outcome);
7893
+ for (const outcomeSummary of outcomeSummaries) {
7894
+ for (const status of outcomeSummary.statuses) {
7895
+ const source = sourceStatuses.get(status.statusIdentityKeyHex);
7896
+ statusCodes.set(`${outcomeSummary.outcome}\0${status.statusIdentityKeyHex}`, {
7897
+ statusCode: source?.statusCode ?? status.display,
7898
+ message: source?.message ?? "",
7899
+ isError: source?.isError ?? outcomeSummary.outcome === "fail",
7900
+ count: loadEngineV2SafeNumber(status.count64, "status count")
7901
+ });
7902
+ }
7903
+ }
7904
+ const expectedCount = loadEngineV2SafeNumber(expectedCount64, "measurement count");
7905
+ const sizeHistogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(size.histogram);
7906
+ return buildHistogramMeasurement({
7907
+ count: expectedCount,
7908
+ allBytes: loadEngineV2SafeNumber(sizeHistogram.exactTotal.toString(), "measurement byte total"),
7909
+ latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(latency.histogram),
7910
+ size: sizeHistogram,
7911
+ statusCodes,
7912
+ lessOrEq800: loadEngineV2SafeNumber((bands.get("0") ?? 0n).toString(), "latency band count"),
7913
+ more800Less1200: loadEngineV2SafeNumber((bands.get("1") ?? 0n).toString(), "latency band count"),
7914
+ moreOrEq1200: loadEngineV2SafeNumber((bands.get("2") ?? 0n).toString(), "latency band count")
7915
+ }, allRequestCount, durationMs);
7916
+ }
7917
+ function requireLoadEngineV2MeasurementDistribution(summary, outcome, unit, distributions) {
7918
+ const distribution = distributions.get(loadEngineV2DistributionProjectionKey(summary.seriesKind, summary.scenarioIndex64, summary.identityKeyHex, outcome, unit));
7919
+ if (!distribution || distribution.scenarioName !== summary.scenarioName) {
7920
+ throw new Error("Load Engine V2 measurement summary is missing its canonical histogram distribution.");
7921
+ }
7922
+ return distribution;
7923
+ }
7924
+ function loadEngineV2DistributionProjectionKey(seriesKind, scenarioIndex64, identityKeyHex, outcome, unit) {
7925
+ return [seriesKind, scenarioIndex64, identityKeyHex, outcome, unit].join("\0");
7926
+ }
7927
+ function compareLoadEngineV2Decimal(left, right) {
7928
+ const a = BigInt(left);
7929
+ const b = BigInt(right);
7930
+ return a < b ? -1 : a > b ? 1 : 0;
7931
+ }
7932
+ function loadEngineV2SafeNumber(value, field) {
7933
+ const parsed = BigInt(value);
7934
+ if (parsed < 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
7935
+ throw new Error(`Load Engine V2 ${field} exceeds the JavaScript safe integer range.`);
7936
+ }
7937
+ return Number(parsed);
7938
+ }
7939
+ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo, requireHistogramArtifact = false) {
6013
7940
  const completedUtc = new Date().toISOString();
6014
7941
  if (!result.success || !result.stats) {
6015
7942
  return attachNodeStatsAliases({
@@ -6042,12 +7969,12 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
6042
7969
  isFailed: true,
6043
7970
  errorCount: 1,
6044
7971
  exceptionMessage: result.error ?? "Agent execution failed."
6045
- }]
7972
+ }],
7973
+ reportingComplete: false
6046
7974
  });
6047
7975
  }
6048
7976
  const metrics = normalizeMetricStatsPayload(result.stats.metrics, result.stats.durationMs ?? 0);
6049
- const scenarioStats = normalizeScenarioStatsPayload(result.stats.scenarioStats);
6050
- const stepStats = scenarioStats.flatMap((value) => value.stepStats);
7977
+ let scenarioStats = normalizeScenarioStatsPayload(result.stats.scenarioStats);
6051
7978
  const thresholds = normalizeThresholdPayload(result.stats.thresholds);
6052
7979
  const pluginsData = normalizePluginsPayload(result.stats.pluginsData);
6053
7980
  const nodeInfo = {
@@ -6060,6 +7987,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
6060
7987
  ...testInfo,
6061
7988
  ...(result.stats.testInfo ?? {})
6062
7989
  };
7990
+ const histogramArtifactBase64 = String(result.stats.histogramArtifactBase64 ?? "");
7991
+ if (requireHistogramArtifact && !histogramArtifactBase64) {
7992
+ throw new Error("Load Engine V2 result omits the mandatory LS-H1 histogram artifact.");
7993
+ }
7994
+ const histogramArtifact = histogramArtifactBase64
7995
+ ? (0, load_engine_v2_js_1.parseLoadEngineV2HistogramArtifact)(Buffer.from(histogramArtifactBase64, "base64"))
7996
+ : undefined;
7997
+ if (histogramArtifact) {
7998
+ scenarioStats = projectLoadEngineV2MeasurementsFromArtifact(scenarioStats, histogramArtifact);
7999
+ }
8000
+ const stepStats = scenarioStats.flatMap((value) => value.stepStats);
6063
8001
  return attachNodeStatsAliases({
6064
8002
  startedUtc: testInfo.createdUtc,
6065
8003
  completedUtc,
@@ -6082,6 +8020,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
6082
8020
  sinkErrors: [],
6083
8021
  reportFiles: [],
6084
8022
  logFiles: normalizeAliasStringArray(result.stats.logFiles),
8023
+ generatorWarnings: (result.stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
8024
+ schedulerSegments: (result.stats.schedulerSegments ?? []).map(normalizeSchedulerSegment),
8025
+ schedulerStats: result.stats.schedulerStats
8026
+ ? normalizeSchedulerStats(result.stats.schedulerStats)
8027
+ : undefined,
8028
+ [LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: histogramArtifact?.distributions
8029
+ .filter((record) => record.seriesKind === "scheduler-decision-lag"
8030
+ || record.seriesKind === "scheduler-start-lag")
8031
+ .map(cloneLoadEngineV2DistributionRecord),
8032
+ observationDeliveryStats: normalizeObservationDeliveryStats(result.stats.observationDeliveryStats ?? emptyObservationDeliveryStats()),
8033
+ reportingComplete: result.stats.reportingComplete ?? false,
6085
8034
  findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
6086
8035
  getScenarioStats: (scenarioName) => {
6087
8036
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -6092,7 +8041,46 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
6092
8041
  }
6093
8042
  });
6094
8043
  }
6095
- function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
8044
+ function emptyObservationDeliveryStats() {
8045
+ return normalizeObservationDeliveryStats({
8046
+ lastBatchSequence64: "-1",
8047
+ capturedCount64: "0",
8048
+ deliveredCount64: "0",
8049
+ droppedBufferCount64: "0",
8050
+ droppedSinkCount64: "0"
8051
+ });
8052
+ }
8053
+ function aggregateObservationDeliveryStats(nodes) {
8054
+ let lastBatchSequence = -1n;
8055
+ let captured = 0n;
8056
+ let delivered = 0n;
8057
+ let droppedBuffer = 0n;
8058
+ let droppedSink = 0n;
8059
+ for (const node of nodes) {
8060
+ const stats = node.observationDeliveryStats ?? emptyObservationDeliveryStats();
8061
+ lastBatchSequence = maxBigInt(lastBatchSequence, parseObservationDecimal(stats.lastBatchSequence64, -1n));
8062
+ captured += parseObservationDecimal(stats.capturedCount64);
8063
+ delivered += parseObservationDecimal(stats.deliveredCount64);
8064
+ droppedBuffer += parseObservationDecimal(stats.droppedBufferCount64);
8065
+ droppedSink += parseObservationDecimal(stats.droppedSinkCount64);
8066
+ }
8067
+ return normalizeObservationDeliveryStats({
8068
+ lastBatchSequence64: lastBatchSequence.toString(),
8069
+ capturedCount64: captured.toString(),
8070
+ deliveredCount64: delivered.toString(),
8071
+ droppedBufferCount64: droppedBuffer.toString(),
8072
+ droppedSinkCount64: droppedSink.toString()
8073
+ });
8074
+ }
8075
+ function parseObservationDecimal(value, fallback = 0n) {
8076
+ try {
8077
+ return BigInt(value);
8078
+ }
8079
+ catch {
8080
+ return fallback;
8081
+ }
8082
+ }
8083
+ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes, requireHistograms = false) {
6096
8084
  if (!nodes.length) {
6097
8085
  return buildEmptyNodeStats({
6098
8086
  startedUtc: testInfo.createdUtc,
@@ -6101,12 +8089,22 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
6101
8089
  testInfo
6102
8090
  });
6103
8091
  }
6104
- const scenarioStats = aggregateScenarioStats(nodes);
8092
+ const scenarioStats = aggregateScenarioStats(nodes, requireHistograms);
6105
8093
  const stepStats = scenarioStats.flatMap((value) => value.stepStats);
6106
8094
  const metrics = aggregateMetricStats(nodes);
6107
8095
  const thresholds = aggregateThresholds(nodes);
6108
8096
  const pluginsData = aggregatePluginsData(nodes);
6109
8097
  const completedUtc = new Date().toISOString();
8098
+ const schedulerSegments = nodes.flatMap((value) => value.schedulerSegments ?? []);
8099
+ const schedulerStatsRows = nodes.flatMap((value) => value.schedulerStats ? [value.schedulerStats] : []);
8100
+ const schedulerStats = schedulerSegments.length || schedulerStatsRows.length
8101
+ ? {
8102
+ configuredMaxInFlight: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.configuredMaxInFlight), 0),
8103
+ maxInFlightObserved: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.maxInFlightObserved), 0),
8104
+ currentInFlight: schedulerStatsRows.reduce((sum, value) => sum + value.currentInFlight, 0),
8105
+ segments: schedulerSegments
8106
+ }
8107
+ : undefined;
6110
8108
  return attachNodeStatsAliases({
6111
8109
  startedUtc: testInfo.createdUtc,
6112
8110
  completedUtc,
@@ -6129,6 +8127,12 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
6129
8127
  sinkErrors: [],
6130
8128
  reportFiles: [],
6131
8129
  logFiles: mergeStringArrays(...nodes.map((value) => value.logFiles ?? [])),
8130
+ generatorWarnings: nodes.flatMap((value) => value.generatorWarnings ?? []),
8131
+ ...(schedulerSegments.length ? { schedulerSegments } : {}),
8132
+ ...(schedulerStats ? { schedulerStats } : {}),
8133
+ [LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: mergeLoadEngineV2SchedulerDistributions(nodes.flatMap((value) => value[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS] ?? [])),
8134
+ observationDeliveryStats: aggregateObservationDeliveryStats(nodes),
8135
+ reportingComplete: nodes.every((value) => value.reportingComplete ?? false),
6132
8136
  findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
6133
8137
  getScenarioStats: (scenarioName) => {
6134
8138
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -6139,7 +8143,7 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
6139
8143
  }
6140
8144
  });
6141
8145
  }
6142
- function aggregateScenarioStats(nodes) {
8146
+ function aggregateScenarioStats(nodes, requireHistograms = false) {
6143
8147
  const grouped = new Map();
6144
8148
  for (const node of nodes) {
6145
8149
  for (const scenario of node.scenarioStats) {
@@ -6153,9 +8157,12 @@ function aggregateScenarioStats(nodes) {
6153
8157
  .map((items) => {
6154
8158
  const allRequestCount = items.reduce((sum, value) => sum + value.allRequestCount, 0);
6155
8159
  const durationMs = items.reduce((max, value) => Math.max(max, value.durationMs), 0);
6156
- const ok = aggregateMeasurementStats(items.map((value) => value.ok), allRequestCount, durationMs);
6157
- const fail = aggregateMeasurementStats(items.map((value) => value.fail), allRequestCount, durationMs);
6158
- const stepStats = aggregateStepStats(items);
8160
+ const ok = aggregateMeasurementStats(items.map((value) => value.ok), allRequestCount, durationMs, requireHistograms);
8161
+ const fail = aggregateMeasurementStats(items.map((value) => value.fail), allRequestCount, durationMs, requireHistograms);
8162
+ const allMeasurement = requireHistograms
8163
+ ? aggregateMeasurementStats([ok, fail], allRequestCount, durationMs, true)
8164
+ : undefined;
8165
+ const stepStats = aggregateStepStats(items, requireHistograms);
6159
8166
  const scenarioName = items[0]?.scenarioName ?? "";
6160
8167
  const currentOperation = selectScenarioOperation(items.map((value) => value.currentOperation));
6161
8168
  const loadSimulationStats = items.find((value) => value.loadSimulationStats.simulationName)?.loadSimulationStats ?? {
@@ -6180,6 +8187,7 @@ function aggregateScenarioStats(nodes) {
6180
8187
  durationMs,
6181
8188
  ok,
6182
8189
  fail,
8190
+ ...(allMeasurement ? { allMeasurement } : {}),
6183
8191
  loadSimulationStats,
6184
8192
  sortIndex: Math.min(...items.map((value) => value.sortIndex)),
6185
8193
  stepStats,
@@ -6195,7 +8203,7 @@ function aggregateScenarioStats(nodes) {
6195
8203
  return attachScenarioStatsAliases(scenario);
6196
8204
  });
6197
8205
  }
6198
- function aggregateStepStats(scenarios) {
8206
+ function aggregateStepStats(scenarios, requireHistograms = false) {
6199
8207
  const grouped = new Map();
6200
8208
  for (const scenario of scenarios) {
6201
8209
  for (const step of scenario.stepStats) {
@@ -6209,8 +8217,11 @@ function aggregateStepStats(scenarios) {
6209
8217
  return Array.from(grouped.values())
6210
8218
  .sort((left, right) => Math.min(...left.map((value) => value.sortIndex)) - Math.min(...right.map((value) => value.sortIndex)))
6211
8219
  .map((items) => {
6212
- const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs);
6213
- const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs);
8220
+ const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs, requireHistograms);
8221
+ const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs, requireHistograms);
8222
+ const allMeasurement = requireHistograms
8223
+ ? aggregateMeasurementStats([ok, fail], allScenarioRequests, scenarioDurationMs, true)
8224
+ : undefined;
6214
8225
  const requestCount = ok.request.count + fail.request.count;
6215
8226
  return {
6216
8227
  scenarioName: items[0]?.scenarioName ?? "",
@@ -6226,14 +8237,53 @@ function aggregateStepStats(scenarios) {
6226
8237
  statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
6227
8238
  ok,
6228
8239
  fail,
8240
+ ...(allMeasurement ? { allMeasurement } : {}),
6229
8241
  sortIndex: Math.min(...items.map((value) => value.sortIndex))
6230
8242
  };
6231
8243
  });
6232
8244
  }
6233
- function aggregateMeasurementStats(measurements, allRequestCount, durationMs) {
8245
+ function aggregateMeasurementStats(measurements, allRequestCount, durationMs, requireHistograms = false) {
6234
8246
  if (!measurements.length) {
6235
8247
  return buildMeasurementPlaceholder(0, allRequestCount, durationMs);
6236
8248
  }
8249
+ if (measurements.every((measurement) => measurement.histogramSidecar)) {
8250
+ const latency = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.latency);
8251
+ const size = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.size);
8252
+ for (const measurement of measurements.slice(1)) {
8253
+ latency.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.latency));
8254
+ size.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.size));
8255
+ }
8256
+ const statusCodes = new Map();
8257
+ for (const measurement of measurements) {
8258
+ for (const status of measurement.statusCodes) {
8259
+ const key = `${status.statusCode}|${status.message}|${status.isError ? "1" : "0"}`;
8260
+ const current = statusCodes.get(key);
8261
+ if (current)
8262
+ current.count += status.count;
8263
+ else
8264
+ statusCodes.set(key, {
8265
+ statusCode: status.statusCode,
8266
+ message: status.message,
8267
+ isError: status.isError,
8268
+ count: status.count
8269
+ });
8270
+ }
8271
+ }
8272
+ const sumSidecar = (key) => Number(measurements.reduce((sum, measurement) => sum + BigInt(measurement.histogramSidecar[key]), 0n));
8273
+ return buildHistogramMeasurement({
8274
+ count: Number(latency.count),
8275
+ allBytes: measurements.reduce((sum, measurement) => sum + measurement.dataTransfer.allBytes, 0),
8276
+ latency,
8277
+ size,
8278
+ statusCodes,
8279
+ lessOrEq800: sumSidecar("lessOrEq80064"),
8280
+ more800Less1200: sumSidecar("more800Less120064"),
8281
+ moreOrEq1200: sumSidecar("moreOrEq120064")
8282
+ }, allRequestCount, durationMs);
8283
+ }
8284
+ if (requireHistograms) {
8285
+ throw new Error("Load Engine V2 aggregation requires canonical histogram state for every node measurement.");
8286
+ }
6237
8287
  const weights = measurements.map((value) => value.request.count);
6238
8288
  const totalCount = measurements.reduce((sum, value) => sum + value.request.count, 0);
6239
8289
  return {
@@ -7449,11 +9499,23 @@ function readRuntimeTrackingId(payload, selector) {
7449
9499
  return null;
7450
9500
  }
7451
9501
  let current = body;
7452
- for (const segment of selector.slice("json:".length).trim().replace(/^\$\./, "").split(".").filter(Boolean)) {
9502
+ const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
9503
+ let segments;
9504
+ try {
9505
+ segments = runtimeSafeJsonPathSegments(path);
9506
+ }
9507
+ catch {
9508
+ return null;
9509
+ }
9510
+ for (const segment of segments) {
7453
9511
  if (!current || typeof current !== "object" || Array.isArray(current)) {
7454
9512
  return null;
7455
9513
  }
7456
- current = current[segment];
9514
+ const record = current;
9515
+ if (!Object.prototype.hasOwnProperty.call(record, segment)) {
9516
+ return null;
9517
+ }
9518
+ current = runtimeReadOwnJsonProperty(record, segment);
7457
9519
  }
7458
9520
  return current == null ? null : String(current);
7459
9521
  }
@@ -7477,23 +9539,57 @@ function runtimeParseBodyAsObject(body) {
7477
9539
  }
7478
9540
  }
7479
9541
  function setRuntimeJsonPathValue(body, path, value) {
7480
- const target = body && typeof body === "object" && !Array.isArray(body)
7481
- ? { ...body }
7482
- : {};
7483
- const segments = path.split(".").filter(Boolean);
9542
+ const target = runtimeCloneJsonRecord(body);
9543
+ const segments = runtimeSafeJsonPathSegments(path);
7484
9544
  if (!segments.length) {
7485
9545
  return target;
7486
9546
  }
7487
9547
  let current = target;
7488
9548
  for (let i = 0; i < segments.length - 1; i += 1) {
7489
9549
  const segment = segments[i];
7490
- const next = current[segment];
9550
+ const next = runtimeReadOwnJsonProperty(current, segment);
9551
+ let child;
7491
9552
  if (!next || typeof next !== "object" || Array.isArray(next)) {
7492
- current[segment] = {};
9553
+ child = {};
9554
+ }
9555
+ else {
9556
+ child = runtimeCloneJsonRecord(next);
7493
9557
  }
7494
- current = current[segment];
9558
+ runtimeDefineJsonProperty(current, segment, child);
9559
+ current = child;
9560
+ }
9561
+ runtimeDefineJsonProperty(current, segments[segments.length - 1], value);
9562
+ return target;
9563
+ }
9564
+ const FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
9565
+ function runtimeSafeJsonPathSegments(path) {
9566
+ const segments = path.split(".").filter(Boolean);
9567
+ const forbidden = segments.find((segment) => FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS.has(segment));
9568
+ if (forbidden) {
9569
+ throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
9570
+ }
9571
+ return segments;
9572
+ }
9573
+ function runtimeDefineJsonProperty(target, key, value) {
9574
+ Object.defineProperty(target, key, {
9575
+ configurable: true,
9576
+ enumerable: true,
9577
+ value,
9578
+ writable: true
9579
+ });
9580
+ }
9581
+ function runtimeReadOwnJsonProperty(target, key) {
9582
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
9583
+ return descriptor && "value" in descriptor ? descriptor.value : undefined;
9584
+ }
9585
+ function runtimeCloneJsonRecord(value) {
9586
+ const target = {};
9587
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9588
+ return target;
9589
+ }
9590
+ for (const [key, entry] of Object.entries(value)) {
9591
+ runtimeDefineJsonProperty(target, key, entry);
7495
9592
  }
7496
- current[segments[segments.length - 1]] = value;
7497
9593
  return target;
7498
9594
  }
7499
9595
  function asTrackingRecord(value) {
@@ -7503,8 +9599,8 @@ function asTrackingRecord(value) {
7503
9599
  }
7504
9600
  function pickTrackingValue(source, ...keys) {
7505
9601
  for (const key of keys) {
7506
- if (key in source) {
7507
- return source[key];
9602
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
9603
+ return runtimeReadOwnJsonProperty(source, key);
7508
9604
  }
7509
9605
  }
7510
9606
  return undefined;
@@ -7758,26 +9854,10 @@ function createDefaultLogger(logFilePath) {
7758
9854
  function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
7759
9855
  const threshold = logLevelOrder(minimumLogLevel);
7760
9856
  return {
7761
- debug: (message) => {
7762
- if (threshold <= 0) {
7763
- baseLogger.debug(message);
7764
- }
7765
- },
7766
- info: (message) => {
7767
- if (threshold <= 1) {
7768
- baseLogger.info(message);
7769
- }
7770
- },
7771
- warn: (message) => {
7772
- if (threshold <= 2) {
7773
- baseLogger.warn(message);
7774
- }
7775
- },
7776
- error: (message) => {
7777
- if (threshold <= 3) {
7778
- baseLogger.error(message);
7779
- }
7780
- }
9857
+ debug: (message) => threshold <= 0 ? baseLogger.debug(message) : undefined,
9858
+ info: (message) => threshold <= 1 ? baseLogger.info(message) : undefined,
9859
+ warn: (message) => threshold <= 2 ? baseLogger.warn(message) : undefined,
9860
+ error: (message) => threshold <= 3 ? baseLogger.error(message) : undefined
7781
9861
  };
7782
9862
  }
7783
9863
  function formatDefaultLoggerLine(level, message) {
@@ -8055,6 +10135,68 @@ function normalizeOptionalReportFormats(value) {
8055
10135
  const normalized = normalizeReportFormats(value);
8056
10136
  return normalized.length ? normalized : undefined;
8057
10137
  }
10138
+ function normalizeDeclaredStepNames(value) {
10139
+ if (!Array.isArray(value)) {
10140
+ throw new TypeError("Declared step names must be provided as text values.");
10141
+ }
10142
+ const seen = new Set();
10143
+ const normalized = [];
10144
+ for (const entry of value) {
10145
+ if (typeof entry !== "string" || !entry.trim()) {
10146
+ throw new Error("Declared step name must be non-empty text.");
10147
+ }
10148
+ const stepName = entry.trim();
10149
+ for (let index = 0; index < stepName.length; index += 1) {
10150
+ const code = stepName.charCodeAt(index);
10151
+ if (code >= 0xd800 && code <= 0xdbff) {
10152
+ const next = stepName.charCodeAt(index + 1);
10153
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
10154
+ throw new Error("Declared step name contains an invalid Unicode scalar.");
10155
+ }
10156
+ index += 1;
10157
+ }
10158
+ else if (code >= 0xdc00 && code <= 0xdfff) {
10159
+ throw new Error("Declared step name contains an invalid Unicode scalar.");
10160
+ }
10161
+ }
10162
+ if (!seen.has(stepName)) {
10163
+ seen.add(stepName);
10164
+ normalized.push(stepName);
10165
+ }
10166
+ }
10167
+ return normalized;
10168
+ }
10169
+ function resolveIterationObservationSettings(options) {
10170
+ return {
10171
+ flushIntervalMs: options.iterationObservationFlushIntervalSeconds === undefined
10172
+ ? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.flushIntervalMs
10173
+ : options.iterationObservationFlushIntervalSeconds * 1000,
10174
+ maxBufferBytes: options.maxIterationObservationBufferBytes
10175
+ ?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBufferBytes,
10176
+ maxObservationsPerBatch: options.maxIterationObservationsPerBatch
10177
+ ?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxObservationsPerBatch,
10178
+ maxBatchBytes: options.maxIterationObservationBatchBytes
10179
+ ?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBatchBytes,
10180
+ sinkQueueDepth: options.iterationObservationSinkQueueDepth
10181
+ ?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkQueueDepth,
10182
+ sinkParallelism: options.iterationObservationSinkParallelism
10183
+ ?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkParallelism,
10184
+ drainTimeoutMs: options.iterationObservationDrainTimeoutSeconds === undefined
10185
+ ? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.drainTimeoutMs
10186
+ : options.iterationObservationDrainTimeoutSeconds * 1000
10187
+ };
10188
+ }
10189
+ function validateRunContextIterationObservationSettings(values) {
10190
+ (0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings({
10191
+ iterationObservationFlushIntervalSeconds: values.IterationObservationFlushIntervalSeconds,
10192
+ maxIterationObservationBufferBytes: values.MaxIterationObservationBufferBytes,
10193
+ maxIterationObservationsPerBatch: values.MaxIterationObservationsPerBatch,
10194
+ maxIterationObservationBatchBytes: values.MaxIterationObservationBatchBytes,
10195
+ iterationObservationSinkQueueDepth: values.IterationObservationSinkQueueDepth,
10196
+ iterationObservationSinkParallelism: values.IterationObservationSinkParallelism,
10197
+ iterationObservationDrainTimeoutSeconds: values.IterationObservationDrainTimeoutSeconds
10198
+ }));
10199
+ }
8058
10200
  function assertNoDisableLicenseEnforcementOption(value, source) {
8059
10201
  if (value == null || typeof value !== "object" || Array.isArray(value)) {
8060
10202
  return;
@@ -8073,12 +10215,121 @@ function normalizeRunContextCollectionShapes(values) {
8073
10215
  TargetScenarios: normalizeOptionalStringArray(values.TargetScenarios),
8074
10216
  AgentTargetScenarios: normalizeOptionalStringArray(values.AgentTargetScenarios),
8075
10217
  CoordinatorTargetScenarios: normalizeOptionalStringArray(values.CoordinatorTargetScenarios),
10218
+ ExpectedAgentIds: normalizeOptionalStringArray(values.ExpectedAgentIds),
8076
10219
  ReportFormats: normalizeOptionalReportFormats(values.ReportFormats)
8077
10220
  };
8078
10221
  validateNamedReportingSinks(normalized.ReportingSinks ?? []);
8079
10222
  validateNamedWorkerPlugins(normalized.WorkerPlugins ?? []);
10223
+ validateLoadEngineV2Options(normalized.LoadEngineContractVersion, normalized.MaxInFlight);
10224
+ validateRunContextIterationObservationSettings(normalized);
8080
10225
  return normalized;
8081
10226
  }
10227
+ function normalizeAliasStringRecord(value) {
10228
+ const source = asAliasRecord(value);
10229
+ const output = {};
10230
+ for (const [key, entry] of Object.entries(source)) {
10231
+ output[key] = String(entry);
10232
+ }
10233
+ return output;
10234
+ }
10235
+ function attachGeneratorWarningAliases(value) {
10236
+ const source = asAliasRecord(value);
10237
+ const projected = {
10238
+ code: pickAliasString(source, "code", "Code"),
10239
+ ...(hasAliasValue(source, "sinkName", "SinkName")
10240
+ ? { sinkName: pickAliasString(source, "sinkName", "SinkName") }
10241
+ : {}),
10242
+ scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
10243
+ ...(hasAliasValue(source, "scenarioIndex", "ScenarioIndex")
10244
+ ? { scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex") }
10245
+ : {}),
10246
+ simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
10247
+ ...(hasAliasValue(source, "simulationKind", "SimulationKind")
10248
+ ? { simulationKind: pickAliasString(source, "simulationKind", "SimulationKind") }
10249
+ : {}),
10250
+ count64: pickAliasString(source, "count64", "Count64"),
10251
+ message: pickAliasString(source, "message", "Message"),
10252
+ firstObservedUtcNs: pickAliasString(source, "firstObservedUtcNs", "FirstObservedUtcNs"),
10253
+ lastObservedUtcNs: pickAliasString(source, "lastObservedUtcNs", "LastObservedUtcNs")
10254
+ };
10255
+ return attachAliasMap(projected, {
10256
+ Code: "code",
10257
+ SinkName: "sinkName",
10258
+ ScenarioName: "scenarioName",
10259
+ ScenarioIndex: "scenarioIndex",
10260
+ SimulationIndex: "simulationIndex",
10261
+ SimulationKind: "simulationKind",
10262
+ Count64: "count64",
10263
+ Message: "message",
10264
+ FirstObservedUtcNs: "firstObservedUtcNs",
10265
+ LastObservedUtcNs: "lastObservedUtcNs"
10266
+ });
10267
+ }
10268
+ function normalizeSchedulerSegment(value) {
10269
+ const source = asAliasRecord(value);
10270
+ return {
10271
+ scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
10272
+ scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex"),
10273
+ simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
10274
+ kind: pickAliasString(source, "kind", "Kind"),
10275
+ shardIndex: pickAliasNumber(source, "shardIndex", "ShardIndex"),
10276
+ shardCount: Math.max(pickAliasNumber(source, "shardCount", "ShardCount"), 1),
10277
+ plannedIterations64: pickAliasString(source, "plannedIterations64", "PlannedIterations64"),
10278
+ dueIterations64: pickAliasString(source, "dueIterations64", "DueIterations64"),
10279
+ startedIterations64: pickAliasString(source, "startedIterations64", "StartedIterations64"),
10280
+ completedIterations64: pickAliasString(source, "completedIterations64", "CompletedIterations64"),
10281
+ droppedIterations64: pickAliasString(source, "droppedIterations64", "DroppedIterations64"),
10282
+ unreachedIterations64: pickAliasString(source, "unreachedIterations64", "UnreachedIterations64"),
10283
+ requestedWorkerSlots64: pickAliasString(source, "requestedWorkerSlots64", "RequestedWorkerSlots64"),
10284
+ startedWorkerSlots64: pickAliasString(source, "startedWorkerSlots64", "StartedWorkerSlots64"),
10285
+ unavailableWorkerSlots64: pickAliasString(source, "unavailableWorkerSlots64", "UnavailableWorkerSlots64"),
10286
+ dropReasons: normalizeAliasStringRecord(pickAliasValue(source, "dropReasons", "DropReasons")),
10287
+ unavailableWorkerReasons: normalizeAliasStringRecord(pickAliasValue(source, "unavailableWorkerReasons", "UnavailableWorkerReasons")),
10288
+ deliveryPercent: pickAliasNumber(source, "deliveryPercent", "DeliveryPercent"),
10289
+ accountingComplete: pickAliasBoolean(source, "accountingComplete", "AccountingComplete")
10290
+ };
10291
+ }
10292
+ function normalizeSchedulerStats(value) {
10293
+ const source = asAliasRecord(value);
10294
+ return {
10295
+ configuredMaxInFlight: pickAliasNumber(source, "configuredMaxInFlight", "ConfiguredMaxInFlight"),
10296
+ maxInFlightObserved: pickAliasNumber(source, "maxInFlightObserved", "MaxInFlightObserved"),
10297
+ currentInFlight: pickAliasNumber(source, "currentInFlight", "CurrentInFlight"),
10298
+ segments: pickAliasArray(source, "segments", "Segments").map(normalizeSchedulerSegment)
10299
+ };
10300
+ }
10301
+ function normalizeObservationDeliveryStats(value) {
10302
+ const source = asAliasRecord(value);
10303
+ return attachAliasMap({
10304
+ lastBatchSequence64: pickAliasString(source, "lastBatchSequence64", "LastBatchSequence64"),
10305
+ capturedCount64: pickAliasString(source, "capturedCount64", "CapturedCount64"),
10306
+ deliveredCount64: pickAliasString(source, "deliveredCount64", "DeliveredCount64"),
10307
+ droppedBufferCount64: pickAliasString(source, "droppedBufferCount64", "DroppedBufferCount64"),
10308
+ droppedSinkCount64: pickAliasString(source, "droppedSinkCount64", "DroppedSinkCount64")
10309
+ }, {
10310
+ LastBatchSequence64: "lastBatchSequence64",
10311
+ CapturedCount64: "capturedCount64",
10312
+ DeliveredCount64: "deliveredCount64",
10313
+ DroppedBufferCount64: "droppedBufferCount64",
10314
+ DroppedSinkCount64: "droppedSinkCount64"
10315
+ });
10316
+ }
10317
+ function validateLoadEngineV2Options(contractVersion, maxInFlight) {
10318
+ if (contractVersion !== undefined && contractVersion !== 1 && contractVersion !== 2) {
10319
+ throw new RangeError("Load engine contract version must be either 1 or 2.");
10320
+ }
10321
+ if (maxInFlight !== undefined) {
10322
+ validateV2MaxInFlight(contractVersion, maxInFlight);
10323
+ }
10324
+ }
10325
+ function validateV2MaxInFlight(contractVersion, maxInFlight) {
10326
+ if (contractVersion !== 2) {
10327
+ throw new Error("MaxInFlight is available only when Load Engine V2 is selected.");
10328
+ }
10329
+ if (!Number.isSafeInteger(maxInFlight) || maxInFlight < 1 || maxInFlight > 1000000) {
10330
+ throw new RangeError("MaxInFlight must be an integer from 1 through 1000000.");
10331
+ }
10332
+ }
8082
10333
  function normalizeRunnerOptionCollectionShapes(options) {
8083
10334
  assertNoDisableLicenseEnforcementOption(options, "LoadStrikeRunner");
8084
10335
  const normalized = {
@@ -8086,10 +10337,13 @@ function normalizeRunnerOptionCollectionShapes(options) {
8086
10337
  targetScenarios: normalizeOptionalStringArray(options.targetScenarios),
8087
10338
  agentTargetScenarios: normalizeOptionalStringArray(options.agentTargetScenarios),
8088
10339
  coordinatorTargetScenarios: normalizeOptionalStringArray(options.coordinatorTargetScenarios),
10340
+ expectedAgentIds: normalizeOptionalStringArray(options.expectedAgentIds),
8089
10341
  reportFormats: normalizeOptionalReportFormats(options.reportFormats)
8090
10342
  };
8091
10343
  validateNamedReportingSinks(normalized.reportingSinks ?? []);
8092
10344
  validateNamedWorkerPlugins(normalized.workerPlugins ?? []);
10345
+ validateLoadEngineV2Options(normalized.loadEngineContractVersion, normalized.maxInFlight);
10346
+ (0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings(normalized));
8093
10347
  return normalized;
8094
10348
  }
8095
10349
  function normalizedRuntimePolicyErrorMode(value) {
@@ -8153,6 +10407,7 @@ function extractContextOverridesFromConfig(config) {
8153
10407
  setString("ReportFileName", "ReportFileName", "LoadStrike:ReportFileName");
8154
10408
  setString("ClusterId", "ClusterId", "LoadStrike:ClusterId");
8155
10409
  setString("AgentGroup", "AgentGroup", "LoadStrike:AgentGroup");
10410
+ setString("AgentId", "AgentId", "LoadStrike:AgentId");
8156
10411
  setString("NatsServerUrl", "NatsServerUrl", "LoadStrike:NatsServerUrl");
8157
10412
  setString("RunnerKey", "RunnerKey", "LoadStrike:RunnerKey");
8158
10413
  setString("RuntimePolicyErrorMode", "RuntimePolicyErrorMode", "LoadStrike:RuntimePolicyErrorMode");
@@ -8182,6 +10437,13 @@ function extractContextOverridesFromConfig(config) {
8182
10437
  }
8183
10438
  }
8184
10439
  setPositiveNumber("ReportingIntervalSeconds", "ReportingIntervalSeconds", "LoadStrike:ReportingIntervalSeconds");
10440
+ setPositiveNumber("IterationObservationFlushIntervalSeconds", "IterationObservationFlushInterval", "IterationObservationFlushIntervalSeconds", "LoadStrike:IterationObservationFlushInterval");
10441
+ setPositiveNumber("MaxIterationObservationBufferBytes", "MaxIterationObservationBufferBytes", "LoadStrike:MaxIterationObservationBufferBytes");
10442
+ setPositiveNumber("MaxIterationObservationsPerBatch", "MaxIterationObservationsPerBatch", "LoadStrike:MaxIterationObservationsPerBatch");
10443
+ setPositiveNumber("MaxIterationObservationBatchBytes", "MaxIterationObservationBatchBytes", "LoadStrike:MaxIterationObservationBatchBytes");
10444
+ setPositiveNumber("IterationObservationSinkQueueDepth", "IterationObservationSinkQueueDepth", "LoadStrike:IterationObservationSinkQueueDepth");
10445
+ setPositiveNumber("IterationObservationSinkParallelism", "IterationObservationSinkParallelism", "LoadStrike:IterationObservationSinkParallelism");
10446
+ setPositiveNumber("IterationObservationDrainTimeoutSeconds", "IterationObservationDrainTimeout", "IterationObservationDrainTimeoutSeconds", "LoadStrike:IterationObservationDrainTimeout");
8185
10447
  setPositiveNumber("ScenarioCompletionTimeoutSeconds", "ScenarioCompletionTimeoutSeconds", "LoadStrike:ScenarioCompletionTimeoutSeconds");
8186
10448
  setPositiveNumber("ClusterCommandTimeoutSeconds", "ClusterCommandTimeoutSeconds", "LoadStrike:ClusterCommandTimeoutSeconds");
8187
10449
  setPositiveNumber("LicenseValidationTimeoutSeconds", "LicenseValidationTimeoutSeconds", "LoadStrike:LicenseValidation:TimeoutSeconds");
@@ -8189,6 +10451,14 @@ function extractContextOverridesFromConfig(config) {
8189
10451
  if (reportingIntervalMs > 0) {
8190
10452
  patch.ReportingIntervalSeconds = reportingIntervalMs / 1000;
8191
10453
  }
10454
+ const observationFlushIntervalMs = toNumber(pick("IterationObservationFlushIntervalMs", "LoadStrike:IterationObservationFlushIntervalMs"));
10455
+ if (observationFlushIntervalMs > 0) {
10456
+ patch.IterationObservationFlushIntervalSeconds = observationFlushIntervalMs / 1000;
10457
+ }
10458
+ const observationDrainTimeoutMs = toNumber(pick("IterationObservationDrainTimeoutMs", "LoadStrike:IterationObservationDrainTimeoutMs"));
10459
+ if (observationDrainTimeoutMs > 0) {
10460
+ patch.IterationObservationDrainTimeoutSeconds = observationDrainTimeoutMs / 1000;
10461
+ }
8192
10462
  const scenarioCompletionTimeoutMs = toNumber(pick("ScenarioCompletionTimeoutMs", "LoadStrike:ScenarioCompletionTimeoutMs"));
8193
10463
  if (scenarioCompletionTimeoutMs > 0) {
8194
10464
  patch.ScenarioCompletionTimeoutSeconds = scenarioCompletionTimeoutMs / 1000;
@@ -8233,6 +10503,10 @@ function extractContextOverridesFromConfig(config) {
8233
10503
  if (agentTargetScenarios.length) {
8234
10504
  patch.AgentTargetScenarios = agentTargetScenarios;
8235
10505
  }
10506
+ const expectedAgentIds = normalizeStringArray(pick("ExpectedAgentIds", "LoadStrike:ExpectedAgentIds"));
10507
+ if (expectedAgentIds.length) {
10508
+ patch.ExpectedAgentIds = expectedAgentIds;
10509
+ }
8236
10510
  const coordinatorTargetScenarios = normalizeStringArray(pick("CoordinatorTargetScenarios", "LoadStrike:CoordinatorTargetScenarios"));
8237
10511
  if (coordinatorTargetScenarios.length) {
8238
10512
  patch.CoordinatorTargetScenarios = coordinatorTargetScenarios;
@@ -8295,10 +10569,14 @@ function toRunContext(options) {
8295
10569
  const normalized = normalizeRunnerOptionCollectionShapes(options);
8296
10570
  return {
8297
10571
  ConsoleMetricsEnabled: normalized.displayConsoleMetrics,
10572
+ LoadEngineContractVersion: normalized.loadEngineContractVersion,
10573
+ MaxInFlight: normalized.maxInFlight,
8298
10574
  NodeType: normalized.nodeType,
8299
10575
  LocalDevClusterEnabled: normalized.localDevClusterEnabled,
8300
10576
  AgentGroup: normalized.agentGroup,
8301
10577
  AgentsCount: normalized.agentsCount,
10578
+ AgentId: normalized.agentId,
10579
+ ExpectedAgentIds: normalized.expectedAgentIds,
8302
10580
  TargetScenarios: normalized.targetScenarios,
8303
10581
  AgentTargetScenarios: normalized.agentTargetScenarios,
8304
10582
  CoordinatorTargetScenarios: normalized.coordinatorTargetScenarios,
@@ -8317,6 +10595,13 @@ function toRunContext(options) {
8317
10595
  ReportFolderPath: normalized.reportFolderPath,
8318
10596
  ReportFormats: normalized.reportFormats,
8319
10597
  ReportingIntervalSeconds: normalized.reportingIntervalSeconds,
10598
+ IterationObservationFlushIntervalSeconds: normalized.iterationObservationFlushIntervalSeconds,
10599
+ MaxIterationObservationBufferBytes: normalized.maxIterationObservationBufferBytes,
10600
+ MaxIterationObservationsPerBatch: normalized.maxIterationObservationsPerBatch,
10601
+ MaxIterationObservationBatchBytes: normalized.maxIterationObservationBatchBytes,
10602
+ IterationObservationSinkQueueDepth: normalized.iterationObservationSinkQueueDepth,
10603
+ IterationObservationSinkParallelism: normalized.iterationObservationSinkParallelism,
10604
+ IterationObservationDrainTimeoutSeconds: normalized.iterationObservationDrainTimeoutSeconds,
8320
10605
  MinimumLogLevel: normalized.minimumLogLevel,
8321
10606
  LoggerConfig: normalized.loggerConfig,
8322
10607
  ReportingSinks: normalized.reportingSinks,
@@ -8331,7 +10616,9 @@ function toRunContext(options) {
8331
10616
  CustomSettings: normalized.customSettings,
8332
10617
  GlobalCustomSettings: normalized.globalCustomSettings,
8333
10618
  AgentExecutionToken: normalized.agentExecutionToken,
8334
- AgentCommandId: normalized.agentCommandId
10619
+ AgentCommandId: normalized.agentCommandId,
10620
+ ClusterShardIndex: normalized.clusterShardIndex,
10621
+ ClusterShardCount: normalized.clusterShardCount
8335
10622
  };
8336
10623
  }
8337
10624
  class RuntimePolicyCallbackError extends Error {
@@ -8419,11 +10706,15 @@ function looksLikeRunContext(value) {
8419
10706
  const keys = new Set(Object.keys(value));
8420
10707
  return [
8421
10708
  "ConsoleMetricsEnabled",
10709
+ "LoadEngineContractVersion",
10710
+ "MaxInFlight",
8422
10711
  "LocalDevClusterEnabled",
8423
10712
  "ConfigPath",
8424
10713
  "InfraConfigPath",
8425
10714
  "AgentGroup",
8426
10715
  "AgentsCount",
10716
+ "AgentId",
10717
+ "ExpectedAgentIds",
8427
10718
  "AgentTargetScenarios",
8428
10719
  "ClusterId",
8429
10720
  "CoordinatorTargetScenarios",
@@ -8438,6 +10729,13 @@ function looksLikeRunContext(value) {
8438
10729
  "ReportFolderPath",
8439
10730
  "ReportFormats",
8440
10731
  "ReportingIntervalSeconds",
10732
+ "IterationObservationFlushIntervalSeconds",
10733
+ "MaxIterationObservationBufferBytes",
10734
+ "MaxIterationObservationsPerBatch",
10735
+ "MaxIterationObservationBatchBytes",
10736
+ "IterationObservationSinkQueueDepth",
10737
+ "IterationObservationSinkParallelism",
10738
+ "IterationObservationDrainTimeoutSeconds",
8441
10739
  "ReportingSinks",
8442
10740
  "SinkRetryCount",
8443
10741
  "SinkRetryBackoffMs",
@@ -8579,6 +10877,18 @@ function resolveSinkSaveRunResult(sink) {
8579
10877
  ? method.bind(sink)
8580
10878
  : undefined;
8581
10879
  }
10880
+ function resolveSinkSaveIterationBatch(sink) {
10881
+ const method = sink.saveIterationBatch ?? sink.SaveIterationBatch;
10882
+ return typeof method === "function"
10883
+ ? method.bind(sink)
10884
+ : undefined;
10885
+ }
10886
+ function resolveSinkCompleteIterationObservationStream(sink) {
10887
+ const method = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
10888
+ return typeof method === "function"
10889
+ ? method.bind(sink)
10890
+ : undefined;
10891
+ }
8582
10892
  function resolveSinkStop(sink) {
8583
10893
  const method = sink.stop ?? sink.Stop;
8584
10894
  return typeof method === "function"
@@ -8996,13 +11306,17 @@ exports.__loadstrikeTestExports = {
8996
11306
  ManagedScenarioTrackingRuntime,
8997
11307
  ScenarioStatsAccumulator,
8998
11308
  StepStatsAccumulator,
11309
+ planRuntimeClusterAssignments,
8999
11310
  TrackingFieldSelector: correlation_js_1.TrackingFieldSelector,
9000
11311
  addCorrelationRow,
9001
11312
  addFailedResponseRow,
9002
11313
  aggregateNodeStats,
11314
+ aggregateMeasurementStats,
9003
11315
  asRecord,
9004
11316
  assertNoDisableLicenseEnforcementOption,
9005
11317
  buildEmptyNodeStats,
11318
+ buildRuntimeLoadEngineV2HistogramArtifact,
11319
+ buildRuntimeLoadEngineV2Plan,
9006
11320
  buildGroupedCorrelationRows,
9007
11321
  buildMeasurementPlaceholder,
9008
11322
  buildRichHtmlReport,
@@ -9057,6 +11371,7 @@ exports.__loadstrikeTestExports = {
9057
11371
  parseStrictBooleanToken,
9058
11372
  percentile,
9059
11373
  pickOptionalTrackingSelectorString,
11374
+ pickTrackingValue,
9060
11375
  pickTrackingNumber,
9061
11376
  produceOrConsumeTrackingPayload,
9062
11377
  readConfiguredSinkName,
@@ -9075,7 +11390,9 @@ exports.__loadstrikeTestExports = {
9075
11390
  resolveSinkName,
9076
11391
  resolveSinkSaveRealtimeMetrics,
9077
11392
  resolveSinkSaveRealtimeStats,
11393
+ resolveSinkSaveIterationBatch,
9078
11394
  resolveSinkSaveRunResult,
11395
+ resolveSinkCompleteIterationObservationStream,
9079
11396
  resolveSinkStart,
9080
11397
  resolveSinkStop,
9081
11398
  resolveWorkerPlugins,
@@ -9096,6 +11413,7 @@ exports.__loadstrikeTestExports = {
9096
11413
  tryParseNodeTypeToken,
9097
11414
  tryReadConfigValue,
9098
11415
  validateNamedReportingSinks,
11416
+ validateLoadEngineV2ScenarioFeatures,
9099
11417
  validateRegisteredScenarios,
9100
11418
  validateRuntimeRedisCorrelationStoreConfiguration,
9101
11419
  validateRuntimeTrackingConfiguration,