@cortexkit/subc-client 0.3.4 → 0.4.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.
@@ -1,5 +1,7 @@
1
- import { type BindIdentity, type ReconnectBackoff, type RouteTarget } from "./client.js";
1
+ import { type BindIdentity, type ReconnectBackoff, type RequestOptions, type RouteTarget } from "./client.js";
2
2
  import { type ConnectionInfo } from "./connection-file.js";
3
+ import { AdmissionClass, Priority } from "./envelope.js";
4
+ import { RouteHandle } from "./route-handle.js";
3
5
  export declare const HELLO_CORR = 1n;
4
6
  export type TrustTier = "first_party" | "reviewed" | "untrusted";
5
7
  export type ExecutionMode = "pure" | "mutating" | "unfenceable";
@@ -135,12 +137,14 @@ export interface ManagementSurfaceManifestOptions {
135
137
  * events and `signal` to learn when the consumer cancelled or the route went away.
136
138
  */
137
139
  export interface ProviderRequestContext {
140
+ /** The immutable route generation that received this request. */
141
+ readonly handle: RouteHandle;
138
142
  /**
139
143
  * Emit an interim event as a StreamData frame on this request's (channel, corr).
140
144
  * The consumer receives it via its subscription `onEvent`. A no-op once the
141
145
  * request has been aborted (cancelled or route-gone).
142
146
  */
143
- emit(body: Uint8Array): Promise<void>;
147
+ emit(body: Uint8Array, opts?: ProviderEmitOptions): Promise<void>;
144
148
  /** Aborts when the consumer sends Cancel for this request, or the route is torn down. */
145
149
  signal: AbortSignal;
146
150
  /**
@@ -159,7 +163,7 @@ export interface ProviderRequestContext {
159
163
  * `void` to end a streaming subscription with a StreamEnd terminal (after emitting
160
164
  * events via `ctx.emit`). Throwing produces an Error terminal.
161
165
  */
162
- export type ProviderHandler = (routeChannel: number, body: Uint8Array, ctx: ProviderRequestContext) => Promise<Uint8Array | void> | Uint8Array | void;
166
+ export type ProviderHandler = (handle: RouteHandle, body: Uint8Array, ctx: ProviderRequestContext) => Promise<Uint8Array | void> | Uint8Array | void;
163
167
  export type ProviderHealthHandler = () => Promise<HealthReport> | HealthReport;
164
168
  export type Principal = {
165
169
  kind: "reserved";
@@ -170,10 +174,16 @@ export type Principal = {
170
174
  kind: "unverified";
171
175
  };
172
176
  export interface RouteBindRequest {
173
- route_channel: number;
177
+ handle: RouteHandle;
174
178
  target: RouteTarget;
175
179
  identity: BindIdentity;
176
180
  principal?: Principal;
181
+ /** Consumer-declared reverse-request capabilities for this bind. This is a declaration, not a verified privilege; providers must treat an omitted field as no reverse-request capability. Known MCP method-family values today are "elicitation", "sampling", and "roots". */
182
+ consumer_capabilities?: string[];
183
+ }
184
+ export interface ProviderEmitOptions {
185
+ priority?: Priority;
186
+ admissionClass?: AdmissionClass;
177
187
  }
178
188
  export type BindDecision = boolean | {
179
189
  accept: boolean;
@@ -201,7 +211,9 @@ export interface SubcProviderConnectOptions {
201
211
  handshakeTimeoutMs?: number;
202
212
  controlOps?: string[] | null;
203
213
  onBind?: (request: RouteBindRequest) => Promise<BindDecision> | BindDecision;
204
- onRouteGone?: (routeChannel: number) => void | Promise<void>;
214
+ /** Runs only after an accepted bind ack is queued and the handle is installed. */
215
+ onBound?: (handle: RouteHandle) => void | Promise<void>;
216
+ onRouteGone?: (handle: RouteHandle) => void | Promise<void>;
205
217
  /** Backoff for provider reconnect after an unexpected socket drop. */
206
218
  reconnectBackoff?: ReconnectBackoff;
207
219
  /** Injectable sleep for timer-free reconnect and debounce tests. */
@@ -234,7 +246,7 @@ export declare class SubcProviderError extends Error {
234
246
  constructor(message: string, code?: string | undefined);
235
247
  }
236
248
  export declare function managementSurfaceManifest(opts: ManagementSurfaceManifestOptions): ManifestInput;
237
- export declare function jsonProviderHandler<Request = unknown, Response = unknown>(handler: (routeChannel: number, request: Request) => Promise<Response> | Response): ProviderHandler;
249
+ export declare function jsonProviderHandler<Request = unknown, Response = unknown>(handler: (handle: RouteHandle, request: Request) => Promise<Response> | Response): ProviderHandler;
238
250
  export declare class SubcProvider {
239
251
  private sock;
240
252
  private currentConn;
@@ -244,6 +256,11 @@ export declare class SubcProvider {
244
256
  private closeStarted;
245
257
  private closedErr;
246
258
  private readonly inflight;
259
+ private readonly pending;
260
+ private readonly liveRoutes;
261
+ private connectionToken;
262
+ private nextCorr;
263
+ private ingressEpochDropCount;
247
264
  private readonly requestGate;
248
265
  private reconnecting;
249
266
  private generation;
@@ -258,16 +275,23 @@ export declare class SubcProvider {
258
275
  */
259
276
  storage: unknown;
260
277
  private constructor();
278
+ /** Number of nonzero-channel ingress frames rejected by endpoint validation. */
279
+ get droppedIngressFrames(): number;
261
280
  get conn(): ConnectionInfo;
262
281
  currentEpoch(): number;
282
+ /** Send a reverse request on exactly one installed route generation. */
283
+ request(handle: RouteHandle, body: Uint8Array, opts?: RequestOptions): Promise<Uint8Array>;
284
+ /** Emit an unsolicited Push after onBound has published the route. */
285
+ push(handle: RouteHandle, body: Uint8Array, opts?: ProviderEmitOptions): Promise<void>;
286
+ cancel(handle: RouteHandle, corr: bigint): void;
287
+ closeRoute(handle: RouteHandle): void;
263
288
  /** Read the connection file, authenticate as a client, register the manifest with HELLO, and serve frames. */
264
289
  static connect(opts: SubcProviderConnectOptions): Promise<SubcProvider>;
265
290
  close(): Promise<void>;
266
291
  private static openConnection;
267
292
  private readLoop;
268
293
  private dispatch;
269
- /** Abort every in-flight request on a route channel for the current socket generation. */
270
- private abortChannel;
294
+ private abortHandle;
271
295
  private abortGeneration;
272
296
  private abortAllInflight;
273
297
  private handleControlRequest;
@@ -283,6 +307,9 @@ export declare class SubcProvider {
283
307
  private cancelRestoredDebounce;
284
308
  private enqueueConnectionState;
285
309
  private drainConnectionStateQueue;
310
+ private isLiveHandle;
311
+ private assertLiveHandle;
312
+ private allocateCorr;
286
313
  private failFatal;
287
314
  private finishClosed;
288
315
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
package/dist/provider.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { AuthError, authenticateClient } from "./auth.js";
3
- import { DEFAULT_RECONNECT_BACKOFF } from "./client.js";
3
+ import { DEFAULT_RECONNECT_BACKOFF, } from "./client.js";
4
4
  import { ConnectionFileError, readConnectionFile } from "./connection-file.js";
5
- import { buildFlags, buildFrame, buildFrameWithVersion, decodeHeader, encodeFrame, FrameType, HEADER_LEN, Priority, PROTOCOL_VERSION, } from "./envelope.js";
5
+ import { AdmissionClass, buildFlags, buildFrame, buildFrameWithVersion, encodeFrame, FrameType, Priority, PROTOCOL_VERSION, } from "./envelope.js";
6
+ import { belongsToConnection, createRouteHandle, newConnectionToken, StaleRouteHandleError, } from "./route-handle.js";
6
7
  import { SocketClosedError, SocketTimeoutError, SocketWriteNotQueuedError, SocketWriteQueuedError, SubcSocket, } from "./socket.js";
7
8
  const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
8
9
  const BODY_READ_TIMEOUT_MS = 30_000;
@@ -88,9 +89,9 @@ export function managementSurfaceManifest(opts) {
88
89
  };
89
90
  }
90
91
  export function jsonProviderHandler(handler) {
91
- return async (routeChannel, body) => {
92
+ return async (handle, body) => {
92
93
  const request = JSON.parse(Buffer.from(body).toString("utf8"));
93
- const response = await handler(routeChannel, request);
94
+ const response = await handler(handle, request);
94
95
  return encodeJson(response);
95
96
  };
96
97
  }
@@ -106,6 +107,11 @@ export class SubcProvider {
106
107
  // A socket drop only makes the reply path stale; handlers may still finish their
107
108
  // durable work, and their late sends are ignored by the generation guard.
108
109
  inflight = new Map();
110
+ pending = new Map();
111
+ liveRoutes = new Map();
112
+ connectionToken = newConnectionToken();
113
+ nextCorr = 1n;
114
+ ingressEpochDropCount = 0;
109
115
  requestGate = new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY);
110
116
  reconnecting = null;
111
117
  generation = 1;
@@ -130,12 +136,58 @@ export class SubcProvider {
130
136
  void this.readLoop(sock, this.generation);
131
137
  this.enqueueConnectionState({ state: "connected", epoch: this.connectionEpoch });
132
138
  }
139
+ /** Number of nonzero-channel ingress frames rejected by endpoint validation. */
140
+ get droppedIngressFrames() {
141
+ return this.ingressEpochDropCount;
142
+ }
133
143
  get conn() {
134
144
  return this.currentConn;
135
145
  }
136
146
  currentEpoch() {
137
147
  return this.connectionEpoch;
138
148
  }
149
+ /** Send a reverse request on exactly one installed route generation. */
150
+ async request(handle, body, opts = {}) {
151
+ this.assertLiveHandle(handle);
152
+ const corr = this.allocateCorr();
153
+ const key = routeKey(handle, corr);
154
+ const timeoutMs = opts.timeoutMs ?? WRITE_TIMEOUT_MS;
155
+ const frame = buildFrame(FrameType.Request, buildFlags(false, opts.priority ?? Priority.Interactive, false, opts.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, corr, body);
156
+ return await new Promise((resolve, reject) => {
157
+ const timer = setTimeout(() => {
158
+ if (this.pending.delete(key))
159
+ reject(new SubcProviderError("reverse request timed out", "request_timeout"));
160
+ }, timeoutMs);
161
+ this.pending.set(key, {
162
+ resolve: (response) => resolve(response.body),
163
+ reject,
164
+ timer,
165
+ });
166
+ this.sendOn(this.sock, this.generation, frame).catch((error) => {
167
+ const pending = this.pending.get(key);
168
+ if (!pending)
169
+ return;
170
+ this.pending.delete(key);
171
+ clearTimeout(pending.timer);
172
+ reject(error instanceof Error ? error : new SubcProviderError(String(error)));
173
+ });
174
+ });
175
+ }
176
+ /** Emit an unsolicited Push after onBound has published the route. */
177
+ async push(handle, body, opts = {}) {
178
+ this.assertLiveHandle(handle);
179
+ await this.sendOn(this.sock, this.generation, buildFrame(FrameType.Push, buildFlags(false, opts.priority ?? Priority.Interactive, false, opts.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, 0n, body));
180
+ }
181
+ cancel(handle, corr) {
182
+ this.assertLiveHandle(handle);
183
+ void this.sendOn(this.sock, this.generation, buildFrame(FrameType.Cancel, controlFlags(), handle.channel, handle.epoch, corr, new Uint8Array(0)));
184
+ }
185
+ closeRoute(handle) {
186
+ this.assertLiveHandle(handle);
187
+ this.liveRoutes.delete(handle.channel);
188
+ this.abortHandle(handle);
189
+ void this.sendOn(this.sock, this.generation, buildFrame(FrameType.Goodbye, controlFlags(), handle.channel, handle.epoch, 0n, new Uint8Array(0)));
190
+ }
139
191
  /** Read the connection file, authenticate as a client, register the manifest with HELLO, and serve frames. */
140
192
  static async connect(opts) {
141
193
  if (opts.manifest.protocol_ver !== PROTOCOL_VERSION) {
@@ -151,7 +203,7 @@ export class SubcProvider {
151
203
  this.cancelRestoredDebounce();
152
204
  const sock = this.sock;
153
205
  try {
154
- await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0n, new Uint8Array(0)));
206
+ await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0, 0n, new Uint8Array(0)));
155
207
  }
156
208
  catch {
157
209
  // The daemon may already have closed the connection; close() remains best-effort.
@@ -182,13 +234,8 @@ export class SubcProvider {
182
234
  async readLoop(sock, generation) {
183
235
  try {
184
236
  for (;;) {
185
- // Header read waits indefinitely idle time between frames is normal.
186
- const headerBytes = await sock.readExact(HEADER_LEN, Number.POSITIVE_INFINITY);
187
- const header = decodeHeader(headerBytes);
188
- const body = header.len === 0
189
- ? new Uint8Array(0)
190
- : await sock.readExact(header.len, Date.now() + BODY_READ_TIMEOUT_MS);
191
- const keepGoing = await this.dispatch({ header, body }, sock, generation);
237
+ const frame = await sock.readFrame(Number.POSITIVE_INFINITY, Date.now() + BODY_READ_TIMEOUT_MS);
238
+ const keepGoing = await this.dispatch(frame, sock, generation);
192
239
  if (!keepGoing) {
193
240
  if (this.sock === sock && this.generation === generation)
194
241
  this.closeStarted = true;
@@ -196,9 +243,9 @@ export class SubcProvider {
196
243
  }
197
244
  }
198
245
  }
199
- catch (err) {
246
+ catch (error) {
200
247
  if (this.sock === sock && this.generation === generation && !this.closeStarted) {
201
- this.handleUnexpectedDrop(sock, generation, err instanceof Error ? err : new SubcProviderError(String(err)));
248
+ this.handleUnexpectedDrop(sock, generation, error instanceof Error ? error : new SubcProviderError(String(error)));
202
249
  return;
203
250
  }
204
251
  }
@@ -211,33 +258,59 @@ export class SubcProvider {
211
258
  }
212
259
  }
213
260
  async dispatch(frame, sock, generation) {
261
+ let handle = null;
262
+ if (frame.header.channel !== 0) {
263
+ handle = this.liveRoutes.get(frame.header.channel) ?? null;
264
+ if (!handle || handle.epoch !== frame.header.epoch) {
265
+ this.ingressEpochDropCount += 1;
266
+ return true;
267
+ }
268
+ }
269
+ if (handle) {
270
+ const pendingKey = routeKey(handle, frame.header.corr);
271
+ const pending = this.pending.get(pendingKey);
272
+ if (pending) {
273
+ if (frame.header.ty === FrameType.Push || frame.header.ty === FrameType.StreamData)
274
+ return true;
275
+ if (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.StreamEnd) {
276
+ this.pending.delete(pendingKey);
277
+ clearTimeout(pending.timer);
278
+ pending.resolve(frame);
279
+ return true;
280
+ }
281
+ if (frame.header.ty === FrameType.Error) {
282
+ this.pending.delete(pendingKey);
283
+ clearTimeout(pending.timer);
284
+ pending.reject(providerErrorFromFrame(frame));
285
+ return true;
286
+ }
287
+ }
288
+ }
214
289
  switch (frame.header.ty) {
215
290
  case FrameType.Ping:
216
291
  if (frame.header.channel === 0) {
217
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, frame.header.corr, new Uint8Array(0)));
292
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, 0, frame.header.corr, new Uint8Array(0)));
218
293
  }
219
294
  return true;
220
295
  case FrameType.Goodbye:
221
- if (frame.header.channel === 0)
296
+ if (!handle)
222
297
  return false;
223
- // The route is gone: abort in-flight requests on that route so streaming
224
- // handlers unwind, then notify the provider owner.
225
- this.abortChannel(generation, frame.header.channel);
226
- await this.opts.onRouteGone?.(frame.header.channel);
298
+ this.liveRoutes.delete(handle.channel);
299
+ this.abortHandle(handle);
300
+ await this.opts.onRouteGone?.(handle);
227
301
  return true;
228
302
  case FrameType.Cancel:
229
- // The consumer cancelled one request: abort the matching handler. Its
230
- // streaming handler observes ctx.signal and ends with a StreamEnd terminal.
231
- this.inflight.get(routeKey(generation, frame.header.channel, frame.header.corr))?.abort();
303
+ if (handle)
304
+ this.inflight.get(routeKey(handle, frame.header.corr))?.abort();
232
305
  return true;
233
306
  case FrameType.Request:
234
307
  if (frame.header.channel === 0) {
235
308
  await this.handleControlRequest(frame, sock, generation);
236
309
  }
237
- else {
238
- void this.handleDataRequest(frame, sock, generation).catch((err) => {
310
+ else if (handle) {
311
+ void this.handleDataRequest(frame, handle, sock, generation).catch((error) => {
239
312
  if (!this.closeStarted && this.sock === sock && this.generation === generation) {
240
- console.warn("SubcProvider handler failed after its request was dispatched", err);
313
+ console.warn("SubcProvider handler failed after its request was dispatched", error);
241
314
  }
242
315
  });
243
316
  }
@@ -246,20 +319,28 @@ export class SubcProvider {
246
319
  return true;
247
320
  }
248
321
  }
249
- /** Abort every in-flight request on a route channel for the current socket generation. */
250
- abortChannel(generation, channel) {
251
- const prefix = `${generation}:${channel}:`;
322
+ abortHandle(handle) {
323
+ const prefix = `${handle.channel}:${handle.epoch}:`;
252
324
  for (const [key, controller] of this.inflight) {
253
325
  if (key.startsWith(prefix))
254
326
  controller.abort();
255
327
  }
328
+ for (const [key, pending] of this.pending) {
329
+ if (!key.startsWith(prefix))
330
+ continue;
331
+ this.pending.delete(key);
332
+ clearTimeout(pending.timer);
333
+ pending.reject(new StaleRouteHandleError(handle));
334
+ }
256
335
  }
257
- abortGeneration(generation) {
258
- const prefix = `${generation}:`;
259
- for (const [key, controller] of this.inflight) {
260
- if (key.startsWith(prefix))
261
- controller.abort();
336
+ abortGeneration(_generation) {
337
+ this.abortAllInflight();
338
+ for (const [key, pending] of this.pending) {
339
+ this.pending.delete(key);
340
+ clearTimeout(pending.timer);
341
+ pending.reject(new SubcProviderError("provider connection dropped", "connection_dropped"));
262
342
  }
343
+ this.liveRoutes.clear();
263
344
  }
264
345
  abortAllInflight() {
265
346
  for (const controller of this.inflight.values())
@@ -268,9 +349,9 @@ export class SubcProvider {
268
349
  async handleControlRequest(frame, sock, generation) {
269
350
  const request = parseJson(frame.body);
270
351
  if (request.op === HEALTH_CHECK_OP) {
271
- void this.handleHealthRequest(frame, sock, generation).catch((err) => {
352
+ void this.handleHealthRequest(frame, sock, generation).catch((error) => {
272
353
  if (!this.closeStarted && this.sock === sock && this.generation === generation) {
273
- console.warn("SubcProvider health handler failed after its request was dispatched", err);
354
+ console.warn("SubcProvider health handler failed after its request was dispatched", error);
274
355
  }
275
356
  });
276
357
  return;
@@ -278,53 +359,88 @@ export class SubcProvider {
278
359
  if (request.op !== "route.bind") {
279
360
  throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
280
361
  }
362
+ const tentative = createRouteHandle(numberField(request.route_channel, "route_channel"), numberField(request.epoch, "epoch"), this.connectionToken);
281
363
  const bindRequest = {
282
- route_channel: numberField(request.route_channel, "route_channel"),
364
+ handle: tentative,
283
365
  target: request.target,
284
366
  identity: request.identity,
285
367
  principal: request.principal,
368
+ consumer_capabilities: request.consumer_capabilities,
286
369
  };
287
- const decision = await this.opts.onBind?.(bindRequest);
370
+ let decision;
371
+ try {
372
+ decision = await this.opts.onBind?.(bindRequest);
373
+ }
374
+ catch (error) {
375
+ try {
376
+ await this.sendError(frame, "route_rejected", error instanceof Error ? error.message : String(error), controlFlags(), sock, generation);
377
+ }
378
+ finally {
379
+ await this.opts.onRouteGone?.(tentative);
380
+ }
381
+ return;
382
+ }
288
383
  const rejection = bindRejection(decision);
289
384
  if (rejection) {
290
- await this.sendError(frame, rejection.code, rejection.message, controlFlags(), sock, generation);
385
+ try {
386
+ await this.sendError(frame, rejection.code, rejection.message, controlFlags(), sock, generation);
387
+ }
388
+ finally {
389
+ await this.opts.onRouteGone?.(tentative);
390
+ }
391
+ return;
392
+ }
393
+ try {
394
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Response, controlFlags(), 0, 0, frame.header.corr, encodeJson({ op: "route.bind" })));
395
+ }
396
+ catch (error) {
397
+ await this.opts.onRouteGone?.(tentative);
398
+ throw error;
399
+ }
400
+ if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr) {
401
+ await this.opts.onRouteGone?.(tentative);
291
402
  return;
292
403
  }
293
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Response, controlFlags(), 0, frame.header.corr, encodeJson({ op: "route.bind" })));
404
+ this.liveRoutes.set(tentative.channel, tentative);
405
+ await this.opts.onBound?.(tentative);
294
406
  }
295
- async handleDataRequest(frame, sock, generation) {
296
- const { channel, corr, ver } = frame.header;
297
- const key = routeKey(generation, channel, corr);
407
+ async handleDataRequest(frame, handle, sock, generation) {
408
+ const { corr, ver } = frame.header;
409
+ const key = routeKey(handle, corr);
298
410
  const controller = new AbortController();
299
411
  this.inflight.set(key, controller);
300
- const dataFlags = buildFlags(false, Priority.Interactive, false);
301
- const ctx = {
412
+ const context = {
413
+ handle,
302
414
  signal: controller.signal,
303
415
  currentEpoch: () => this.connectionEpoch,
304
- emit: async (eventBody) => {
305
- // Once aborted (cancel / route-gone / socket drop), drop further events silently.
416
+ emit: async (eventBody, options = {}) => {
417
+ this.assertLiveHandle(handle);
306
418
  if (controller.signal.aborted)
307
419
  return;
308
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamData, dataFlags, channel, corr, eventBody));
420
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamData, buildFlags(false, options.priority ?? Priority.Interactive, false, options.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, corr, eventBody));
309
421
  },
310
422
  };
311
423
  const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
424
+ const dataFlags = buildFlags(false, Priority.Interactive, false);
312
425
  try {
313
- const body = await this.opts.handler(channel, frame.body, ctx);
426
+ const body = await this.opts.handler(handle, frame.body, context);
427
+ if (controller.signal.aborted)
428
+ return;
429
+ this.assertLiveHandle(handle);
314
430
  if (body === undefined) {
315
- // A streaming handler that ended: close the held-open request with a
316
- // StreamEnd terminal (the consumer's subscription resolves).
317
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, channel, corr, new Uint8Array(0)));
431
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, handle.channel, handle.epoch, corr, new Uint8Array(0)));
318
432
  }
319
433
  else if (body instanceof Uint8Array) {
320
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, channel, corr, body));
434
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, handle.channel, handle.epoch, corr, body));
321
435
  }
322
436
  else {
323
437
  throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
324
438
  }
325
439
  }
326
- catch (err) {
327
- await this.sendError(frame, err instanceof SubcProviderError && err.code ? err.code : "handler_error", err instanceof Error ? err.message : String(err), dataFlags, sock, generation);
440
+ catch (error) {
441
+ if (error instanceof StaleRouteHandleError || controller.signal.aborted)
442
+ return;
443
+ await this.sendError(frame, error instanceof SubcProviderError && error.code ? error.code : "handler_error", error instanceof Error ? error.message : String(error), dataFlags, sock, generation);
328
444
  }
329
445
  finally {
330
446
  releasePermit();
@@ -333,8 +449,8 @@ export class SubcProvider {
333
449
  }
334
450
  }
335
451
  async handleHealthRequest(frame, sock, generation) {
336
- const { channel, corr, ver } = frame.header;
337
- const key = routeKey(generation, channel, corr);
452
+ const { corr, ver } = frame.header;
453
+ const key = `control:${generation}:${corr}`;
338
454
  const controller = new AbortController();
339
455
  this.inflight.set(key, controller);
340
456
  const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
@@ -342,15 +458,15 @@ export class SubcProvider {
342
458
  if (controller.signal.aborted)
343
459
  return;
344
460
  const report = await this.opts.health();
345
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(), channel, corr, encodeJson({
461
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(), 0, 0, corr, encodeJson({
346
462
  op: HEALTH_CHECK_OP,
347
463
  status: report.status,
348
464
  ...(report.detail === undefined ? {} : { detail: report.detail }),
349
465
  ...(report.metrics === undefined ? {} : { metrics: report.metrics }),
350
466
  })));
351
467
  }
352
- catch (err) {
353
- await this.sendError(frame, err instanceof SubcProviderError && err.code ? err.code : "health_error", err instanceof Error ? err.message : String(err), controlFlags(), sock, generation);
468
+ catch (error) {
469
+ await this.sendError(frame, error instanceof SubcProviderError && error.code ? error.code : "health_error", error instanceof Error ? error.message : String(error), controlFlags(), sock, generation);
354
470
  }
355
471
  finally {
356
472
  releasePermit();
@@ -359,7 +475,7 @@ export class SubcProvider {
359
475
  }
360
476
  }
361
477
  async sendError(frame, code, message, flags, sock, generation) {
362
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Error, flags, frame.header.channel, frame.header.corr, encodeJson({ code, message })));
478
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Error, flags, frame.header.channel, frame.header.epoch, frame.header.corr, encodeJson({ code, message })));
363
479
  }
364
480
  async sendOn(sock, generation, frame) {
365
481
  if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr)
@@ -426,6 +542,9 @@ export class SubcProvider {
426
542
  this.storage = opened.ack.storage;
427
543
  this.closedErr = null;
428
544
  this.connectionEpoch += 1;
545
+ this.connectionToken = newConnectionToken();
546
+ this.liveRoutes.clear();
547
+ this.nextCorr = 1n;
429
548
  const generation = this.generation;
430
549
  void this.readLoop(opened.sock, generation);
431
550
  this.scheduleRestored(generation, this.connectionEpoch);
@@ -489,6 +608,24 @@ export class SubcProvider {
489
608
  void this.drainConnectionStateQueue();
490
609
  }
491
610
  }
611
+ isLiveHandle(handle) {
612
+ return belongsToConnection(handle, this.connectionToken) && this.liveRoutes.get(handle.channel) === handle;
613
+ }
614
+ assertLiveHandle(handle) {
615
+ if (!this.isLiveHandle(handle))
616
+ throw new StaleRouteHandleError(handle);
617
+ }
618
+ allocateCorr() {
619
+ const maximum = 0xffffffffffffffffn;
620
+ if (this.nextCorr > maximum) {
621
+ const error = new SubcProviderError("channel-0 correlation id allocator exhausted", "corr_exhausted");
622
+ this.handleUnexpectedDrop(this.sock, this.generation, error);
623
+ throw error;
624
+ }
625
+ const corr = this.nextCorr;
626
+ this.nextCorr += 1n;
627
+ return corr;
628
+ }
492
629
  failFatal(err) {
493
630
  if (!this.closedErr)
494
631
  this.closedErr = err;
@@ -502,8 +639,17 @@ export class SubcProvider {
502
639
  this.resolveClosed();
503
640
  }
504
641
  }
505
- function routeKey(generation, channel, corr) {
506
- return `${generation}:${channel}:${corr}`;
642
+ function routeKey(handle, corr) {
643
+ return `${handle.channel}:${handle.epoch}:${corr}`;
644
+ }
645
+ function providerErrorFromFrame(frame) {
646
+ try {
647
+ const body = parseJson(frame.body);
648
+ return new SubcProviderError(body.message ?? "subc error", body.code);
649
+ }
650
+ catch {
651
+ return new SubcProviderError(Buffer.from(frame.body).toString("utf8") || "subc error");
652
+ }
507
653
  }
508
654
  function launchNonce(opts) {
509
655
  const nonce = opts.launchNonce ?? process.env[SUBC_LAUNCH_NONCE_ENV];
@@ -519,6 +665,7 @@ function normalizeProviderConnectOptions(opts) {
519
665
  handshakeTimeoutMs: opts.handshakeTimeoutMs,
520
666
  controlOps: opts.controlOps,
521
667
  onBind: opts.onBind,
668
+ onBound: opts.onBound,
522
669
  onRouteGone: opts.onRouteGone,
523
670
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
524
671
  sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
@@ -536,7 +683,7 @@ function normalizedControlOps(controlOps) {
536
683
  }
537
684
  function buildHelloFrame(opts) {
538
685
  const nonce = launchNonce(opts);
539
- return buildFrame(FrameType.Hello, controlFlags(), 0, HELLO_CORR, encodeJson({
686
+ return buildFrame(FrameType.Hello, controlFlags(), 0, 0, HELLO_CORR, encodeJson({
540
687
  manifest: normalizeManifest(opts.manifest),
541
688
  protocol_ver: PROTOCOL_VERSION,
542
689
  control_ops: normalizedControlOps(opts.controlOps),
@@ -584,14 +731,17 @@ async function sendFrame(sock, frame) {
584
731
  await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
585
732
  }
586
733
  async function expectHelloAck(sock, deadline) {
587
- const header = decodeHeader(await sock.readExact(HEADER_LEN, deadline));
588
- const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, deadline);
589
- const frame = { header, body };
590
- switch (header.ty) {
591
- case FrameType.HelloAck:
592
- return parseJson(body);
734
+ const frame = await sock.readFrame(deadline, deadline);
735
+ switch (frame.header.ty) {
736
+ case FrameType.HelloAck: {
737
+ const ack = parseJson(frame.body);
738
+ if (ack.negotiated_ver !== PROTOCOL_VERSION) {
739
+ throw new SubcProviderError(`subc negotiated protocol ${ack.negotiated_ver}; expected exactly ${PROTOCOL_VERSION}`, "unsupported_version");
740
+ }
741
+ return ack;
742
+ }
593
743
  case FrameType.Error: {
594
- const error = parseJson(body);
744
+ const error = parseJson(frame.body);
595
745
  throw new SubcProviderError(`subc rejected HELLO: ${error.code ?? "unknown"} — ${error.message ?? "subc error"}`, error.code);
596
746
  }
597
747
  default: