@zlink-systems/framework 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +2 -0
  3. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  4. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  5. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  6. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  7. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  8. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
  9. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  10. package/dist/runtime/actors/actor-mailbox.js +2 -2
  11. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  12. package/dist/runtime/actors/index.d.ts +2 -0
  13. package/dist/runtime/actors/index.js +21 -6
  14. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  15. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  16. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  17. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  18. package/dist/runtime/foundation/operation-registry.js +5 -1
  19. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  20. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  21. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  22. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  23. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  24. package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
  25. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  26. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  27. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  28. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  29. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  30. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  31. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  32. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  33. package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
  34. package/dist/runtime/host/index.d.ts +1 -1
  35. package/dist/runtime/host/index.js +3 -2
  36. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  37. package/dist/runtime/host/instance-activation-authority.js +46 -97
  38. package/dist/runtime/host/spot-address-transport.js +1 -1
  39. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  40. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  41. package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
  42. package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
  43. package/dist/runtime/locations/in-memory-authority-store.js +47 -12
  44. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
  45. package/dist/runtime/locations/in-memory-location-store.js +15 -10
  46. package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
  47. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  48. package/dist/runtime/locations/location-store-repository.d.ts +2 -0
  49. package/dist/runtime/locations/location-store-repository.js +130 -54
  50. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  51. package/dist/runtime/spots/spot-entry-activation.js +3 -0
  52. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
  53. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
  54. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  55. package/dist/runtime/spots/spot-serial-executor.js +14 -2
  56. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  57. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  58. package/dist/runtime/streams/index.d.ts +0 -1
  59. package/dist/runtime/streams/index.js +4 -3
  60. package/dist/runtime/streams/protocol.js +0 -9
  61. package/dist/runtime/streams/session-context.d.ts +0 -4
  62. package/dist/runtime/streams/session-context.js +0 -26
  63. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  64. package/dist/runtime/streams/session-serial-executor.js +0 -3
  65. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  66. package/dist/runtime/streams/stream-message-utils.js +0 -20
  67. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  68. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  69. package/package.json +3 -3
  70. package/src/contracts/Configuration/Builders.ts +2 -0
  71. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  72. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
  73. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  74. package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
  75. package/src/runtime/actors/actor-mailbox.ts +7 -2
  76. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  77. package/src/runtime/actors/index.ts +77 -42
  78. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  79. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  80. package/src/runtime/foundation/operation-registry.ts +16 -9
  81. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  82. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  83. package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
  84. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  85. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  86. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  87. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  88. package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
  89. package/src/runtime/host/index.ts +6 -2
  90. package/src/runtime/host/instance-activation-authority.ts +58 -125
  91. package/src/runtime/host/spot-address-transport.ts +1 -1
  92. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  93. package/src/runtime/locations/domain-store-contract.ts +6 -1
  94. package/src/runtime/locations/in-memory-authority-store.ts +83 -12
  95. package/src/runtime/locations/in-memory-location-store.ts +22 -10
  96. package/src/runtime/locations/internal-location-contracts.ts +6 -0
  97. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  98. package/src/runtime/locations/location-store-repository.ts +182 -69
  99. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  100. package/src/runtime/spots/spot-entry-activation.ts +4 -0
  101. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
  102. package/src/runtime/spots/spot-serial-executor.ts +14 -5
  103. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  104. package/src/runtime/streams/index.ts +3 -1
  105. package/src/runtime/streams/protocol.ts +0 -13
  106. package/src/runtime/streams/session-context.ts +0 -32
  107. package/src/runtime/streams/session-serial-executor.ts +0 -8
  108. package/src/runtime/streams/stream-message-utils.ts +1 -27
  109. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  110. package/dist/runtime/streams/session-requests.d.ts +0 -25
  111. package/dist/runtime/streams/session-requests.js +0 -101
  112. package/src/runtime/streams/session-requests.ts +0 -110
@@ -129,14 +129,15 @@ export declare class ServiceStatefulRegistry {
129
129
  runTurn<T>(owner: string, work: () => T | Promise<T>): Promise<T>;
130
130
  private updateBindingMembership;
131
131
  }
132
- export declare class ServiceTerminalOperationRegistry<T> {
132
+ export declare class ServiceTerminalOperationRegistry<T, Context = unknown> {
133
133
  private readonly operations;
134
- constructor(operations?: OperationRegistry<T>);
135
- register(timeoutMs: number, timeoutOwner?: 'registry' | 'sender'): PendingOperation<T>;
134
+ constructor(operations?: OperationRegistry<T, Context>);
135
+ register(timeoutMs: number, timeoutOwner?: 'registry' | 'sender', context?: Context): PendingOperation<T>;
136
136
  reply(id: bigint, value: T): boolean;
137
137
  fail(id: bigint, error: unknown): boolean;
138
138
  cancel(id: bigint): boolean;
139
139
  isPending(id: bigint): boolean;
140
+ context(id: bigint): Context | undefined;
140
141
  get size(): number;
141
142
  expire(nowMs: number, turnDeadlineMs: number): number;
142
143
  close(): void;
@@ -430,8 +430,8 @@ class ServiceTerminalOperationRegistry {
430
430
  constructor(operations = new operation_registry_1.OperationRegistry()) {
431
431
  this.operations = operations;
432
432
  }
433
- register(timeoutMs, timeoutOwner = 'registry') {
434
- return this.operations.register(timeoutMs, timeoutOwner);
433
+ register(timeoutMs, timeoutOwner = 'registry', context) {
434
+ return this.operations.register(timeoutMs, timeoutOwner, context);
435
435
  }
436
436
  reply(id, value) {
437
437
  return this.operations.complete(id, value);
@@ -445,6 +445,9 @@ class ServiceTerminalOperationRegistry {
445
445
  isPending(id) {
446
446
  return this.operations.isPending(id);
447
447
  }
448
+ context(id) {
449
+ return this.operations.context(id);
450
+ }
448
451
  get size() {
449
452
  return this.operations.size;
450
453
  }
@@ -125,9 +125,6 @@ export type ServiceInstanceAuthorityRead = {
125
125
  export type ServiceInstanceAuthorityReserve = {
126
126
  readonly kind: 'reserved';
127
127
  readonly reservation: ServiceInstanceActivationReservation;
128
- } | {
129
- readonly kind: 'ready';
130
- readonly route: ServiceInstanceRouteFence;
131
128
  };
132
129
  /**
133
130
  * Synchronous authority port used by the raw ingress turn. An asynchronous
@@ -161,10 +158,6 @@ export interface ServiceAsyncInstanceActivationAuthority {
161
158
  complete(target: ServiceInstanceActivationTarget, route: ServiceInstanceRouteFence): Promise<ServiceInstanceRouteFence>;
162
159
  abort(target: ServiceInstanceActivationTarget, reservation: ServiceInstanceActivationReservation): Promise<void>;
163
160
  }
164
- export declare class ServiceInstanceActivationRedirectError extends Error {
165
- readonly route: ServiceInstanceRouteFence;
166
- constructor(route: ServiceInstanceRouteFence);
167
- }
168
161
  export interface ServiceUserSpotOperationResult {
169
162
  readonly terminalResult: number;
170
163
  readonly failureCode: number;
@@ -326,7 +319,7 @@ export declare class ServiceStatefulRuntime {
326
319
  private beginInstanceAuthorityOperation;
327
320
  private completeInstanceAuthorityOperation;
328
321
  private completeInstanceApplicationWaitersIfQuiescent;
329
- private relayMissingInstanceActivation;
322
+ private finishMissingInstanceActivationFailure;
330
323
  private requireInstanceActivation;
331
324
  private requireReadyInstanceApplicationRoute;
332
325
  private validateInstanceIngress;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceStatefulRuntime = exports.ServiceInstanceActivationRedirectError = void 0;
3
+ exports.ServiceStatefulRuntime = void 0;
4
4
  exports.statefulMailboxData = statefulMailboxData;
5
5
  const stream_wire_1 = require("@zlink-systems/stream-wire");
6
6
  const node_async_hooks_1 = require("node:async_hooks");
@@ -31,15 +31,6 @@ const SPOT_MOVING = 34;
31
31
  const USER_SPOT_OPERATION_CAPACITY = 65_536;
32
32
  const USER_SPOT_OPERATION_REPLAY_RETENTION_MS = 5 * 60_000;
33
33
  const ACTOR_ROUTE_NOT_FOUND = 1;
34
- class ServiceInstanceActivationRedirectError extends Error {
35
- route;
36
- constructor(route) {
37
- super(`Instance Spot '${route.targetSpotId}' is owned by '${route.targetNodeRid}'.`);
38
- this.route = route;
39
- this.name = 'ServiceInstanceActivationRedirectError';
40
- }
41
- }
42
- exports.ServiceInstanceActivationRedirectError = ServiceInstanceActivationRedirectError;
43
34
  /**
44
35
  * Owns M6B routing, identity fences and terminal operations. Application turns
45
36
  * remain in the common mailbox, where a claimed Spot or Actor owner is serial.
@@ -410,7 +401,7 @@ class ServiceStatefulRuntime {
410
401
  const committed = await authority.commit(target, reservation, spot);
411
402
  if (committed.kind !== 'committed') {
412
403
  this.registry.closeSpot(spot.ref);
413
- throw new ServiceInstanceActivationRedirectError(committed.route);
404
+ throw activationRefused();
414
405
  }
415
406
  return this.finishRecoveredInstanceActivation(envelope, committed.route, expectedCurrentRoute);
416
407
  }
@@ -763,7 +754,7 @@ class ServiceStatefulRuntime {
763
754
  const timeoutMs = remainingDeadlineMs(deadlineUnixMs);
764
755
  const pending = this.operations.register(timeoutMs);
765
756
  this.submitRequest(pending, target.targetNodeRid, instanceOperationParts([
766
- (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high: this.nodeGeneration, low: pending.id }, deadlineUnixMs, pending.id, metadataFrame !== undefined),
757
+ (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high: operation_identity_1.ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id }, deadlineUnixMs, pending.id, metadataFrame !== undefined),
767
758
  payloadFrame
768
759
  ], metadataFrame), timeoutMs, 'instanceSpotRequest');
769
760
  return pending;
@@ -1116,6 +1107,10 @@ class ServiceStatefulRuntime {
1116
1107
  const metadata = (record.flags & service_stateful_wire_codec_1.M6bServiceWireFlag.metadata) !== 0;
1117
1108
  decoded = (0, service_stateful_wire_codec_1.decodeStatefulHeader)(record.parts[0], record.parts[metadata ? 2 : 1], metadata ? record.parts[1] : undefined);
1118
1109
  }
1110
+ if (decoded.kind === 'instanceSpot' &&
1111
+ (decoded.operationKind === 'request') !== (record.requestSequence !== undefined)) {
1112
+ return 'protocolError';
1113
+ }
1119
1114
  const hasPayload = [
1120
1115
  'spotSend',
1121
1116
  'spotRequest',
@@ -1164,6 +1159,12 @@ class ServiceStatefulRuntime {
1164
1159
  return await this.handleIngress(record, decoded, payloadFrame, metadataFrame, canonicalActorJoin, canonicalPayload);
1165
1160
  }
1166
1161
  catch (error) {
1162
+ if (decoded.kind === 'instanceSpot' &&
1163
+ decoded.activation === 'missing' &&
1164
+ (error instanceof contracts_1.ZLinkFrameworkException || error instanceof service_stateful_registry_1.ServiceStaleGenerationError)) {
1165
+ this.finishMissingInstanceActivationFailure(record, decoded, error);
1166
+ return 'infrastructure';
1167
+ }
1167
1168
  const correlation = statefulCorrelation(decoded);
1168
1169
  if (correlation !== undefined) {
1169
1170
  const result = failure(error);
@@ -1254,7 +1255,7 @@ class ServiceStatefulRuntime {
1254
1255
  sourceSpotId: record.sourceSpotId,
1255
1256
  targetSpot: record.target.spot,
1256
1257
  ...(record.kind === 'spotRequest'
1257
- ? { reply: this.replyPort(ingress, record.correlation, 'spotRequest') }
1258
+ ? { reply: this.replyPort(ingress, record.correlation) }
1258
1259
  : {})
1259
1260
  });
1260
1261
  }
@@ -1290,7 +1291,7 @@ class ServiceStatefulRuntime {
1290
1291
  originalReplyRouteId: ingress.requestSequence ?? 0n
1291
1292
  },
1292
1293
  ...(record.kind === 'actorRequest'
1293
- ? { reply: this.replyPort(ingress, record.correlation, 'actorRequest') }
1294
+ ? { reply: this.replyPort(ingress, record.correlation) }
1294
1295
  : {})
1295
1296
  });
1296
1297
  }
@@ -1324,10 +1325,10 @@ class ServiceStatefulRuntime {
1324
1325
  return this.handleUserSpotOperation(ingress, record);
1325
1326
  }
1326
1327
  }
1327
- enqueueInstanceSpot(ingress, record, payloadFrame, metadataFrame) {
1328
+ enqueueInstanceSpot(ingress, record, payloadFrame, metadataFrame, localReply) {
1328
1329
  if (record.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
1329
1330
  const ownedIngress = shareIngressOwnership(ingress);
1330
- void this.continueMissingInstanceActivation(ownedIngress, record, payloadFrame, undefined, metadataFrame).finally(() => ownedIngress.applicationJobOwner?.close());
1331
+ void this.continueMissingInstanceActivation(ownedIngress, record, payloadFrame, localReply, metadataFrame).finally(() => ownedIngress.applicationJobOwner?.close());
1331
1332
  return 'infrastructure';
1332
1333
  }
1333
1334
  if (record.activation === 'ready' &&
@@ -1344,7 +1345,7 @@ class ServiceStatefulRuntime {
1344
1345
  objectGeneration: spot.ref.generation
1345
1346
  }
1346
1347
  : this.instanceApplicationTarget(record);
1347
- return this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, spot, undefined, this.instanceApplicationTerminalCompletion(applicationTarget), metadataFrame);
1348
+ return this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, spot, localReply, this.instanceApplicationTerminalCompletion(applicationTarget), metadataFrame);
1348
1349
  }
1349
1350
  needsInstanceApplicationMaterialization(ingress, record) {
1350
1351
  const lifecycle = this.instanceApplicationLifecycle;
@@ -1376,7 +1377,10 @@ class ServiceStatefulRuntime {
1376
1377
  return 'application';
1377
1378
  }
1378
1379
  const directReply = record.operationKind === 'request'
1379
- ? (localReply ?? this.replyPort(ingress, record.replyRouteId, 'instanceSpotRequest'))
1380
+ ? (localReply ??
1381
+ (ingress.requestSequence === undefined
1382
+ ? undefined
1383
+ : this.replyPort(ingress, record.replyRouteId)))
1380
1384
  : undefined;
1381
1385
  const applicationTarget = applicationTargetOverride ??
1382
1386
  (record.activation === 'missing'
@@ -1410,7 +1414,7 @@ class ServiceStatefulRuntime {
1410
1414
  ...(metadataFrame === undefined ? {} : { applicationMetadata: metadataFrame }),
1411
1415
  ...(terminalCompletion === undefined ? {} : { onTerminalCompletion: terminalCompletion }),
1412
1416
  ...(onHandlerTurnStarted === undefined ? {} : { onHandlerTurnStarted }),
1413
- ...(record.operationKind === 'request'
1417
+ ...(record.operationKind === 'request' && directReply !== undefined
1414
1418
  ? {
1415
1419
  reply: onTerminalCompletion === undefined
1416
1420
  ? directReply
@@ -1432,37 +1436,18 @@ class ServiceStatefulRuntime {
1432
1436
  async continueMissingInstanceActivation(ingress, record, payloadFrame, localReply, metadataFrame) {
1433
1437
  try {
1434
1438
  this.validateInstanceIngress(ingress, record);
1435
- const activation = await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame);
1436
- if (this.closed)
1437
- return;
1438
- const terminal = this.activationTerminalCompletion(record.target, activation.route);
1439
- const admitted = this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, activation.spot, localReply, terminal.onTerminalCompletion, metadataFrame, terminal.onHandlerTurnStarted, this.instanceApplicationTarget(record, activation.route.objectGeneration));
1440
- if (admitted !== 'application') {
1441
- throw new Error('Activated Instance message was not admitted to the local queue.');
1442
- }
1443
- }
1444
- catch (error) {
1445
- let terminalError = error;
1446
- if (error instanceof ServiceInstanceActivationRedirectError && !this.closed) {
1447
- try {
1448
- await this.relayMissingInstanceActivation(record, payloadFrame, error.route, ingress, localReply, metadataFrame);
1439
+ await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
1440
+ if (this.closed)
1449
1441
  return;
1442
+ const terminal = this.activationTerminalCompletion(record.target, activation.route);
1443
+ const admitted = this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, activation.spot, localReply, terminal.onTerminalCompletion, metadataFrame, terminal.onHandlerTurnStarted, this.instanceApplicationTarget(record, activation.route.objectGeneration));
1444
+ if (admitted !== 'application') {
1445
+ throw new Error('Activated Instance message was not admitted to the local queue.');
1450
1446
  }
1451
- catch (relayError) {
1452
- terminalError = relayError;
1453
- }
1454
- }
1455
- if (record.operationKind !== 'request')
1456
- return;
1457
- const result = failure(terminalError);
1458
- if (localReply !== undefined) {
1459
- localReply(result.terminalResult, result.failureCode);
1460
- }
1461
- else {
1462
- if (record.replyRouteId === undefined)
1463
- return;
1464
- this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
1465
- }
1447
+ });
1448
+ }
1449
+ catch (error) {
1450
+ this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
1466
1451
  }
1467
1452
  }
1468
1453
  async continueReadyInstanceMaterialization(ingress, record, payloadFrame, metadataFrame) {
@@ -1492,7 +1477,9 @@ class ServiceStatefulRuntime {
1492
1477
  }
1493
1478
  }
1494
1479
  catch (error) {
1495
- if (record.operationKind !== 'request' || record.replyRouteId === undefined)
1480
+ if (record.operationKind !== 'request' ||
1481
+ record.replyRouteId === undefined ||
1482
+ ingress.requestSequence === undefined)
1496
1483
  return;
1497
1484
  const result = failure(error);
1498
1485
  this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
@@ -1545,8 +1532,11 @@ class ServiceStatefulRuntime {
1545
1532
  return { onTerminalCompletion, onHandlerTurnStarted };
1546
1533
  }
1547
1534
  instanceApplicationTerminalCompletion(target) {
1535
+ const lifecycle = this.instanceApplicationLifecycle;
1536
+ if (lifecycle === undefined)
1537
+ return undefined;
1548
1538
  return async () => {
1549
- const authorityReleased = (await this.instanceApplicationLifecycle?.completeTerminal(target)) ?? false;
1539
+ const authorityReleased = await lifecycle.completeTerminal(target);
1550
1540
  if (!authorityReleased)
1551
1541
  return;
1552
1542
  const current = this.instanceIntents.get(target.targetSpotId);
@@ -1601,39 +1591,33 @@ class ServiceStatefulRuntime {
1601
1591
  for (const waiter of waiters)
1602
1592
  waiter();
1603
1593
  }
1604
- async relayMissingInstanceActivation(record, payloadFrame, route, ingress, localReply, metadataFrame) {
1605
- const remainingMs = Number(record.deadlineUnixMs - BigInt(Date.now()));
1606
- if (remainingMs <= 0) {
1607
- throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', route.targetSpotId);
1608
- }
1609
- const redirectedTarget = {
1610
- ...record.target,
1611
- targetNodeRid: route.targetNodeRid,
1612
- targetNodeGeneration: route.targetNodeGeneration,
1613
- targetSpotId: route.targetSpotId
1614
- };
1615
- if (record.operationKind === 'send') {
1616
- const submitted = await this.submitOneWay(route.targetNodeRid, instanceOperationParts([
1617
- (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(redirectedTarget, this.nodeGeneration, this.nodeRid, record.sourceSpotId, 'send', record.operation, record.deadlineUnixMs, undefined, metadataFrame !== undefined),
1618
- payloadFrame
1619
- ], metadataFrame));
1620
- if (submitted !== runtime_values_1.SubmitResult.Ok) {
1621
- throw new Error(`Instance activation redirect was not admitted: ${submitted}.`);
1594
+ finishMissingInstanceActivationFailure(ingress, record, error, localReply) {
1595
+ if (record.operationKind === 'request') {
1596
+ if (record.deadlineUnixMs <= BigInt(Date.now()))
1597
+ return;
1598
+ const result = failure(error);
1599
+ if (localReply !== undefined) {
1600
+ localReply(result.terminalResult, result.failureCode);
1601
+ }
1602
+ else if (ingress.requestSequence !== undefined && record.replyRouteId !== undefined) {
1603
+ this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
1622
1604
  }
1623
1605
  return;
1624
1606
  }
1625
- const pending = this.operations.register(remainingMs);
1626
- this.submitRequest(pending, route.targetNodeRid, instanceOperationParts([
1627
- (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(redirectedTarget, this.nodeGeneration, this.nodeRid, record.sourceSpotId, 'request', record.operation, record.deadlineUnixMs, pending.id, metadataFrame !== undefined),
1628
- payloadFrame
1629
- ], metadataFrame), remainingMs, 'instanceSpotRequest');
1630
- const result = await pending.promise;
1631
- if (localReply !== undefined) {
1632
- localReply(result.terminalResult, result.failureCode, result.payload);
1633
- return;
1634
- }
1635
- if (record.replyRouteId !== undefined) {
1636
- this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode, result.payload);
1607
+ const reporter = this.dispatchErrors;
1608
+ if (reporter?.captureEnabled() === true) {
1609
+ reporter.report({
1610
+ surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.InstanceSpot,
1611
+ messageKind: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send,
1612
+ reason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.StaleTarget,
1613
+ action: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorAction.Drop,
1614
+ meshName: this.dispatchErrorMeshName,
1615
+ sourceRid: record.sourceNodeRid,
1616
+ spotId: record.target.targetSpotId,
1617
+ instanceSpotType: record.target.stableType,
1618
+ activationState: 'activating',
1619
+ error
1620
+ });
1637
1621
  }
1638
1622
  }
1639
1623
  requireInstanceActivation(ingress, record) {
@@ -1731,12 +1715,9 @@ class ServiceStatefulRuntime {
1731
1715
  return local;
1732
1716
  }
1733
1717
  if (current.kind === 'ready') {
1734
- throw new ServiceInstanceActivationRedirectError(current.route);
1718
+ throw activationRefused();
1735
1719
  }
1736
1720
  const reserved = authority.reserve(target, record.operation, record.deadlineUnixMs);
1737
- if (reserved.kind === 'ready') {
1738
- throw new ServiceInstanceActivationRedirectError(reserved.route);
1739
- }
1740
1721
  let activation;
1741
1722
  try {
1742
1723
  activation = this.activateInstanceSpot(target.targetSpotId, target.stableType, reserved.reservation.attempt, reserved.reservation.authorityOwnerGeneration);
@@ -1749,7 +1730,7 @@ class ServiceStatefulRuntime {
1749
1730
  if (committed.kind === 'lost') {
1750
1731
  if (activation.created)
1751
1732
  this.registry.closeSpot(activation.spot.ref);
1752
- throw new ServiceInstanceActivationRedirectError(committed.route);
1733
+ throw activationRefused();
1753
1734
  }
1754
1735
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
1755
1736
  if (activation.created)
@@ -1758,12 +1739,23 @@ class ServiceStatefulRuntime {
1758
1739
  }
1759
1740
  return activation.spot;
1760
1741
  }
1761
- activateMissingInstanceAsync(record, payloadFrame, metadataFrame) {
1762
- const key = instanceActivationKey(record);
1742
+ activateMissingInstanceAsync(record, payloadFrame, metadataFrame, admit) {
1743
+ const key = record.target.targetSpotId;
1763
1744
  const pending = this.pendingInstanceActivations.get(key);
1764
- if (pending !== undefined)
1765
- return pending;
1766
- const activation = this.runMissingInstanceActivation(record, payloadFrame, metadataFrame);
1745
+ const ready = pending ??
1746
+ this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
1747
+ ...activation,
1748
+ stableType: record.target.stableType
1749
+ }));
1750
+ const operation = ready.then((value) => {
1751
+ if (value.stableType !== record.target.stableType) {
1752
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.TypeMismatch, 'Instance Spot type does not match.');
1753
+ }
1754
+ admit(value);
1755
+ return value;
1756
+ });
1757
+ // The caller owns operation failure. Keep the activation available for later participants.
1758
+ const activation = operation.then((value) => value, () => ready);
1767
1759
  this.pendingInstanceActivations.set(key, activation);
1768
1760
  const clear = () => {
1769
1761
  if (this.pendingInstanceActivations.get(key) === activation) {
@@ -1771,7 +1763,7 @@ class ServiceStatefulRuntime {
1771
1763
  }
1772
1764
  };
1773
1765
  void activation.then(clear, clear);
1774
- return activation;
1766
+ return operation;
1775
1767
  }
1776
1768
  async runMissingInstanceActivation(record, payloadFrame, metadataFrame) {
1777
1769
  const target = record.target;
@@ -1803,12 +1795,12 @@ class ServiceStatefulRuntime {
1803
1795
  }
1804
1796
  };
1805
1797
  }
1798
+ if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
1799
+ throw activationRefused();
1800
+ }
1806
1801
  if (current.kind === 'ready' &&
1807
1802
  current.authority !== undefined &&
1808
1803
  (0, service_authority_payload_codec_1.decodeServiceClosingSpotAuthority)(current.authority.payload) !== undefined) {
1809
- if (current.route.targetNodeRid !== this.nodeRid) {
1810
- throw new ServiceInstanceActivationRedirectError(current.route);
1811
- }
1812
1804
  if (current.route.targetNodeGeneration !== this.nodeGeneration ||
1813
1805
  (local !== undefined &&
1814
1806
  !routeMatchesLocal(current.route, local, this.nodeRid, this.nodeGeneration)) ||
@@ -1821,17 +1813,11 @@ class ServiceStatefulRuntime {
1821
1813
  };
1822
1814
  }
1823
1815
  if (!this.acceptsLocalMissingInstancePlacement()) {
1824
- if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
1825
- throw new ServiceInstanceActivationRedirectError(current.route);
1826
- }
1827
1816
  // A draining or retiring node must not create a successor generation
1828
1817
  // from a stale client route while relocation is publishing its target.
1829
1818
  throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', target.targetSpotId);
1830
1819
  }
1831
1820
  if (current.kind === 'ready') {
1832
- if (current.route.targetNodeRid !== this.nodeRid) {
1833
- throw new ServiceInstanceActivationRedirectError(current.route);
1834
- }
1835
1821
  if (current.route.targetNodeGeneration !== this.nodeGeneration) {
1836
1822
  throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', target.targetSpotId);
1837
1823
  }
@@ -1867,16 +1853,9 @@ class ServiceStatefulRuntime {
1867
1853
  await lifecycle.discard(target);
1868
1854
  materialized = lifecycle.isMaterialized(target);
1869
1855
  }
1870
- // The authority can still be in a Creating reservation after the local
1871
- // registry exposes the activation. Allow the authority reserve operation
1872
- // to join that attempt instead of treating the local projection as stale.
1873
- // A newer reservation can also become visible after an explicit close
1874
- // releases authority but before local disposal removes the old
1875
- // projection. The close gate serializes that replacement; rejecting it
1876
- // here turns a valid next-generation Instance intent into a stale-route
1877
- // failure.
1878
- // If disposal did not remove an orphaned materialization, keep fencing
1879
- // it instead of admitting work into two application generations.
1856
+ // The local projection does not decide reservation ownership. Preserve
1857
+ // the Creating fence for Reserve, which refuses a competing attempt.
1858
+ // Keep fencing an orphan that disposal did not remove.
1880
1859
  if ((!joinsCreating && current.kind !== 'missing') ||
1881
1860
  (!joinsCreating && materialized !== false && !materializing)) {
1882
1861
  throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', target.targetSpotId);
@@ -1901,9 +1880,6 @@ class ServiceStatefulRuntime {
1901
1880
  finally {
1902
1881
  deadline.close();
1903
1882
  }
1904
- if (reserved.kind === 'ready') {
1905
- throw new ServiceInstanceActivationRedirectError(reserved.route);
1906
- }
1907
1883
  if (this.closed || record.deadlineUnixMs < BigInt(Date.now())) {
1908
1884
  await authority.abort(target, reserved.reservation);
1909
1885
  throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', target.targetSpotId);
@@ -1932,7 +1908,7 @@ class ServiceStatefulRuntime {
1932
1908
  if (activation.created)
1933
1909
  this.registry.closeSpot(activation.spot.ref);
1934
1910
  await this.instanceApplicationLifecycle?.discard(target);
1935
- throw new ServiceInstanceActivationRedirectError(committed.route);
1911
+ throw activationRefused();
1936
1912
  }
1937
1913
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
1938
1914
  if (activation.created)
@@ -1960,7 +1936,7 @@ class ServiceStatefulRuntime {
1960
1936
  const confirmed = await authority.read(target);
1961
1937
  if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
1962
1938
  if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
1963
- throw new ServiceInstanceActivationRedirectError(confirmed.route);
1939
+ throw activationRefused();
1964
1940
  }
1965
1941
  throw new service_stateful_registry_1.ServiceStaleGenerationError('spot', target.targetSpotId);
1966
1942
  }
@@ -2056,7 +2032,7 @@ class ServiceStatefulRuntime {
2056
2032
  kindData: control,
2057
2033
  ...(canonicalApplicationPayload === undefined ? {} : { canonicalApplicationPayload }),
2058
2034
  reply: (terminalResult, failureCode, replyPayload, tail) => {
2059
- return this.replyPort(ingress, record.correlation, 'actorJoin')(terminalResult, failureCode, replyPayload, tail);
2035
+ return this.replyPort(ingress, record.correlation)(terminalResult, failureCode, replyPayload, tail);
2060
2036
  }
2061
2037
  });
2062
2038
  }
@@ -2538,20 +2514,20 @@ class ServiceStatefulRuntime {
2538
2514
  }
2539
2515
  return accepted;
2540
2516
  }
2541
- replyPort(ingress, correlation, operationKind) {
2517
+ replyPort(ingress, correlation) {
2542
2518
  if (correlation === undefined)
2543
2519
  throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Request correlation is missing.');
2544
2520
  return (terminalResult, failureCode, payload, tail) => {
2545
2521
  this.replyWire(ingress, correlation, terminalResult, failureCode, payload, tail);
2546
- void operationKind;
2547
2522
  return true;
2548
2523
  };
2549
2524
  }
2550
2525
  replyWire(ingress, correlation, terminalResult, failureCode, payload, tail) {
2551
- this.raw.replyService(ingress, [
2526
+ const parts = [
2552
2527
  (0, service_stateful_wire_codec_1.encodeStatefulReply)(correlation, terminalResult, failureCode, tail),
2553
2528
  ...(payload === undefined ? [] : [(0, service_wire_m6a_codec_1.encodeApplicationPayload)(payload)])
2554
- ]);
2529
+ ];
2530
+ this.raw.replyService(ingress, parts);
2555
2531
  }
2556
2532
  handleUserSpotOperation(ingress, record) {
2557
2533
  if (ingress.requestSequence === undefined ||
@@ -3094,24 +3070,11 @@ class ServiceStatefulRuntime {
3094
3070
  return;
3095
3071
  }
3096
3072
  if (decoded.kind === 'instanceSpot') {
3097
- if (decoded.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
3098
- void this.continueMissingInstanceActivation(ingress, decoded, payloadFrame, localReply);
3099
- return;
3100
- }
3101
- try {
3102
- const activation = this.requireInstanceActivation(ingress, decoded);
3103
- this.enqueueApplicationFrame(ingress, `spot:${activation.ref.spotId}`, payloadFrame, {
3104
- receiveKind: service_runtime_contracts_1.ReceiveKind.InstanceSpotActivation,
3105
- operationKind: service_runtime_contracts_1.OperationKind.InstanceSpotRequest,
3106
- correlation: pending.id,
3107
- ...(decoded.sourceSpotId === undefined ? {} : { sourceSpotId: decoded.sourceSpotId }),
3108
- targetSpot: activation.ref,
3109
- reply: localReply
3110
- });
3111
- }
3112
- catch (error) {
3113
- this.operations.reply(pending.id, failure(error));
3114
- }
3073
+ const hasMetadata = (ingress.flags & service_stateful_wire_codec_1.M6bServiceWireFlag.metadata) !== 0;
3074
+ const metadataFrame = hasMetadata
3075
+ ? (0, service_metadata_codec_1.validateServiceMetadataFrame)(ingress.parts[1])
3076
+ : undefined;
3077
+ this.enqueueInstanceSpot(ingress, decoded, ingress.parts[hasMetadata ? 2 : 1], metadataFrame, localReply);
3115
3078
  return;
3116
3079
  }
3117
3080
  this.operations.reply(pending.id, {
@@ -3292,9 +3255,7 @@ class ServiceStatefulRuntime {
3292
3255
  ...(wire.correlation === undefined ? {} : { correlation: wire.correlation }),
3293
3256
  sourceSpotId: wire.sourceSpotId,
3294
3257
  targetSpot: wire.target.spot,
3295
- ...(wire.kind === 'spotRequest'
3296
- ? { reply: this.replyPort(ingress, wire.correlation, 'spotRequest') }
3297
- : {})
3258
+ ...(wire.kind === 'spotRequest' ? { reply: this.replyPort(ingress, wire.correlation) } : {})
3298
3259
  });
3299
3260
  }
3300
3261
  async drainSpotMessageFollow(state) {
@@ -3671,6 +3632,9 @@ function actorLocation(actor) {
3671
3632
  membershipEpoch: actor.membershipEpoch
3672
3633
  };
3673
3634
  }
3635
+ function activationRefused() {
3636
+ return new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Instance cold activation authority was refused.');
3637
+ }
3674
3638
  function failure(error) {
3675
3639
  if (error instanceof service_stateful_registry_1.ServiceStaleGenerationError) {
3676
3640
  return (0, framework_errors_internal_1.internalFrameworkWireReply)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorLocationStale);
@@ -3903,10 +3867,6 @@ function instanceOperationKey(record) {
3903
3867
  return (`${record.sourceNodeRid}\0${record.sourceNodeGeneration}\0` +
3904
3868
  `${record.operation.high}\0${record.operation.low}`);
3905
3869
  }
3906
- function instanceActivationKey(record) {
3907
- return (`${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
3908
- `${record.target.targetSpotId}\0${record.target.stableType}`);
3909
- }
3910
3870
  function subscriptionKey(channelName, topicFilter) {
3911
3871
  if (channelName.length === 0 || topicFilter.length === 0) {
3912
3872
  throw new TypeError('Channel name and topic filter must be non-empty.');
@@ -105,8 +105,6 @@ export type ServiceWireRelocationObject = {
105
105
  readonly spotId: string;
106
106
  readonly objectGeneration: bigint;
107
107
  };
108
- /** Shared CRC-32C wire bounds for the direct relocation payload transfer. */
109
- export declare const RELOCATION_PAYLOAD_TOTAL_LENGTH_MAX = 274877906944n;
110
108
  export declare const RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = 4096;
111
109
  export { RELOCATION_STATE_CHUNK_DATA_MAX_BYTES } from '../../contracts/Configuration/InternalDefaults';
112
110
  interface ServiceWireRelocationBase {
@@ -576,15 +574,6 @@ export declare function decodeMaintenanceReplyRelayAck(frame: Uint8Array): Servi
576
574
  export declare function decodeStatefulReply(frame: Uint8Array, expectedCorrelation: bigint, operationKind: 'spotRequest' | 'actorRequest' | 'actorLookup' | 'actorDestroy' | 'actorJoin' | 'streamBind' | 'streamUnbind' | 'instanceSpotRequest' | 'userSpotCreate' | 'userSpotClose' | 'actorCreate', hasPayload?: boolean): ServiceStatefulReply;
577
575
  export declare function sessionBindingFromWire(actor: ServiceActorRef, sessionRid: string, sessionOwnerNodeRid: string, generation: bigint, membershipEpoch: bigint, sessionOwnerNodeGeneration?: bigint, sessionOwnerId?: string, sessionOwnerLeaseGeneration?: bigint): ServiceSessionBinding;
578
576
  export declare function decodeServiceWireFrozenRecord(bytes: Uint8Array): ServiceWireFrozenRecord;
579
- /**
580
- * Consumes exactly one canonical frozen record at `offset`. Relocation
581
- * saved-work is a vector of back-to-back records, so its boundary is the
582
- * record grammar itself rather than an enclosing length field.
583
- */
584
- export declare function decodeServiceWireFrozenRecordPrefix(bytes: Uint8Array, offset?: number): {
585
- readonly record: ServiceWireFrozenRecord;
586
- readonly length: number;
587
- };
588
577
  export declare function encodeServiceWireFrozenRecord(record: ServiceWireFrozenRecord): Buffer;
589
578
  export declare function encodeServiceWireFrozenActorApplicationRecord(input: {
590
579
  readonly source: ServiceWireRequestSourceFence;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.M6bServiceWireFlag = exports.RELOCATION_STATE_CHUNK_DATA_MAX_BYTES = exports.RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = exports.RELOCATION_PAYLOAD_TOTAL_LENGTH_MAX = exports.M6bServiceWireCommand = void 0;
3
+ exports.M6bServiceWireFlag = exports.RELOCATION_STATE_CHUNK_DATA_MAX_BYTES = exports.RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = exports.M6bServiceWireCommand = void 0;
4
4
  exports.serviceSessionRelocationIdentityKey = serviceSessionRelocationIdentityKey;
5
5
  exports.encodeSpotHeader = encodeSpotHeader;
6
6
  exports.encodeActorHeader = encodeActorHeader;
@@ -33,7 +33,6 @@ exports.decodeMaintenanceReplyRelayAck = decodeMaintenanceReplyRelayAck;
33
33
  exports.decodeStatefulReply = decodeStatefulReply;
34
34
  exports.sessionBindingFromWire = sessionBindingFromWire;
35
35
  exports.decodeServiceWireFrozenRecord = decodeServiceWireFrozenRecord;
36
- exports.decodeServiceWireFrozenRecordPrefix = decodeServiceWireFrozenRecordPrefix;
37
36
  exports.encodeServiceWireFrozenRecord = encodeServiceWireFrozenRecord;
38
37
  exports.encodeServiceWireFrozenActorApplicationRecord = encodeServiceWireFrozenActorApplicationRecord;
39
38
  const stream_wire_1 = require("@zlink-systems/stream-wire");
@@ -55,8 +54,6 @@ const MAGIC_0 = service_wire_constants_generated_1.SERVICE_WIRE_MAGIC[0];
55
54
  const MAGIC_1 = service_wire_constants_generated_1.SERVICE_WIRE_MAGIC[1];
56
55
  const MAJOR = service_wire_constants_generated_1.SERVICE_WIRE_MAJOR;
57
56
  exports.M6bServiceWireCommand = service_wire_constants_generated_1.ServiceWireCommand;
58
- /** Shared CRC-32C wire bounds for the direct relocation payload transfer. */
59
- exports.RELOCATION_PAYLOAD_TOTAL_LENGTH_MAX = 274877906944n;
60
57
  exports.RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = 4096;
61
58
  var InternalDefaults_2 = require("../../contracts/Configuration/InternalDefaults");
62
59
  Object.defineProperty(exports, "RELOCATION_STATE_CHUNK_DATA_MAX_BYTES", { enumerable: true, get: function () { return InternalDefaults_2.RELOCATION_STATE_CHUNK_DATA_MAX_BYTES; } });
@@ -1623,25 +1620,6 @@ function decodeServiceWireFrozenRecord(bytes) {
1623
1620
  reader.end('frozen record');
1624
1621
  return { ...record, canonicalBytes: Buffer.from(bytes) };
1625
1622
  }
1626
- /**
1627
- * Consumes exactly one canonical frozen record at `offset`. Relocation
1628
- * saved-work is a vector of back-to-back records, so its boundary is the
1629
- * record grammar itself rather than an enclosing length field.
1630
- */
1631
- function decodeServiceWireFrozenRecordPrefix(bytes, offset = 0) {
1632
- if (!Number.isSafeInteger(offset) || offset < 0 || offset >= bytes.byteLength) {
1633
- fail('Frozen record offset is outside the encoded bytes.');
1634
- }
1635
- const reader = new FrozenReader(bytes.subarray(offset));
1636
- const record = decodeServiceWireFrozenRecordFrom(reader);
1637
- return {
1638
- record: {
1639
- ...record,
1640
- canonicalBytes: Buffer.from(bytes.subarray(offset, offset + reader.position))
1641
- },
1642
- length: reader.position
1643
- };
1644
- }
1645
1623
  function decodeServiceWireFrozenRecordFrom(reader) {
1646
1624
  const recordKind = reader.u8('recordKind');
1647
1625
  if (recordKind < 1 || recordKind > 14)