@zlink-systems/framework 0.22.0 → 0.24.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 (104) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +1 -0
  3. package/dist/contracts/Streams/IZLinkSession.d.ts +2 -1
  4. package/dist/runtime/actors/actor-context.js +4 -4
  5. package/dist/runtime/actors/actor-join-deferred-scope.d.ts +0 -1
  6. package/dist/runtime/actors/actor-join-deferred-scope.js +0 -18
  7. package/dist/runtime/actors/deferred-join-accepted-journal.js +2 -6
  8. package/dist/runtime/backend/contracts/index.d.ts +2 -0
  9. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +4 -0
  10. package/dist/runtime/backend/node/node-raw-mesh-backend.js +24 -8
  11. package/dist/runtime/channels/channel-dispatch-pipeline.js +2 -2
  12. package/dist/runtime/channels/channel-transports.js +3 -9
  13. package/dist/runtime/channels/dispatch-error-reporter.d.ts +1 -0
  14. package/dist/runtime/channels/dispatch-error-reporter.js +6 -38
  15. package/dist/runtime/diagnostics/dispatch-error-details.d.ts +6 -0
  16. package/dist/runtime/diagnostics/dispatch-error-details.js +55 -0
  17. package/dist/runtime/diagnostics/flow-context.d.ts +2 -1
  18. package/dist/runtime/diagnostics/flow-context.js +2 -2
  19. package/dist/runtime/diagnostics/message-flow.js +11 -3
  20. package/dist/runtime/foundation/actor-join-recovery-codec.js +0 -4
  21. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +4 -2
  22. package/dist/runtime/foundation/raw-service-mesh-runtime.js +34 -9
  23. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +1 -0
  24. package/dist/runtime/foundation/service-stateful-runtime.d.ts +5 -0
  25. package/dist/runtime/foundation/service-stateful-runtime.js +52 -2
  26. package/dist/runtime/foundation/service-topology-registry.d.ts +1 -0
  27. package/dist/runtime/foundation/service-topology-registry.js +12 -4
  28. package/dist/runtime/host/index.js +7 -3
  29. package/dist/runtime/locations/in-memory-location-store.js +5 -0
  30. package/dist/runtime/locations/location-store-repository.js +3 -1
  31. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +20 -18
  32. package/dist/runtime/spots/index.js +9 -3
  33. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +2 -1
  34. package/dist/runtime/spots/spot-actor-packet-dispatch.js +6 -1
  35. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  36. package/dist/runtime/spots/spot-node-runtime-manager.js +5 -1
  37. package/dist/runtime/spots/spot-route-packet-dispatch.js +1 -1
  38. package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +1 -1
  39. package/dist/runtime/spots/spot-subscription-dispatch.js +1 -1
  40. package/dist/runtime/spots/spot-timer.js +1 -1
  41. package/dist/runtime/streams/actor-session-binding-registry.d.ts +21 -8
  42. package/dist/runtime/streams/actor-session-binding-registry.js +71 -42
  43. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +1 -0
  44. package/dist/runtime/streams/bound-actor-relay-sender.js +2 -1
  45. package/dist/runtime/streams/bound-session-response-target.js +8 -1
  46. package/dist/runtime/streams/bound-session-service.d.ts +1 -0
  47. package/dist/runtime/streams/bound-session-service.js +6 -3
  48. package/dist/runtime/streams/index.js +8 -1
  49. package/dist/runtime/streams/managed-stream.d.ts +3 -0
  50. package/dist/runtime/streams/managed-stream.js +20 -1
  51. package/dist/runtime/streams/protocol.d.ts +5 -1
  52. package/dist/runtime/streams/protocol.js +41 -4
  53. package/dist/runtime/streams/session-actor-coordinator.js +3 -4
  54. package/dist/runtime/streams/session-context.d.ts +9 -2
  55. package/dist/runtime/streams/session-context.js +20 -4
  56. package/dist/runtime/streams/session-local-actors.d.ts +3 -1
  57. package/dist/runtime/streams/session-local-actors.js +17 -2
  58. package/dist/runtime/streams/stream-frame-factory.d.ts +1 -1
  59. package/dist/runtime/streams/stream-frame-factory.js +3 -2
  60. package/dist/runtime/streams/stream-session-runtime.js +42 -12
  61. package/package.json +3 -3
  62. package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -0
  63. package/src/contracts/Streams/IZLinkSession.ts +2 -1
  64. package/src/runtime/actors/actor-context.ts +4 -4
  65. package/src/runtime/actors/actor-join-deferred-scope.ts +0 -32
  66. package/src/runtime/actors/deferred-join-accepted-journal.ts +2 -6
  67. package/src/runtime/backend/contracts/index.ts +5 -0
  68. package/src/runtime/backend/node/node-raw-mesh-backend.ts +43 -11
  69. package/src/runtime/channels/channel-dispatch-pipeline.ts +4 -2
  70. package/src/runtime/channels/channel-transports.ts +3 -9
  71. package/src/runtime/channels/dispatch-error-reporter.ts +7 -45
  72. package/src/runtime/diagnostics/dispatch-error-details.ts +62 -0
  73. package/src/runtime/diagnostics/flow-context.ts +6 -4
  74. package/src/runtime/diagnostics/message-flow.ts +12 -3
  75. package/src/runtime/foundation/actor-join-recovery-codec.ts +0 -4
  76. package/src/runtime/foundation/raw-service-mesh-runtime.ts +54 -13
  77. package/src/runtime/foundation/service-session-binding-ingress-port.ts +1 -0
  78. package/src/runtime/foundation/service-stateful-runtime.ts +71 -5
  79. package/src/runtime/foundation/service-topology-registry.ts +18 -5
  80. package/src/runtime/host/index.ts +11 -4
  81. package/src/runtime/locations/in-memory-location-store.ts +7 -0
  82. package/src/runtime/locations/location-store-repository.ts +4 -1
  83. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +18 -17
  84. package/src/runtime/spots/index.ts +22 -2
  85. package/src/runtime/spots/spot-actor-packet-dispatch.ts +20 -3
  86. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  87. package/src/runtime/spots/spot-node-runtime-manager.ts +5 -1
  88. package/src/runtime/spots/spot-route-packet-dispatch.ts +2 -1
  89. package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +2 -1
  90. package/src/runtime/spots/spot-subscription-dispatch.ts +2 -1
  91. package/src/runtime/spots/spot-timer.ts +6 -1
  92. package/src/runtime/streams/actor-session-binding-registry.ts +150 -63
  93. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +1 -0
  94. package/src/runtime/streams/bound-actor-relay-sender.ts +10 -2
  95. package/src/runtime/streams/bound-session-response-target.ts +10 -1
  96. package/src/runtime/streams/bound-session-service.ts +13 -3
  97. package/src/runtime/streams/index.ts +8 -1
  98. package/src/runtime/streams/managed-stream.ts +34 -12
  99. package/src/runtime/streams/protocol.ts +44 -5
  100. package/src/runtime/streams/session-actor-coordinator.ts +12 -4
  101. package/src/runtime/streams/session-context.ts +31 -4
  102. package/src/runtime/streams/session-local-actors.ts +19 -3
  103. package/src/runtime/streams/stream-frame-factory.ts +4 -2
  104. package/src/runtime/streams/stream-session-runtime.ts +58 -13
@@ -163,8 +163,23 @@ class DefaultZLinkSessionContext {
163
163
  findBoundActor(actorId) {
164
164
  return this.localActors.find(actorId);
165
165
  }
166
- bindLocal(actor, token) {
167
- this.localActors.bind(actor, token);
166
+ actorForSlot(actorSlot) {
167
+ if (actorSlot === undefined)
168
+ return undefined;
169
+ return this.localActors.findBySlot(actorSlot);
170
+ }
171
+ get actorSlotControls() {
172
+ if (this.stream.enqueueActorBound === undefined ||
173
+ this.stream.enqueueActorUnbound === undefined) {
174
+ return undefined;
175
+ }
176
+ return {
177
+ enqueueBound: (actorSlot, actorId) => this.stream.enqueueActorBound(actorSlot, actorId),
178
+ enqueueUnbound: (actorSlot) => this.stream.enqueueActorUnbound(actorSlot)
179
+ };
180
+ }
181
+ bindLocal(actor, token, actorSlot) {
182
+ this.localActors.bind(actor, token, actorSlot);
168
183
  }
169
184
  unbindLocal(actorId, token) {
170
185
  this.localActors.unbind(actorId, token);
@@ -313,11 +328,12 @@ class DefaultZLinkSessionActor {
313
328
  }
314
329
  exports.DefaultZLinkSessionActor = DefaultZLinkSessionActor;
315
330
  const SESSION_DISPATCH_HEADERS = new WeakMap();
316
- function createSessionDispatchContext(header) {
331
+ function createSessionDispatchContext(header, actor) {
317
332
  const dispatch = {
318
333
  packetName: header.name,
319
334
  metadata: header.metadata,
320
- canReply: header.requestSeq !== undefined
335
+ canReply: header.requestSeq !== undefined,
336
+ ...(actor === undefined ? {} : { actor })
321
337
  };
322
338
  SESSION_DISPATCH_HEADERS.set(dispatch, header);
323
339
  return dispatch;
@@ -3,8 +3,10 @@ export interface ZLinkSessionLocalActor {
3
3
  }
4
4
  export declare class ZLinkSessionLocalActorBindings<TActor extends ZLinkSessionLocalActor> {
5
5
  private readonly actors;
6
+ private readonly actorIdsBySlot;
6
7
  snapshot(): readonly TActor[];
7
8
  find(actorId: string): TActor | undefined;
8
- bind(actor: TActor, token: string): void;
9
+ findBySlot(actorSlot: number): TActor | undefined;
10
+ bind(actor: TActor, token: string, actorSlot?: number): void;
9
11
  unbind(actorId: string, token: string): void;
10
12
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkSessionLocalActorBindings = void 0;
4
4
  class ZLinkSessionLocalActorBindings {
5
5
  actors = new Map();
6
+ actorIdsBySlot = new Map();
6
7
  snapshot() {
7
8
  const snapshot = [];
8
9
  for (const entry of this.actors.values()) {
@@ -13,13 +14,27 @@ class ZLinkSessionLocalActorBindings {
13
14
  find(actorId) {
14
15
  return this.actors.get(actorId)?.actor;
15
16
  }
16
- bind(actor, token) {
17
- this.actors.set(actor.actorId, { actor, token });
17
+ findBySlot(actorSlot) {
18
+ const actorId = this.actorIdsBySlot.get(actorSlot);
19
+ return actorId === undefined ? undefined : this.actors.get(actorId)?.actor;
20
+ }
21
+ bind(actor, token, actorSlot) {
22
+ const previous = this.actors.get(actor.actorId);
23
+ if (previous?.actorSlot !== undefined && previous.actorSlot !== actorSlot) {
24
+ this.actorIdsBySlot.delete(previous.actorSlot);
25
+ }
26
+ this.actors.set(actor.actorId, { actor, token, actorSlot });
27
+ if (actorSlot !== undefined) {
28
+ this.actorIdsBySlot.set(actorSlot, actor.actorId);
29
+ }
18
30
  }
19
31
  unbind(actorId, token) {
20
32
  const current = this.actors.get(actorId);
21
33
  if (current?.token === token) {
22
34
  this.actors.delete(actorId);
35
+ if (current.actorSlot !== undefined) {
36
+ this.actorIdsBySlot.delete(current.actorSlot);
37
+ }
23
38
  }
24
39
  }
25
40
  }
@@ -27,7 +27,7 @@ export declare class ZLinkStreamFrameMessageFactory {
27
27
  constructor(options: ZLinkStreamFrameMessageFactoryOptions);
28
28
  createTextMessage(payload: string): Message;
29
29
  createBinaryMessage(payload: Uint8Array): Message;
30
- createJsonFrameMessage(kind: ZLinkStreamMessageKind, packetName: string, metadata: ReadonlyMap<string, string>, compressed: boolean, requestSeq: bigint | undefined, payload: unknown, correlationId?: string): Message;
30
+ createJsonFrameMessage(kind: ZLinkStreamMessageKind, packetName: string, metadata: ReadonlyMap<string, string>, compressed: boolean, requestSeq: bigint | undefined, payload: unknown, correlationId?: string, actorSlot?: number): Message;
31
31
  createJsonReplyFrameMessage(requestHeader: ZLinkStreamFrameHeader, kind: ZLinkStreamReplyMessageKind, metadata: ReadonlyMap<string, string>, compressed: boolean, payload: unknown): Message;
32
32
  private createJsonFrameMessageWithHeader;
33
33
  /**
@@ -26,7 +26,7 @@ class ZLinkStreamFrameMessageFactory {
26
26
  }
27
27
  return factory.createTextMessage((0, protocol_1.utf8Decode)(payload));
28
28
  }
29
- createJsonFrameMessage(kind, packetName, metadata, compressed, requestSeq, payload, correlationId) {
29
+ createJsonFrameMessage(kind, packetName, metadata, compressed, requestSeq, payload, correlationId, actorSlot) {
30
30
  const flow = (this.options.flowCreationEnabled?.() ?? true)
31
31
  ? (0, flow_context_1.currentOrCreateFlow)('Application')
32
32
  : undefined;
@@ -41,7 +41,8 @@ class ZLinkStreamFrameMessageFactory {
41
41
  metadata,
42
42
  correlationId,
43
43
  flowId: flow?.flowId,
44
- flowOrigin: flow?.flowOrigin
44
+ flowOrigin: flow?.flowOrigin,
45
+ actorSlot
45
46
  }));
46
47
  }
47
48
  createJsonReplyFrameMessage(requestHeader, kind, metadata, compressed, payload) {
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkStreamSessionNodeRuntime = exports.ZLinkStreamSessionRuntime = void 0;
4
4
  const framework_errors_internal_1 = require("../framework-errors-internal");
5
- const contracts_1 = require("../../contracts");
6
5
  const internal_event_contracts_1 = require("../diagnostics/internal-event-contracts");
7
6
  const ZLinkDispatchOptions_1 = require("../../contracts/Dispatch/ZLinkDispatchOptions");
8
7
  const abort_1 = require("../abort");
9
8
  const channels_1 = require("../channels");
9
+ const bound_session_response_target_1 = require("./bound-session-response-target");
10
10
  const diagnostics_1 = require("../diagnostics");
11
11
  const payload_codec_1 = require("../messaging/payload-codec");
12
12
  const protocol_1 = require("./protocol");
@@ -329,6 +329,40 @@ class ZLinkStreamSessionRuntime {
329
329
  if (this.context.tryCompleteResponse(decodedHeader, dispatchPayload)) {
330
330
  return;
331
331
  }
332
+ const dispatchActor = this.context.actorForSlot(decodedHeader.actorSlot);
333
+ if (decodedHeader.actorSlot !== undefined && dispatchActor === undefined) {
334
+ const staleFlowEnabled = this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true;
335
+ await (0, flow_context_1.runWithFlow)((0, flow_context_1.createInboundFlow)(decodedHeader.flowId, decodedHeader.flowOrigin, staleFlowEnabled, staleFlowEnabled ? this.stream.sessionId : undefined), async () => {
336
+ if (decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Request) {
337
+ const error = (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.InvalidOperation, 'Actor slot is not a current binding.');
338
+ this.options.dispatchErrors?.report({
339
+ surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
340
+ messageKind: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Request,
341
+ reason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.StaleTarget,
342
+ action: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorAction.ReplyError,
343
+ packetName: decodedHeader.name,
344
+ sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
345
+ correlationId: decodedHeader.correlationId,
346
+ flowId: decodedHeader.flowId,
347
+ flowOrigin: decodedHeader.flowOrigin,
348
+ error
349
+ });
350
+ await this.replyDispatchError(decodedHeader, error);
351
+ }
352
+ else {
353
+ (0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Dropped)?.trace({
354
+ outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Dropped,
355
+ surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
356
+ messageKind: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send,
357
+ packetName: decodedHeader.name,
358
+ correlationId: decodedHeader.correlationId,
359
+ sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
360
+ errorReason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.StaleTarget
361
+ });
362
+ }
363
+ });
364
+ return;
365
+ }
332
366
  this.context.enterDispatch(decodedHeader);
333
367
  enteredDispatch = true;
334
368
  const session = await this.requireSession();
@@ -337,7 +371,8 @@ class ZLinkStreamSessionRuntime {
337
371
  : ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send;
338
372
  const streamCorr = decodedHeader.correlationId;
339
373
  const inboundHeader = decodedHeader;
340
- await (0, flow_context_1.runWithFlow)((0, flow_context_1.createInboundFlow)(inboundHeader.flowId, inboundHeader.flowOrigin, this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true), async () => {
374
+ const flowEnabled = this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true;
375
+ await (0, flow_context_1.runWithFlow)((0, flow_context_1.createInboundFlow)(inboundHeader.flowId, inboundHeader.flowOrigin, flowEnabled, flowEnabled ? this.stream.sessionId : undefined), async () => {
341
376
  (0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Received)?.trace({
342
377
  outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Received,
343
378
  surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
@@ -363,7 +398,7 @@ class ZLinkStreamSessionRuntime {
363
398
  correlationId: streamCorr,
364
399
  sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId)
365
400
  });
366
- await session.onDispatch?.((0, session_context_1.createSessionDispatchContext)(inboundHeader), (0, payload_codec_1.wrapFrameworkPayloadMessage)(dispatchPayload, this.options.messageSerializers, (0, protocol_1.streamCodecContentType)(inboundHeader.codec), inboundHeader.name));
401
+ await session.onDispatch?.((0, session_context_1.createSessionDispatchContext)(inboundHeader, dispatchActor), (0, payload_codec_1.wrapFrameworkPayloadMessage)(dispatchPayload, this.options.messageSerializers, (0, protocol_1.streamCodecContentType)(inboundHeader.codec), inboundHeader.name));
367
402
  if (streamKind === ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send) {
368
403
  (0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Completed)?.trace({
369
404
  outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Completed,
@@ -478,14 +513,7 @@ class ZLinkStreamSessionRuntime {
478
513
  // record (spec 26 §2.2) and confuse the peer's reply matching.
479
514
  return;
480
515
  }
481
- const message = this.context.createJsonReplyFrameMessage(header, protocol_1.ZLinkStreamMessageKind.Error, new Map(), false, {
482
- code: error instanceof contracts_1.ZLinkFrameworkException
483
- ? contracts_1.ZLinkFrameworkErrorKind[error.kind]
484
- : error instanceof Error
485
- ? error.constructor.name
486
- : 'RemoteError',
487
- message: error instanceof Error ? error.message : String(error)
488
- });
516
+ const message = this.context.createJsonReplyFrameMessage(header, protocol_1.ZLinkStreamMessageKind.Error, new Map(), false, (0, bound_session_response_target_1.boundSessionErrorPayload)(error));
489
517
  try {
490
518
  if (!this.context.stream.writeRaw(message, ZLINK_SEND_DONT_WAIT)) {
491
519
  throw new Error('Client stream error reply send failed.');
@@ -518,7 +546,8 @@ class ZLinkStreamSessionRuntime {
518
546
  : ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send,
519
547
  packetName: packetName.length === 0 ? undefined : packetName,
520
548
  correlationId,
521
- sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId)
549
+ sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
550
+ streamSessionId: this.stream.sessionId
522
551
  });
523
552
  }
524
553
  async complete(error, notifyDisconnected) {
@@ -627,6 +656,7 @@ class ZLinkStreamSessionRuntime {
627
656
  packetName: header.name === '' ? undefined : header.name,
628
657
  correlationId: header.correlationId,
629
658
  sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
659
+ streamSessionId: this.stream.sessionId,
630
660
  flowId: flowEnabled ? header.flowId : undefined,
631
661
  flowOrigin: flowEnabled ? header.flowOrigin : undefined,
632
662
  errorReason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.Shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/framework",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@opentelemetry/api": "^1.9.1",
21
21
  "@opentelemetry/api-logs": "^0.221.0",
22
- "@zlink-systems/stream-wire": "0.22.0",
23
- "@zlink-systems/zlink": "1.2.1"
22
+ "@zlink-systems/stream-wire": "0.24.0",
23
+ "@zlink-systems/zlink": "1.6.0"
24
24
  }
25
25
  }
@@ -80,6 +80,7 @@ export interface ZLinkRuntimeMessageFlowEvent {
80
80
  readonly instanceSpotType?: string;
81
81
  readonly activationState?: 'activating' | 'ready' | 'closing';
82
82
  readonly actorId?: string;
83
+ readonly streamSessionId?: string;
83
84
  /** Internal service-wire command carried by a RouteMesh control record. */
84
85
  readonly commandId?: number;
85
86
  readonly messageSize?: number;
@@ -1,5 +1,5 @@
1
1
  import type { RoutingId, ZLinkMessage } from '../Common';
2
- import type { ZLinkSessionActors } from './IZLinkSessionActor';
2
+ import type { ZLinkSessionActor, ZLinkSessionActors } from './IZLinkSessionActor';
3
3
  import type { ZLinkStreamError } from './ZLinkStreamError';
4
4
  import type { ZLinkSessionHandlerRegistry } from './IZLinkSessionPacketHandler';
5
5
 
@@ -16,6 +16,7 @@ export interface ZLinkSessionDispatchContext {
16
16
  readonly packetName: string;
17
17
  readonly metadata: ReadonlyMap<string, string>;
18
18
  readonly canReply: boolean;
19
+ readonly actor?: ZLinkSessionActor;
19
20
  }
20
21
 
21
22
  export interface ZLinkSessionFactory<TSession extends ZLinkSession = ZLinkSession> {
@@ -39,6 +39,7 @@ import {
39
39
  import { createRandomOperationIdentity } from '../foundation/operation-identity';
40
40
  import { deferActorJoin } from './actor-join-deferred-scope';
41
41
  import { captureZLinkSpotSerialTurn } from '../execution';
42
+ import { dispatchErrorDetails } from '../diagnostics/dispatch-error-details';
42
43
 
43
44
  export const ZLINK_ACTOR_JOIN_ENTRY_SPOT_RUNTIME = Symbol('zlink.actor.join-entry-spot-runtime');
44
45
 
@@ -219,7 +220,6 @@ class DefaultZLinkActorJoinSpotCall implements ZLinkActorJoinSpotCall {
219
220
  };
220
221
  try {
221
222
  deferActorJoin({
222
- requestBytes: requestMessage.data().byteLength,
223
223
  prepare: async () => {
224
224
  if (prepared) return;
225
225
  prepared = true;
@@ -358,7 +358,6 @@ class DefaultZLinkActorJoinEntrySpotCall implements ZLinkActorJoinEntrySpotCall
358
358
  };
359
359
  try {
360
360
  deferActorJoin({
361
- requestBytes: requestMessage.data().byteLength,
362
361
  prepare: async () => {
363
362
  if (prepared) return;
364
363
  prepared = true;
@@ -588,6 +587,7 @@ async function notifyJoinFailure(
588
587
  // The completion carries only an error kind, so trace the cause on the
589
588
  // message flow under the same flow id as the Join that produced it.
590
589
  const tracePoint = coordinator?.messageFlow?.()?.begin(ZLinkMessageFlowOutcome.Error);
590
+ const errorDetails = tracePoint === undefined ? undefined : dispatchErrorDetails(error);
591
591
  tracePoint?.trace({
592
592
  outcome: ZLinkMessageFlowOutcome.Error,
593
593
  surface: ZLinkDispatchErrorSurface.SpotActor,
@@ -596,8 +596,8 @@ async function notifyJoinFailure(
596
596
  actorId: actor.context.actorId,
597
597
  errorReason: ZLinkDispatchErrorReason.HandlerException,
598
598
  errorAction: ZLinkDispatchErrorAction.ReplyError,
599
- errorType: String(frameworkError.kind),
600
- errorMessage: String(error)
599
+ errorType: errorDetails?.errorType,
600
+ errorMessage: errorDetails?.errorMessage
601
601
  });
602
602
  await actor.onJoinCompleted?.({
603
603
  status: 'failed',
@@ -1,16 +1,7 @@
1
- import {
2
- ZLinkFrameworkInternalErrorKind,
3
- createInternalFrameworkException
4
- } from '../framework-errors-internal';
5
1
  import { AsyncLocalStorage } from 'node:async_hooks';
6
2
  import { ZLinkConfigurationException } from '../configuration';
7
3
 
8
- const MAX_DEFERRED_JOINS = 64;
9
- const MAX_DEFERRED_JOIN_REQUEST_BYTES = 1024 * 1024;
10
- const MAX_DEFERRED_JOIN_BYTES = 8 * 1024 * 1024;
11
-
12
4
  interface DeferredActorJoin {
13
- readonly requestBytes: number;
14
5
  /** Starts target admission after the handler terminal, before reply admission. */
15
6
  readonly prepare?: () => Promise<void> | void;
16
7
  readonly execute: () => Promise<void>;
@@ -19,7 +10,6 @@ interface DeferredActorJoin {
19
10
 
20
11
  class ActorJoinRegistrationScope {
21
12
  private readonly intents: DeferredActorJoin[] = [];
22
- private totalBytes = 0;
23
13
  private open = true;
24
14
  private prepared = false;
25
15
 
@@ -34,29 +24,7 @@ class ActorJoinRegistrationScope {
34
24
  'Actor join defer requires an open Framework actor handler scope.'
35
25
  );
36
26
  }
37
- if (this.intents.length >= MAX_DEFERRED_JOINS) {
38
- discardWithoutWaiting(intent);
39
- throw createInternalFrameworkException(
40
- ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
41
- `An actor handler may defer at most ${MAX_DEFERRED_JOINS} joins.`
42
- );
43
- }
44
- if (intent.requestBytes > MAX_DEFERRED_JOIN_REQUEST_BYTES) {
45
- discardWithoutWaiting(intent);
46
- throw createInternalFrameworkException(
47
- ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
48
- `A deferred actor join request may use at most ${MAX_DEFERRED_JOIN_REQUEST_BYTES} bytes.`
49
- );
50
- }
51
- if (this.totalBytes + intent.requestBytes > MAX_DEFERRED_JOIN_BYTES) {
52
- discardWithoutWaiting(intent);
53
- throw createInternalFrameworkException(
54
- ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
55
- `Deferred actor join requests may use at most ${MAX_DEFERRED_JOIN_BYTES} bytes per handler.`
56
- );
57
- }
58
27
  this.intents.push(intent);
59
- this.totalBytes += intent.requestBytes;
60
28
  }
61
29
 
62
30
  sealAfterHandlerTerminal(): void {
@@ -44,7 +44,6 @@ import {
44
44
  const RETENTION_MS = 24 * 60 * 60 * 1_000;
45
45
  const ROOT_VERSION = 2;
46
46
  const ROOT_MAGIC = Buffer.from('ZLJR');
47
- const MAX_ROOT_BYTES = 1024 * 1024;
48
47
  const JOURNAL_INVENTORY_DOMAIN = Buffer.from('zlink-node-deferred-join-authority-v1\0', 'utf8');
49
48
 
50
49
  export type ZLinkDeferredJoinDeliveryCursor = 'prepared' | 'committed' | 'delivered';
@@ -695,17 +694,14 @@ function encodeRoot(
695
694
  'utf8'
696
695
  )
697
696
  ]);
698
- if (encoded.byteLength > MAX_ROOT_BYTES) {
699
- throw new Error('Deferred Join completion root exceeds 1 MiB.');
700
- }
701
697
  return encoded;
702
698
  }
703
699
 
704
700
  function decodeRoot(
705
701
  payload: Uint8Array
706
702
  ): Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'> {
707
- if (payload.byteLength === 0 || payload.byteLength > MAX_ROOT_BYTES) {
708
- throw new Error('Deferred Join completion root size is invalid.');
703
+ if (payload.byteLength === 0) {
704
+ throw new Error('Deferred Join completion root is empty.');
709
705
  }
710
706
  const bytes = Buffer.from(payload);
711
707
  const encoded = hasJournalRootMagic(bytes) ? bytes.subarray(ROOT_MAGIC.byteLength) : bytes;
@@ -60,6 +60,11 @@ export type ZLinkBackendObjectPlacement =
60
60
  | { readonly kind: 'unavailable' };
61
61
 
62
62
  export interface ZLinkBackendMeshNode {
63
+ completeClosedInstance?(spotId: string, objectGeneration: bigint): void;
64
+ setDispatchErrorReporter?(
65
+ reporter: import('../../channels/dispatch-error-reporter').ZLinkDispatchErrorReporter,
66
+ meshName: string
67
+ ): void;
63
68
  setRoutingId(routingId: unknown): void;
64
69
  setBind(endpoint: string): void;
65
70
  setAdvertiseHost(host: string): void;
@@ -1,5 +1,11 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import { setImmediate as yieldToIO } from 'node:timers/promises';
3
+ import {
4
+ decodeStreamWireFrame,
5
+ decodeStreamWireHeader,
6
+ encodeStreamWireFrame,
7
+ encodeStreamWireHeader
8
+ } from '@zlink-systems/stream-wire';
3
9
  import { disconnectStreamPeer } from './node-socket-backend-adapter';
4
10
  import { ZLinkFrameworkException } from '../../../contracts';
5
11
  import { internalFrameworkWireReply } from '../../framework-errors-internal';
@@ -20,6 +26,7 @@ import {
20
26
  } from '../runtime-values';
21
27
  import { ZLinkBufferMessage } from '../runtime-message';
22
28
  import type { Message as FrameworkMessage } from '../../../contracts/Common/Message';
29
+ import type { ZLinkDispatchErrorReporter } from '../../channels/dispatch-error-reporter';
23
30
  import type {
24
31
  MeshOperationId,
25
32
  MeshPeerEntry,
@@ -178,6 +185,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
178
185
  private messageFollowHandler?: (
179
186
  record: import('../../foundation/service-stateful-wire-codec').ServiceMessageFollowRecord
180
187
  ) => void;
188
+ private dispatchErrors?: ZLinkDispatchErrorReporter;
181
189
  private readonly peerDisconnectedHandlers = new Set<(endpoint: string) => void>();
182
190
  constructor(
183
191
  private readonly meshName: string,
@@ -228,6 +236,11 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
228
236
  this.stateful?.setMailboxDropHandler(handler);
229
237
  }
230
238
 
239
+ setDispatchErrorReporter(reporter: ZLinkDispatchErrorReporter): void {
240
+ this.dispatchErrors = reporter;
241
+ this.stateful?.setDispatchErrorReporter(reporter, this.meshName);
242
+ }
243
+
231
244
  setProtocolErrorHandler(
232
245
  handler: (record: {
233
246
  readonly sourceNodeRid: string;
@@ -381,6 +394,9 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
381
394
  descriptor.nodeRoutingId,
382
395
  descriptor.lifecycleGeneration
383
396
  );
397
+ if (this.dispatchErrors !== undefined) {
398
+ this.stateful.setDispatchErrorReporter(this.dispatchErrors, this.meshName);
399
+ }
384
400
  if (this.mailboxRecordDropped !== undefined) {
385
401
  this.stateful.setMailboxDropHandler(this.mailboxRecordDropped);
386
402
  }
@@ -649,12 +665,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
649
665
  channelName: string,
650
666
  parts: MessageLike | readonly MessageLike[]
651
667
  ): Promise<SubmitResultValue> {
652
- return (await this.requireRuntime().sendToChannel(
653
- channelName,
654
- encodeMultipartApplicationFrame(parts)
655
- ))
656
- ? SubmitResult.Ok
657
- : SubmitResult.NotConnected;
668
+ return this.requireRuntime().sendToChannel(channelName, encodeMultipartApplicationFrame(parts));
658
669
  }
659
670
 
660
671
  requestToChannel(
@@ -667,9 +678,6 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
667
678
  encodeMultipartApplicationFrame(parts),
668
679
  options?.timeoutMs ?? 30_000
669
680
  );
670
- if (pending === undefined) {
671
- return this.enqueueImmediateFailure(OperationKind.ChannelRequest, RequestResult.NotFound);
672
- }
673
681
  return this.observeCompletion(pending.id, OperationKind.ChannelRequest, pending.promise);
674
682
  }
675
683
 
@@ -1054,6 +1062,10 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
1054
1062
  );
1055
1063
  }
1056
1064
 
1065
+ completeClosedInstance(spotId: string, objectGeneration: bigint): void {
1066
+ this.stateful?.completeClosedInstance(spotId, objectGeneration);
1067
+ }
1068
+
1057
1069
  rememberSpotRoute(
1058
1070
  route: ServiceDirectSpotRouteFence,
1059
1071
  expectedCurrentRoute?: ServiceDirectSpotRouteFence | null
@@ -1875,7 +1887,8 @@ class RawStreamSessionService implements StreamSessionService {
1875
1887
  sessionKey,
1876
1888
  actor,
1877
1889
  timeoutMs,
1878
- (targetSessionRid, payloadFrame) => this.deliver(targetSessionRid, payloadFrame),
1890
+ (targetSessionRid, payloadFrame) =>
1891
+ this.deliver(targetSessionRid, actor.actorId, payloadFrame),
1879
1892
  onBindingReplaced,
1880
1893
  serviceSessionBindingIngressPortIfRegistered(this),
1881
1894
  actorAuthority
@@ -1913,12 +1926,22 @@ class RawStreamSessionService implements StreamSessionService {
1913
1926
  )) as SubmitResultValue;
1914
1927
  }
1915
1928
 
1916
- private async deliver(sessionRid: string, payload: Uint8Array): Promise<boolean> {
1929
+ private async deliver(
1930
+ sessionRid: string,
1931
+ actorId: string,
1932
+ payload: Uint8Array
1933
+ ): Promise<boolean> {
1917
1934
  const target = this.sessionTargets.get(sessionRid);
1918
1935
  if (target === undefined) return false;
1919
1936
  const operation = this.stream.send(target as unknown as BindingRoutingId);
1920
1937
  const parts = decodeMultipartBuffers(decodeApplicationPayloadView(payload).payload);
1921
1938
  if (parts.length === 0) return false;
1939
+ const actorSlot = await serviceSessionBindingIngressPortIfRegistered(this)?.actorSlot(
1940
+ actorId,
1941
+ sessionRid
1942
+ );
1943
+ if (actorSlot === undefined) return false;
1944
+ parts[0] = Buffer.from(streamFrameWithActorSlot(parts[0]!, actorSlot));
1922
1945
  let submit = operation.message(parts[0]!);
1923
1946
  for (let index = 1; index < parts.length; index++) {
1924
1947
  submit = submit.message(parts[index]!);
@@ -1956,6 +1979,15 @@ class RawStreamSessionService implements StreamSessionService {
1956
1979
  }
1957
1980
  }
1958
1981
 
1982
+ function streamFrameWithActorSlot(frame: Uint8Array, actorSlot: number): Uint8Array {
1983
+ const decodedFrame = decodeStreamWireFrame(frame);
1984
+ const decodedHeader = decodeStreamWireHeader(decodedFrame.header);
1985
+ return encodeStreamWireFrame(
1986
+ encodeStreamWireHeader({ ...decodedHeader, actorSlot }),
1987
+ decodedFrame.payload
1988
+ );
1989
+ }
1990
+
1959
1991
  class RawReadyBatch implements ReadyBatch {
1960
1992
  readonly records: ReadyRecord[] = [];
1961
1993
  private readonly claims: RawClaim[] = [];
@@ -110,7 +110,8 @@ export class ZLinkChannelDispatchPipeline {
110
110
  const flow = createInboundFlow(
111
111
  dispatch.fields.flowId,
112
112
  dispatch.fields.flowOrigin,
113
- this.options.dispatchErrors.flow.flowCreationEnabled()
113
+ this.options.dispatchErrors.flow.flowCreationEnabled(),
114
+ undefined
114
115
  );
115
116
  const invocation = await runWithFlow(flow, () =>
116
117
  this.invoke(
@@ -161,7 +162,8 @@ export class ZLinkChannelDispatchPipeline {
161
162
  const flow = createInboundFlow(
162
163
  dispatch.fields.flowId,
163
164
  dispatch.fields.flowOrigin,
164
- this.options.dispatchErrors.flow.flowCreationEnabled()
165
+ this.options.dispatchErrors.flow.flowCreationEnabled(),
166
+ undefined
165
167
  );
166
168
  const invocation = await runWithFlow(flow, () =>
167
169
  this.invoke(
@@ -1140,15 +1140,9 @@ export function meshRequestFailure(
1140
1140
  const wireKind = canonical
1141
1141
  ? internalFrameworkErrorKindFromWireReply(result, nativeErrno)
1142
1142
  : undefined;
1143
- // NotFound without an errno is the selection itself coming back empty: the
1144
- // send path and its connection are there, and applying eligibility and drain
1145
- // left no member to pick. The spec names that Unavailable, not NotFound, and
1146
- // not the protocol error a non-canonical pair would otherwise produce
1147
- // (06-framework-api "no eligible select-one member"). A named target that
1148
- // does not exist arrives with an errno and still ends as NotFound.
1149
- const noEligibleMember = result === RequestResult.NotFound && nativeErrno === 0;
1150
- const kind: ZLinkFrameworkInternalErrorKindType = noEligibleMember
1151
- ? ZLinkFrameworkInternalErrorKind.RouteNotConnected
1143
+ const localNoMember = result === RequestResult.NotFound && nativeErrno === 0;
1144
+ const kind: ZLinkFrameworkInternalErrorKindType = localNoMember
1145
+ ? ZLinkFrameworkInternalErrorKind.RequestTargetNotFound
1152
1146
  : !canonical
1153
1147
  ? ZLinkFrameworkInternalErrorKind.RequestProtocolError
1154
1148
  : result === RequestResult.NotFound