@zlink-systems/framework 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +0 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +1 -0
  4. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -2
  5. package/dist/contracts/Locations/Authority.d.ts +1 -7
  6. package/dist/runtime/actors/actor-authority-publication.js +25 -22
  7. package/dist/runtime/actors/actor-creation.js +30 -9
  8. package/dist/runtime/actors/actor-runtime-state.d.ts +3 -0
  9. package/dist/runtime/actors/index.d.ts +3 -0
  10. package/dist/runtime/actors/index.js +14 -3
  11. package/dist/runtime/admission.d.ts +2 -0
  12. package/dist/runtime/admission.js +11 -5
  13. package/dist/runtime/backend/contracts/index.d.ts +4 -1
  14. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -0
  15. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  16. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +2 -1
  17. package/dist/runtime/backend/node/node-raw-binding-port.js +12 -5
  18. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +1 -1
  19. package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -0
  20. package/dist/runtime/channels/channel-socket-options.js +7 -0
  21. package/dist/runtime/channels/channel-socket-registry.js +3 -0
  22. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -1
  23. package/dist/runtime/foundation/service-stateful-runtime.js +1 -5
  24. package/dist/runtime/host/actor-placement-coordinator.d.ts +14 -5
  25. package/dist/runtime/host/actor-placement-coordinator.js +159 -47
  26. package/dist/runtime/host/index.d.ts +2 -1
  27. package/dist/runtime/host/index.js +77 -120
  28. package/dist/runtime/host/location-runtime-owner.js +4 -2
  29. package/dist/runtime/host/remote-actor-join-receiver.js +12 -3
  30. package/dist/runtime/locations/creation-operation-id.d.ts +4 -0
  31. package/dist/runtime/locations/creation-operation-id.js +12 -0
  32. package/dist/runtime/locations/in-memory-authority-store.js +8 -22
  33. package/dist/runtime/locations/location-store-repository.js +36 -59
  34. package/dist/runtime/locations/opaque-record-key.d.ts +5 -0
  35. package/dist/runtime/locations/opaque-record-key.js +16 -0
  36. package/dist/runtime/locations/runtime.d.ts +7 -0
  37. package/dist/runtime/locations/runtime.js +130 -42
  38. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1672 -0
  39. package/dist/runtime/protocol/service_wire_codec.generated.js +9169 -0
  40. package/dist/runtime/spots/spot-node-runtime-manager.js +2 -2
  41. package/package.json +2 -2
  42. package/src/contracts/Configuration/Builders.ts +0 -2
  43. package/src/contracts/Configuration/Configs.ts +1 -0
  44. package/src/contracts/Configuration/RegistrationTypes.ts +1 -2
  45. package/src/contracts/Locations/Authority.ts +1 -8
  46. package/src/runtime/actors/actor-authority-publication.ts +25 -22
  47. package/src/runtime/actors/actor-creation.ts +45 -11
  48. package/src/runtime/actors/actor-runtime-state.ts +4 -0
  49. package/src/runtime/actors/index.ts +32 -4
  50. package/src/runtime/admission.ts +8 -5
  51. package/src/runtime/backend/contracts/index.ts +4 -1
  52. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +2 -1
  53. package/src/runtime/backend/node/node-raw-binding-port.ts +16 -5
  54. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -1
  55. package/src/runtime/backend/node/node-socket-backend-adapter.ts +7 -0
  56. package/src/runtime/channels/channel-socket-options.ts +9 -0
  57. package/src/runtime/channels/channel-socket-registry.ts +4 -0
  58. package/src/runtime/foundation/service-stateful-runtime.ts +2 -6
  59. package/src/runtime/host/actor-placement-coordinator.ts +216 -77
  60. package/src/runtime/host/index.ts +95 -135
  61. package/src/runtime/host/location-runtime-owner.ts +4 -2
  62. package/src/runtime/host/remote-actor-join-receiver.ts +15 -3
  63. package/src/runtime/locations/creation-operation-id.ts +11 -0
  64. package/src/runtime/locations/in-memory-authority-store.ts +7 -29
  65. package/src/runtime/locations/location-store-repository.ts +71 -69
  66. package/src/runtime/locations/opaque-record-key.ts +26 -0
  67. package/src/runtime/locations/runtime.ts +154 -60
  68. package/src/runtime/protocol/service_wire_codec.generated.ts +8302 -0
  69. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  70. package/src/runtime/spots/spot-node-runtime-manager.ts +2 -2
@@ -279,7 +279,6 @@ export class ZLinkFrameworkRuntimeHost implements
279
279
  private spotManager?: DefaultZLinkSpotManager;
280
280
  private ownerLeaseRecoveryRuntime?: ZLinkLocationRuntime;
281
281
  private ownerLeaseRecoveryHandler?: () => void;
282
- private ownerLeaseFailureHandler?: () => void;
283
282
  private registerUserSpotHandlers?: (runtime: ZLinkSpotNodeRuntimeManager) => void;
284
283
  private readonly destroyedActorRefs = new Map<string, ActorRef>();
285
284
  private readonly runtimeEventPublisher: ZLinkRuntimeEventPublisher;
@@ -287,7 +286,7 @@ export class ZLinkFrameworkRuntimeHost implements
287
286
  private readonly applicationJobQueue: ApplicationJobQueue;
288
287
  private readonly capacityStatus: HostCapacityStatusProjection;
289
288
  private readonly metricRegistrations: import('../diagnostics').ZLinkRuntimeMetricRegistration[] = [];
290
- private readonly admission = new ZLinkRuntimeAdmissionGate();
289
+ private readonly admission = new ZLinkRuntimeAdmissionGate(() => this.ownerAdmissionOpen());
291
290
  private cachedLocationSpotRouteResolver?: ZLinkSpotRouteResolver;
292
291
  private actorClientLocationResolver?: ZLinkStoreLocationResolvers;
293
292
  // Shared, runtime-mutable message-flow mode cell — installed once so
@@ -939,6 +938,14 @@ export class ZLinkFrameworkRuntimeHost implements
939
938
  : ZLinkFrameworkRelocationReason.RuntimeNotReady
940
939
  });
941
940
  }
941
+ if (!this.admission.acceptsNewWork) {
942
+ return Promise.resolve({
943
+ mode: options.mode,
944
+ effectiveTargetApplicationVersion,
945
+ outcome: ZLinkFrameworkRelocationOutcome.Blocked,
946
+ reason: ZLinkFrameworkRelocationReason.StoreUnavailable
947
+ });
948
+ }
942
949
  if (hasUnsupportedManualTopology(this.options.registration)) {
943
950
  return Promise.resolve({
944
951
  mode: options.mode,
@@ -1526,103 +1533,45 @@ export class ZLinkFrameworkRuntimeHost implements
1526
1533
  spotNodeRuntime: ZLinkSpotNodeRuntimeManager,
1527
1534
  channelRuntime: ZLinkChannelRuntimeManager
1528
1535
  ): void {
1529
- let publishing = false;
1530
- let lastPublishedOwnerToken = runtime.currentOwnerToken;
1531
- let recoveryRequired = false;
1532
- let recoveringServices: Promise<void> | undefined;
1533
- let stoppingServices: Promise<void> | undefined;
1534
- let transportFence = Promise.resolve();
1535
1536
  const handler = () => {
1536
- const ownerToken = runtime.currentOwnerToken;
1537
- if (
1538
- publishing
1539
- || ownerToken === undefined
1540
- || (ownerToken === lastPublishedOwnerToken && !recoveryRequired)
1541
- ) return;
1542
- if (runtime.ownerLeaseUsable === false) return;
1543
- publishing = true;
1544
- const signal = this.executionState?.abortController.signal;
1545
- const recoverServices = async (): Promise<void> => {
1546
- if (stoppingServices !== undefined) {
1547
- await stoppingServices;
1548
- stoppingServices = undefined;
1549
- }
1550
- if (this.statefulAuthorityRoutes !== undefined) return;
1551
- const store = this.locationOwner.currentStores?.locationStore;
1552
- if (store === undefined || this.executionState === undefined) return;
1553
- if (recoveringServices !== undefined) return await recoveringServices;
1554
- recoveringServices = (async () => {
1555
- if (this.requiresStatefulAuthorityRuntime()) {
1556
- const routes = this.createStatefulAuthorityRoutes(store, spotNodeRuntime);
1557
- await routes.start(this.executionState?.abortController.signal);
1558
- this.statefulAuthorityRoutes = routes;
1559
- }
1560
- })();
1561
- try {
1562
- await recoveringServices;
1563
- } finally {
1564
- recoveringServices = undefined;
1565
- }
1566
- };
1567
- void transportFence
1568
- .then(() => spotNodeRuntime.publishMeshNodeState(
1569
- ZLinkFrameworkRuntimeState.Preparing,
1570
- signal
1571
- ))
1572
- .then(() => recoverServices())
1573
- .then(() => {
1574
- const currentOwnerToken = runtime.currentOwnerToken;
1575
- if (runtime.ownerLeaseUsable === false
1576
- || currentOwnerToken === undefined
1577
- || currentOwnerToken.ownerId !== ownerToken.ownerId
1578
- || currentOwnerToken.leaseGeneration !== ownerToken.leaseGeneration) {
1579
- throw new Error('Owner lease changed during recovery.');
1580
- }
1581
- return spotNodeRuntime.publishMeshNodeState(this.runtimeState, signal);
1582
- })
1583
- .then(() => spotNodeRuntime.startLocationAutoConnect(signal))
1584
- .then(() => channelRuntime.reclaimLocationOwnerRows(signal))
1585
- .then(() => this.locationOwner.currentLifecycle?.reclaimOwnerRows() ?? Promise.resolve())
1586
- .then(
1587
- () => {
1588
- lastPublishedOwnerToken = ownerToken;
1589
- recoveryRequired = false;
1590
- publishing = false;
1591
- },
1592
- error => {
1537
+ if (!this.ownerAdmissionOpen()) return;
1538
+ void this.resumeOwnerLeaseOwnedWork(runtime, spotNodeRuntime, channelRuntime)
1539
+ .catch(error => {
1540
+ runtime.closeOwnerLeaseAdmission(error);
1593
1541
  this.runtimeOrPreStartErrorSink.reportRuntimeTaskException(
1594
1542
  'owner lease recovery',
1595
1543
  error
1596
1544
  );
1597
- publishing = false;
1598
- }
1599
- );
1545
+ });
1600
1546
  };
1601
1547
  this.ownerLeaseRecoveryRuntime = runtime;
1602
1548
  this.ownerLeaseRecoveryHandler = handler;
1603
- const failureHandler = () => {
1604
- recoveryRequired = true;
1605
- if (runtime.ownerLeaseUsable) return;
1606
- // A Store failure invalidates new ownership and discovery work, but it
1607
- // must not tear down transports that were already established. The
1608
- // auto-connect reconciler applies storeFailureGraceMs to the last
1609
- // complete descriptor set; fencing here would bypass that contract and
1610
- // make existing requests fail during the grace window.
1611
- const routes = this.statefulAuthorityRoutes;
1612
- if (routes === undefined) return;
1613
- this.statefulAuthorityRoutes = undefined;
1614
- stoppingServices = routes.stop();
1615
- void stoppingServices.catch(error =>
1616
- this.runtimeOrPreStartErrorSink.reportRuntimeTaskException(
1617
- 'stateful authority route fencing',
1618
- error
1619
- ));
1620
- };
1621
- this.ownerLeaseFailureHandler = failureHandler;
1622
1549
  runtime.addOwnerLeaseRenewedHandler(handler);
1623
- if (typeof runtime.addOwnerLeaseRenewalFailedHandler === 'function') {
1624
- runtime.addOwnerLeaseRenewalFailedHandler(failureHandler);
1550
+ }
1551
+
1552
+ private async resumeOwnerLeaseOwnedWork(
1553
+ runtime: ZLinkLocationRuntime,
1554
+ spotNodeRuntime: ZLinkSpotNodeRuntimeManager,
1555
+ channelRuntime: ZLinkChannelRuntimeManager
1556
+ ): Promise<void> {
1557
+ if (!this.ownerAdmissionOpen()) return;
1558
+ const signal = this.executionState?.abortController.signal;
1559
+ await spotNodeRuntime.publishMeshNodeState(ZLinkFrameworkRuntimeState.Preparing, signal);
1560
+ const store = this.locationOwner.currentStores?.locationStore;
1561
+ if (this.statefulAuthorityRoutes === undefined
1562
+ && store !== undefined
1563
+ && this.executionState !== undefined
1564
+ && this.requiresStatefulAuthorityRuntime()) {
1565
+ const routes = this.createStatefulAuthorityRoutes(store, spotNodeRuntime);
1566
+ await routes.start(signal);
1567
+ this.statefulAuthorityRoutes = routes;
1625
1568
  }
1569
+ if (runtime.ownerLeaseUsable === false) return;
1570
+ await spotNodeRuntime.publishMeshNodeState(this.runtimeState, signal);
1571
+ await spotNodeRuntime.startLocationAutoConnect(signal);
1572
+ await channelRuntime.startLocationAutoConnect(signal);
1573
+ await channelRuntime.reclaimLocationOwnerRows(signal);
1574
+ await this.locationOwner.currentLifecycle?.reclaimOwnerRows();
1626
1575
  }
1627
1576
 
1628
1577
  private createStatefulAuthorityRoutes(
@@ -1715,14 +1664,14 @@ export class ZLinkFrameworkRuntimeHost implements
1715
1664
  if (this.ownerLeaseRecoveryRuntime !== undefined && this.ownerLeaseRecoveryHandler !== undefined) {
1716
1665
  this.ownerLeaseRecoveryRuntime.removeOwnerLeaseRenewedHandler(this.ownerLeaseRecoveryHandler);
1717
1666
  }
1718
- if (this.ownerLeaseRecoveryRuntime !== undefined && this.ownerLeaseFailureHandler !== undefined) {
1719
- if (typeof this.ownerLeaseRecoveryRuntime.removeOwnerLeaseRenewalFailedHandler === 'function') {
1720
- this.ownerLeaseRecoveryRuntime.removeOwnerLeaseRenewalFailedHandler(this.ownerLeaseFailureHandler);
1721
- }
1722
- }
1723
1667
  this.ownerLeaseRecoveryRuntime = undefined;
1724
1668
  this.ownerLeaseRecoveryHandler = undefined;
1725
- this.ownerLeaseFailureHandler = undefined;
1669
+ }
1670
+
1671
+ private ownerAdmissionOpen(): boolean {
1672
+ const runtime = this.locationOwner.currentRuntime;
1673
+ if (runtime !== undefined) return runtime.ownerLeaseUsable;
1674
+ return this.locationOwner.currentStores?.locationStore === undefined;
1726
1675
  }
1727
1676
 
1728
1677
  async onApplicationBootstrap(): Promise<void> {
@@ -2068,14 +2017,7 @@ export class ZLinkFrameworkRuntimeHost implements
2068
2017
  detachedTaskRunner: this.detachedTaskRunner(),
2069
2018
  metrics: this.metrics,
2070
2019
  admission: this.admission,
2071
- statefulExecutionAllowed: () => {
2072
- const locationRuntime = this.locationOwner.currentRuntime;
2073
- if (locationRuntime !== undefined) return locationRuntime.ownerLeaseUsable;
2074
- // A host configured with a Location Store must not execute stateful
2075
- // timers while its lease runtime is unavailable. Hosts without a
2076
- // Location Store have no owner lease to fence.
2077
- return this.locationOwner.currentStores?.locationStore === undefined;
2078
- }
2020
+ statefulExecutionAllowed: () => this.ownerAdmissionOpen()
2079
2021
  }).create(this.actorTransferRuntime),
2080
2022
  activationConcurrencyLimitProvider: (meshName: string) =>
2081
2023
  this.options.registration.spotNodes.get(meshName)?.activationConcurrencyLimit ?? 128,
@@ -2165,17 +2107,24 @@ export class ZLinkFrameworkRuntimeHost implements
2165
2107
  );
2166
2108
  if (selected === undefined) return undefined;
2167
2109
  const localStatus = meshNode?.status();
2110
+ if (localStatus === undefined) {
2111
+ throw createInternalFrameworkException(
2112
+ ZLinkFrameworkInternalErrorKind.ObjectClientNotConfigured,
2113
+ `RouteMesh '${meshName}' has no local client node.`
2114
+ );
2115
+ }
2168
2116
  return {
2169
2117
  meshName,
2170
2118
  nodeRid: selected.rid,
2171
2119
  nodeGeneration: selected.lifecycleGeneration,
2120
+ sourceNodeRid: String(localStatus.routingId),
2121
+ sourceNodeGeneration: localStatus.lifecycleGeneration,
2172
2122
  entrySpotId: selected.entrySpotId!,
2173
2123
  owner: {
2174
2124
  ownerId: selected.ownerId,
2175
2125
  leaseGeneration: selected.leaseGeneration
2176
2126
  },
2177
- isLocal: localStatus !== undefined
2178
- && String(localStatus.routingId) === String(selected.rid)
2127
+ isLocal: String(localStatus.routingId) === String(selected.rid)
2179
2128
  && localStatus.lifecycleGeneration === selected.lifecycleGeneration
2180
2129
  };
2181
2130
  }
@@ -2394,6 +2343,13 @@ export class ZLinkFrameworkRuntimeHost implements
2394
2343
  requestPayload,
2395
2344
  this.options.registration.messageSerializers
2396
2345
  );
2346
+ const entrySpotId = this.spotNodeRuntime
2347
+ ?.meshNodeDescriptor(meshName)?.entrySpotId;
2348
+ if (entrySpotId === undefined) {
2349
+ throw new ZLinkConfigurationException(
2350
+ `Remote Actor creation requires the '${meshName}' MeshNode Entry Spot descriptor.`
2351
+ );
2352
+ }
2397
2353
  const entrySpot = node.entrySpot().status();
2398
2354
  const nativeActorRef = node.restoreActorAuthority?.(
2399
2355
  record.actorId,
@@ -2416,6 +2372,9 @@ export class ZLinkFrameworkRuntimeHost implements
2416
2372
  createSignal,
2417
2373
  nativeActorRef
2418
2374
  );
2375
+ if (local.status === 'failed') {
2376
+ return { result: 'failed' as const, error: local.error };
2377
+ }
2419
2378
  if (local.status === 'rejected') {
2420
2379
  let reply: Buffer | undefined;
2421
2380
  if (local.reply !== undefined) {
@@ -2461,7 +2420,7 @@ export class ZLinkFrameworkRuntimeHost implements
2461
2420
  ...local.actorRef,
2462
2421
  meshName
2463
2422
  },
2464
- entrySpotId: String(entrySpot.routingId),
2423
+ entrySpotId,
2465
2424
  entrySpotGeneration: entrySpot.lifecycleGeneration,
2466
2425
  ...(reply === undefined ? {} : { reply }),
2467
2426
  onPublished: () => actors.adoptCreatedAuthority(
@@ -2849,37 +2808,38 @@ export class ZLinkFrameworkRuntimeHost implements
2849
2808
  }
2850
2809
  case ReceiveKind.NodeSend:
2851
2810
  case ReceiveKind.NodeRequest: {
2852
- if (await this.serviceRelocation.tryHandleControl(meshName, record, signal)) {
2853
- return;
2854
- }
2855
- if (record.kind === ReceiveKind.NodeRequest && record.parts.length === 1) {
2856
- const terminal = decodeRemoteActorSourceLeaveTerminal(record.parts[0]!.data());
2857
- if (terminal !== undefined) {
2858
- if (
2859
- await this.spotManager?.completeFormalSourceLeaveTerminal(
2860
- terminal.actorId,
2861
- terminal.transferId,
2862
- terminal.succeeded
2863
- ) !== true
2864
- ) {
2865
- throw new ZLinkConfigurationException(
2866
- `Actor '${terminal.actorId}' has no matching formal transfer terminal gate.`
2867
- );
2868
- }
2869
- if (record.reply(Buffer.alloc(0)) !== SubmitResult.Ok) {
2870
- throw new ZLinkConfigurationException(
2871
- `Actor '${terminal.actorId}' formal transfer terminal acknowledgement failed.`
2872
- );
2811
+ return this.admission.run(meshName, 'RouteMesh node dispatch', async () => {
2812
+ if (await this.serviceRelocation.tryHandleControl(meshName, record, signal)) {
2813
+ return;
2814
+ }
2815
+ if (record.kind === ReceiveKind.NodeRequest && record.parts.length === 1) {
2816
+ const terminal = decodeRemoteActorSourceLeaveTerminal(record.parts[0]!.data());
2817
+ if (terminal !== undefined) {
2818
+ if (
2819
+ await this.spotManager?.completeFormalSourceLeaveTerminal(
2820
+ terminal.actorId,
2821
+ terminal.transferId,
2822
+ terminal.succeeded
2823
+ ) !== true
2824
+ ) {
2825
+ throw new ZLinkConfigurationException(
2826
+ `Actor '${terminal.actorId}' has no matching formal transfer terminal gate.`
2827
+ );
2828
+ }
2829
+ if (record.reply(Buffer.alloc(0)) !== SubmitResult.Ok) {
2830
+ throw new ZLinkConfigurationException(
2831
+ `Actor '${terminal.actorId}' formal transfer terminal acknowledgement failed.`
2832
+ );
2833
+ }
2834
+ return;
2873
2835
  }
2874
- return Promise.resolve();
2875
2836
  }
2876
- }
2877
- const channelRuntime = this.channelRuntime;
2878
- if (channelRuntime === undefined) {
2879
- throw new ZLinkConfigurationException('MeshNode node-direct dispatch requires the channel runtime.');
2880
- }
2881
- return this.admission.run(meshName, 'RouteMesh node dispatch', () =>
2882
- channelRuntime.dispatchMeshRoute(meshName, record, signal));
2837
+ const channelRuntime = this.channelRuntime;
2838
+ if (channelRuntime === undefined) {
2839
+ throw new ZLinkConfigurationException('MeshNode node-direct dispatch requires the channel runtime.');
2840
+ }
2841
+ return await channelRuntime.dispatchMeshRoute(meshName, record, signal);
2842
+ });
2883
2843
  }
2884
2844
  case ReceiveKind.SpotSend:
2885
2845
  case ReceiveKind.SpotRequest:
@@ -212,14 +212,16 @@ export class ZLinkLocationRuntimeOwner {
212
212
  this.options.registration.locations.options,
213
213
  this.currentEvents
214
214
  );
215
- await spotNodeRuntime.startLocationAutoConnect();
216
215
  channelRuntime.configureLocationAutoConnect(
217
216
  runtime,
218
217
  stores,
219
218
  this.options.registration.locations.options,
220
219
  this.currentEvents
221
220
  );
222
- await channelRuntime.startLocationAutoConnect();
221
+ if (runtime.ownerLeaseUsable) {
222
+ await spotNodeRuntime.startLocationAutoConnect();
223
+ await channelRuntime.startLocationAutoConnect();
224
+ }
223
225
  return runtime;
224
226
  }
225
227
 
@@ -215,9 +215,21 @@ function hasAuthorityFence(
215
215
  readonly expectedAuthorityOwnerGeneration: string;
216
216
  readonly expectedOwnerLeaseGeneration: string;
217
217
  } {
218
- return join.actorNodeGeneration !== undefined
219
- && join.expectedAuthorityOwnerGeneration !== undefined
220
- && join.expectedOwnerLeaseGeneration !== undefined;
218
+ const fields = [
219
+ join.actorNodeGeneration,
220
+ join.expectedAuthorityOwnerGeneration,
221
+ join.expectedOwnerLeaseGeneration
222
+ ];
223
+ if (fields.every(value => value === undefined)) {
224
+ return false;
225
+ }
226
+ if (fields.some(value => value === undefined)) {
227
+ throw createInternalFrameworkException(
228
+ ZLinkFrameworkInternalErrorKind.RequestProtocolError,
229
+ `Actor '${join.actorId}' Join carries an incomplete Authority fence.`
230
+ );
231
+ }
232
+ return true;
221
233
  }
222
234
 
223
235
  function decodeJoinAuthorityFence(join: JoinAuthorityFence): {
@@ -0,0 +1,11 @@
1
+ import { randomBytes } from 'node:crypto';
2
+
3
+ export function randomOperationId(
4
+ entropy: Uint8Array = randomBytes(16)
5
+ ): { readonly high: bigint; readonly low: bigint } {
6
+ if (entropy.byteLength !== 16) throw new RangeError('Operation ID entropy must be 16 bytes.');
7
+ const bytes = Buffer.from(entropy);
8
+ const high = bytes.readBigUInt64BE(0);
9
+ const low = bytes.readBigUInt64BE(8);
10
+ return high === 0n && low === 0n ? { high, low: 1n } : { high, low };
11
+ }
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
1
+ import { randomUUID } from 'node:crypto';
2
2
  import type {
3
3
  ZLinkAggregateAbortResult,
4
4
  ZLinkAggregateCommitResult,
@@ -31,6 +31,7 @@ import type {
31
31
  ZLinkPlacementAllocation
32
32
  } from './internal-location-contracts';
33
33
  import { encodeAuthorityKey } from './authority-key-codec';
34
+ import { creationTerminalPreimage } from './opaque-record-key';
34
35
 
35
36
  const MAX_GENERATION = 0x7fff_ffff_ffff_ffffn;
36
37
  const MAX_PAYLOAD_BYTES = 1024 * 1024;
@@ -394,9 +395,6 @@ export class ZLinkInMemoryAuthorityStore {
394
395
  }
395
396
  const terminalRecord = validateTerminalForMutation(
396
397
  request.completion.terminal,
397
- request.completion.kind,
398
- request.reservationId,
399
- request.key.kind,
400
398
  this.now()
401
399
  )!;
402
400
  const existingTerminal = this.operationTerminals.get(
@@ -486,7 +484,7 @@ export class ZLinkInMemoryAuthorityStore {
486
484
  this.operationTerminals.delete(key);
487
485
  return { kind: 'missing', storeNow: this.now() };
488
486
  }
489
- return { kind: 'found', record: copyTerminalRecord(record) };
487
+ return { kind: 'found', terminalEnvelope: Buffer.from(record.terminalEnvelope) };
490
488
  }
491
489
 
492
490
  private creationTerminalAvailable(record: ZLinkCreationTerminalRecord): boolean {
@@ -843,7 +841,7 @@ function validateCreationOperation(operation: ZLinkCreationOperationIdentity): v
843
841
  }
844
842
  if (
845
843
  operation.sourceNodeGeneration < 1n
846
- || operation.sourceNodeGeneration > MAX_GENERATION
844
+ || operation.sourceNodeGeneration > MAX_U64
847
845
  || operation.operationId.high < 0n
848
846
  || operation.operationId.high > MAX_U64
849
847
  || operation.operationId.low < 0n
@@ -856,22 +854,12 @@ function validateCreationOperation(operation: ZLinkCreationOperationIdentity): v
856
854
 
857
855
  function validateTerminalForMutation(
858
856
  publication: ZLinkCreationTerminalPublication | undefined,
859
- state: ZLinkCreationTerminalRecord['state'],
860
- reservationId: string,
861
- objectKind: ZLinkPlacementAllocation['objectKind'],
862
857
  storeNow: Date
863
858
  ): ZLinkCreationTerminalRecord | undefined {
864
859
  if (publication === undefined) return undefined;
865
860
  validateCreationOperation(publication.operation);
866
- if (
867
- publication.terminalEnvelope.byteLength > MAX_PAYLOAD_BYTES
868
- || publication.terminalEnvelopeSha256.byteLength !== 32
869
- ) {
870
- throw new RangeError('Creation terminal envelope must not exceed 1 MiB and requires a SHA-256 digest.');
871
- }
872
- const actualSha = createHash('sha256').update(publication.terminalEnvelope).digest();
873
- if (!timingSafeEqual(actualSha, Buffer.from(publication.terminalEnvelopeSha256))) {
874
- throw new TypeError('Creation terminal envelope SHA-256 does not match its bytes.');
861
+ if (publication.terminalEnvelope.byteLength > MAX_PAYLOAD_BYTES) {
862
+ throw new RangeError('Creation terminal envelope must not exceed 1 MiB.');
875
863
  }
876
864
  const deadlineMs = publication.operationDeadline.getTime();
877
865
  const expiresAtMs = deadlineMs + CREATION_TERMINAL_RETENTION_MS;
@@ -883,24 +871,15 @@ function validateTerminalForMutation(
883
871
  throw new RangeError('Creation terminal expiry must be the live operation deadline plus five minutes.');
884
872
  }
885
873
  return {
886
- state,
887
874
  operation: copyCreationOperation(publication.operation),
888
- reservationId: requireText(reservationId, 'creation reservation ID'),
889
- objectKind,
890
875
  terminalEnvelope: Buffer.from(publication.terminalEnvelope),
891
- terminalEnvelopeSha256: Buffer.from(publication.terminalEnvelopeSha256),
892
876
  expiresAt: new Date(expiresAtMs),
893
877
  storeNow: new Date(storeNow.getTime())
894
878
  };
895
879
  }
896
880
 
897
881
  function creationOperationKey(operation: ZLinkCreationOperationIdentity): string {
898
- return [
899
- String(operation.sourceNodeRid),
900
- operation.sourceNodeGeneration.toString(),
901
- operation.operationId.high.toString(16).padStart(16, '0'),
902
- operation.operationId.low.toString(16).padStart(16, '0')
903
- ].join('\0');
882
+ return creationTerminalPreimage(operation);
904
883
  }
905
884
 
906
885
  function copyCreationOperation(
@@ -921,7 +900,6 @@ function copyTerminalRecord(record: ZLinkCreationTerminalRecord): ZLinkCreationT
921
900
  ...record,
922
901
  operation: copyCreationOperation(record.operation),
923
902
  terminalEnvelope: Buffer.from(record.terminalEnvelope),
924
- terminalEnvelopeSha256: Buffer.from(record.terminalEnvelopeSha256),
925
903
  expiresAt: new Date(record.expiresAt),
926
904
  storeNow: new Date(record.storeNow)
927
905
  };