@loadstrike/loadstrike-sdk 1.0.30201 → 1.0.30401

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.
@@ -1,5 +1,11 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { cpus } from "node:os";
2
3
  import { EndpointAdapterFactory } from "./transports.js";
4
+ import { parseLoadEngineV2HistogramArtifact, serializeLoadEngineV2HistogramArtifact } from "./load-engine-v2.js";
5
+ const EMPTY_LOAD_ENGINE_V2_HISTOGRAM_ARTIFACT_BASE64 = serializeLoadEngineV2HistogramArtifact({
6
+ distributions: [],
7
+ measurementSummaries: []
8
+ }).toString("base64");
3
9
  export class LocalClusterCoordinator {
4
10
  /**
5
11
  * Exposes the public constructor operation.
@@ -120,6 +126,1444 @@ export function planAgentScenarioAssignments(scenarioNames, agentCount, options
120
126
  }
121
127
  return assignments;
122
128
  }
129
+ export const LOAD_ENGINE_V2_CLUSTER_CAPABILITIES = Object.freeze({
130
+ schedulerVersions: ["2"],
131
+ histogramVersions: ["1"],
132
+ shardingVersions: ["1"],
133
+ resultChunkVersions: ["1"],
134
+ iterationObservationSchemas: ["loadstrike.iteration-observation/1"],
135
+ // Distributed correlation is negotiated only when the runtime has a shared
136
+ // correlation implementation. An empty list is deliberately fail-closed.
137
+ correlationSetVersions: [],
138
+ planHashVersions: ["LS-SP1"],
139
+ callbackExecutionModes: ["async"],
140
+ maximumResultChunkBytes: 256 * 1024
141
+ });
142
+ const loadEngineV2ProcessInstanceId = randomBytes(32).toString("hex");
143
+ const LOAD_ENGINE_V2_MAX_RESULT_BYTES = 64 * 1024 * 1024;
144
+ const LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES = 128 * 1024;
145
+ export function createLoadEngineV2Registration(input) {
146
+ validateBoundedV2Identifier(input.runId, "run ID", 128);
147
+ validateBoundedV2Identifier(input.sessionId, "session ID", 128);
148
+ validateBoundedV2Identifier(input.coordinatorNonce, "coordinator nonce", 128);
149
+ validateLoadEngineV2AgentId(input.agentId);
150
+ if (input.targetSubject !== buildLoadEngineV2ScopedTargetSubject(input.runId, input.sessionId, input.coordinatorNonce, input.agentId)) {
151
+ throw new Error("Load Engine V2 registration targeted subject is outside the run/session/nonce scope.");
152
+ }
153
+ const processInstanceId = input.processInstanceId ?? loadEngineV2ProcessInstanceId;
154
+ validateLowercaseHash(processInstanceId, "process instance id");
155
+ const registrationSequence = parseCanonicalNonNegativeInt64(input.registrationSequence64, "registration sequence");
156
+ const leaseIssued = parseCanonicalNonNegativeInt64(input.leaseIssuedUtcNs64, "registration lease issue");
157
+ const leaseExpires = parseCanonicalNonNegativeInt64(input.leaseExpiresUtcNs64, "registration lease expiry");
158
+ if (leaseExpires <= leaseIssued || leaseExpires - leaseIssued > 30000000000n) {
159
+ throw new Error("Load Engine V2 registration lease must be positive and no longer than 30 seconds.");
160
+ }
161
+ void registrationSequence;
162
+ const schedulerVisibleProcessorCount = input.schedulerVisibleProcessorCount ?? Math.max(cpus().length, 1);
163
+ if (!Number.isSafeInteger(schedulerVisibleProcessorCount) || schedulerVisibleProcessorCount <= 0) {
164
+ throw new Error("Load Engine V2 registration scheduler-visible processor count is invalid.");
165
+ }
166
+ const sdkLanguage = input.sdkLanguage ?? "typescript";
167
+ const sdkPackageVersion = input.sdkPackageVersion ?? process.env.npm_package_version ?? "0.0.0";
168
+ const runtimeVersion = input.runtimeVersion ?? process.version;
169
+ validateBoundedV2Identifier(sdkLanguage, "SDK language", 128);
170
+ validateBoundedV2Identifier(sdkPackageVersion, "SDK package version", 128);
171
+ validateBoundedV2Identifier(runtimeVersion, "runtime version", 128);
172
+ const selected = { ...LOAD_ENGINE_V2_CLUSTER_CAPABILITIES, ...(input.capabilities ?? {}) };
173
+ const registration = {
174
+ runId: input.runId,
175
+ sessionId: input.sessionId,
176
+ coordinatorNonce: input.coordinatorNonce,
177
+ agentId: input.agentId,
178
+ targetSubject: input.targetSubject,
179
+ processInstanceId,
180
+ schedulerVisibleProcessorCount,
181
+ sdkLanguage,
182
+ sdkPackageVersion,
183
+ runtimeVersion,
184
+ registrationSequence64: input.registrationSequence64,
185
+ leaseIssuedUtcNs64: input.leaseIssuedUtcNs64,
186
+ leaseExpiresUtcNs64: input.leaseExpiresUtcNs64,
187
+ schedulerVersions: canonicalV2CapabilityList(selected.schedulerVersions, "scheduler versions"),
188
+ histogramVersions: canonicalV2CapabilityList(selected.histogramVersions, "histogram versions"),
189
+ shardingVersions: canonicalV2CapabilityList(selected.shardingVersions, "sharding versions"),
190
+ resultChunkVersions: canonicalV2CapabilityList(selected.resultChunkVersions, "result chunk versions"),
191
+ iterationObservationSchemas: canonicalV2CapabilityList(selected.iterationObservationSchemas, "iteration observation schemas"),
192
+ correlationSetVersions: canonicalV2CapabilityList(selected.correlationSetVersions, "correlation versions"),
193
+ planHashVersions: canonicalV2CapabilityList(selected.planHashVersions, "plan hash versions"),
194
+ callbackExecutionModes: canonicalV2CallbackModes(selected.callbackExecutionModes),
195
+ maximumResultChunkBytes: selected.maximumResultChunkBytes,
196
+ registrationBindingHash: ""
197
+ };
198
+ if (!Number.isSafeInteger(registration.maximumResultChunkBytes)
199
+ || registration.maximumResultChunkBytes < 16 * 1024
200
+ || registration.maximumResultChunkBytes > 256 * 1024) {
201
+ throw new Error("Load Engine V2 registration result chunk bound must be 16384..262144 bytes.");
202
+ }
203
+ registration.registrationBindingHash = buildLoadEngineV2RegistrationBindingHash(registration);
204
+ return registration;
205
+ }
206
+ export function negotiateLoadEngineV2Registrations(runId, sessionId, coordinatorNonce, expectedAgentIds, registrations, nowUtcNs64) {
207
+ const expected = normalizeExpectedAgentIds(expectedAgentIds);
208
+ const now = parseCanonicalInt64(nowUtcNs64, "coordinator UTC nanoseconds");
209
+ if (registrations.length !== expected.length) {
210
+ throw new Error("Load Engine V2 capability negotiation requires the exact expected participant set.");
211
+ }
212
+ const actual = [...registrations].sort((left, right) => compareAgentIds(left.agentId, right.agentId));
213
+ if (new Set(actual.map((value) => value.agentId)).size !== actual.length
214
+ || actual.some((value, index) => value.agentId !== expected[index])) {
215
+ throw new Error("Load Engine V2 capability negotiation did not receive the exact expected participant set.");
216
+ }
217
+ for (const registration of actual) {
218
+ validateLoadEngineV2AgentId(registration.agentId);
219
+ if (registration.runId !== runId || registration.sessionId !== sessionId
220
+ || registration.coordinatorNonce !== coordinatorNonce
221
+ || registration.targetSubject !== buildLoadEngineV2ScopedTargetSubject(runId, sessionId, coordinatorNonce, registration.agentId)) {
222
+ throw new Error("Load Engine V2 registration does not match the coordinator run/session/nonce scope.");
223
+ }
224
+ validateLowercaseHash(registration.processInstanceId, "process instance id");
225
+ const issued = parseCanonicalNonNegativeInt64(registration.leaseIssuedUtcNs64, "registration lease issue");
226
+ const expires = parseCanonicalNonNegativeInt64(registration.leaseExpiresUtcNs64, "registration lease expiry");
227
+ parseCanonicalNonNegativeInt64(registration.registrationSequence64, "registration sequence");
228
+ if (issued > now || expires <= now || expires <= issued || expires - issued > 30000000000n) {
229
+ throw new Error("Load Engine V2 registration lease expired before commit.");
230
+ }
231
+ if (!hashEquals(buildLoadEngineV2RegistrationBindingHash({ ...registration, registrationBindingHash: "" }), registration.registrationBindingHash))
232
+ throw new Error("Load Engine V2 registration binding hash is invalid.");
233
+ requireCapability(registration.schedulerVersions, "2", "scheduler-v2");
234
+ requireCapability(registration.histogramVersions, "1", "histogram-v1");
235
+ requireCapability(registration.shardingVersions, "1", "global-sharding-v1");
236
+ requireCapability(registration.resultChunkVersions, "1", "result-chunks-v1");
237
+ requireCapability(registration.iterationObservationSchemas, "loadstrike.iteration-observation/1", "loadstrike.iteration-observation/1");
238
+ requireCapability(registration.planHashVersions, "LS-SP1", "canonical LS-SP1 plan hash");
239
+ }
240
+ return actual;
241
+ }
242
+ export function buildLoadEngineV2ScopedRegistrationSubject(runId, sessionId, coordinatorNonce) {
243
+ [runId, sessionId, coordinatorNonce].forEach((value, index) => validateBoundedV2Identifier(value, ["run ID", "session ID", "coordinator nonce"][index], 128));
244
+ const bytes = Buffer.concat([
245
+ Buffer.from("LS-RS1\n", "ascii"), frameUtf8("1"), frameUtf8(runId), frameUtf8(sessionId),
246
+ frameUtf8(coordinatorNonce)
247
+ ]);
248
+ return `loadstrike.v2.${sha256Hex(bytes)}.register`;
249
+ }
250
+ export function buildLoadEngineV2ScopedTargetSubject(runId, sessionId, coordinatorNonce, agentId) {
251
+ validateBoundedV2Identifier(runId, "run ID", 128);
252
+ validateBoundedV2Identifier(sessionId, "session ID", 128);
253
+ validateBoundedV2Identifier(coordinatorNonce, "coordinator nonce", 128);
254
+ validateLoadEngineV2AgentId(agentId);
255
+ const bytes = Buffer.concat([
256
+ Buffer.from("LS-TS1\n", "ascii"), frameUtf8("1"), frameUtf8(runId), frameUtf8(sessionId),
257
+ frameUtf8(coordinatorNonce), frameUtf8(agentId)
258
+ ]);
259
+ return `loadstrike.v2.target.${sha256Hex(bytes)}`;
260
+ }
261
+ export function buildLoadEngineV2GlobalInvocationId(runId, scenarioIndex, simulationIndex, identityKind, primaryOrdinal, secondaryOrdinal) {
262
+ validateBoundedV2Identifier(runId, "run ID", 128);
263
+ const scenario = parseCanonicalNonNegativeInt64(String(scenarioIndex), "scenario index");
264
+ const simulation = parseCanonicalNonNegativeInt64(String(simulationIndex), "simulation index");
265
+ const primary = parseCanonicalNonNegativeInt64(String(primaryOrdinal), "primary ordinal");
266
+ const secondary = parseCanonicalNonNegativeInt64(String(secondaryOrdinal), "secondary ordinal");
267
+ if (!["arrival", "worker-iteration", "constant-iteration"].includes(identityKind)) {
268
+ throw new Error("Load Engine V2 invocation identity kind is invalid.");
269
+ }
270
+ return sha256Hex(Buffer.concat([
271
+ Buffer.from("LS-I1\n", "ascii"),
272
+ frameUtf8(runId),
273
+ frameUtf8(scenario.toString()),
274
+ frameUtf8(simulation.toString()),
275
+ frameUtf8(identityKind),
276
+ frameUtf8(primary.toString()),
277
+ frameUtf8(secondary.toString())
278
+ ]));
279
+ }
280
+ export function createLoadEngineV2CoordinatorHello(input) {
281
+ const expectedAgentIds = normalizeExpectedAgentIds(input.expectedAgentIds);
282
+ if (input.registrationSubject !== buildLoadEngineV2ScopedRegistrationSubject(input.runId, input.sessionId, input.coordinatorNonce))
283
+ throw new Error("Load Engine V2 coordinator hello registration subject is invalid.");
284
+ parseCanonicalNonNegativeInt64(input.expiresUtcNs64, "coordinator hello expiry");
285
+ const hello = { ...input, expectedAgentIds, bindingHash: "" };
286
+ hello.bindingHash = buildLoadEngineV2CoordinatorHelloBindingHash(hello);
287
+ return hello;
288
+ }
289
+ export function validateLoadEngineV2CoordinatorHello(hello, expectedSessionId, expectedAgentId, nowUtcNs64) {
290
+ validateLoadEngineV2AgentId(expectedAgentId);
291
+ if (hello.sessionId !== expectedSessionId || !hello.expectedAgentIds.includes(expectedAgentId)) {
292
+ throw new Error("Load Engine V2 coordinator hello does not authorize this session and agent.");
293
+ }
294
+ if (hello.registrationSubject !== buildLoadEngineV2ScopedRegistrationSubject(hello.runId, hello.sessionId, hello.coordinatorNonce) || parseCanonicalNonNegativeInt64(hello.expiresUtcNs64, "coordinator hello expiry")
295
+ <= parseCanonicalNonNegativeInt64(nowUtcNs64, "coordinator hello validation time")) {
296
+ throw new Error("Load Engine V2 coordinator hello subject or expiry is invalid.");
297
+ }
298
+ const expected = buildLoadEngineV2CoordinatorHelloBindingHash({ ...hello, bindingHash: "" });
299
+ if (!hashEquals(expected, hello.bindingHash)) {
300
+ throw new Error("Load Engine V2 coordinator hello binding hash is invalid.");
301
+ }
302
+ }
303
+ function buildLoadEngineV2CoordinatorHelloBindingHash(hello) {
304
+ const expected = normalizeExpectedAgentIds(hello.expectedAgentIds);
305
+ const fields = [
306
+ Buffer.from("LS-CH1\n", "ascii"), frameUtf8("1"), frameUtf8(hello.runId),
307
+ frameUtf8(hello.sessionId), frameUtf8(hello.coordinatorNonce), frameUtf8(hello.registrationSubject),
308
+ frameUtf8(hello.expiresUtcNs64), frameUtf8(expected.length.toString()),
309
+ ...expected.map(frameUtf8)
310
+ ];
311
+ return sha256Hex(Buffer.concat(fields));
312
+ }
313
+ function buildLoadEngineV2RegistrationBindingHash(registration) {
314
+ const fields = [
315
+ Buffer.from("LS-RG1\n", "ascii"), frameUtf8("1"), frameUtf8(registration.runId),
316
+ frameUtf8(registration.sessionId), frameUtf8(registration.coordinatorNonce),
317
+ frameUtf8(registration.agentId), frameUtf8(registration.processInstanceId),
318
+ frameUtf8(registration.targetSubject), frameUtf8(registration.schedulerVisibleProcessorCount.toString()),
319
+ frameUtf8(registration.sdkLanguage), frameUtf8(registration.sdkPackageVersion),
320
+ frameUtf8(registration.runtimeVersion)
321
+ ];
322
+ for (const values of [
323
+ registration.schedulerVersions, registration.histogramVersions, registration.shardingVersions,
324
+ registration.resultChunkVersions, registration.iterationObservationSchemas,
325
+ registration.correlationSetVersions, registration.planHashVersions, registration.callbackExecutionModes
326
+ ]) {
327
+ const ordered = canonicalV2CapabilityList(values, "registration capability");
328
+ fields.push(frameUtf8(ordered.length.toString()), ...ordered.map(frameUtf8));
329
+ }
330
+ fields.push(frameUtf8(registration.maximumResultChunkBytes.toString()), frameUtf8(registration.registrationSequence64), frameUtf8(registration.leaseIssuedUtcNs64), frameUtf8(registration.leaseExpiresUtcNs64));
331
+ return sha256Hex(Buffer.concat(fields));
332
+ }
333
+ function canonicalV2CapabilityList(values, field) {
334
+ if (!Array.isArray(values) || values.length > 32 || new Set(values).size !== values.length) {
335
+ throw new Error(`Load Engine V2 registration ${field} are invalid.`);
336
+ }
337
+ values.forEach((value) => validateBoundedV2Identifier(value, field, 128));
338
+ return [...values].sort(compareUtf8);
339
+ }
340
+ function canonicalV2CallbackModes(values) {
341
+ const result = canonicalV2CapabilityList(values, "callback execution modes");
342
+ if (!result.length || result.some((value) => value !== "async" && value !== "blocking")) {
343
+ throw new Error("Load Engine V2 registration callback execution modes are invalid.");
344
+ }
345
+ return result;
346
+ }
347
+ function acceptLoadEngineV2RegistrationRenewal(registrations, next, runId, sessionId, coordinatorNonce, nowUtcNs64) {
348
+ negotiateLoadEngineV2Registrations(runId, sessionId, coordinatorNonce, [next.agentId], [next], nowUtcNs64);
349
+ const current = registrations.get(next.agentId);
350
+ if (!current) {
351
+ registrations.set(next.agentId, next);
352
+ return true;
353
+ }
354
+ const currentSequence = parseCanonicalNonNegativeInt64(current.registrationSequence64, "registration sequence");
355
+ const nextSequence = parseCanonicalNonNegativeInt64(next.registrationSequence64, "registration sequence");
356
+ if (nextSequence === currentSequence) {
357
+ if (!hashEquals(current.registrationBindingHash, next.registrationBindingHash)) {
358
+ throw new Error("Load Engine V2 registration has a conflicting retry for the same sequence.");
359
+ }
360
+ return false;
361
+ }
362
+ const immutableKeys = [
363
+ "runId", "sessionId", "coordinatorNonce", "agentId", "processInstanceId", "targetSubject",
364
+ "schedulerVisibleProcessorCount", "sdkLanguage", "sdkPackageVersion", "runtimeVersion",
365
+ "maximumResultChunkBytes"
366
+ ];
367
+ const immutableLists = [
368
+ "schedulerVersions", "histogramVersions", "shardingVersions", "resultChunkVersions",
369
+ "iterationObservationSchemas", "correlationSetVersions", "planHashVersions", "callbackExecutionModes"
370
+ ];
371
+ if (nextSequence !== currentSequence + 1n
372
+ || immutableKeys.some((key) => current[key] !== next[key])
373
+ || immutableLists.some((key) => JSON.stringify(current[key]) !== JSON.stringify(next[key]))
374
+ || parseCanonicalNonNegativeInt64(next.leaseIssuedUtcNs64, "registration lease issue")
375
+ < parseCanonicalNonNegativeInt64(current.leaseIssuedUtcNs64, "registration lease issue")
376
+ || parseCanonicalNonNegativeInt64(next.leaseExpiresUtcNs64, "registration lease expiry")
377
+ <= parseCanonicalNonNegativeInt64(current.leaseExpiresUtcNs64, "registration lease expiry")) {
378
+ throw new Error("Load Engine V2 registration lease renewal is stale or changed immutable metadata.");
379
+ }
380
+ registrations.set(next.agentId, next);
381
+ return true;
382
+ }
383
+ async function refreshLoadEngineV2Registrations(consumer, registrations, runId, sessionId, coordinatorNonce, expectedAgentIds, timeoutMs, requiredFutureNs) {
384
+ const deadline = Date.now() + timeoutMs;
385
+ for (;;) {
386
+ const now = utcNowNs();
387
+ const live = expectedAgentIds.every((agentId) => {
388
+ const registration = registrations.get(agentId);
389
+ return registration && parseCanonicalNonNegativeInt64(registration.leaseExpiresUtcNs64, "registration lease expiry") > now + requiredFutureNs;
390
+ });
391
+ if (live) {
392
+ return negotiateLoadEngineV2Registrations(runId, sessionId, coordinatorNonce, expectedAgentIds, [...registrations.values()], now.toString());
393
+ }
394
+ if (Date.now() >= deadline) {
395
+ throw new Error("Load Engine V2 registration lease renewal timed out before the next commit.");
396
+ }
397
+ const payload = await consumer.consume();
398
+ const body = recordOrUndefined(payload?.body);
399
+ if (!body || !expectedAgentIds.includes(body.agentId)) {
400
+ await sleep(5);
401
+ continue;
402
+ }
403
+ acceptLoadEngineV2RegistrationRenewal(registrations, body, runId, sessionId, coordinatorNonce, utcNowNs64());
404
+ }
405
+ }
406
+ export function buildLoadEngineV2Plan(input) {
407
+ const expected = normalizeExpectedAgentIds(input.expectedAgentIds);
408
+ validateBoundedV2Identifier(input.runId, "run ID", 128);
409
+ validateBoundedV2Identifier(input.sessionId, "session ID", 128);
410
+ validateBoundedV2Identifier(input.registrationNonce, "registration nonce", 128);
411
+ const maxInFlight = parseCanonicalPositiveInt64(input.maxInFlight64, "MaxInFlight");
412
+ const reportingInterval = parseCanonicalPositiveInt64(input.reportingIntervalNs64, "reporting interval");
413
+ const schedulerSeed = parseCanonicalNonNegativeInt64(input.schedulerSeed64 ?? "0", "scheduler seed");
414
+ const processorCount = parseCanonicalPositiveInt64(input.schedulerVisibleProcessorCount64 ?? "1", "scheduler-visible processor count");
415
+ const maxStatisticsStateBytes = parseV2Limit(input.maxStatisticsStateBytes64 ?? "536870912", "MaxStatisticsStateBytes", 67108864n, 4294967296n);
416
+ const maxDistributionSeries = parseV2Limit(input.maxDistributionSeries64 ?? "256", "MaxDistributionSeries", 16n, 100000n);
417
+ const maxCorrelationGroups = parseV2Limit(input.maxCorrelationGroups64 ?? "512", "MaxCorrelationGroups", 16n, 100000n);
418
+ const maxStatusGroups = parseV2Limit(input.maxStatusGroupsPerSeries64 ?? "64", "MaxStatusGroupsPerSeries", 8n, 10000n);
419
+ const maxPortalBytes = parseV2Limit(input.maxPortalRunDistributionBytes64 ?? "1073741824", "MaxPortalRunDistributionBytes", 134217728n, 8589934592n);
420
+ if (maxInFlight > 1000000n || reportingInterval > 9223372036854775807n
421
+ || schedulerSeed > 4294967295n || processorCount > 2147483647n) {
422
+ throw new Error("Load Engine V2 plan settings exceed the supported non-correlation profile.");
423
+ }
424
+ const scenarios = [...input.scenarios].sort((left, right) => compareCanonicalIntegers(left.scenarioIndex64, right.scenarioIndex64));
425
+ if (!scenarios.length || new Set(scenarios.map((value) => value.scenarioIndex64)).size !== scenarios.length) {
426
+ throw new Error("Load Engine V2 requires unique selected scenario declaration indexes.");
427
+ }
428
+ const declaredSteps = new Map();
429
+ const portalSeries = [];
430
+ let simulationCount = 0n;
431
+ for (const scenario of scenarios) {
432
+ parseCanonicalNonNegativeInt64(scenario.scenarioIndex64, "scenario index");
433
+ validateBoundedV2Identifier(scenario.scenarioName, "scenario name", 1024);
434
+ if (scenario.target !== "agent") {
435
+ throw new Error("The supported non-correlation LS-SP1 profile requires agent-target scenarios.");
436
+ }
437
+ const mode = scenario.callbackExecutionMode ?? "async";
438
+ if (mode !== "async" && mode !== "blocking")
439
+ throw new Error("Load Engine V2 callback mode is invalid.");
440
+ const byIdentity = new Map();
441
+ for (const stepName of scenario.declaredStepNames ?? []) {
442
+ const row = buildV2DeclaredStep(scenario.scenarioIndex64, stepName);
443
+ const key = row.identity.toString("hex");
444
+ const prior = byIdentity.get(key);
445
+ if (!prior || Buffer.compare(row.display, prior.display) < 0)
446
+ byIdentity.set(key, row);
447
+ }
448
+ const steps = [...byIdentity.values()].sort((left, right) => Buffer.compare(left.identity, right.identity));
449
+ declaredSteps.set(scenario.scenarioIndex64, steps);
450
+ addV2MeasurementPortalSeries(portalSeries, buildV2Identity(0x01, scenario.scenarioIndex64));
451
+ for (const step of steps)
452
+ addV2MeasurementPortalSeries(portalSeries, step.identity);
453
+ addV2MeasurementPortalSeries(portalSeries, buildV2Identity(0x04, scenario.scenarioIndex64));
454
+ const simulations = [...scenario.simulations].sort((left, right) => compareCanonicalIntegers(left.simulationIndex64, right.simulationIndex64));
455
+ if (!simulations.length
456
+ || new Set(simulations.map((value) => value.simulationIndex64)).size !== simulations.length) {
457
+ throw new Error("Load Engine V2 requires unique simulation indexes in every selected scenario.");
458
+ }
459
+ for (const simulation of simulations) {
460
+ validateV2Simulation(simulation);
461
+ addV2SchedulerPortalSeries(portalSeries, buildV2Identity(0x05, scenario.scenarioIndex64, simulation.simulationIndex64));
462
+ addV2SchedulerPortalSeries(portalSeries, buildV2Identity(0x06, scenario.scenarioIndex64, simulation.simulationIndex64));
463
+ simulationCount += 1n;
464
+ }
465
+ }
466
+ const staticMeasurementCount = 2n * BigInt(scenarios.length)
467
+ + [...declaredSteps.values()].reduce((sum, rows) => sum + BigInt(rows.length), 0n);
468
+ if (staticMeasurementCount > maxDistributionSeries) {
469
+ throw new Error("Load Engine V2 static scenario/step identities exceed MaxDistributionSeries.");
470
+ }
471
+ portalSeries.sort((left, right) => Buffer.compare(Buffer.from(left.seriesKeyHex, "hex"), Buffer.from(right.seriesKeyHex, "hex")) || compareUtf8(left.unit, right.unit) || compareUtf8(left.outcome, right.outcome));
472
+ if (BigInt(portalSeries.length) !== 6n * staticMeasurementCount + 2n * simulationCount) {
473
+ throw new Error("Load Engine V2 frozen portal series enumeration is incomplete.");
474
+ }
475
+ const exactPlanStringBytes = calculateV2ExactPlanStringBytes(input, expected, portalSeries, declaredSteps);
476
+ const globalAggregateScratch = calculateV2StatisticsReservation(maxDistributionSeries, simulationCount, maxStatusGroups, exactPlanStringBytes);
477
+ if (globalAggregateScratch > maxStatisticsStateBytes) {
478
+ throw new Error(`Load Engine V2 statistics reservation ${globalAggregateScratch} exceeds configured ${maxStatisticsStateBytes}.`);
479
+ }
480
+ const resultOwnerCount = BigInt(expected.length);
481
+ const receiverScratch = checkedV2Sum(globalAggregateScratch, 262144n, 131072n);
482
+ let effectivePortalPointLimit = 0n;
483
+ let portalLiveStateBytes = 0n;
484
+ for (let points = 1000n; points >= 2n; points -= 1n) {
485
+ const candidate = calculateV2PortalLiveState(BigInt(portalSeries.length), resultOwnerCount, points);
486
+ if (candidate + receiverScratch <= maxPortalBytes) {
487
+ effectivePortalPointLimit = points;
488
+ portalLiveStateBytes = candidate;
489
+ break;
490
+ }
491
+ }
492
+ if (effectivePortalPointLimit === 0n) {
493
+ throw new Error("Load Engine V2 portal reservation cannot retain the two protected endpoints.");
494
+ }
495
+ const maxEncodedNonDetailArtifactBytes = 67108864n;
496
+ const maxEncodedRecoveryOverlayBytes = 4096n;
497
+ const latestPerOwner = 262144n + maxEncodedNonDetailArtifactBytes;
498
+ const latestSnapshotSpoolBytes = BigInt(expected.length)
499
+ * (latestPerOwner + maxEncodedRecoveryOverlayBytes)
500
+ + (latestPerOwner > maxEncodedRecoveryOverlayBytes ? latestPerOwner : maxEncodedRecoveryOverlayBytes);
501
+ const blocking = scenarios.some((scenario) => (scenario.callbackExecutionMode ?? "async") === "blocking");
502
+ const configuredBlockingWorkers = blocking
503
+ ? minimumBigIntV2(maxInFlight, 1024n, maximumBigIntV2(32n, processorCount * 32n))
504
+ : 0n;
505
+ const fields = [Buffer.from("LS-SP1\n", "ascii")];
506
+ const write = (value) => { fields.push(frameUtf8(value)); };
507
+ [
508
+ "1", input.runId, input.sessionId, input.registrationNonce, "2", "2", "1", "1", "", "1",
509
+ "131072", "0", "", "", "", "0", schedulerSeed.toString(), "global",
510
+ maxInFlight.toString(), reportingInterval.toString(), "0", maxStatisticsStateBytes.toString(),
511
+ maxDistributionSeries.toString(), maxCorrelationGroups.toString(), maxStatusGroups.toString(),
512
+ maxPortalBytes.toString(), "0", "0", "0", "0", "0", "0",
513
+ "0", "-1", "0", "0", "0", "0", "0", resultOwnerCount.toString(),
514
+ expected.length.toString(), portalSeries.length.toString(), effectivePortalPointLimit.toString(),
515
+ globalAggregateScratch.toString(), portalLiveStateBytes.toString(), receiverScratch.toString(),
516
+ latestSnapshotSpoolBytes.toString(), "0", expected.length.toString()
517
+ ].forEach(write);
518
+ expected.forEach((agentId, processGroupIndex) => {
519
+ [
520
+ agentId, processGroupIndex.toString(), "1", processorCount.toString(),
521
+ configuredBlockingWorkers.toString(), maxEncodedNonDetailArtifactBytes.toString(),
522
+ maxEncodedRecoveryOverlayBytes.toString(), "0"
523
+ ].forEach(write);
524
+ });
525
+ portalSeries.forEach((series) => {
526
+ write(series.seriesKeyHex);
527
+ write(series.unit);
528
+ write(series.outcome);
529
+ });
530
+ write(scenarios.length.toString());
531
+ for (const scenario of scenarios) {
532
+ const mode = scenario.callbackExecutionMode ?? "async";
533
+ write(scenario.scenarioIndex64);
534
+ write(scenario.scenarioName);
535
+ write(scenario.target);
536
+ write("");
537
+ ["", "", "-1", "0", ""].forEach(write);
538
+ const steps = declaredSteps.get(scenario.scenarioIndex64) ?? [];
539
+ write(steps.length.toString());
540
+ steps.forEach((step) => {
541
+ write(step.identity.toString("hex"));
542
+ write(step.display.toString("hex"));
543
+ });
544
+ write("0");
545
+ write(expected.length.toString());
546
+ expected.forEach((agentId, shardIndex) => {
547
+ write(agentId);
548
+ write(shardIndex.toString());
549
+ write(expected.length.toString());
550
+ write(mode);
551
+ });
552
+ const simulations = [...scenario.simulations].sort((left, right) => compareCanonicalIntegers(left.simulationIndex64, right.simulationIndex64));
553
+ write(simulations.length.toString());
554
+ for (const simulation of simulations) {
555
+ write(simulation.simulationIndex64);
556
+ write("-1");
557
+ write("");
558
+ write(simulation.kind);
559
+ [simulation.rate64, simulation.minRate64, simulation.maxRate64, simulation.copies64,
560
+ simulation.iterations64, simulation.intervalNs64, simulation.durationNs64, "0"].forEach(write);
561
+ }
562
+ }
563
+ const bytes = Buffer.concat(fields);
564
+ return { bytes, hash: sha256Hex(bytes), expectedAgentIds: expected, portalSeries };
565
+ }
566
+ /** Canonical LoadStrikeIdentityKeyV1 scenario bytes. */
567
+ export function buildLoadEngineV2ScenarioIdentityKey(scenarioIndex64) {
568
+ parseCanonicalNonNegativeInt64(scenarioIndex64, "scenario identity index");
569
+ return buildV2Identity(0x01, scenarioIndex64);
570
+ }
571
+ /** Canonical LoadStrikeIdentityKeyV1 reserved step-other bytes. */
572
+ export function buildLoadEngineV2ReservedStepOtherIdentityKey(scenarioIndex64) {
573
+ parseCanonicalNonNegativeInt64(scenarioIndex64, "reserved step identity index");
574
+ return buildV2Identity(0x04, scenarioIndex64);
575
+ }
576
+ export function buildLoadEngineV2StepIdentityKey(scenarioIndex64, stepName) {
577
+ parseCanonicalNonNegativeInt64(scenarioIndex64, "step identity index");
578
+ const row = buildV2DeclaredStep(scenarioIndex64, stepName);
579
+ return { identity: row.identity, display: new TextDecoder("utf-8", { fatal: true }).decode(row.display) };
580
+ }
581
+ /** Resolves a runtime step to its frozen signed identity or the reserved `<other>` bucket. */
582
+ export function resolveLoadEngineV2StepIdentityKey(scenarioIndex64, stepName, declaredStepNames) {
583
+ const observed = buildV2DeclaredStep(scenarioIndex64, stepName);
584
+ let signedDisplay;
585
+ for (const declaredName of declaredStepNames) {
586
+ const declared = buildV2DeclaredStep(scenarioIndex64, declaredName);
587
+ if (!declared.identity.equals(observed.identity))
588
+ continue;
589
+ if (!signedDisplay || Buffer.compare(declared.display, signedDisplay) < 0) {
590
+ signedDisplay = declared.display;
591
+ }
592
+ }
593
+ if (!signedDisplay) {
594
+ return {
595
+ identity: buildLoadEngineV2ReservedStepOtherIdentityKey(scenarioIndex64),
596
+ display: "<other>",
597
+ routedToOther: true
598
+ };
599
+ }
600
+ return {
601
+ identity: observed.identity,
602
+ display: new TextDecoder("utf-8", { fatal: true }).decode(signedDisplay),
603
+ routedToOther: false
604
+ };
605
+ }
606
+ export function buildLoadEngineV2SchedulerIdentityKey(kind, scenarioIndex64, simulationIndex64) {
607
+ parseCanonicalNonNegativeInt64(scenarioIndex64, "scheduler identity scenario index");
608
+ parseCanonicalNonNegativeInt64(simulationIndex64, "scheduler identity simulation index");
609
+ return buildV2Identity(kind === "decision" ? 0x05 : 0x06, scenarioIndex64, simulationIndex64);
610
+ }
611
+ export function buildLoadEngineV2StatusIdentityKey(status, message) {
612
+ validateUnicodeScalars(status);
613
+ validateUnicodeScalars(message);
614
+ if (!status && !message)
615
+ return undefined;
616
+ const payload = Buffer.concat([Buffer.from("LS-ST1\n", "ascii"), frameUtf8(status), frameUtf8(message)]);
617
+ const digest = sha256Hex(payload);
618
+ const identity = payload.length <= 1024
619
+ ? buildV2Identity(0x0b, status, message)
620
+ : buildV2Identity(0x0c, payload.length.toString(), digest);
621
+ const source = !message ? status : !status ? message : `${status}: ${message}`;
622
+ let prefix = "";
623
+ for (const scalar of source) {
624
+ if (Buffer.byteLength(prefix + scalar, "utf8") > 512)
625
+ break;
626
+ prefix += scalar;
627
+ }
628
+ return {
629
+ identity,
630
+ display: Buffer.byteLength(source, "utf8") <= 512 ? source : `${prefix}…#${digest.slice(0, 32)}`
631
+ };
632
+ }
633
+ /** Canonical LoadStrikePortalSeriesKeyV1 bytes. */
634
+ export function buildLoadEngineV2PortalSeriesKey(identity, unit, outcome) {
635
+ if (!identity.length)
636
+ throw new Error("Load Engine V2 portal identity cannot be empty.");
637
+ return Buffer.concat([
638
+ Buffer.from("LS-PS1\n", "ascii"),
639
+ frameUtf8("1"),
640
+ frameBytes(Buffer.from(identity)),
641
+ frameUtf8(unit),
642
+ frameUtf8(outcome)
643
+ ]);
644
+ }
645
+ function buildV2Identity(tag, ...values) {
646
+ if (!Number.isInteger(tag) || tag < 1 || tag > 0x0e) {
647
+ throw new Error("Load Engine V2 identity tag is invalid.");
648
+ }
649
+ return Buffer.concat([
650
+ Buffer.from("LS-ID1\n", "ascii"),
651
+ Buffer.from([tag]),
652
+ ...values.map(frameUtf8)
653
+ ]);
654
+ }
655
+ function buildV2DeclaredStep(scenarioIndex64, stepName) {
656
+ validateUnicodeScalars(stepName);
657
+ const bytes = Buffer.from(stepName, "utf8");
658
+ if (bytes.length <= 1024) {
659
+ return { identity: buildV2Identity(0x02, scenarioIndex64, stepName), display: bytes };
660
+ }
661
+ const digest = sha256Hex(bytes);
662
+ let prefix = "";
663
+ for (const scalar of stepName) {
664
+ if (Buffer.byteLength(prefix + scalar, "utf8") > 512)
665
+ break;
666
+ prefix += scalar;
667
+ }
668
+ return {
669
+ identity: buildV2Identity(0x03, scenarioIndex64, bytes.length.toString(), digest),
670
+ display: Buffer.from(`${prefix}…#${digest.slice(0, 32)}`, "utf8")
671
+ };
672
+ }
673
+ function addV2MeasurementPortalSeries(target, identity) {
674
+ for (const unit of ["microseconds", "bytes"]) {
675
+ for (const outcome of ["ok", "fail", "all"]) {
676
+ target.push({
677
+ seriesKeyHex: buildLoadEngineV2PortalSeriesKey(identity, unit, outcome).toString("hex"),
678
+ unit,
679
+ outcome
680
+ });
681
+ }
682
+ }
683
+ }
684
+ function addV2SchedulerPortalSeries(target, identity) {
685
+ target.push({
686
+ seriesKeyHex: buildLoadEngineV2PortalSeriesKey(identity, "microseconds", "none").toString("hex"),
687
+ unit: "microseconds",
688
+ outcome: "none"
689
+ });
690
+ }
691
+ function validateV2Simulation(simulation) {
692
+ const allowed = new Set([
693
+ "inject", "inject-random", "ramping-inject", "keep-constant", "ramping-constant",
694
+ "iterations-for-inject", "iterations-for-constant", "pause"
695
+ ]);
696
+ if (!allowed.has(simulation.kind))
697
+ throw new Error("Load Engine V2 simulation kind is invalid.");
698
+ [simulation.simulationIndex64, simulation.rate64, simulation.minRate64,
699
+ simulation.maxRate64, simulation.copies64, simulation.iterations64,
700
+ simulation.intervalNs64, simulation.durationNs64]
701
+ .forEach((value) => parseCanonicalNonNegativeInt64(value, "simulation plan integer"));
702
+ }
703
+ function calculateV2ExactPlanStringBytes(input, expectedAgentIds, portalSeries, declaredSteps) {
704
+ let total = 0n;
705
+ const addText = (value) => { total += BigInt(Buffer.byteLength(value, "utf8")); };
706
+ [input.runId, input.sessionId, input.registrationNonce, ...expectedAgentIds].forEach(addText);
707
+ for (const scenario of input.scenarios) {
708
+ addText(scenario.scenarioName);
709
+ // Agent-target scenarios have no coordinator callback mode. Each signed
710
+ // participant owns both its agent ID and callback execution mode bytes.
711
+ for (const agentId of expectedAgentIds) {
712
+ addText(agentId);
713
+ addText(scenario.callbackExecutionMode ?? "async");
714
+ }
715
+ for (const simulation of scenario.simulations)
716
+ addText(simulation.kind);
717
+ for (const step of declaredSteps.get(scenario.scenarioIndex64) ?? []) {
718
+ total += BigInt(step.identity.length + step.display.length);
719
+ }
720
+ }
721
+ for (const series of portalSeries) {
722
+ total += BigInt(series.seriesKeyHex.length / 2);
723
+ addText(series.unit);
724
+ addText(series.outcome);
725
+ }
726
+ if (total > 9223372036854775807n)
727
+ throw new Error("Load Engine V2 plan strings exceed signed storage.");
728
+ return total;
729
+ }
730
+ function calculateV2StatisticsReservation(maxDistributionSeries, simulationCount, maxStatusGroups, exactPlanStringBytes) {
731
+ return checkedV2Sum(maxDistributionSeries * 786432n, maxDistributionSeries * (256n + 1088n), 2n * maxDistributionSeries * maxStatusGroups * (128n + 1664n), simulationCount * 212992n, 16n * 1024n * 1024n, 1024n * 1024n, exactPlanStringBytes);
732
+ }
733
+ function calculateV2PortalLiveState(frozenSeriesCount, resultOwnerCount, pointLimit) {
734
+ return checkedV2Sum(frozenSeriesCount * ((resultOwnerCount + 1n) * 98304n + pointLimit * 256n + 4096n), resultOwnerCount * 4096n, 1048576n);
735
+ }
736
+ function checkedV2Sum(...values) {
737
+ let result = 0n;
738
+ for (const value of values) {
739
+ if (value < 0n)
740
+ throw new Error("Load Engine V2 resource calculation became negative.");
741
+ result += value;
742
+ if (result > 9223372036854775807n) {
743
+ throw new Error("Load Engine V2 resource calculation exceeds signed 64-bit storage.");
744
+ }
745
+ }
746
+ return result;
747
+ }
748
+ function parseV2Limit(value, field, minimum, maximum) {
749
+ const parsed = parseCanonicalNonNegativeInt64(value, field);
750
+ if (parsed < minimum || parsed > maximum) {
751
+ throw new Error(`${field} must be from ${minimum} through ${maximum}.`);
752
+ }
753
+ return parsed;
754
+ }
755
+ function parseCanonicalPositiveInt64(value, field) {
756
+ const parsed = parseCanonicalNonNegativeInt64(value, field);
757
+ if (parsed === 0n)
758
+ throw new Error(`${field} must be positive.`);
759
+ return parsed;
760
+ }
761
+ function validateBoundedV2Identifier(value, field, maximumBytes) {
762
+ validateUnicodeScalars(value);
763
+ const length = Buffer.byteLength(value, "utf8");
764
+ if (length === 0 || length > maximumBytes) {
765
+ throw new Error(`Load Engine V2 ${field} must be from 1 through ${maximumBytes} UTF-8 bytes.`);
766
+ }
767
+ }
768
+ function frameBytes(value) {
769
+ if (value.length > 4294967295)
770
+ throw new Error("Load Engine V2 canonical bytes exceed the framing limit.");
771
+ const frame = Buffer.allocUnsafe(4 + value.length);
772
+ frame.writeUInt32BE(value.length, 0);
773
+ value.copy(frame, 4);
774
+ return frame;
775
+ }
776
+ function minimumBigIntV2(...values) {
777
+ return values.reduce((minimum, value) => value < minimum ? value : minimum);
778
+ }
779
+ function maximumBigIntV2(...values) {
780
+ return values.reduce((maximum, value) => value > maximum ? value : maximum);
781
+ }
782
+ function orderedLoadEngineV2Segments(input) {
783
+ return [...input.scenarios]
784
+ .sort((left, right) => compareCanonicalIntegers(left.scenarioIndex64, right.scenarioIndex64))
785
+ .flatMap((scenario) => [...scenario.simulations]
786
+ .sort((left, right) => compareCanonicalIntegers(left.simulationIndex64, right.simulationIndex64))
787
+ .map((simulation) => ({
788
+ scenarioIndex64: scenario.scenarioIndex64,
789
+ simulationIndex64: simulation.simulationIndex64
790
+ })));
791
+ }
792
+ export function buildLoadEngineV2BarrierScopeId(planHash, scope) {
793
+ validateLowercaseHash(planHash, "plan hash");
794
+ const fields = [Buffer.from("LS-BS1\n", "ascii"), frameUtf8(planHash), frameUtf8(scope.kind)];
795
+ if (scope.kind === "scenario") {
796
+ parseCanonicalNonNegativeInt64(scope.scenarioIndex64, "barrier scenario index");
797
+ parseCanonicalNonNegativeInt64(scope.simulationIndex64, "barrier simulation index");
798
+ fields.push(frameUtf8(scope.scenarioIndex64), frameUtf8(scope.simulationIndex64));
799
+ }
800
+ else {
801
+ validateLowercaseHash(scope.trafficMixId, "traffic mix id");
802
+ parseCanonicalNonNegativeInt64(scope.trafficMixPhaseIndex64, "traffic mix phase index");
803
+ fields.push(frameUtf8(scope.trafficMixId), frameUtf8(scope.trafficMixPhaseIndex64));
804
+ }
805
+ return sha256Hex(Buffer.concat(fields));
806
+ }
807
+ export function buildLoadEngineV2AssignmentHash(barrierScopeId, assignments) {
808
+ validateLowercaseHash(barrierScopeId, "barrier scope id");
809
+ if (!assignments.length)
810
+ throw new Error("Load Engine V2 barrier assignment requires participants.");
811
+ const ordered = [...assignments].sort(compareBarrierAssignments);
812
+ const fields = [Buffer.from("LS-BA1\n", "ascii")];
813
+ const write = (value) => { fields.push(frameUtf8(value)); };
814
+ write("1");
815
+ write(barrierScopeId);
816
+ write(ordered.length.toString());
817
+ const identities = new Set();
818
+ for (const assignment of ordered) {
819
+ validateLoadEngineV2AgentId(assignment.agentId);
820
+ parseCanonicalNonNegativeInt64(assignment.scenarioIndex64, "scenario index");
821
+ parseCanonicalNonNegativeInt64(assignment.simulationIndex64, "simulation index");
822
+ const lane = assignment.trafficMixLaneIndex64 ?? "-1";
823
+ const phase = assignment.trafficMixPhaseIndex64 ?? "-1";
824
+ parseCanonicalInt64(lane, "traffic mix lane index");
825
+ parseCanonicalInt64(phase, "traffic mix phase index");
826
+ parseCanonicalNonNegativeInt64(assignment.shardIndex64, "shard index");
827
+ const shardCount = parseCanonicalNonNegativeInt64(assignment.shardCount64, "shard count");
828
+ if (shardCount <= 0n || BigInt(assignment.shardIndex64) >= shardCount) {
829
+ throw new Error("Load Engine V2 shard assignment is invalid.");
830
+ }
831
+ const identity = [assignment.scenarioIndex64, assignment.simulationIndex64,
832
+ assignment.trafficMixId ?? "", lane, phase, assignment.agentId, assignment.shardIndex64].join("\u0000");
833
+ if (identities.has(identity))
834
+ throw new Error("Load Engine V2 barrier assignment contains a duplicate record.");
835
+ identities.add(identity);
836
+ write(assignment.scenarioIndex64);
837
+ write(assignment.simulationIndex64);
838
+ write(assignment.trafficMixId ?? "");
839
+ write(lane);
840
+ write(phase);
841
+ write(assignment.agentId);
842
+ write(assignment.shardIndex64);
843
+ write(assignment.shardCount64);
844
+ }
845
+ return sha256Hex(Buffer.concat(fields));
846
+ }
847
+ function compareBarrierAssignments(left, right) {
848
+ const numeric = (a, b) => {
849
+ const av = parseCanonicalInt64(a, "barrier assignment index");
850
+ const bv = parseCanonicalInt64(b, "barrier assignment index");
851
+ return av < bv ? -1 : av > bv ? 1 : 0;
852
+ };
853
+ return numeric(left.scenarioIndex64, right.scenarioIndex64)
854
+ || numeric(left.simulationIndex64, right.simulationIndex64)
855
+ || numeric(left.trafficMixLaneIndex64 ?? "-1", right.trafficMixLaneIndex64 ?? "-1")
856
+ || compareAgentIds(left.agentId, right.agentId)
857
+ || numeric(left.shardIndex64, right.shardIndex64);
858
+ }
859
+ export function evaluateLoadEngineV2ClockProbe(sample) {
860
+ if (!Number.isInteger(sample.probeIndex) || sample.probeIndex < 0 || sample.probeIndex > 4) {
861
+ throw new Error("Load Engine V2 clock probe index must be from zero through four.");
862
+ }
863
+ const c0 = parseCanonicalInt64(sample.coordinatorSendUtcNs64, "clock c0");
864
+ const a1 = parseCanonicalInt64(sample.agentReceiveUtcNs64, "clock a1");
865
+ const a2 = parseCanonicalInt64(sample.agentSendUtcNs64, "clock a2");
866
+ const c3 = parseCanonicalInt64(sample.coordinatorReceiveUtcNs64, "clock c3");
867
+ const coordinatorResolution = parseCanonicalNonNegativeInt64(sample.coordinatorClockResolutionNs64, "coordinator clock resolution");
868
+ const agentResolution = parseCanonicalNonNegativeInt64(sample.agentClockResolutionNs64, "agent clock resolution");
869
+ if (c3 < c0 || a2 < a1)
870
+ throw new Error("Load Engine V2 clock probe timestamps moved backwards.");
871
+ const roundTrip = (c3 - c0) - (a2 - a1);
872
+ if (roundTrip < 0n)
873
+ throw new Error("Load Engine V2 clock probe round trip is negative.");
874
+ const offset = ((a1 - c0) + (a2 - c3)) / 2n;
875
+ const uncertainty = ((roundTrip + 1n) / 2n) + coordinatorResolution + agentResolution;
876
+ return {
877
+ ...sample,
878
+ roundTripNs64: roundTrip.toString(),
879
+ offsetAgentMinusCoordinatorNs64: offset.toString(),
880
+ uncertaintyNs64: uncertainty.toString()
881
+ };
882
+ }
883
+ export function selectLoadEngineV2ClockProbe(samples, latenessToleranceNs64) {
884
+ if (samples.length !== 5 || new Set(samples.map((value) => value.probeIndex)).size !== 5) {
885
+ throw new Error("Load Engine V2 clock negotiation requires exactly five distinct probes.");
886
+ }
887
+ const selected = samples.map(evaluateLoadEngineV2ClockProbe).sort((left, right) => {
888
+ const uncertainty = parseCanonicalInt64(left.uncertaintyNs64, "uncertainty")
889
+ - parseCanonicalInt64(right.uncertaintyNs64, "uncertainty");
890
+ if (uncertainty !== 0n)
891
+ return uncertainty < 0n ? -1 : 1;
892
+ const roundTrip = parseCanonicalInt64(left.roundTripNs64, "round trip")
893
+ - parseCanonicalInt64(right.roundTripNs64, "round trip");
894
+ return roundTrip === 0n ? left.probeIndex - right.probeIndex : roundTrip < 0n ? -1 : 1;
895
+ })[0];
896
+ let bound = 25000000n;
897
+ if (latenessToleranceNs64 !== undefined) {
898
+ const tolerance = parseCanonicalNonNegativeInt64(latenessToleranceNs64, "lateness tolerance");
899
+ if (tolerance < bound)
900
+ bound = tolerance;
901
+ }
902
+ if (parseCanonicalInt64(selected.uncertaintyNs64, "clock uncertainty") > bound) {
903
+ throw new Error("Load Engine V2 clock uncertainty exceeds the permitted start-barrier bound.");
904
+ }
905
+ return selected;
906
+ }
907
+ export function validateLoadEngineV2BarrierAcks(phase, expectedAgentIds, acknowledgements, planHash, assignmentHash, barrierEpoch64, nowUtcNs64, barrierScopeId) {
908
+ const expected = normalizeExpectedAgentIds(expectedAgentIds);
909
+ const ordered = [...acknowledgements].sort((left, right) => compareAgentIds(left.agentId, right.agentId));
910
+ if (ordered.length !== expected.length || ordered.some((value, index) => value.agentId !== expected[index])) {
911
+ throw new Error(`Load Engine V2 ${phase} barrier is missing an exact participant acknowledgement.`);
912
+ }
913
+ const now = parseCanonicalInt64(nowUtcNs64, "barrier validation time");
914
+ for (const acknowledgement of ordered) {
915
+ const deadline = parseCanonicalInt64(acknowledgement.deadlineUtcNs64, "barrier deadline");
916
+ const acknowledged = parseCanonicalInt64(acknowledgement.acknowledgedUtcNs64, "barrier acknowledgement time");
917
+ if (!acknowledgement.accepted || acknowledgement.phase !== phase
918
+ || acknowledgement.planHash !== planHash || acknowledgement.assignmentHash !== assignmentHash
919
+ || (barrierScopeId !== undefined && acknowledgement.barrierScopeId !== barrierScopeId)
920
+ || acknowledgement.barrierEpoch64 !== barrierEpoch64 || acknowledged >= deadline) {
921
+ throw new Error(`Load Engine V2 ${phase} barrier acknowledgement is invalid.`);
922
+ }
923
+ if (now >= deadline)
924
+ throw new Error(`Load Engine V2 ${phase} barrier deadline has elapsed.`);
925
+ }
926
+ }
927
+ const LOAD_ENGINE_V2_RESULT_EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
928
+ const LOAD_ENGINE_V2_MAX_MANIFEST_BYTES = 262144;
929
+ const LOAD_ENGINE_V2_MAX_MANIFEST_ENTRIES = 1029;
930
+ const LOAD_ENGINE_V2_MAX_DETAIL_PARTS = 1024;
931
+ const LOAD_ENGINE_V2_MAIN_ARTIFACTS = [
932
+ ["scheduler", "scheduler-snapshot-v2"],
933
+ ["histograms", "histogram-set-v1"],
934
+ ["correlations", "correlation-set-v1"],
935
+ ["portal-deltas", "portal-delta-set-v1"],
936
+ ["correlation-detail-index", "correlation-detail-index-v1"]
937
+ ];
938
+ export function loadEngineV2PresentArtifact(kind, artifactId, schema, payload) {
939
+ return { kind, artifactId, schema, applicability: "present", payload: Buffer.from(payload) };
940
+ }
941
+ export function loadEngineV2EmptyArtifact(kind, artifactId, schema) {
942
+ return { kind, artifactId, schema, applicability: "empty", payload: Buffer.alloc(0) };
943
+ }
944
+ export function loadEngineV2NotApplicableArtifact(kind, artifactId) {
945
+ return { kind, artifactId, schema: "0", applicability: "not-applicable", payload: Buffer.alloc(0) };
946
+ }
947
+ export function createLoadEngineV2ResultSnapshot(runId, sessionId, resultOwnerId, snapshotSequence64, planHash, isFinal, chunkBytes, artifacts) {
948
+ validateV2ResultScope(runId, "run ID");
949
+ validateV2ResultScope(sessionId, "session ID");
950
+ validateV2ResultOwner(resultOwnerId);
951
+ validateLowercaseHash(planHash, "plan hash");
952
+ parseCanonicalNonNegativeInt64(snapshotSequence64, "snapshot sequence");
953
+ validateV2ResultChunkBound(chunkBytes);
954
+ const source = artifacts.map((artifact) => ({ ...artifact, payload: Buffer.from(artifact.payload) }));
955
+ validateV2ResultArtifactOrder(source);
956
+ const entries = source.map((artifact) => {
957
+ if (artifact.applicability === "present" && artifact.payload.length > 0) {
958
+ return {
959
+ kind: artifact.kind, artifactId: artifact.artifactId, schema: artifact.schema,
960
+ applicability: "present", uncompressedLength64: artifact.payload.length.toString(),
961
+ chunkCount: Math.ceil(artifact.payload.length / chunkBytes),
962
+ artifactSha256: sha256Hex(artifact.payload)
963
+ };
964
+ }
965
+ if (artifact.applicability === "empty" && artifact.payload.length === 0) {
966
+ return {
967
+ kind: artifact.kind, artifactId: artifact.artifactId, schema: artifact.schema,
968
+ applicability: "empty", uncompressedLength64: "0", chunkCount: 0,
969
+ artifactSha256: LOAD_ENGINE_V2_RESULT_EMPTY_SHA256
970
+ };
971
+ }
972
+ if (artifact.applicability === "not-applicable" && artifact.schema === "0"
973
+ && artifact.payload.length === 0) {
974
+ return {
975
+ kind: artifact.kind, artifactId: artifact.artifactId, schema: "0",
976
+ applicability: "not-applicable", uncompressedLength64: "0", chunkCount: 0,
977
+ artifactSha256: ""
978
+ };
979
+ }
980
+ throw new Error("Load Engine V2 artifact applicability or payload is invalid.");
981
+ });
982
+ const partialManifest = {
983
+ runId, sessionId, resultOwnerId, snapshotSequence64, planHash, isFinal,
984
+ correlationDetailPartCount: source.length - 5, entries,
985
+ bytes: Buffer.alloc(0), hash: ""
986
+ };
987
+ const manifestBytes = encodeLoadEngineV2ResultManifest(partialManifest);
988
+ if (manifestBytes.length > LOAD_ENGINE_V2_MAX_MANIFEST_BYTES) {
989
+ throw new Error("Load Engine V2 manifest exceeds its encoded bound.");
990
+ }
991
+ const manifest = {
992
+ ...partialManifest, bytes: manifestBytes, hash: sha256Hex(manifestBytes)
993
+ };
994
+ const chunks = [];
995
+ let nonDetailBytes = 0n;
996
+ let detailBytes = 0n;
997
+ entries.forEach((entry, entryIndex) => {
998
+ const payload = source[entryIndex].payload;
999
+ if (entry.applicability === "present") {
1000
+ for (let chunkIndex = 0; chunkIndex < entry.chunkCount; chunkIndex += 1) {
1001
+ const chunkPayload = Buffer.from(payload.subarray(chunkIndex * chunkBytes, Math.min((chunkIndex + 1) * chunkBytes, payload.length)));
1002
+ const partial = {
1003
+ runId, sessionId, resultOwnerId, snapshotSequence64,
1004
+ manifestSha256: manifest.hash, kind: entry.kind, artifactId: entry.artifactId,
1005
+ schema: entry.schema, artifactSha256: entry.artifactSha256,
1006
+ chunkIndex, chunkCount: entry.chunkCount,
1007
+ uncompressedTotalLength64: entry.uncompressedLength64,
1008
+ payload: chunkPayload, chunkSha256: sha256Hex(chunkPayload), bytes: Buffer.alloc(0)
1009
+ };
1010
+ chunks.push({ ...partial, bytes: encodeLoadEngineV2ResultChunk(partial) });
1011
+ }
1012
+ }
1013
+ const length = BigInt(entry.uncompressedLength64);
1014
+ if (entry.kind === "correlation-detail")
1015
+ detailBytes += length;
1016
+ else
1017
+ nonDetailBytes += length;
1018
+ });
1019
+ const partialCommit = {
1020
+ runId, sessionId, resultOwnerId, snapshotSequence64, planHash,
1021
+ manifestLength: manifestBytes.length, manifestSha256: manifest.hash,
1022
+ entryCount: entries.length, totalNonDetailBytes64: nonDetailBytes.toString(),
1023
+ totalDetailBytes64: detailBytes.toString(), bytes: Buffer.alloc(0)
1024
+ };
1025
+ return {
1026
+ manifest, chunks,
1027
+ commit: { ...partialCommit, bytes: encodeLoadEngineV2ResultCommit(partialCommit) }
1028
+ };
1029
+ }
1030
+ export function decodeLoadEngineV2ResultManifest(bytesValue) {
1031
+ const bytes = Buffer.from(bytesValue);
1032
+ if (bytes.length > LOAD_ENGINE_V2_MAX_MANIFEST_BYTES) {
1033
+ throw new Error("Load Engine V2 manifest exceeds its encoded bound.");
1034
+ }
1035
+ const cursor = new LoadEngineV2ResultCursor(bytes);
1036
+ cursor.requireMagic("LS-M1\n");
1037
+ cursor.require("1", "manifest version");
1038
+ const manifest = {
1039
+ runId: cursor.readText(), sessionId: cursor.readText(), resultOwnerId: cursor.readText(),
1040
+ snapshotSequence64: cursor.readNonNegative("snapshot sequence"), planHash: cursor.readText(),
1041
+ isFinal: cursor.readBit("final bit"), correlationDetailPartCount: cursor.readCount("detail part count", LOAD_ENGINE_V2_MAX_DETAIL_PARTS), entries: [], bytes, hash: sha256Hex(bytes)
1042
+ };
1043
+ const entryCount = cursor.readCount("manifest entry count", LOAD_ENGINE_V2_MAX_MANIFEST_ENTRIES);
1044
+ if (entryCount !== 5 + manifest.correlationDetailPartCount) {
1045
+ throw new Error("Load Engine V2 manifest entry count does not reconcile.");
1046
+ }
1047
+ for (let index = 0; index < entryCount; index += 1) {
1048
+ manifest.entries.push({
1049
+ kind: cursor.readText(), artifactId: cursor.readText(), schema: cursor.readText(),
1050
+ applicability: cursor.readText(),
1051
+ uncompressedLength64: cursor.readNonNegative("artifact length"),
1052
+ chunkCount: cursor.readCount("chunk count", 2147483647), artifactSha256: cursor.readText()
1053
+ });
1054
+ }
1055
+ cursor.requireEnd();
1056
+ validateDecodedV2ResultManifest(manifest);
1057
+ if (!manifest.bytes.equals(encodeLoadEngineV2ResultManifest(manifest))) {
1058
+ throw new Error("Load Engine V2 manifest encoding is invalid.");
1059
+ }
1060
+ return manifest;
1061
+ }
1062
+ export function decodeLoadEngineV2ResultChunk(bytesValue, maximumChunkBytes) {
1063
+ validateV2ResultChunkBound(maximumChunkBytes);
1064
+ const bytes = Buffer.from(bytesValue);
1065
+ if (bytes.length > maximumChunkBytes + 4096) {
1066
+ throw new Error("Load Engine V2 result chunk exceeds its encoded bound.");
1067
+ }
1068
+ const cursor = new LoadEngineV2ResultCursor(bytes);
1069
+ cursor.requireMagic("LS-RC1\n");
1070
+ cursor.require("1", "result chunk version");
1071
+ const runId = cursor.readText();
1072
+ const sessionId = cursor.readText();
1073
+ const resultOwnerId = cursor.readText();
1074
+ const snapshotSequence64 = cursor.readNonNegative("snapshot sequence");
1075
+ const manifestSha256 = cursor.readText();
1076
+ const kind = cursor.readText();
1077
+ const artifactId = cursor.readText();
1078
+ const schema = cursor.readText();
1079
+ const artifactSha256 = cursor.readText();
1080
+ const chunkIndex = cursor.readCount("chunk index", 2147483647);
1081
+ const chunkCount = cursor.readCount("chunk count", 2147483647);
1082
+ const uncompressedTotalLength64 = cursor.readNonNegative("uncompressed total length");
1083
+ const chunkLength = cursor.readCount("chunk length", maximumChunkBytes);
1084
+ const chunkSha256 = cursor.readText();
1085
+ const payload = cursor.readRaw(chunkLength);
1086
+ cursor.requireEnd();
1087
+ const result = {
1088
+ runId, sessionId, resultOwnerId, snapshotSequence64, manifestSha256, kind,
1089
+ artifactId, schema, artifactSha256, chunkIndex, chunkCount,
1090
+ uncompressedTotalLength64, payload, chunkSha256, bytes
1091
+ };
1092
+ if (!hashEquals(sha256Hex(payload), chunkSha256)
1093
+ || !bytes.equals(encodeLoadEngineV2ResultChunk(result))) {
1094
+ throw new Error("Load Engine V2 result chunk hash or encoding is invalid.");
1095
+ }
1096
+ return result;
1097
+ }
1098
+ export function decodeLoadEngineV2ResultCommit(bytesValue) {
1099
+ const bytes = Buffer.from(bytesValue);
1100
+ if (bytes.length > 4096)
1101
+ throw new Error("Load Engine V2 snapshot commit exceeds its encoded bound.");
1102
+ const cursor = new LoadEngineV2ResultCursor(bytes);
1103
+ cursor.requireMagic("LS-MC1\n");
1104
+ cursor.require("1", "snapshot commit version");
1105
+ const result = {
1106
+ runId: cursor.readText(), sessionId: cursor.readText(), resultOwnerId: cursor.readText(),
1107
+ snapshotSequence64: cursor.readNonNegative("snapshot sequence"), planHash: cursor.readText(),
1108
+ manifestLength: cursor.readCount("manifest length", LOAD_ENGINE_V2_MAX_MANIFEST_BYTES),
1109
+ manifestSha256: cursor.readText(),
1110
+ entryCount: cursor.readCount("entry count", LOAD_ENGINE_V2_MAX_MANIFEST_ENTRIES),
1111
+ totalNonDetailBytes64: cursor.readNonNegative("non-detail byte total"),
1112
+ totalDetailBytes64: cursor.readNonNegative("detail byte total"), bytes
1113
+ };
1114
+ cursor.requireEnd();
1115
+ if (!bytes.equals(encodeLoadEngineV2ResultCommit(result))) {
1116
+ throw new Error("Load Engine V2 snapshot commit encoding is invalid.");
1117
+ }
1118
+ return result;
1119
+ }
1120
+ export class LoadEngineV2ResultSnapshotReceiver {
1121
+ constructor(runId, sessionId, resultOwnerId, planHash, chunkBytes, maximumResultBytes) {
1122
+ this.runId = runId;
1123
+ this.sessionId = sessionId;
1124
+ this.resultOwnerId = resultOwnerId;
1125
+ this.planHash = planHash;
1126
+ this.chunkBytes = chunkBytes;
1127
+ this.maximumResultBytes = maximumResultBytes;
1128
+ this.lastSequence = -1n;
1129
+ this.lastManifestHash = "";
1130
+ this.lastCommitBytes = Buffer.alloc(0);
1131
+ this.lastArtifacts = new Map();
1132
+ this.ownerLost = false;
1133
+ validateV2ResultScope(runId, "run ID");
1134
+ validateV2ResultScope(sessionId, "session ID");
1135
+ validateV2ResultOwner(resultOwnerId);
1136
+ validateLowercaseHash(planHash, "plan hash");
1137
+ validateV2ResultChunkBound(chunkBytes);
1138
+ if (!Number.isSafeInteger(maximumResultBytes) || maximumResultBytes <= 0) {
1139
+ throw new RangeError("Load Engine V2 receiver bounds are invalid.");
1140
+ }
1141
+ }
1142
+ begin(manifest) {
1143
+ if (this.ownerLost)
1144
+ throw new Error("Load Engine V2 result owner is sealed as lost.");
1145
+ if (manifest.runId !== this.runId || manifest.sessionId !== this.sessionId
1146
+ || manifest.resultOwnerId !== this.resultOwnerId || manifest.planHash !== this.planHash
1147
+ || !manifest.bytes.equals(encodeLoadEngineV2ResultManifest(manifest))
1148
+ || !hashEquals(manifest.hash, sha256Hex(manifest.bytes))) {
1149
+ throw new Error("Load Engine V2 manifest has an unsigned scope or invalid hash.");
1150
+ }
1151
+ const sequence = BigInt(manifest.snapshotSequence64);
1152
+ if (sequence === this.lastSequence && manifest.hash === this.lastManifestHash)
1153
+ return;
1154
+ if (sequence !== this.lastSequence + 1n) {
1155
+ throw new Error("Load Engine V2 snapshot sequence is stale or skipped.");
1156
+ }
1157
+ if (this.pending) {
1158
+ if (this.pending.manifest.hash === manifest.hash)
1159
+ return;
1160
+ throw new Error("Load Engine V2 result owner already has an uncommitted snapshot.");
1161
+ }
1162
+ let total = 0n;
1163
+ for (const entry of manifest.entries) {
1164
+ const length = BigInt(entry.uncompressedLength64);
1165
+ if (entry.applicability === "present"
1166
+ && entry.chunkCount !== Number((length + BigInt(this.chunkBytes) - 1n) / BigInt(this.chunkBytes))) {
1167
+ throw new Error("Load Engine V2 manifest chunk count is not canonical for the signed chunk size.");
1168
+ }
1169
+ total += length;
1170
+ if (total > BigInt(this.maximumResultBytes)) {
1171
+ throw new Error("Load Engine V2 manifest exceeds receiver quota.");
1172
+ }
1173
+ }
1174
+ this.pending = { manifest, chunks: new Map() };
1175
+ }
1176
+ add(chunk) {
1177
+ const pending = this.pending;
1178
+ if (!pending)
1179
+ throw new Error("Load Engine V2 chunk has no active manifest.");
1180
+ const manifest = pending.manifest;
1181
+ if (chunk.runId !== this.runId || chunk.sessionId !== this.sessionId
1182
+ || chunk.resultOwnerId !== this.resultOwnerId
1183
+ || chunk.snapshotSequence64 !== manifest.snapshotSequence64
1184
+ || chunk.manifestSha256 !== manifest.hash) {
1185
+ throw new Error("Load Engine V2 result chunk identity is invalid.");
1186
+ }
1187
+ const entry = manifest.entries.find((value) => value.kind === chunk.kind && value.artifactId === chunk.artifactId);
1188
+ if (!entry)
1189
+ throw new Error("Load Engine V2 result chunk is undeclared.");
1190
+ const offset = BigInt(chunk.chunkIndex) * BigInt(this.chunkBytes);
1191
+ const length = BigInt(entry.uncompressedLength64);
1192
+ const expectedLength = Number(length - offset < BigInt(this.chunkBytes)
1193
+ ? length - offset : BigInt(this.chunkBytes));
1194
+ if (entry.applicability !== "present" || entry.schema !== chunk.schema
1195
+ || entry.artifactSha256 !== chunk.artifactSha256 || entry.chunkCount !== chunk.chunkCount
1196
+ || entry.uncompressedLength64 !== chunk.uncompressedTotalLength64
1197
+ || chunk.chunkIndex < 0 || chunk.chunkIndex >= chunk.chunkCount
1198
+ || offset < 0n || offset >= length || chunk.payload.length !== expectedLength
1199
+ || !hashEquals(chunk.chunkSha256, sha256Hex(chunk.payload))
1200
+ || !chunk.bytes.equals(encodeLoadEngineV2ResultChunk(chunk))) {
1201
+ throw new Error("Load Engine V2 result chunk hash, bounds, or declaration is invalid.");
1202
+ }
1203
+ const key = `${chunk.kind}/${chunk.artifactId}/${chunk.chunkIndex}`;
1204
+ const existing = pending.chunks.get(key);
1205
+ if (existing) {
1206
+ if (!existing.bytes.equals(chunk.bytes)) {
1207
+ throw new Error("Load Engine V2 result chunk retry changed bytes.");
1208
+ }
1209
+ return;
1210
+ }
1211
+ pending.chunks.set(key, chunk);
1212
+ }
1213
+ get hasCompleteSnapshot() {
1214
+ if (!this.pending)
1215
+ return false;
1216
+ return this.pending.manifest.entries
1217
+ .filter((entry) => entry.applicability === "present")
1218
+ .every((entry) => Array.from({ length: entry.chunkCount }, (_, index) => this.pending.chunks.has(`${entry.kind}/${entry.artifactId}/${index}`)).every(Boolean));
1219
+ }
1220
+ commitSnapshot(commit) {
1221
+ if (BigInt(commit.snapshotSequence64) === this.lastSequence
1222
+ && commit.bytes.equals(this.lastCommitBytes))
1223
+ return this.lastArtifacts;
1224
+ const pending = this.pending;
1225
+ if (!pending)
1226
+ throw new Error("Load Engine V2 commit has no active manifest.");
1227
+ const manifest = pending.manifest;
1228
+ if (commit.runId !== this.runId || commit.sessionId !== this.sessionId
1229
+ || commit.resultOwnerId !== this.resultOwnerId
1230
+ || commit.snapshotSequence64 !== manifest.snapshotSequence64
1231
+ || commit.planHash !== this.planHash || commit.manifestLength !== manifest.bytes.length
1232
+ || commit.manifestSha256 !== manifest.hash || commit.entryCount !== manifest.entries.length
1233
+ || !commit.bytes.equals(encodeLoadEngineV2ResultCommit(commit))) {
1234
+ throw new Error("Load Engine V2 commit does not match its manifest.");
1235
+ }
1236
+ const artifacts = new Map();
1237
+ let nonDetail = 0n;
1238
+ let detail = 0n;
1239
+ for (const entry of manifest.entries) {
1240
+ const artifactKey = `${entry.kind}/${entry.artifactId}`;
1241
+ if (entry.applicability === "present") {
1242
+ const pieces = [];
1243
+ for (let index = 0; index < entry.chunkCount; index += 1) {
1244
+ const chunk = pending.chunks.get(`${artifactKey}/${index}`);
1245
+ if (!chunk)
1246
+ throw new Error("Load Engine V2 commit is missing chunks.");
1247
+ pieces.push(chunk.payload);
1248
+ }
1249
+ const payload = Buffer.concat(pieces);
1250
+ if (payload.length.toString() !== entry.uncompressedLength64
1251
+ || !hashEquals(sha256Hex(payload), entry.artifactSha256)) {
1252
+ throw new Error("Load Engine V2 reassembled artifact is invalid.");
1253
+ }
1254
+ artifacts.set(artifactKey, payload);
1255
+ }
1256
+ else if (entry.applicability === "empty") {
1257
+ artifacts.set(artifactKey, Buffer.alloc(0));
1258
+ }
1259
+ const length = BigInt(entry.uncompressedLength64);
1260
+ if (entry.kind === "correlation-detail")
1261
+ detail += length;
1262
+ else
1263
+ nonDetail += length;
1264
+ }
1265
+ if (nonDetail.toString() !== commit.totalNonDetailBytes64
1266
+ || detail.toString() !== commit.totalDetailBytes64) {
1267
+ throw new Error("Load Engine V2 commit byte totals are invalid.");
1268
+ }
1269
+ this.lastSequence = BigInt(manifest.snapshotSequence64);
1270
+ this.lastManifestHash = manifest.hash;
1271
+ this.lastCommitBytes = Buffer.from(commit.bytes);
1272
+ this.lastArtifacts = artifacts;
1273
+ this.pending = undefined;
1274
+ return artifacts;
1275
+ }
1276
+ sealOwnerLost() {
1277
+ this.ownerLost = true;
1278
+ this.pending = undefined;
1279
+ }
1280
+ }
1281
+ function encodeLoadEngineV2ResultManifest(value) {
1282
+ const fields = [Buffer.from("LS-M1\n", "ascii")];
1283
+ const write = (textValue) => { fields.push(frameUtf8(textValue)); };
1284
+ ["1", value.runId, value.sessionId, value.resultOwnerId, value.snapshotSequence64,
1285
+ value.planHash, value.isFinal ? "1" : "0", value.correlationDetailPartCount.toString(),
1286
+ value.entries.length.toString()].forEach(write);
1287
+ value.entries.forEach((entry) => {
1288
+ [entry.kind, entry.artifactId, entry.schema, entry.applicability,
1289
+ entry.uncompressedLength64, entry.chunkCount.toString(), entry.artifactSha256].forEach(write);
1290
+ });
1291
+ return Buffer.concat(fields);
1292
+ }
1293
+ function encodeLoadEngineV2ResultChunk(value) {
1294
+ const fields = [Buffer.from("LS-RC1\n", "ascii")];
1295
+ const write = (textValue) => { fields.push(frameUtf8(textValue)); };
1296
+ ["1", value.runId, value.sessionId, value.resultOwnerId, value.snapshotSequence64,
1297
+ value.manifestSha256, value.kind, value.artifactId, value.schema, value.artifactSha256,
1298
+ value.chunkIndex.toString(), value.chunkCount.toString(), value.uncompressedTotalLength64,
1299
+ value.payload.length.toString(), value.chunkSha256].forEach(write);
1300
+ fields.push(value.payload);
1301
+ return Buffer.concat(fields);
1302
+ }
1303
+ function encodeLoadEngineV2ResultCommit(value) {
1304
+ const fields = [Buffer.from("LS-MC1\n", "ascii")];
1305
+ const write = (textValue) => { fields.push(frameUtf8(textValue)); };
1306
+ ["1", value.runId, value.sessionId, value.resultOwnerId, value.snapshotSequence64,
1307
+ value.planHash, value.manifestLength.toString(), value.manifestSha256,
1308
+ value.entryCount.toString(), value.totalNonDetailBytes64, value.totalDetailBytes64].forEach(write);
1309
+ return Buffer.concat(fields);
1310
+ }
1311
+ function validateV2ResultArtifactOrder(artifacts) {
1312
+ if (artifacts.length < 5 || artifacts.length > LOAD_ENGINE_V2_MAX_MANIFEST_ENTRIES) {
1313
+ throw new Error("Load Engine V2 manifest entry count is invalid.");
1314
+ }
1315
+ for (let index = 0; index < 5; index += 1) {
1316
+ const artifact = artifacts[index];
1317
+ const [kind, schema] = LOAD_ENGINE_V2_MAIN_ARTIFACTS[index];
1318
+ if (artifact.kind !== kind || artifact.artifactId !== "main"
1319
+ || (artifact.applicability !== "not-applicable" && artifact.schema !== schema)
1320
+ || (index < 2 && artifact.applicability === "not-applicable")) {
1321
+ throw new Error("Load Engine V2 manifest main entry order or schema is invalid.");
1322
+ }
1323
+ }
1324
+ const detailCount = artifacts.length - 5;
1325
+ if (detailCount > LOAD_ENGINE_V2_MAX_DETAIL_PARTS) {
1326
+ throw new Error("Load Engine V2 detail part count exceeds its bound.");
1327
+ }
1328
+ for (let index = 0; index < detailCount; index += 1) {
1329
+ const artifact = artifacts[index + 5];
1330
+ if (artifact.kind !== "correlation-detail"
1331
+ || artifact.artifactId !== `part-${index.toString().padStart(20, "0")}`
1332
+ || artifact.schema !== "correlation-detail-v1" || artifact.applicability !== "present") {
1333
+ throw new Error("Load Engine V2 correlation detail sequence is invalid.");
1334
+ }
1335
+ }
1336
+ }
1337
+ function validateDecodedV2ResultManifest(manifest) {
1338
+ validateV2ResultScope(manifest.runId, "run ID");
1339
+ validateV2ResultScope(manifest.sessionId, "session ID");
1340
+ validateV2ResultOwner(manifest.resultOwnerId);
1341
+ validateLowercaseHash(manifest.planHash, "plan hash");
1342
+ validateV2ResultArtifactOrder(manifest.entries.map((entry) => ({
1343
+ kind: entry.kind, artifactId: entry.artifactId, schema: entry.schema,
1344
+ applicability: entry.applicability,
1345
+ payload: entry.applicability === "present" ? Buffer.from([1]) : Buffer.alloc(0)
1346
+ })));
1347
+ for (const entry of manifest.entries) {
1348
+ if (entry.applicability === "present") {
1349
+ validateLowercaseHash(entry.artifactSha256, "artifact hash");
1350
+ if (BigInt(entry.uncompressedLength64) <= 0n || entry.chunkCount <= 0) {
1351
+ throw new Error("Load Engine V2 present manifest entry is invalid.");
1352
+ }
1353
+ }
1354
+ else if (entry.applicability === "empty") {
1355
+ if (entry.uncompressedLength64 !== "0" || entry.chunkCount !== 0
1356
+ || entry.artifactSha256 !== LOAD_ENGINE_V2_RESULT_EMPTY_SHA256) {
1357
+ throw new Error("Load Engine V2 empty manifest entry is invalid.");
1358
+ }
1359
+ }
1360
+ else if (entry.applicability === "not-applicable") {
1361
+ if (entry.schema !== "0" || entry.uncompressedLength64 !== "0"
1362
+ || entry.chunkCount !== 0 || entry.artifactSha256 !== "") {
1363
+ throw new Error("Load Engine V2 not-applicable manifest entry is invalid.");
1364
+ }
1365
+ }
1366
+ else {
1367
+ throw new Error("Load Engine V2 manifest applicability is invalid.");
1368
+ }
1369
+ }
1370
+ }
1371
+ function validateV2ResultScope(value, label) {
1372
+ validateUnicodeScalars(value);
1373
+ if (!value.trim() || Buffer.byteLength(value, "utf8") > 128) {
1374
+ throw new Error(`Load Engine V2 ${label} is invalid.`);
1375
+ }
1376
+ }
1377
+ function validateV2ResultOwner(value) {
1378
+ validateUnicodeScalars(value);
1379
+ if (Buffer.byteLength(value, "utf8") > 256) {
1380
+ throw new Error("Load Engine V2 result owner ID is invalid.");
1381
+ }
1382
+ }
1383
+ function validateV2ResultChunkBound(value) {
1384
+ if (!Number.isSafeInteger(value) || value < 16384 || value > 262144) {
1385
+ throw new RangeError("Load Engine V2 result chunk size must be from 16384 through 262144 bytes.");
1386
+ }
1387
+ }
1388
+ class LoadEngineV2ResultCursor {
1389
+ constructor(bytes) {
1390
+ this.bytes = bytes;
1391
+ this.position = 0;
1392
+ }
1393
+ requireMagic(value) {
1394
+ const expected = Buffer.from(value, "ascii");
1395
+ if (!this.bytes.subarray(this.position, this.position + expected.length).equals(expected)) {
1396
+ throw new Error("Load Engine V2 artifact magic is invalid.");
1397
+ }
1398
+ this.position += expected.length;
1399
+ }
1400
+ readText() {
1401
+ if (this.bytes.length - this.position < 4)
1402
+ throw new Error("Load Engine V2 artifact is truncated.");
1403
+ const length = this.bytes.readUInt32BE(this.position);
1404
+ this.position += 4;
1405
+ if (this.bytes.length - this.position < length)
1406
+ throw new Error("Load Engine V2 artifact field is truncated.");
1407
+ const raw = this.bytes.subarray(this.position, this.position + length);
1408
+ this.position += length;
1409
+ try {
1410
+ return new TextDecoder("utf-8", { fatal: true }).decode(raw);
1411
+ }
1412
+ catch {
1413
+ throw new Error("Load Engine V2 artifact field is not strict UTF-8.");
1414
+ }
1415
+ }
1416
+ readRaw(length) {
1417
+ if (length < 0 || this.bytes.length - this.position < length) {
1418
+ throw new Error("Load Engine V2 artifact body is truncated.");
1419
+ }
1420
+ const value = Buffer.from(this.bytes.subarray(this.position, this.position + length));
1421
+ this.position += length;
1422
+ return value;
1423
+ }
1424
+ require(expected, field) {
1425
+ if (this.readText() !== expected)
1426
+ throw new Error(`Load Engine V2 ${field} is invalid.`);
1427
+ }
1428
+ readBit(field) {
1429
+ const value = this.readText();
1430
+ if (value === "0")
1431
+ return false;
1432
+ if (value === "1")
1433
+ return true;
1434
+ throw new Error(`Load Engine V2 ${field} is invalid.`);
1435
+ }
1436
+ readNonNegative(field) {
1437
+ const value = this.readText();
1438
+ parseCanonicalNonNegativeInt64(value, field);
1439
+ return value;
1440
+ }
1441
+ readCount(field, maximum) {
1442
+ const value = this.readNonNegative(field);
1443
+ const parsed = BigInt(value);
1444
+ if (parsed > BigInt(maximum))
1445
+ throw new Error(`Load Engine V2 ${field} exceeds its bound.`);
1446
+ return Number(parsed);
1447
+ }
1448
+ requireEnd() {
1449
+ if (this.position !== this.bytes.length)
1450
+ throw new Error("Load Engine V2 artifact has trailing bytes.");
1451
+ }
1452
+ }
1453
+ export function createLoadEngineV2ResultChunks(result, chunkBytes = LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES) {
1454
+ if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 16384 || chunkBytes > 262144) {
1455
+ throw new RangeError("Load Engine V2 result chunk size must be from 16384 through 262144 bytes.");
1456
+ }
1457
+ const payload = Buffer.from(JSON.stringify(result), "utf8");
1458
+ if (payload.length > LOAD_ENGINE_V2_MAX_RESULT_BYTES) {
1459
+ throw new Error("Load Engine V2 mergeable result exceeds the bounded result spool.");
1460
+ }
1461
+ const chunkCount = Math.max(1, Math.ceil(payload.length / chunkBytes));
1462
+ const record = result;
1463
+ const chunks = [];
1464
+ for (let index = 0; index < chunkCount; index += 1) {
1465
+ const data = payload.subarray(index * chunkBytes, Math.min((index + 1) * chunkBytes, payload.length));
1466
+ chunks.push({
1467
+ commandId: String(record.commandId ?? record.CommandId ?? ""),
1468
+ agentId: String(record.agentId ?? record.AgentId ?? ""),
1469
+ chunkIndex: index,
1470
+ chunkCount,
1471
+ totalLength64: payload.length.toString(),
1472
+ payloadSha256: sha256Hex(payload),
1473
+ chunkSha256: sha256Hex(data),
1474
+ dataBase64: data.toString("base64")
1475
+ });
1476
+ }
1477
+ return chunks;
1478
+ }
1479
+ export class LoadEngineV2ResultChunkAccumulator {
1480
+ constructor(maximumResultBytes = LOAD_ENGINE_V2_MAX_RESULT_BYTES) {
1481
+ this.maximumResultBytes = maximumResultBytes;
1482
+ this.chunks = new Map();
1483
+ this.complete = false;
1484
+ }
1485
+ add(message) {
1486
+ const totalLength = Number(parseCanonicalNonNegativeInt64(message.totalLength64, "result total length"));
1487
+ if (!Number.isSafeInteger(totalLength) || totalLength > this.maximumResultBytes
1488
+ || message.chunkCount <= 0 || message.chunkIndex < 0 || message.chunkIndex >= message.chunkCount) {
1489
+ throw new Error("Load Engine V2 result chunk bounds are invalid.");
1490
+ }
1491
+ validateLowercaseHash(message.payloadSha256, "result payload hash");
1492
+ validateLowercaseHash(message.chunkSha256, "result chunk hash");
1493
+ const data = Buffer.from(message.dataBase64, "base64");
1494
+ if (data.length > 262144 || !hashEquals(sha256Hex(data), message.chunkSha256)) {
1495
+ throw new Error("Load Engine V2 result chunk hash does not match its payload.");
1496
+ }
1497
+ const nextManifest = {
1498
+ commandId: message.commandId,
1499
+ agentId: message.agentId,
1500
+ chunkCount: message.chunkCount,
1501
+ totalLength64: message.totalLength64,
1502
+ payloadSha256: message.payloadSha256
1503
+ };
1504
+ if (!this.manifest)
1505
+ this.manifest = nextManifest;
1506
+ else if (JSON.stringify(this.manifest) !== JSON.stringify(nextManifest)) {
1507
+ throw new Error("Load Engine V2 result chunk manifest changed within one snapshot.");
1508
+ }
1509
+ const existing = this.chunks.get(message.chunkIndex);
1510
+ if (existing) {
1511
+ if (!existing.equals(data))
1512
+ throw new Error("Load Engine V2 result chunk retry changed bytes.");
1513
+ return undefined;
1514
+ }
1515
+ if (this.complete)
1516
+ throw new Error("Load Engine V2 result stream is already complete.");
1517
+ this.chunks.set(message.chunkIndex, data);
1518
+ if (this.chunks.size !== message.chunkCount)
1519
+ return undefined;
1520
+ const payload = Buffer.concat(Array.from({ length: message.chunkCount }, (_, index) => {
1521
+ const chunk = this.chunks.get(index);
1522
+ if (!chunk)
1523
+ throw new Error("Load Engine V2 result chunks are incomplete.");
1524
+ return chunk;
1525
+ }));
1526
+ if (payload.length !== totalLength || !hashEquals(sha256Hex(payload), message.payloadSha256)) {
1527
+ throw new Error("Load Engine V2 result snapshot hash or length is invalid.");
1528
+ }
1529
+ this.complete = true;
1530
+ return JSON.parse(payload.toString("utf8"));
1531
+ }
1532
+ }
1533
+ export function buildLoadEngineV2OwnerLoss(input) {
1534
+ const missing = normalizeExpectedAgentIds(input.missingAgentIds);
1535
+ const assignments = input.assignments.filter((value) => missing.includes(value.agentId));
1536
+ const schedulerSegments = assignments.flatMap((assignment) => assignment.scenarios.flatMap((scenario) => scenario.simulations.map((simulation) => {
1537
+ parseCanonicalNonNegativeInt64(simulation.plannedIterations64, "owned planned iterations");
1538
+ return {
1539
+ scenarioName: scenario.scenarioName,
1540
+ scenarioIndex: Number(parseCanonicalNonNegativeInt64(scenario.scenarioIndex64, "scenario index")),
1541
+ simulationIndex: Number(parseCanonicalNonNegativeInt64(simulation.simulationIndex64, "simulation index")),
1542
+ kind: simulation.kind,
1543
+ shardIndex: Number(parseCanonicalNonNegativeInt64(assignment.shardIndex64, "shard index")),
1544
+ shardCount: Number(parseCanonicalNonNegativeInt64(assignment.shardCount64, "shard count")),
1545
+ plannedIterations64: simulation.plannedIterations64,
1546
+ dueIterations64: "0",
1547
+ startedIterations64: "0",
1548
+ completedIterations64: "0",
1549
+ droppedIterations64: "0",
1550
+ unreachedIterations64: simulation.plannedIterations64,
1551
+ requestedWorkerSlots64: "0",
1552
+ startedWorkerSlots64: "0",
1553
+ unavailableWorkerSlots64: "0",
1554
+ dropReasons: {},
1555
+ unavailableWorkerReasons: {},
1556
+ deliveryPercent: simulation.plannedIterations64 === "0" ? 100 : 0,
1557
+ accountingComplete: false
1558
+ };
1559
+ })));
1560
+ return {
1561
+ reportingComplete: false,
1562
+ applicationFailureCount64: "0",
1563
+ generatorWarnings: missing.map((agentId) => ({ code: "cluster_result_owner_lost", agentId, count64: "1" })),
1564
+ schedulerSegments
1565
+ };
1566
+ }
123
1567
  export class DistributedClusterCoordinator {
124
1568
  /**
125
1569
  * Exposes the public constructor operation.
@@ -133,8 +1577,8 @@ export class DistributedClusterCoordinator {
133
1577
  const timeoutMs = Math.max(this.options.commandTimeoutMs ?? 120000, 1);
134
1578
  const runSubject = this.buildRunSubject();
135
1579
  const replySubject = this.buildReplySubject();
136
- const commandProducer = EndpointAdapterFactory.create(this.natsEndpoint("Produce", "coordinator-command", runSubject));
137
- const resultConsumer = EndpointAdapterFactory.create(this.natsEndpoint("Consume", "coordinator-result", replySubject));
1580
+ const commandProducer = this.createAdapter(this.natsEndpoint("Produce", "coordinator-command", runSubject));
1581
+ const resultConsumer = this.createAdapter(this.natsEndpoint("Consume", "coordinator-result", replySubject));
138
1582
  try {
139
1583
  // Prime the reply subscription before commands are published because core NATS subjects do not retain messages.
140
1584
  await resultConsumer.consume();
@@ -192,12 +1636,19 @@ export class DistributedClusterCoordinator {
192
1636
  await commandProducer.dispose?.().catch(() => { });
193
1637
  }
194
1638
  }
1639
+ /** Runs the native V2 capability, targeted-command, barrier, and chunk-result protocol. */
1640
+ async dispatchV2(assignments, planInput, buildAgentRunToken) {
1641
+ return dispatchDistributedV2(this.options, assignments, planInput, buildAgentRunToken, (definition) => this.createAdapter(definition));
1642
+ }
195
1643
  buildRunSubject() {
196
1644
  return `loadstrike.${sanitizeToken(this.options.clusterId)}.${sanitizeToken(this.options.agentGroup ?? "default")}.run`;
197
1645
  }
198
1646
  buildReplySubject() {
199
1647
  return `loadstrike.${sanitizeToken(this.options.clusterId)}.${sanitizeToken(this.options.sessionId)}.reply.${randomUUID().replace(/-/g, "")}`;
200
1648
  }
1649
+ createAdapter(definition) {
1650
+ return (this.options.adapterFactory ?? EndpointAdapterFactory).create(definition);
1651
+ }
201
1652
  natsEndpoint(mode, name, subject) {
202
1653
  return {
203
1654
  kind: "Nats",
@@ -212,6 +1663,303 @@ export class DistributedClusterCoordinator {
212
1663
  };
213
1664
  }
214
1665
  }
1666
+ async function dispatchDistributedV2(options, assignments, planInput, buildAgentRunToken, createAdapter) {
1667
+ if (options.loadEngineContractVersion !== 2) {
1668
+ throw new Error("dispatchV2 requires LoadEngineContractVersion 2.");
1669
+ }
1670
+ const expected = normalizeExpectedAgentIds(options.expectedAgentIds ?? []);
1671
+ if (options.expectedAgentResults !== expected.length || assignments.length !== expected.length) {
1672
+ throw new Error("Load Engine V2 requires assignments for the exact ExpectedAgentIds set.");
1673
+ }
1674
+ if (planInput.sessionId !== options.sessionId) {
1675
+ throw new Error("Load Engine V2 plan session does not match the coordinator session.");
1676
+ }
1677
+ const canonicalInput = { ...planInput, expectedAgentIds: expected };
1678
+ const plan = buildLoadEngineV2Plan(canonicalInput);
1679
+ const segments = orderedLoadEngineV2Segments(canonicalInput);
1680
+ if (!segments.length) {
1681
+ throw new Error("Load Engine V2 distributed execution requires at least one signed simulation segment.");
1682
+ }
1683
+ const timeoutMs = Math.max(options.commandTimeoutMs ?? 120000, 1);
1684
+ const prefix = `loadstrike.${sanitizeToken(options.clusterId)}.${sanitizeToken(options.sessionId)}.v2`;
1685
+ const helloSubject = `${prefix}.hello`;
1686
+ const registerSubject = buildLoadEngineV2ScopedRegistrationSubject(canonicalInput.runId, canonicalInput.sessionId, canonicalInput.registrationNonce);
1687
+ const controlSubject = `${prefix}.control.${sanitizeToken(canonicalInput.registrationNonce)}`;
1688
+ const resultSubject = `${prefix}.result.${sanitizeToken(canonicalInput.registrationNonce)}`;
1689
+ const endpoint = (mode, name, subject) => ({
1690
+ kind: "Nats", mode, name, trackingField: "header:x-cluster-command-id",
1691
+ nats: { ...(options.nats ?? {}), Subject: subject, StartFromEarliest: true }
1692
+ });
1693
+ const registrationConsumer = createAdapter(endpoint("Consume", "coordinator-v2-registration", registerSubject));
1694
+ const controlConsumer = createAdapter(endpoint("Consume", "coordinator-v2-control", controlSubject));
1695
+ const resultConsumer = createAdapter({
1696
+ ...endpoint("Consume", "coordinator-v2-result", resultSubject),
1697
+ contentType: "application/octet-stream", messagePayloadType: "binary"
1698
+ });
1699
+ const producers = [];
1700
+ try {
1701
+ await Promise.all([registrationConsumer.consume(), controlConsumer.consume(), resultConsumer.consume()]);
1702
+ const helloProducer = createAdapter(endpoint("Produce", "coordinator-v2-hello", helloSubject));
1703
+ producers.push(helloProducer);
1704
+ const hello = createLoadEngineV2CoordinatorHello({
1705
+ runId: canonicalInput.runId,
1706
+ sessionId: canonicalInput.sessionId,
1707
+ coordinatorNonce: canonicalInput.registrationNonce,
1708
+ registrationSubject: registerSubject,
1709
+ expiresUtcNs64: (utcNowNs() + BigInt(timeoutMs) * 1000000n).toString(),
1710
+ expectedAgentIds: expected
1711
+ });
1712
+ const registrations = new Map();
1713
+ const registrationDeadline = Date.now() + timeoutMs;
1714
+ let lastHelloMs = 0;
1715
+ while (registrations.size < expected.length && Date.now() < registrationDeadline) {
1716
+ if (Date.now() - lastHelloMs >= 100) {
1717
+ await helloProducer.produce({ headers: {}, body: hello });
1718
+ lastHelloMs = Date.now();
1719
+ }
1720
+ const payload = await registrationConsumer.consume();
1721
+ const body = recordOrUndefined(payload?.body);
1722
+ if (!body) {
1723
+ await sleep(5);
1724
+ continue;
1725
+ }
1726
+ const registration = body;
1727
+ if (!expected.includes(registration.agentId))
1728
+ continue;
1729
+ acceptLoadEngineV2RegistrationRenewal(registrations, registration, canonicalInput.runId, options.sessionId, canonicalInput.registrationNonce, utcNowNs64());
1730
+ }
1731
+ let negotiated = negotiateLoadEngineV2Registrations(canonicalInput.runId, options.sessionId, canonicalInput.registrationNonce, expected, [...registrations.values()], utcNowNs64());
1732
+ const commandId = randomUUID().replace(/-/g, "");
1733
+ const leadTimeMs = Math.max(options.barrierLeadTimeMs ?? 3000, 3000);
1734
+ const assignmentRows = expected.map((agentId, index) => ({
1735
+ agentId,
1736
+ shardIndex64: index.toString(),
1737
+ shardCount64: expected.length.toString(),
1738
+ scenarioNames: [...(assignments[index] ?? [])]
1739
+ }));
1740
+ const commands = new Map();
1741
+ for (const registration of negotiated) {
1742
+ const index = expected.indexOf(registration.agentId);
1743
+ const command = {
1744
+ commandId,
1745
+ sessionId: options.sessionId,
1746
+ testSuite: options.testSuite,
1747
+ testName: options.testName,
1748
+ agentIndex: index,
1749
+ agentCount: expected.length,
1750
+ targetScenarios: [...(assignments[index] ?? [])]
1751
+ };
1752
+ if (buildAgentRunToken)
1753
+ command.agentRunToken = await buildAgentRunToken(command);
1754
+ commands.set(registration.agentId, command);
1755
+ }
1756
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) {
1757
+ const segment = segments[segmentIndex];
1758
+ const barrierScopeId = buildLoadEngineV2BarrierScopeId(plan.hash, {
1759
+ kind: "scenario",
1760
+ scenarioIndex64: segment.scenarioIndex64,
1761
+ simulationIndex64: segment.simulationIndex64
1762
+ });
1763
+ const barrierAssignments = expected.map((agentId, shardIndex) => ({
1764
+ scenarioIndex64: segment.scenarioIndex64,
1765
+ simulationIndex64: segment.simulationIndex64,
1766
+ agentId,
1767
+ shardIndex64: shardIndex.toString(),
1768
+ shardCount64: expected.length.toString()
1769
+ }));
1770
+ const assignmentHash = buildLoadEngineV2AssignmentHash(barrierScopeId, barrierAssignments);
1771
+ const barrierEpoch64 = segmentIndex.toString();
1772
+ if (segmentIndex > 0) {
1773
+ const previous = segments[segmentIndex - 1];
1774
+ const previousScope = buildLoadEngineV2BarrierScopeId(plan.hash, {
1775
+ kind: "scenario", scenarioIndex64: previous.scenarioIndex64,
1776
+ simulationIndex64: previous.simulationIndex64
1777
+ });
1778
+ const previousAssignments = expected.map((agentId, shardIndex) => ({
1779
+ scenarioIndex64: previous.scenarioIndex64, simulationIndex64: previous.simulationIndex64,
1780
+ agentId, shardIndex64: shardIndex.toString(), shardCount64: expected.length.toString()
1781
+ }));
1782
+ const previousAssignmentHash = buildLoadEngineV2AssignmentHash(previousScope, previousAssignments);
1783
+ const drained = await collectV2Acks(controlConsumer, "drained", expected, commandId, timeoutMs);
1784
+ validateV2DrainAcks(expected, drained, plan.hash, previousAssignmentHash, (segmentIndex - 1).toString(), previousScope);
1785
+ }
1786
+ negotiated = await refreshLoadEngineV2Registrations(registrationConsumer, registrations, canonicalInput.runId, options.sessionId, canonicalInput.registrationNonce, expected, timeoutMs, BigInt(Math.max(options.barrierLeadTimeMs ?? 3000, 3000) + 1000) * 1000000n);
1787
+ const coordinatorResolution = options.v2ClockResolutionNs64 ?? "1000000";
1788
+ parseCanonicalNonNegativeInt64(coordinatorResolution, "coordinator clock resolution");
1789
+ const clockSamples = new Map();
1790
+ for (let probeIndex = 0; probeIndex < 5; probeIndex += 1) {
1791
+ for (const registration of negotiated) {
1792
+ const producer = createAdapter(endpoint("Produce", `coordinator-v2-clock-${segmentIndex}-${registration.agentId}-${probeIndex}`, registration.targetSubject));
1793
+ producers.push(producer);
1794
+ await producer.produce({ headers: { "x-cluster-command-id": commandId }, body: {
1795
+ phase: "clock-probe", commandId, runId: canonicalInput.runId,
1796
+ sessionId: options.sessionId, coordinatorNonce: canonicalInput.registrationNonce,
1797
+ agentId: registration.agentId, probeIndex,
1798
+ coordinatorSendUtcNs64: utcNowNs64(),
1799
+ coordinatorClockResolutionNs64: coordinatorResolution,
1800
+ barrierEpoch64, barrierScopeId, replySubject: controlSubject
1801
+ } });
1802
+ }
1803
+ const round = await collectV2ClockProbeResults(controlConsumer, expected, commandId, probeIndex, timeoutMs, barrierEpoch64);
1804
+ for (const [agentId, sample] of round) {
1805
+ const rows = clockSamples.get(agentId) ?? [];
1806
+ rows.push(sample);
1807
+ clockSamples.set(agentId, rows);
1808
+ }
1809
+ }
1810
+ const selectedClocks = new Map(expected.map((agentId) => [
1811
+ agentId, selectLoadEngineV2ClockProbe(clockSamples.get(agentId) ?? [])
1812
+ ]));
1813
+ const deadlineUtcNs64 = (utcNowNs() + BigInt(leadTimeMs) * 1000000n).toString();
1814
+ for (const registration of negotiated) {
1815
+ const index = expected.indexOf(registration.agentId);
1816
+ const producer = createAdapter(endpoint("Produce", `coordinator-v2-prepare-${segmentIndex}-${index}`, registration.targetSubject));
1817
+ producers.push(producer);
1818
+ await producer.produce({ headers: { "x-cluster-command-id": commandId }, body: {
1819
+ ...commands.get(registration.agentId), phase: "prepare", agentId: registration.agentId,
1820
+ runId: canonicalInput.runId, coordinatorNonce: canonicalInput.registrationNonce,
1821
+ planInput: canonicalInput, planHash: plan.hash, barrierScopeId, barrierAssignments,
1822
+ assignmentHash, assignments: assignmentRows, barrierEpoch64, deadlineUtcNs64,
1823
+ replySubject: controlSubject, resultSubject,
1824
+ resultAckSubject: `${resultSubject}.ack.${sanitizeToken(registration.agentId)}`,
1825
+ negotiatedResultChunkBytes: LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES,
1826
+ leaseExpiresUtcNs64: registration.leaseExpiresUtcNs64,
1827
+ clockOffsetNs64: selectedClocks.get(registration.agentId).offsetAgentMinusCoordinatorNs64,
1828
+ clockUncertaintyNs64: selectedClocks.get(registration.agentId).uncertaintyNs64
1829
+ } });
1830
+ }
1831
+ const armed = await collectV2Acks(controlConsumer, "armed", expected, commandId, timeoutMs);
1832
+ if (utcNowNs() >= parseCanonicalInt64(deadlineUtcNs64, "barrier deadline") - 1000000000n) {
1833
+ throw new Error("Load Engine V2 armed barrier did not retain the required one-second commit window.");
1834
+ }
1835
+ validateLoadEngineV2BarrierAcks("armed", expected, armed, plan.hash, assignmentHash, barrierEpoch64, utcNowNs64(), barrierScopeId);
1836
+ const armedHash = buildV2ArmedHash(armed);
1837
+ for (const registration of negotiated) {
1838
+ const producer = createAdapter(endpoint("Produce", `coordinator-v2-commit-${segmentIndex}-${registration.agentId}`, registration.targetSubject));
1839
+ producers.push(producer);
1840
+ await producer.produce({ headers: { "x-cluster-command-id": commandId }, body: {
1841
+ phase: "commit", commandId, runId: canonicalInput.runId, sessionId: options.sessionId,
1842
+ coordinatorNonce: canonicalInput.registrationNonce, agentId: registration.agentId,
1843
+ planHash: plan.hash, barrierScopeId, assignmentHash, barrierEpoch64,
1844
+ deadlineUtcNs64, armedHash, armedAcks: armed, replySubject: controlSubject, resultSubject
1845
+ } });
1846
+ }
1847
+ const committed = await collectV2Acks(controlConsumer, "committed", expected, commandId, timeoutMs);
1848
+ validateLoadEngineV2BarrierAcks("committed", expected, committed, plan.hash, assignmentHash, barrierEpoch64, utcNowNs64(), barrierScopeId);
1849
+ }
1850
+ const finalSegment = segments[segments.length - 1];
1851
+ const finalScope = buildLoadEngineV2BarrierScopeId(plan.hash, {
1852
+ kind: "scenario", scenarioIndex64: finalSegment.scenarioIndex64,
1853
+ simulationIndex64: finalSegment.simulationIndex64
1854
+ });
1855
+ const finalAssignments = expected.map((agentId, shardIndex) => ({
1856
+ scenarioIndex64: finalSegment.scenarioIndex64, simulationIndex64: finalSegment.simulationIndex64,
1857
+ agentId, shardIndex64: shardIndex.toString(), shardCount64: expected.length.toString()
1858
+ }));
1859
+ const finalDrained = await collectV2Acks(controlConsumer, "drained", expected, commandId, timeoutMs);
1860
+ validateV2DrainAcks(expected, finalDrained, plan.hash, buildLoadEngineV2AssignmentHash(finalScope, finalAssignments), (segments.length - 1).toString(), finalScope);
1861
+ const receivers = new Map(expected.map((agentId) => [
1862
+ agentId,
1863
+ new LoadEngineV2ResultSnapshotReceiver(canonicalInput.runId, canonicalInput.sessionId, agentId, plan.hash, LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES, LOAD_ENGINE_V2_MAX_RESULT_BYTES)
1864
+ ]));
1865
+ const results = new Map();
1866
+ const resultDeadline = Date.now() + timeoutMs;
1867
+ while (results.size < expected.length && Date.now() < resultDeadline) {
1868
+ const payload = await resultConsumer.consume();
1869
+ const bytes = loadEngineV2BinaryBody(payload?.body);
1870
+ if (!bytes) {
1871
+ await sleep(5);
1872
+ continue;
1873
+ }
1874
+ if (bytes.subarray(0, 6).toString("ascii") === "LS-M1\n") {
1875
+ const manifest = decodeLoadEngineV2ResultManifest(bytes);
1876
+ const receiver = receivers.get(manifest.resultOwnerId);
1877
+ if (!receiver || results.has(manifest.resultOwnerId)) {
1878
+ throw new Error("Load Engine V2 received a manifest for an unallocated result owner.");
1879
+ }
1880
+ receiver.begin(manifest);
1881
+ continue;
1882
+ }
1883
+ if (bytes.subarray(0, 7).toString("ascii") === "LS-RC1\n") {
1884
+ const chunk = decodeLoadEngineV2ResultChunk(bytes, LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES);
1885
+ const receiver = receivers.get(chunk.resultOwnerId);
1886
+ if (!receiver || results.has(chunk.resultOwnerId)) {
1887
+ throw new Error("Load Engine V2 received a chunk for an unallocated result owner.");
1888
+ }
1889
+ receiver.add(chunk);
1890
+ continue;
1891
+ }
1892
+ if (bytes.subarray(0, 7).toString("ascii") !== "LS-MC1\n") {
1893
+ throw new Error("Load Engine V2 result subject received an unknown canonical artifact.");
1894
+ }
1895
+ const commit = decodeLoadEngineV2ResultCommit(bytes);
1896
+ const receiver = receivers.get(commit.resultOwnerId);
1897
+ if (!receiver || results.has(commit.resultOwnerId)) {
1898
+ throw new Error("Load Engine V2 received a commit for an unallocated result owner.");
1899
+ }
1900
+ if (!receiver.hasCompleteSnapshot)
1901
+ continue;
1902
+ const artifacts = receiver.commitSnapshot(commit);
1903
+ const scheduler = artifacts.get("scheduler/main");
1904
+ const histograms = artifacts.get("histograms/main");
1905
+ if (!scheduler || !histograms) {
1906
+ throw new Error("Load Engine V2 canonical result omits scheduler or histogram artifacts.");
1907
+ }
1908
+ let decoded;
1909
+ try {
1910
+ decoded = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(scheduler));
1911
+ }
1912
+ catch {
1913
+ throw new Error("Load Engine V2 scheduler result artifact is invalid.");
1914
+ }
1915
+ const decodedRecord = recordOrUndefined(decoded);
1916
+ if (!decodedRecord)
1917
+ throw new Error("Load Engine V2 scheduler result artifact must be an object.");
1918
+ const stats = recordOrUndefined(decodedRecord.stats ?? decodedRecord.Stats) ?? {};
1919
+ decodedRecord.stats = { ...stats, histogramArtifactBase64: histograms.toString("base64") };
1920
+ const parsed = parseRunResult(decodedRecord, true);
1921
+ if (parsed.commandId !== commandId || parsed.agentId !== commit.resultOwnerId) {
1922
+ throw new Error("Load Engine V2 canonical result identity does not match its command owner.");
1923
+ }
1924
+ results.set(commit.resultOwnerId, parsed);
1925
+ const ackProducer = createAdapter(endpoint("Produce", `coordinator-v2-result-ack-${commit.resultOwnerId}`, `${resultSubject}.ack.${sanitizeToken(commit.resultOwnerId)}`));
1926
+ producers.push(ackProducer);
1927
+ await ackProducer.produce({ headers: { "x-cluster-command-id": commandId }, body: {
1928
+ phase: "result-ack", commandId, resultOwnerId: commit.resultOwnerId,
1929
+ snapshotSequence64: commit.snapshotSequence64,
1930
+ manifestSha256: commit.manifestSha256, accepted: true
1931
+ } });
1932
+ }
1933
+ const nodeResults = expected.flatMap((agentId) => {
1934
+ const result = results.get(agentId);
1935
+ return result ? [convertRunResult(result)] : [];
1936
+ });
1937
+ const missingAgentIds = expected.filter((agentId) => !results.has(agentId));
1938
+ const ownerLoss = missingAgentIds.length ? buildLoadEngineV2OwnerLoss({
1939
+ missingAgentIds,
1940
+ assignments: buildOwnerLossAssignments(canonicalInput, missingAgentIds, expected)
1941
+ }) : undefined;
1942
+ return {
1943
+ nodeResults,
1944
+ allRequestCount: nodeResults.reduce((sum, row) => sum + row.allRequestCount, 0),
1945
+ allOkCount: nodeResults.reduce((sum, row) => sum + row.allOkCount, 0),
1946
+ allFailCount: nodeResults.reduce((sum, row) => sum + row.allFailCount, 0),
1947
+ failedNodes: nodeResults.filter((row) => !row.success).length,
1948
+ missingNodes: missingAgentIds.length,
1949
+ runSubject: registerSubject,
1950
+ replySubject: resultSubject,
1951
+ ownerLoss
1952
+ };
1953
+ }
1954
+ finally {
1955
+ await Promise.all([
1956
+ registrationConsumer.dispose?.().catch(() => { }),
1957
+ controlConsumer.dispose?.().catch(() => { }),
1958
+ resultConsumer.dispose?.().catch(() => { }),
1959
+ ...producers.map((producer) => producer.dispose?.().catch(() => { }))
1960
+ ]);
1961
+ }
1962
+ }
215
1963
  export class DistributedClusterAgent {
216
1964
  /**
217
1965
  * Exposes the public constructor operation.
@@ -219,17 +1967,36 @@ export class DistributedClusterAgent {
219
1967
  */
220
1968
  constructor(options) {
221
1969
  this.commandConsumer = null;
1970
+ this.v2HelloConsumer = null;
1971
+ this.v2CommandConsumer = null;
1972
+ this.v2RegistrationProducer = null;
1973
+ this.v2LastRegistrationMs = 0;
1974
+ this.v2Hello = null;
1975
+ this.v2CurrentRegistration = null;
1976
+ this.v2RegistrationSequence = -1n;
1977
+ this.v2RenewalTimer = null;
1978
+ this.v2RenewalError = null;
1979
+ this.v2Prepared = null;
222
1980
  this.options = options;
223
1981
  }
224
1982
  async dispose() {
225
1983
  await this.commandConsumer?.dispose?.().catch(() => { });
1984
+ await this.v2HelloConsumer?.dispose?.().catch(() => { });
1985
+ await this.v2CommandConsumer?.dispose?.().catch(() => { });
1986
+ await this.v2RegistrationProducer?.dispose?.().catch(() => { });
1987
+ if (this.v2RenewalTimer)
1988
+ clearInterval(this.v2RenewalTimer);
226
1989
  this.commandConsumer = null;
1990
+ this.v2HelloConsumer = null;
1991
+ this.v2CommandConsumer = null;
1992
+ this.v2RegistrationProducer = null;
1993
+ this.v2RenewalTimer = null;
227
1994
  }
228
1995
  async pollAndExecuteOnce(execute) {
229
1996
  const runSubject = `loadstrike.${sanitizeToken(this.options.clusterId)}.${sanitizeToken(this.options.agentGroup ?? "default")}.run`;
230
1997
  const queueGroup = `loadstrike.${sanitizeToken(this.options.clusterId)}.${sanitizeToken(this.options.agentGroup ?? "default")}.agents`;
231
1998
  if (!this.commandConsumer) {
232
- this.commandConsumer = EndpointAdapterFactory.create({
1999
+ this.commandConsumer = this.createAdapter({
233
2000
  kind: "Nats",
234
2001
  mode: "Consume",
235
2002
  name: `agent-${this.options.agentId}`,
@@ -252,7 +2019,7 @@ export class DistributedClusterAgent {
252
2019
  }
253
2020
  const replySubject = stringOrDefault(payload.headers?.["x-cluster-reply-subject"], command.replySubject ?? "");
254
2021
  const resultProducer = replySubject
255
- ? EndpointAdapterFactory.create({
2022
+ ? this.createAdapter({
256
2023
  kind: "Nats",
257
2024
  mode: "Produce",
258
2025
  name: `agent-${this.options.agentId}-result`,
@@ -307,6 +2074,373 @@ export class DistributedClusterAgent {
307
2074
  await resultProducer?.dispose?.().catch(() => { });
308
2075
  }
309
2076
  }
2077
+ /** Polls one targeted V2 message. A callback can run only after a matching commit. */
2078
+ async pollAndExecuteV2Once(execute) {
2079
+ if (this.options.loadEngineContractVersion !== 2) {
2080
+ throw new Error("pollAndExecuteV2Once requires LoadEngineContractVersion 2.");
2081
+ }
2082
+ validateLoadEngineV2AgentId(this.options.agentId);
2083
+ if (!(await this.ensureV2Registration()))
2084
+ return false;
2085
+ if (this.v2RenewalError)
2086
+ throw this.v2RenewalError;
2087
+ const payload = await this.ensureV2CommandConsumer().consume();
2088
+ const body = recordOrUndefined(payload?.body);
2089
+ if (!body)
2090
+ return false;
2091
+ const phase = String(body.phase ?? "");
2092
+ if (String(body.agentId ?? "") !== this.options.agentId || !this.matchesV2Scope(body))
2093
+ return false;
2094
+ if (phase === "clock-probe") {
2095
+ await this.publishV2ClockProbe(body);
2096
+ return false;
2097
+ }
2098
+ if (phase === "abort") {
2099
+ if (this.v2Prepared && body.assignmentHash === this.v2Prepared.assignmentHash
2100
+ && body.barrierEpoch64 === this.v2Prepared.barrierEpoch64)
2101
+ this.v2Prepared = null;
2102
+ return false;
2103
+ }
2104
+ if (phase === "prepare") {
2105
+ await this.acceptV2Prepare(body);
2106
+ return false;
2107
+ }
2108
+ if (phase !== "commit")
2109
+ return false;
2110
+ let activeCommand = await this.acceptV2Commit(body);
2111
+ const command = parseRunCommand(activeCommand);
2112
+ const signedSegments = orderedLoadEngineV2Segments(activeCommand.planInput);
2113
+ let activeSegmentIndex = -1;
2114
+ let activeSegmentDrained = true;
2115
+ const segmentLifecycle = {
2116
+ beforeSegment: async (scenarioIndex, simulationIndex) => {
2117
+ const nextIndex = activeSegmentIndex + 1;
2118
+ const expected = signedSegments[nextIndex];
2119
+ if (!expected || expected.scenarioIndex64 !== scenarioIndex.toString()
2120
+ || expected.simulationIndex64 !== simulationIndex.toString() || !activeSegmentDrained) {
2121
+ throw new Error("Load Engine V2 scheduler attempted a segment outside the signed serial order.");
2122
+ }
2123
+ if (nextIndex > 0) {
2124
+ for (;;) {
2125
+ const nextPayload = await this.ensureV2CommandConsumer().consume();
2126
+ const nextBody = recordOrUndefined(nextPayload?.body);
2127
+ if (!nextBody || String(nextBody.agentId ?? "") !== this.options.agentId
2128
+ || !this.matchesV2Scope(nextBody)) {
2129
+ await sleep(2);
2130
+ continue;
2131
+ }
2132
+ const nextPhase = String(nextBody.phase ?? "");
2133
+ if (nextPhase === "clock-probe") {
2134
+ if (String(nextBody.barrierEpoch64 ?? "") !== nextIndex.toString()) {
2135
+ throw new Error("Load Engine V2 clock probe does not match the next scoped epoch.");
2136
+ }
2137
+ await this.publishV2ClockProbe(nextBody);
2138
+ continue;
2139
+ }
2140
+ if (nextPhase === "prepare") {
2141
+ if (String(nextBody.barrierEpoch64 ?? "") !== nextIndex.toString()) {
2142
+ throw new Error("Load Engine V2 prepare does not match the next scoped epoch.");
2143
+ }
2144
+ await this.acceptV2Prepare(nextBody);
2145
+ continue;
2146
+ }
2147
+ if (nextPhase === "abort") {
2148
+ throw new Error("Load Engine V2 coordinator aborted the next scoped epoch.");
2149
+ }
2150
+ if (nextPhase !== "commit") {
2151
+ await sleep(2);
2152
+ continue;
2153
+ }
2154
+ activeCommand = await this.acceptV2Commit(nextBody);
2155
+ break;
2156
+ }
2157
+ }
2158
+ const expectedScope = buildLoadEngineV2BarrierScopeId(String(activeCommand.planHash), {
2159
+ kind: "scenario", scenarioIndex64: expected.scenarioIndex64,
2160
+ simulationIndex64: expected.simulationIndex64
2161
+ });
2162
+ if (String(activeCommand.barrierEpoch64) !== nextIndex.toString()
2163
+ || String(activeCommand.barrierScopeId) !== expectedScope) {
2164
+ throw new Error("Load Engine V2 committed scope does not match the scheduler segment.");
2165
+ }
2166
+ activeSegmentIndex = nextIndex;
2167
+ activeSegmentDrained = false;
2168
+ },
2169
+ afterSegment: async (scenarioIndex, simulationIndex) => {
2170
+ const expected = signedSegments[activeSegmentIndex];
2171
+ if (!expected || activeSegmentDrained || expected.scenarioIndex64 !== scenarioIndex.toString()
2172
+ || expected.simulationIndex64 !== simulationIndex.toString()) {
2173
+ throw new Error("Load Engine V2 scheduler drain does not match the active scoped epoch.");
2174
+ }
2175
+ await this.publishV2Registration(true);
2176
+ await this.publishV2Ack(activeCommand, "drained", true, {
2177
+ finalDeltaAcknowledged: true,
2178
+ accountingComplete: true
2179
+ });
2180
+ activeSegmentDrained = true;
2181
+ }
2182
+ };
2183
+ let result;
2184
+ try {
2185
+ const node = await execute({
2186
+ scenarioNames: command.targetScenarios,
2187
+ commandId: command.commandId,
2188
+ agentRunToken: command.agentRunToken,
2189
+ agentIndex: command.agentIndex,
2190
+ agentCount: command.agentCount,
2191
+ segmentLifecycle
2192
+ });
2193
+ if (activeSegmentIndex !== signedSegments.length - 1 || !activeSegmentDrained) {
2194
+ throw new Error("Load Engine V2 execution returned before every signed segment drained.");
2195
+ }
2196
+ result = {
2197
+ commandId: command.commandId,
2198
+ agentId: this.options.agentId,
2199
+ isSuccess: node.success,
2200
+ errorMessage: node.error,
2201
+ stats: node.stats ?? {
2202
+ allRequestCount: node.allRequestCount,
2203
+ allOkCount: node.allOkCount,
2204
+ allFailCount: node.allFailCount
2205
+ }
2206
+ };
2207
+ }
2208
+ catch (error) {
2209
+ result = {
2210
+ commandId: command.commandId,
2211
+ agentId: this.options.agentId,
2212
+ isSuccess: false,
2213
+ errorMessage: String(error ?? "agent execution failed"),
2214
+ stats: {
2215
+ allRequestCount: 0,
2216
+ allOkCount: 0,
2217
+ allFailCount: 0,
2218
+ histogramArtifactBase64: EMPTY_LOAD_ENGINE_V2_HISTOGRAM_ARTIFACT_BASE64
2219
+ }
2220
+ };
2221
+ }
2222
+ try {
2223
+ await this.publishV2CanonicalResult(activeCommand, result);
2224
+ }
2225
+ finally {
2226
+ this.v2Prepared = null;
2227
+ }
2228
+ return true;
2229
+ }
2230
+ async publishV2CanonicalResult(command, result) {
2231
+ const resultSubject = String(command.resultSubject ?? "");
2232
+ const ackSubject = String(command.resultAckSubject ?? "");
2233
+ const chunkBytes = Number(command.negotiatedResultChunkBytes ?? LOAD_ENGINE_V2_DEFAULT_CHUNK_BYTES);
2234
+ if (!resultSubject || !ackSubject) {
2235
+ throw new Error("Load Engine V2 canonical result subjects are incomplete.");
2236
+ }
2237
+ validateV2ResultChunkBound(chunkBytes);
2238
+ const histogramBase64 = String(result.stats?.histogramArtifactBase64 ?? "");
2239
+ const histogramBytes = Buffer.from(histogramBase64, "base64");
2240
+ if (!histogramBase64 || histogramBytes.toString("base64") !== histogramBase64) {
2241
+ throw new Error("Load Engine V2 canonical result requires a canonical histogram artifact.");
2242
+ }
2243
+ parseLoadEngineV2HistogramArtifact(histogramBytes);
2244
+ const schedulerProjection = JSON.parse(JSON.stringify(result));
2245
+ for (const key of ["stats", "details"]) {
2246
+ const value = recordOrUndefined(schedulerProjection[key]);
2247
+ if (value) {
2248
+ delete value.histogramArtifactBase64;
2249
+ }
2250
+ }
2251
+ const schedulerPayload = Buffer.from(JSON.stringify(schedulerProjection), "utf8");
2252
+ const snapshot = createLoadEngineV2ResultSnapshot(String(command.runId), String(command.sessionId), this.options.agentId, "0", String(command.planHash), true, chunkBytes, [
2253
+ loadEngineV2PresentArtifact("scheduler", "main", "scheduler-snapshot-v2", schedulerPayload),
2254
+ loadEngineV2PresentArtifact("histograms", "main", "histogram-set-v1", histogramBytes),
2255
+ loadEngineV2NotApplicableArtifact("correlations", "main"),
2256
+ loadEngineV2NotApplicableArtifact("portal-deltas", "main"),
2257
+ loadEngineV2NotApplicableArtifact("correlation-detail-index", "main")
2258
+ ]);
2259
+ const producer = this.createAdapter(this.endpoint("Produce", `agent-${this.options.agentId}-v2-result`, resultSubject, true));
2260
+ const acknowledgement = this.createAdapter(this.endpoint("Consume", `agent-${this.options.agentId}-v2-result-ack`, ackSubject));
2261
+ try {
2262
+ await acknowledgement.consume();
2263
+ for (let attempt = 0; attempt < 5; attempt += 1) {
2264
+ for (const bytes of [
2265
+ snapshot.manifest.bytes,
2266
+ ...snapshot.chunks.map((chunk) => chunk.bytes),
2267
+ snapshot.commit.bytes
2268
+ ]) {
2269
+ await producer.produce({
2270
+ headers: { "x-cluster-command-id": result.commandId },
2271
+ body: bytes,
2272
+ contentType: "application/octet-stream",
2273
+ messagePayloadType: "binary"
2274
+ });
2275
+ }
2276
+ const deadline = Date.now() + 100 * (2 ** attempt);
2277
+ while (Date.now() < deadline) {
2278
+ const payload = await acknowledgement.consume();
2279
+ const body = recordOrUndefined(payload?.body);
2280
+ if (body?.phase === "result-ack" && body.accepted === true
2281
+ && body.commandId === result.commandId && body.resultOwnerId === this.options.agentId
2282
+ && body.snapshotSequence64 === snapshot.commit.snapshotSequence64
2283
+ && body.manifestSha256 === snapshot.commit.manifestSha256)
2284
+ return;
2285
+ await sleep(5);
2286
+ }
2287
+ }
2288
+ throw new Error("Load Engine V2 canonical result acknowledgement timed out.");
2289
+ }
2290
+ finally {
2291
+ await Promise.all([
2292
+ producer.dispose?.().catch(() => { }),
2293
+ acknowledgement.dispose?.().catch(() => { })
2294
+ ]);
2295
+ }
2296
+ }
2297
+ async publishV2ClockProbe(body) {
2298
+ const a1 = utcNowNs64();
2299
+ const agentResolution = this.options.v2ClockResolutionNs64 ?? "1000000";
2300
+ parseCanonicalNonNegativeInt64(agentResolution, "agent clock resolution");
2301
+ const producer = this.createAdapter(this.endpoint("Produce", `agent-${this.options.agentId}-v2-clock`, String(body.replySubject)));
2302
+ try {
2303
+ await producer.produce({ headers: { "x-cluster-command-id": String(body.commandId) }, body: {
2304
+ phase: "clock-probe-result", commandId: String(body.commandId), agentId: this.options.agentId,
2305
+ probeIndex: Number(body.probeIndex), barrierEpoch64: String(body.barrierEpoch64 ?? "0"),
2306
+ coordinatorSendUtcNs64: String(body.coordinatorSendUtcNs64),
2307
+ agentReceiveUtcNs64: a1, agentSendUtcNs64: utcNowNs64(),
2308
+ coordinatorClockResolutionNs64: String(body.coordinatorClockResolutionNs64),
2309
+ agentClockResolutionNs64: agentResolution
2310
+ } });
2311
+ }
2312
+ finally {
2313
+ await producer.dispose?.().catch(() => { });
2314
+ }
2315
+ }
2316
+ async acceptV2Prepare(body) {
2317
+ const planInput = body.planInput;
2318
+ this.options.validateV2Plan?.(planInput);
2319
+ const recomputed = buildLoadEngineV2Plan(planInput);
2320
+ const barrierAssignments = body.barrierAssignments;
2321
+ const assignmentHash = buildLoadEngineV2AssignmentHash(String(body.barrierScopeId), barrierAssignments);
2322
+ const deadline = parseCanonicalInt64(String(body.deadlineUtcNs64), "barrier deadline");
2323
+ const leaseExpiry = parseCanonicalInt64(String(body.leaseExpiresUtcNs64), "registration lease expiry");
2324
+ const now = utcNowNs();
2325
+ if (!hashEquals(recomputed.hash, String(body.planHash))
2326
+ || !hashEquals(assignmentHash, String(body.assignmentHash))
2327
+ || !barrierAssignments.some((row) => row.agentId === this.options.agentId)
2328
+ || deadline - now < 1000000000n || leaseExpiry <= now) {
2329
+ throw new Error("Load Engine V2 targeted prepare failed signed plan, assignment, lead-time, or lease validation.");
2330
+ }
2331
+ this.v2Prepared = { ...body, receivedMonotonicNs64: process.hrtime.bigint().toString() };
2332
+ await this.publishV2Ack(body, "armed", true);
2333
+ }
2334
+ async acceptV2Commit(body) {
2335
+ const prepared = this.v2Prepared;
2336
+ if (!prepared || prepared.commandId !== body.commandId || prepared.planHash !== body.planHash
2337
+ || prepared.barrierScopeId !== body.barrierScopeId || prepared.assignmentHash !== body.assignmentHash
2338
+ || prepared.barrierEpoch64 !== body.barrierEpoch64 || prepared.deadlineUtcNs64 !== body.deadlineUtcNs64) {
2339
+ throw new Error("Load Engine V2 commit does not match a prepared scoped epoch.");
2340
+ }
2341
+ const armedAcks = Array.isArray(body.armedAcks) ? body.armedAcks : [];
2342
+ if (!hashEquals(buildV2ArmedHash(armedAcks), String(body.armedHash))) {
2343
+ throw new Error("Load Engine V2 commit armed-acknowledgement hash is invalid.");
2344
+ }
2345
+ const deadline = parseCanonicalInt64(String(body.deadlineUtcNs64), "barrier deadline");
2346
+ const leaseExpiry = parseCanonicalNonNegativeInt64(String(prepared.leaseExpiresUtcNs64), "registration lease expiry");
2347
+ if (deadline - utcNowNs() < 100000000n || leaseExpiry <= utcNowNs()) {
2348
+ throw new Error("Load Engine V2 commit arrived too late to start traffic.");
2349
+ }
2350
+ const committed = { ...prepared, ...body };
2351
+ await this.publishV2Ack(committed, "committed", true);
2352
+ const delayMs = Number((deadline - utcNowNs()) / 1000000n);
2353
+ if (delayMs > 0)
2354
+ await sleep(delayMs);
2355
+ return committed;
2356
+ }
2357
+ async ensureV2Registration() {
2358
+ const prefix = `loadstrike.${sanitizeToken(this.options.clusterId)}.${sanitizeToken(this.options.sessionId)}.v2`;
2359
+ if (!this.v2HelloConsumer) {
2360
+ this.v2HelloConsumer = this.createAdapter(this.endpoint("Consume", `agent-${this.options.agentId}-v2-hello`, `${prefix}.hello`));
2361
+ }
2362
+ if (!this.v2Hello) {
2363
+ const payload = await this.v2HelloConsumer.consume();
2364
+ const body = recordOrUndefined(payload?.body);
2365
+ if (!body)
2366
+ return false;
2367
+ const hello = body;
2368
+ validateLoadEngineV2CoordinatorHello(hello, this.options.sessionId, this.options.agentId, utcNowNs64());
2369
+ this.v2Hello = hello;
2370
+ this.v2RegistrationProducer = this.createAdapter(this.endpoint("Produce", `agent-${this.options.agentId}-v2-registration`, hello.registrationSubject));
2371
+ this.v2CommandConsumer = this.createAdapter(this.endpoint("Consume", `agent-${this.options.agentId}-v2-command`, buildLoadEngineV2ScopedTargetSubject(hello.runId, hello.sessionId, hello.coordinatorNonce, this.options.agentId)));
2372
+ await this.v2CommandConsumer.initialize?.();
2373
+ await this.publishV2Registration(true);
2374
+ this.v2RenewalTimer = setInterval(() => {
2375
+ void this.publishV2Registration(true).catch((error) => { this.v2RenewalError = error; });
2376
+ }, 10000);
2377
+ this.v2RenewalTimer.unref?.();
2378
+ return true;
2379
+ }
2380
+ if (Date.now() - this.v2LastRegistrationMs >= 100)
2381
+ await this.publishV2Registration(false);
2382
+ return true;
2383
+ }
2384
+ ensureV2CommandConsumer() {
2385
+ if (!this.v2CommandConsumer) {
2386
+ throw new Error("Load Engine V2 command consumer requires an authenticated coordinator hello.");
2387
+ }
2388
+ return this.v2CommandConsumer;
2389
+ }
2390
+ async publishV2Registration(renewLease) {
2391
+ const hello = this.v2Hello;
2392
+ const producer = this.v2RegistrationProducer;
2393
+ if (!hello || !producer)
2394
+ throw new Error("Load Engine V2 registration requires an authenticated coordinator hello.");
2395
+ if (!this.v2CurrentRegistration || renewLease) {
2396
+ this.v2RegistrationSequence += 1n;
2397
+ const issued = utcNowNs();
2398
+ this.v2CurrentRegistration = createLoadEngineV2Registration({
2399
+ runId: hello.runId,
2400
+ sessionId: hello.sessionId,
2401
+ coordinatorNonce: hello.coordinatorNonce,
2402
+ agentId: this.options.agentId,
2403
+ targetSubject: buildLoadEngineV2ScopedTargetSubject(hello.runId, hello.sessionId, hello.coordinatorNonce, this.options.agentId),
2404
+ registrationSequence64: this.v2RegistrationSequence.toString(),
2405
+ leaseIssuedUtcNs64: issued.toString(),
2406
+ leaseExpiresUtcNs64: (issued + 30000000000n).toString(),
2407
+ capabilities: this.options.v2Capabilities
2408
+ });
2409
+ }
2410
+ await producer.produce({ headers: {}, body: this.v2CurrentRegistration });
2411
+ this.v2LastRegistrationMs = Date.now();
2412
+ }
2413
+ matchesV2Scope(body) {
2414
+ const hello = this.v2Hello;
2415
+ return !!hello && String(body.runId ?? "") === hello.runId
2416
+ && String(body.sessionId ?? "") === hello.sessionId
2417
+ && String(body.coordinatorNonce ?? "") === hello.coordinatorNonce;
2418
+ }
2419
+ async publishV2Ack(command, phase, accepted, extra = {}) {
2420
+ const producer = this.createAdapter(this.endpoint("Produce", `agent-${this.options.agentId}-v2-${phase}`, String(command.replySubject)));
2421
+ try {
2422
+ await producer.produce({ headers: { "x-cluster-command-id": String(command.commandId) }, body: {
2423
+ phase, commandId: String(command.commandId), agentId: this.options.agentId, accepted,
2424
+ planHash: String(command.planHash), barrierScopeId: String(command.barrierScopeId),
2425
+ assignmentHash: String(command.assignmentHash), barrierEpoch64: String(command.barrierEpoch64),
2426
+ deadlineUtcNs64: String(command.deadlineUtcNs64), acknowledgedUtcNs64: utcNowNs64(),
2427
+ ...extra
2428
+ } });
2429
+ }
2430
+ finally {
2431
+ await producer.dispose?.().catch(() => { });
2432
+ }
2433
+ }
2434
+ endpoint(mode, name, subject, binary = false) {
2435
+ return {
2436
+ kind: "Nats", mode, name, trackingField: "header:x-cluster-command-id",
2437
+ nats: { ...(this.options.nats ?? {}), Subject: subject, StartFromEarliest: true },
2438
+ ...(binary ? { contentType: "application/octet-stream", messagePayloadType: "binary" } : {})
2439
+ };
2440
+ }
2441
+ createAdapter(definition) {
2442
+ return (this.options.adapterFactory ?? EndpointAdapterFactory).create(definition);
2443
+ }
310
2444
  }
311
2445
  function parseRunCommand(value) {
312
2446
  const targetScenarios = Array.isArray(value.targetScenarios)
@@ -326,11 +2460,24 @@ function parseRunCommand(value) {
326
2460
  replySubject: stringOrDefault(value.replySubject, stringOrDefault(value.ReplySubject, ""))
327
2461
  };
328
2462
  }
329
- function parseRunResult(value) {
2463
+ function parseRunResult(value, requireHistogramArtifact = false) {
330
2464
  const statsValue = value.stats ?? value.Stats;
331
2465
  const statsRecord = statsValue && typeof statsValue === "object" && !Array.isArray(statsValue)
332
2466
  ? statsValue
333
2467
  : null;
2468
+ const histogramArtifactBase64 = statsRecord
2469
+ ? stringOrDefault(statsRecord.histogramArtifactBase64, stringOrDefault(statsRecord.HistogramArtifactBase64, ""))
2470
+ : "";
2471
+ if (requireHistogramArtifact && !histogramArtifactBase64) {
2472
+ throw new Error("Load Engine V2 completed result requires a canonical histogram artifact.");
2473
+ }
2474
+ if (histogramArtifactBase64) {
2475
+ const decoded = Buffer.from(histogramArtifactBase64, "base64");
2476
+ if (decoded.toString("base64") !== histogramArtifactBase64) {
2477
+ throw new Error("Load Engine V2 histogram artifact base64 is not canonical.");
2478
+ }
2479
+ parseLoadEngineV2HistogramArtifact(decoded);
2480
+ }
334
2481
  return {
335
2482
  commandId: stringOrDefault(value.commandId, stringOrDefault(value.CommandId, "")),
336
2483
  agentId: stringOrDefault(value.agentId, stringOrDefault(value.AgentId, "")),
@@ -348,7 +2495,20 @@ function parseRunResult(value) {
348
2495
  thresholds: arrayOrUndefined(statsRecord.thresholds ?? statsRecord.Thresholds),
349
2496
  pluginsData: arrayOrUndefined(statsRecord.pluginsData ?? statsRecord.PluginsData),
350
2497
  nodeInfo: recordOrUndefined(statsRecord.nodeInfo ?? statsRecord.NodeInfo),
351
- testInfo: recordOrUndefined(statsRecord.testInfo ?? statsRecord.TestInfo)
2498
+ testInfo: recordOrUndefined(statsRecord.testInfo ?? statsRecord.TestInfo),
2499
+ logFiles: arrayOrUndefined(statsRecord.logFiles ?? statsRecord.LogFiles)?.map((value) => String(value)),
2500
+ generatorWarnings: arrayOrUndefined(statsRecord.generatorWarnings ?? statsRecord.GeneratorWarnings),
2501
+ observationDeliveryStats: recordOrUndefined(statsRecord.observationDeliveryStats ?? statsRecord.ObservationDeliveryStats),
2502
+ schedulerSegments: arrayOrUndefined(statsRecord.schedulerSegments ?? statsRecord.SchedulerSegments),
2503
+ schedulerStats: recordOrUndefined(statsRecord.schedulerStats ?? statsRecord.SchedulerStats),
2504
+ histogramSidecars: arrayOrUndefined(statsRecord.histogramSidecars ?? statsRecord.HistogramSidecars),
2505
+ ...(histogramArtifactBase64 ? { histogramArtifactBase64 } : {}),
2506
+ ...(Object.prototype.hasOwnProperty.call(statsRecord, "reportingComplete")
2507
+ || Object.prototype.hasOwnProperty.call(statsRecord, "ReportingComplete")
2508
+ ? {
2509
+ reportingComplete: booleanOrDefault(statsRecord.reportingComplete, booleanOrDefault(statsRecord.ReportingComplete, false))
2510
+ }
2511
+ : {})
352
2512
  }
353
2513
  : undefined
354
2514
  };
@@ -364,12 +2524,224 @@ function convertRunResult(result) {
364
2524
  error: result.errorMessage ?? ""
365
2525
  };
366
2526
  }
2527
+ function utcNowNs() {
2528
+ return BigInt(Date.now()) * 1000000n;
2529
+ }
2530
+ function utcNowNs64() {
2531
+ return utcNowNs().toString();
2532
+ }
2533
+ async function collectV2Acks(consumer, phase, expectedAgentIds, commandId, timeoutMs) {
2534
+ const byAgent = new Map();
2535
+ const deadline = Date.now() + timeoutMs;
2536
+ while (byAgent.size < expectedAgentIds.length && Date.now() < deadline) {
2537
+ const payload = await consumer.consume();
2538
+ const body = recordOrUndefined(payload?.body);
2539
+ if (!body || body.phase !== phase || body.commandId !== commandId) {
2540
+ await sleep(5);
2541
+ continue;
2542
+ }
2543
+ const acknowledgement = body;
2544
+ if (!expectedAgentIds.includes(acknowledgement.agentId))
2545
+ continue;
2546
+ const previous = byAgent.get(acknowledgement.agentId);
2547
+ if (previous && JSON.stringify(previous) !== JSON.stringify(acknowledgement)) {
2548
+ throw new Error(`Load Engine V2 ${phase} acknowledgement changed during retry.`);
2549
+ }
2550
+ byAgent.set(acknowledgement.agentId, acknowledgement);
2551
+ }
2552
+ return [...byAgent.values()];
2553
+ }
2554
+ async function collectV2ClockProbeResults(consumer, expectedAgentIds, commandId, probeIndex, timeoutMs, barrierEpoch64) {
2555
+ const results = new Map();
2556
+ const deadline = Date.now() + timeoutMs;
2557
+ while (results.size < expectedAgentIds.length && Date.now() < deadline) {
2558
+ const payload = await consumer.consume();
2559
+ const body = recordOrUndefined(payload?.body);
2560
+ if (!body || body.phase !== "clock-probe-result" || body.commandId !== commandId
2561
+ || Number(body.probeIndex) !== probeIndex
2562
+ || (barrierEpoch64 !== undefined && String(body.barrierEpoch64 ?? "") !== barrierEpoch64)) {
2563
+ await sleep(2);
2564
+ continue;
2565
+ }
2566
+ const agentId = String(body.agentId ?? "");
2567
+ if (!expectedAgentIds.includes(agentId))
2568
+ continue;
2569
+ results.set(agentId, {
2570
+ probeIndex,
2571
+ coordinatorSendUtcNs64: String(body.coordinatorSendUtcNs64),
2572
+ agentReceiveUtcNs64: String(body.agentReceiveUtcNs64),
2573
+ agentSendUtcNs64: String(body.agentSendUtcNs64),
2574
+ coordinatorReceiveUtcNs64: utcNowNs64(),
2575
+ coordinatorClockResolutionNs64: String(body.coordinatorClockResolutionNs64),
2576
+ agentClockResolutionNs64: String(body.agentClockResolutionNs64)
2577
+ });
2578
+ }
2579
+ if (results.size !== expectedAgentIds.length) {
2580
+ throw new Error("Load Engine V2 clock negotiation is missing an exact participant probe response.");
2581
+ }
2582
+ return results;
2583
+ }
2584
+ function validateV2DrainAcks(expectedAgentIds, acknowledgements, planHash, assignmentHash, barrierEpoch64, barrierScopeId) {
2585
+ const expected = normalizeExpectedAgentIds(expectedAgentIds);
2586
+ const ordered = [...acknowledgements].sort((left, right) => compareAgentIds(left.agentId, right.agentId));
2587
+ if (ordered.length !== expected.length || ordered.some((value, index) => value.agentId !== expected[index])) {
2588
+ throw new Error("Load Engine V2 drained barrier is missing an exact participant acknowledgement.");
2589
+ }
2590
+ for (const acknowledgement of ordered) {
2591
+ const row = acknowledgement;
2592
+ parseCanonicalInt64(row.acknowledgedUtcNs64, "drain acknowledgement time");
2593
+ if (!row.accepted || row.phase !== "drained" || row.planHash !== planHash
2594
+ || row.assignmentHash !== assignmentHash || row.barrierScopeId !== barrierScopeId
2595
+ || row.barrierEpoch64 !== barrierEpoch64 || row.finalDeltaAcknowledged !== true
2596
+ || row.accountingComplete !== true) {
2597
+ throw new Error("Load Engine V2 drained acknowledgement is invalid.");
2598
+ }
2599
+ }
2600
+ }
2601
+ function buildV2ArmedHash(acknowledgements) {
2602
+ const fields = [Buffer.from("LS-AR1\n", "ascii"), frameUtf8("1")];
2603
+ const ordered = [...acknowledgements].sort((left, right) => compareAgentIds(left.agentId, right.agentId));
2604
+ fields.push(frameUtf8(ordered.length.toString()));
2605
+ for (const acknowledgement of ordered) {
2606
+ for (const value of [
2607
+ acknowledgement.agentId,
2608
+ acknowledgement.planHash,
2609
+ acknowledgement.barrierScopeId ?? "",
2610
+ acknowledgement.assignmentHash,
2611
+ acknowledgement.barrierEpoch64,
2612
+ acknowledgement.deadlineUtcNs64,
2613
+ acknowledgement.acknowledgedUtcNs64
2614
+ ])
2615
+ fields.push(frameUtf8(value));
2616
+ }
2617
+ return sha256Hex(Buffer.concat(fields));
2618
+ }
2619
+ function buildOwnerLossAssignments(plan, missingAgentIds, expectedAgentIds) {
2620
+ return missingAgentIds.map((agentId) => {
2621
+ const shardIndex = expectedAgentIds.indexOf(agentId);
2622
+ return {
2623
+ agentId,
2624
+ shardIndex64: shardIndex.toString(),
2625
+ shardCount64: expectedAgentIds.length.toString(),
2626
+ scenarios: plan.scenarios.filter((scenario) => scenario.target === "agent").map((scenario) => ({
2627
+ scenarioName: scenario.scenarioName,
2628
+ scenarioIndex64: scenario.scenarioIndex64,
2629
+ simulations: scenario.simulations.map((simulation) => ({
2630
+ simulationIndex64: simulation.simulationIndex64,
2631
+ kind: simulation.kind,
2632
+ plannedIterations64: ownedPlannedIterations(simulation, shardIndex, expectedAgentIds.length).toString()
2633
+ }))
2634
+ }))
2635
+ };
2636
+ });
2637
+ }
2638
+ function ownedPlannedIterations(simulation, shardIndex, shardCount) {
2639
+ let global = 0n;
2640
+ if (simulation.kind === "iterations-for-inject" || simulation.kind === "iterations-for-constant") {
2641
+ global = parseCanonicalNonNegativeInt64(simulation.iterations64, "planned iterations");
2642
+ }
2643
+ else if (simulation.kind === "inject") {
2644
+ const duration = parseCanonicalNonNegativeInt64(simulation.durationNs64, "duration");
2645
+ const interval = parseCanonicalNonNegativeInt64(simulation.intervalNs64, "interval");
2646
+ const rate = parseCanonicalNonNegativeInt64(simulation.rate64, "rate");
2647
+ if (duration > 0n && interval > 0n)
2648
+ global = ((duration + interval - 1n) / interval) * rate;
2649
+ }
2650
+ const index = BigInt(shardIndex);
2651
+ const count = BigInt(shardCount);
2652
+ return global <= index ? 0n : ((global - 1n - index) / count) + 1n;
2653
+ }
367
2654
  function sanitizeToken(value) {
368
2655
  if (!value.trim()) {
369
2656
  return "default";
370
2657
  }
371
2658
  return value.replace(/[^a-zA-Z0-9\-_]/g, "_");
372
2659
  }
2660
+ function validateLoadEngineV2AgentId(agentId) {
2661
+ if (!agentId)
2662
+ throw new Error("Load Engine V2 remote agents require an explicit stable AgentId.");
2663
+ validateUnicodeScalars(agentId);
2664
+ if (Buffer.byteLength(agentId, "utf8") > 256) {
2665
+ throw new Error("Load Engine V2 AgentId must be at most 256 UTF-8 bytes.");
2666
+ }
2667
+ }
2668
+ function normalizeExpectedAgentIds(values) {
2669
+ if (!values.length)
2670
+ throw new Error("Load Engine V2 requires an exact ExpectedAgentIds participant set.");
2671
+ values.forEach(validateLoadEngineV2AgentId);
2672
+ if (new Set(values).size !== values.length) {
2673
+ throw new Error("Load Engine V2 expected agent identities must be unique.");
2674
+ }
2675
+ return [...values].sort(compareUtf8);
2676
+ }
2677
+ function compareUtf8(left, right) {
2678
+ validateUnicodeScalars(left);
2679
+ validateUnicodeScalars(right);
2680
+ return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
2681
+ }
2682
+ function compareAgentIds(left, right) {
2683
+ return compareUtf8(left, right);
2684
+ }
2685
+ function validateUnicodeScalars(value) {
2686
+ for (let index = 0; index < value.length; index += 1) {
2687
+ const code = value.charCodeAt(index);
2688
+ if (code >= 0xd800 && code <= 0xdbff) {
2689
+ const next = value.charCodeAt(index + 1);
2690
+ if (next < 0xdc00 || next > 0xdfff)
2691
+ throw new Error("Load Engine V2 text must contain valid Unicode scalars.");
2692
+ index += 1;
2693
+ }
2694
+ else if (code >= 0xdc00 && code <= 0xdfff) {
2695
+ throw new Error("Load Engine V2 text must contain valid Unicode scalars.");
2696
+ }
2697
+ }
2698
+ }
2699
+ function frameUtf8(value) {
2700
+ validateUnicodeScalars(value);
2701
+ const bytes = Buffer.from(value, "utf8");
2702
+ const frame = Buffer.allocUnsafe(4 + bytes.length);
2703
+ frame.writeUInt32BE(bytes.length, 0);
2704
+ bytes.copy(frame, 4);
2705
+ return frame;
2706
+ }
2707
+ function parseCanonicalInt64(value, field) {
2708
+ if (!/^-?(0|[1-9][0-9]*)$/.test(value) || value === "-0") {
2709
+ throw new Error(`${field} must be a canonical decimal string.`);
2710
+ }
2711
+ const parsed = BigInt(value);
2712
+ if (parsed < -9223372036854775808n || parsed > 9223372036854775807n) {
2713
+ throw new Error(`${field} exceeds signed 64-bit storage.`);
2714
+ }
2715
+ return parsed;
2716
+ }
2717
+ function parseCanonicalNonNegativeInt64(value, field) {
2718
+ const parsed = parseCanonicalInt64(value, field);
2719
+ if (parsed < 0n)
2720
+ throw new Error(`${field} must be non-negative.`);
2721
+ return parsed;
2722
+ }
2723
+ function compareCanonicalIntegers(left, right) {
2724
+ const leftValue = parseCanonicalNonNegativeInt64(left, "canonical index");
2725
+ const rightValue = parseCanonicalNonNegativeInt64(right, "canonical index");
2726
+ return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
2727
+ }
2728
+ function validateLowercaseHash(value, field) {
2729
+ if (!/^[0-9a-f]{64}$/.test(value)) {
2730
+ throw new Error(`Load Engine V2 ${field} must be 64 lowercase hexadecimal characters.`);
2731
+ }
2732
+ }
2733
+ function sha256Hex(value) {
2734
+ return createHash("sha256").update(value).digest("hex");
2735
+ }
2736
+ function hashEquals(left, right) {
2737
+ validateLowercaseHash(left, "hash");
2738
+ validateLowercaseHash(right, "hash");
2739
+ return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
2740
+ }
2741
+ function requireCapability(offered, required, display) {
2742
+ if (!offered.includes(required))
2743
+ throw new Error(`Load Engine V2 capability negotiation requires ${display}.`);
2744
+ }
373
2745
  function buildWeightedCycle(weights) {
374
2746
  const cycle = [];
375
2747
  for (let i = 0; i < weights.length; i += 1) {
@@ -402,6 +2774,13 @@ function recordOrUndefined(value) {
402
2774
  ? value
403
2775
  : undefined;
404
2776
  }
2777
+ function loadEngineV2BinaryBody(value) {
2778
+ if (value instanceof Uint8Array)
2779
+ return Buffer.from(value);
2780
+ if (value instanceof ArrayBuffer)
2781
+ return Buffer.from(value);
2782
+ return undefined;
2783
+ }
405
2784
  function arrayOrUndefined(value) {
406
2785
  return Array.isArray(value) ? value : undefined;
407
2786
  }