@cortexkit/subc-client 0.8.0 → 0.11.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.
package/dist/client.d.ts CHANGED
@@ -30,15 +30,61 @@ export interface RouteOpenOptions {
30
30
  /** Optional consumer-declared reverse-request capabilities for this route.open. 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". */
31
31
  consumerCapabilities?: string[];
32
32
  }
33
+ export interface CatalogCapabilityRequirement {
34
+ capability: string;
35
+ need: "required" | "optional";
36
+ }
37
+ export interface CatalogCapabilities {
38
+ provides: string[];
39
+ requires: CatalogCapabilityRequirement[];
40
+ must_never_reach: string[];
41
+ }
42
+ /** A self-signal's anchor relative to the external surface it shapes. */
43
+ export type CatalogSignalAnchor = "fixed_interval" | {
44
+ event: {
45
+ event: string;
46
+ };
47
+ };
48
+ /** The source of a self-signal's effective cadence. */
49
+ export type CatalogSignalCadence = {
50
+ literal: {
51
+ interval_ms: number;
52
+ };
53
+ } | {
54
+ derived: {
55
+ source: string;
56
+ };
57
+ };
58
+ /** A self-signal declaration mirrored from a registered module manifest. */
59
+ export interface CatalogSelfSignalDeclaration {
60
+ name: string;
61
+ /** Open string for forward-compatible self-signal kinds. */
62
+ kind: string;
63
+ effect: "observe" | "mutate";
64
+ anchored_to: CatalogSignalAnchor;
65
+ cadence?: CatalogSignalCadence | null;
66
+ domain?: string | null;
67
+ note?: string | null;
68
+ }
33
69
  export interface CatalogEntry {
34
70
  module_id: string;
35
71
  roles: unknown[];
36
72
  control_ops: string[];
73
+ /** Static capability claims mirrored from the registered module manifest. */
74
+ capabilities?: CatalogCapabilities | null;
75
+ /** Self-signal declarations mirrored verbatim from the registered manifest. */
76
+ self_signals?: CatalogSelfSignalDeclaration[] | null;
37
77
  }
38
78
  export interface RequestOptions {
39
79
  priority?: Priority;
40
80
  admissionClass?: AdmissionClass;
41
81
  timeoutMs?: number;
82
+ /**
83
+ * Set when the request body is opaque bytes rather than JSON. This flag is the
84
+ * receiver's only signal about the body representation; reply decoding follows
85
+ * the response frame's flag instead of this request option.
86
+ */
87
+ binary?: boolean;
42
88
  /** Called for each interim PUSH / StreamData frame before the terminal reply. */
43
89
  onProgress?: (body: Uint8Array) => void;
44
90
  }
@@ -106,6 +152,13 @@ export declare class SubcCallError extends Error {
106
152
  readonly code?: string | undefined;
107
153
  readonly cause?: unknown | undefined;
108
154
  constructor(kind: SubcCallErrorKind, message: string, code?: string | undefined, cause?: unknown | undefined);
155
+ /**
156
+ * Machine-parsable payload from the wire `ErrorBody.detail`, surfaced from
157
+ * the underlying Error frame when one caused this failure. Typed refusal
158
+ * reasons (for example certification refusals) ride this field; consumers
159
+ * classify on it and must not have to reach into `cause`.
160
+ */
161
+ get detail(): unknown;
109
162
  }
110
163
  /**
111
164
  * A live subscription to a provider's event stream, riding a single held-open
@@ -122,7 +175,21 @@ export interface Subscription {
122
175
  }
123
176
  export declare class SubcError extends Error {
124
177
  readonly code?: string | undefined;
125
- constructor(message: string, code?: string | undefined);
178
+ /**
179
+ * Machine-parsable payload from the wire `ErrorBody.detail` field, carried
180
+ * verbatim. Providers use it for typed refusal reasons (for example
181
+ * synapse's certification-refusal reasons); dropping it here would strand
182
+ * those reasons at the transport boundary.
183
+ */
184
+ readonly detail?: unknown | undefined;
185
+ constructor(message: string, code?: string | undefined,
186
+ /**
187
+ * Machine-parsable payload from the wire `ErrorBody.detail` field, carried
188
+ * verbatim. Providers use it for typed refusal reasons (for example
189
+ * synapse's certification-refusal reasons); dropping it here would strand
190
+ * those reasons at the transport boundary.
191
+ */
192
+ detail?: unknown | undefined);
126
193
  }
127
194
  export interface ConnectOptions {
128
195
  connectionFile: string;
@@ -135,6 +202,14 @@ export interface ConnectOptions {
135
202
  reconnectBackoff?: ReconnectBackoff;
136
203
  /** Injectable sleep for timer-free reconnect tests. */
137
204
  sleep?: (ms: number) => Promise<void>;
205
+ /**
206
+ * How long managed calls keep retrying route.open in-place on retryable
207
+ * refusals (module_reloading, module_warming, target_unavailable, …) before
208
+ * settling not_sent. This deadline is the ONLY binder on those retries —
209
+ * module reloads legitimately take tens of seconds (the daemon's drain alone
210
+ * defaults to 30s), so the default matches the daemon's drain ceiling.
211
+ */
212
+ routeOpenRetryDeadlineMs?: number;
138
213
  /**
139
214
  * Window the post-deadline liveness probe waits for ANY inbound frame after
140
215
  * sending its channel-0 Ping before convicting the socket as half-open.
@@ -171,6 +246,23 @@ export interface ControlPush {
171
246
  /** The full parsed body, `op` included, for op-specific fields. */
172
247
  body: Record<string, unknown>;
173
248
  }
249
+ /** A known route-close reason, excluding forward-compatible unknown values. */
250
+ export type KnownRouteCloseReason = "reload" | "restart" | "disable" | "crash" | "capability_denied";
251
+ /** A route-close reason decoded from a daemon control push. */
252
+ export type RouteCloseReason = KnownRouteCloseReason | "unknown";
253
+ /** Whether the close reason alone permits reopening a route automatically. */
254
+ export type RouteCloseDisposition = "may_reopen" | "must_not_reopen";
255
+ /**
256
+ * Decode a daemon close reason without rejecting a future wire value.
257
+ *
258
+ * An unknown reason remains observable as `unknown`; callers must use the
259
+ * strict disposition instead of treating an unrecognized policy change as safe.
260
+ */
261
+ export declare function parseRouteCloseReason(reason: unknown): RouteCloseReason;
262
+ /** Unknown close reasons take the strictest action and must not trigger a reopen. */
263
+ export declare function classifyRouteCloseReason(reason: unknown): RouteCloseDisposition;
264
+ /** Decode a close reason from a route lifecycle control push, if present. */
265
+ export declare function routeCloseReason(push: ControlPush): RouteCloseReason | undefined;
174
266
  export declare class SubcClient {
175
267
  private sock;
176
268
  private currentConn;
@@ -193,6 +285,15 @@ export declare class SubcClient {
193
285
  static connect(opts: ConnectOptions): Promise<SubcClient>;
194
286
  /** List modules subc knows about (channel-0 catalog.list). */
195
287
  catalogList(moduleId?: string): Promise<CatalogEntry[]>;
288
+ /**
289
+ * Resolve the sole catalog claimant for a capability.
290
+ *
291
+ * This expresses singular intent even when the fleet permits plural claims;
292
+ * ambiguity is returned instead of selecting an arbitrary module.
293
+ */
294
+ resolveProvider(capability: string): Promise<string>;
295
+ /** Resolve every catalog claimant in deterministic module-id order. */
296
+ resolveProviders(capability: string): Promise<string[]>;
196
297
  /** Open a route and return its connection-bound immutable handle. */
197
298
  routeOpen(target: RouteTarget, identity: BindIdentity, opts?: RouteOpenOptions): Promise<RouteHandle>;
198
299
  /** Send a data-plane request on exactly the supplied route generation. */
@@ -202,6 +303,14 @@ export declare class SubcClient {
202
303
  * reconnecting and re-opening cached routes after connection drops.
203
304
  */
204
305
  call<Response = unknown>(moduleId: string, method: string, params?: unknown, opts?: ManagedCallOptions): Promise<Response>;
306
+ /**
307
+ * Managed route + raw opaque-body request convenience. Unlike call(), this
308
+ * sends bytes without a JSON method envelope because the BINARY flag is the
309
+ * receiver's only signal that the body is not JSON. A BINARY reply resolves
310
+ * to Uint8Array; a non-binary reply is decoded as JSON.
311
+ */
312
+ callBinary(moduleId: string, body: Uint8Array, opts?: ManagedCallOptions): Promise<Uint8Array | unknown>;
313
+ private managedCall;
205
314
  /** Open a held request on exactly one route generation. */
206
315
  subscribe(handle: RouteHandle, body: unknown, onEvent: (event: Uint8Array) => void, opts?: SubscribeOptions): Subscription;
207
316
  /** Send a pure-header cancellation for an in-flight request. */
@@ -257,11 +366,26 @@ export declare class SubcClient {
257
366
  * enough to delay the read loop delays this probe's own timer identically,
258
367
  * and when both wake the buffered Pong dispatches before the check runs.
259
368
  *
369
+ * A PENDING CHANNEL-0 REQUEST SUSPENDS THE PROBE (both at launch and at the
370
+ * conviction check). The daemon's connection loop handles frames one at a
371
+ * time, and some channel-0 ops legally park it for seconds — route.open
372
+ * awaits the module's bind ack inline for up to route_bind_relay_timeout
373
+ * (~12s in production) — during which OUR Ping sits unread in the daemon's
374
+ * socket buffer. On an otherwise-quiet connection that silence is fully
375
+ * explained by our own in-flight control op, and convicting on it would
376
+ * tear down a healthy connection mid-bind (BROCA's Athena panel flagged
377
+ * this exact premise; the FIFO fact is verified in subc-core server.rs
378
+ * connection_loop + control.rs handle_route_open). The client always knows
379
+ * its own channel-0 pendings, so the gate is local and exact: no probe
380
+ * while one is in flight, no conviction if one appeared during the window.
381
+ *
260
382
  * Scope: hooked from the managed-call path only. Control-plane requests
261
383
  * (route.open, catalog.list) are short-lived and their failures already
262
384
  * escalate through reconnect classification; the managed path is where a
263
385
  * plugin lives for hours and where the pin was observed in production.
264
386
  */
387
+ /** Any in-flight channel-0 request (pendings keyed without a route handle). */
388
+ private hasControlPending;
265
389
  private probeLivenessAfterDeadline;
266
390
  private ensureConnectedForManaged;
267
391
  private scheduleReconnectAfterDrop;
@@ -301,6 +425,8 @@ export declare class SubcClient {
301
425
  private allocateCorr;
302
426
  private closeConnectionAfterCleanupFailure;
303
427
  private encode;
428
+ /** Decode a terminal response according to the representation on the wire. */
429
+ private decodeReply;
304
430
  private parseJson;
305
431
  }
306
432
  export declare function isConsumerReconnectTransient(err: unknown): boolean;
@@ -310,12 +436,15 @@ export declare function isConsumerReconnectTransient(err: unknown): boolean;
310
436
  * is booting, mid-reload, transiently absent, or the bind relay timed out. A
311
437
  * daemon-rejected route.open is provably pre-send (no data frame ever left the
312
438
  * client), so these classify as not_sent; the managed path retries them in-place
313
- * within ROUTE_OPEN_RETRY_DEADLINE_MS. Permanent rejections (bad_consumer_identity,
314
- * config_divergence, unknown_target, ...) are excluded — they are pre-send but
315
- * would never succeed, so retrying them would only storm the daemon. Kept
316
- * byte-identical to subc-client-rs is_retryable_route_open_code for cross-client
439
+ * within ROUTE_OPEN_RETRY_DEADLINE_MS. Permanent rejections (module_removed,
440
+ * bad_consumer_identity, config_divergence, unknown_target, ...) are excluded — they are pre-send but
441
+ * would never succeed, so retrying them would only storm the daemon. In
442
+ * particular, capability_forbidden is an explicit non-retryable policy refusal.
443
+ * Kept byte-identical to subc-client-rs is_retryable_route_open_code for cross-client
317
444
  * classification parity.
318
445
  */
319
446
  export declare function isRetryableRouteOpenCode(code: string | undefined): boolean;
320
447
  export declare function connectionFileExists(path: string): Promise<boolean>;
448
+ /** Validate the exact capability-grammar identifier before catalog I/O. */
449
+ export declare function isValidCapabilityIdentifier(identifier: string): boolean;
321
450
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -12,7 +12,7 @@ import { promises as fs } from "node:fs";
12
12
  import { debuglog } from "node:util";
13
13
  import { AuthError, authenticateClient } from "./auth.js";
14
14
  import { ConnectionFileError, readConnectionFile } from "./connection-file.js";
15
- import { AdmissionClass, buildFrame, buildFlags, encodeFrame, FrameType, Priority, } from "./envelope.js";
15
+ import { AdmissionClass, buildFrame, buildFlags, encodeFrame, FrameType, hasBinary, Priority, } from "./envelope.js";
16
16
  import { belongsToConnection, createRouteHandle, newConnectionToken, sameRouteHandle, StaleRouteHandleError, } from "./route-handle.js";
17
17
  import { SocketClosedError, SocketTimeoutError, SocketWriteNotQueuedError, SocketWriteQueuedError, SubcSocket, writeBorrowed, writeTrackedBorrowed, } from "./socket.js";
18
18
  const debug = debuglog("subc-client");
@@ -89,14 +89,75 @@ export class SubcCallError extends Error {
89
89
  this.cause = cause;
90
90
  this.name = "SubcCallError";
91
91
  }
92
+ /**
93
+ * Machine-parsable payload from the wire `ErrorBody.detail`, surfaced from
94
+ * the underlying Error frame when one caused this failure. Typed refusal
95
+ * reasons (for example certification refusals) ride this field; consumers
96
+ * classify on it and must not have to reach into `cause`.
97
+ */
98
+ get detail() {
99
+ return this.cause instanceof SubcError ? this.cause.detail : undefined;
100
+ }
92
101
  }
93
102
  export class SubcError extends Error {
94
103
  code;
95
- constructor(message, code) {
104
+ detail;
105
+ constructor(message, code,
106
+ /**
107
+ * Machine-parsable payload from the wire `ErrorBody.detail` field, carried
108
+ * verbatim. Providers use it for typed refusal reasons (for example
109
+ * synapse's certification-refusal reasons); dropping it here would strand
110
+ * those reasons at the transport boundary.
111
+ */
112
+ detail) {
96
113
  super(message);
97
114
  this.code = code;
115
+ this.detail = detail;
116
+ }
117
+ }
118
+ function requireBinaryBody(body) {
119
+ if (body instanceof Uint8Array)
120
+ return body;
121
+ const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
122
+ throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
123
+ }
124
+ /**
125
+ * Decode a daemon close reason without rejecting a future wire value.
126
+ *
127
+ * An unknown reason remains observable as `unknown`; callers must use the
128
+ * strict disposition instead of treating an unrecognized policy change as safe.
129
+ */
130
+ export function parseRouteCloseReason(reason) {
131
+ switch (reason) {
132
+ case "reload":
133
+ case "restart":
134
+ case "disable":
135
+ case "crash":
136
+ case "capability_denied":
137
+ return reason;
138
+ default:
139
+ return "unknown";
140
+ }
141
+ }
142
+ /** Unknown close reasons take the strictest action and must not trigger a reopen. */
143
+ export function classifyRouteCloseReason(reason) {
144
+ switch (parseRouteCloseReason(reason)) {
145
+ case "reload":
146
+ case "restart":
147
+ return "may_reopen";
148
+ case "disable":
149
+ case "crash":
150
+ case "capability_denied":
151
+ case "unknown":
152
+ return "must_not_reopen";
98
153
  }
99
154
  }
155
+ /** Decode a close reason from a route lifecycle control push, if present. */
156
+ export function routeCloseReason(push) {
157
+ if (push.op !== "route.closing" && push.op !== "route.closed")
158
+ return undefined;
159
+ return parseRouteCloseReason(push.body.reason);
160
+ }
100
161
  export class SubcClient {
101
162
  sock;
102
163
  currentConn;
@@ -139,6 +200,33 @@ export class SubcClient {
139
200
  const parsed = this.parseJson(reply);
140
201
  return parsed.modules ?? [];
141
202
  }
203
+ /**
204
+ * Resolve the sole catalog claimant for a capability.
205
+ *
206
+ * This expresses singular intent even when the fleet permits plural claims;
207
+ * ambiguity is returned instead of selecting an arbitrary module.
208
+ */
209
+ async resolveProvider(capability) {
210
+ const claimants = await this.resolveProviders(capability);
211
+ if (claimants.length === 0) {
212
+ throw new SubcError(`no catalog claimant for capability ${capability}`, "capability_unprovided");
213
+ }
214
+ if (claimants.length > 1) {
215
+ throw new SubcError(`multiple catalog claimants for capability ${capability}: ${claimants.join(", ")}`, "capability_ambiguous");
216
+ }
217
+ return claimants[0];
218
+ }
219
+ /** Resolve every catalog claimant in deterministic module-id order. */
220
+ async resolveProviders(capability) {
221
+ if (!isValidCapabilityIdentifier(capability)) {
222
+ throw new SubcError(`malformed capability identifier ${JSON.stringify(capability)}`, "invalid_capability_identifier");
223
+ }
224
+ const modules = await this.catalogList();
225
+ return modules
226
+ .filter((module) => module.capabilities?.provides.includes(capability) ?? false)
227
+ .map((module) => module.module_id)
228
+ .sort();
229
+ }
142
230
  /** Open a route and return its connection-bound immutable handle. */
143
231
  async routeOpen(target, identity, opts = {}) {
144
232
  const consumerIdentity = routeOpenConsumerIdentity(opts);
@@ -185,23 +273,40 @@ export class SubcClient {
185
273
  /** Send a data-plane request on exactly the supplied route generation. */
186
274
  async request(handle, body, opts = {}) {
187
275
  this.assertLiveHandle(handle);
188
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
276
+ const binary = opts.binary ?? false;
277
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
189
278
  const priority = opts.priority ?? Priority.Interactive;
190
279
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
191
- const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
192
- return this.parseJson(reply);
280
+ const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary);
281
+ return this.decodeReply(reply);
193
282
  }
194
283
  /**
195
284
  * Managed route + request convenience. Opens and caches a route for the module,
196
285
  * reconnecting and re-opening cached routes after connection drops.
197
286
  */
198
287
  async call(moduleId, method, params, opts = {}) {
288
+ if (opts.binary) {
289
+ throw new SubcError("call() builds a JSON body and cannot send a binary request; use callBinary(moduleId, body, opts) with a Uint8Array", "binary_call_requires_call_binary");
290
+ }
199
291
  const body = params === undefined ? { method } : { method, params };
292
+ return (await this.managedCall(moduleId, body, opts));
293
+ }
294
+ /**
295
+ * Managed route + raw opaque-body request convenience. Unlike call(), this
296
+ * sends bytes without a JSON method envelope because the BINARY flag is the
297
+ * receiver's only signal that the body is not JSON. A BINARY reply resolves
298
+ * to Uint8Array; a non-binary reply is decoded as JSON.
299
+ */
300
+ async callBinary(moduleId, body, opts = {}) {
301
+ const bytes = requireBinaryBody(body);
302
+ return this.managedCall(moduleId, bytes, { ...opts, binary: true });
303
+ }
304
+ async managedCall(moduleId, body, opts) {
200
305
  let retriedUnknownChannel = false;
201
306
  for (;;) {
202
307
  const routeHandle = await this.cachedRouteHandle(moduleId, opts);
203
308
  try {
204
- return (await this.managedRequest(routeHandle, body, opts));
309
+ return await this.managedRequest(routeHandle, body, opts);
205
310
  }
206
311
  catch (err) {
207
312
  if (!(err instanceof SubcCallError))
@@ -408,7 +513,7 @@ export class SubcClient {
408
513
  async controlRpc(body, acceptFrame, onLateResponse) {
409
514
  return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
410
515
  }
411
- send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
516
+ send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false) {
412
517
  if (handle)
413
518
  this.assertLiveHandle(handle);
414
519
  if (this.closedErr)
@@ -423,7 +528,7 @@ export class SubcClient {
423
528
  const key = pendingKey(handle, corr);
424
529
  const channel = handle?.channel ?? 0;
425
530
  const epoch = handle?.epoch ?? 0;
426
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
531
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), channel, epoch, corr, body);
427
532
  return new Promise((resolve, reject) => {
428
533
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
429
534
  const pending = {
@@ -479,12 +584,13 @@ export class SubcClient {
479
584
  setImmediate(arbitrate);
480
585
  }
481
586
  async managedRequest(handle, body, opts) {
482
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
587
+ const binary = opts.binary ?? false;
588
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
483
589
  const priority = opts.priority ?? Priority.Interactive;
484
590
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
485
591
  try {
486
- const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
487
- return this.parseJson(reply);
592
+ const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary);
593
+ return this.decodeReply(reply);
488
594
  }
489
595
  catch (error) {
490
596
  if (error instanceof SubcCallError)
@@ -492,7 +598,7 @@ export class SubcClient {
492
598
  throw this.terminalCallError("managed call failed", error);
493
599
  }
494
600
  }
495
- sendManaged(handle, body, priority, admission, timeoutMs, onProgress) {
601
+ sendManaged(handle, body, priority, admission, timeoutMs, onProgress, binary = false) {
496
602
  try {
497
603
  this.assertLiveHandle(handle);
498
604
  }
@@ -510,7 +616,7 @@ export class SubcClient {
510
616
  return Promise.reject(this.notSentCallError("request correlation allocator was exhausted", error));
511
617
  }
512
618
  const key = pendingKey(handle, corr);
513
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, body);
619
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), handle.channel, handle.epoch, corr, body);
514
620
  let handedToSocket = false;
515
621
  const classifyFailure = (error) => {
516
622
  if (!handedToSocket)
@@ -572,7 +678,7 @@ export class SubcClient {
572
678
  return cached.opening;
573
679
  }
574
680
  async openCachedRoute(cached) {
575
- const routeRetryDeadline = Date.now() + ROUTE_OPEN_RETRY_DEADLINE_MS;
681
+ const routeRetryDeadline = Date.now() + this.opts.routeOpenRetryDeadlineMs;
576
682
  let routeRetryDelay = this.opts.reconnectBackoff.baseMs;
577
683
  let routeRetryAttempt = 0;
578
684
  for (;;) {
@@ -611,13 +717,20 @@ export class SubcClient {
611
717
  continue;
612
718
  }
613
719
  if (!this.closeStarted && error instanceof SubcError && isRetryableRouteOpenCode(error.code)) {
720
+ // The deadline is the ONLY binder here. An attempt cap used to share
721
+ // this condition, and because the capped backoff sums to ~3.1s it
722
+ // strictly dominated the 30s deadline — the advertised reload
723
+ // patience was never delivered, so every module restart whose reload
724
+ // exceeded ~3s failed managed callers with module_reloading. Module
725
+ // reloads legitimately take tens of seconds (drain alone defaults to
726
+ // 30s); the backoff cap below bounds retry pressure instead.
614
727
  routeRetryAttempt += 1;
615
- if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
728
+ if (Date.now() < routeRetryDeadline) {
616
729
  await this.opts.sleep(routeRetryDelay);
617
730
  routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
618
731
  continue;
619
732
  }
620
- throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry budget exhausted)`, error);
733
+ throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry deadline exhausted after ${routeRetryAttempt} attempts)`, error);
621
734
  }
622
735
  throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error);
623
736
  }
@@ -645,14 +758,37 @@ export class SubcClient {
645
758
  * enough to delay the read loop delays this probe's own timer identically,
646
759
  * and when both wake the buffered Pong dispatches before the check runs.
647
760
  *
761
+ * A PENDING CHANNEL-0 REQUEST SUSPENDS THE PROBE (both at launch and at the
762
+ * conviction check). The daemon's connection loop handles frames one at a
763
+ * time, and some channel-0 ops legally park it for seconds — route.open
764
+ * awaits the module's bind ack inline for up to route_bind_relay_timeout
765
+ * (~12s in production) — during which OUR Ping sits unread in the daemon's
766
+ * socket buffer. On an otherwise-quiet connection that silence is fully
767
+ * explained by our own in-flight control op, and convicting on it would
768
+ * tear down a healthy connection mid-bind (BROCA's Athena panel flagged
769
+ * this exact premise; the FIFO fact is verified in subc-core server.rs
770
+ * connection_loop + control.rs handle_route_open). The client always knows
771
+ * its own channel-0 pendings, so the gate is local and exact: no probe
772
+ * while one is in flight, no conviction if one appeared during the window.
773
+ *
648
774
  * Scope: hooked from the managed-call path only. Control-plane requests
649
775
  * (route.open, catalog.list) are short-lived and their failures already
650
776
  * escalate through reconnect classification; the managed path is where a
651
777
  * plugin lives for hours and where the pin was observed in production.
652
778
  */
779
+ /** Any in-flight channel-0 request (pendings keyed without a route handle). */
780
+ hasControlPending() {
781
+ for (const pending of this.pending.values()) {
782
+ if (pending.handle === null)
783
+ return true;
784
+ }
785
+ return false;
786
+ }
653
787
  probeLivenessAfterDeadline() {
654
788
  if (this.livenessProbe || this.closeStarted || this.closedErr)
655
789
  return;
790
+ if (this.hasControlPending())
791
+ return; // silence would be self-explained; re-armed by the next settle
656
792
  const sock = this.sock;
657
793
  const generation = this.generation;
658
794
  let corr;
@@ -674,6 +810,10 @@ export class SubcClient {
674
810
  return;
675
811
  if (this.lastInboundAtMs >= t0)
676
812
  return; // link proven — keep the socket
813
+ // A control op that started during the window explains the silence: the
814
+ // daemon's read loop may be parked in its inline handler, not dead.
815
+ if (this.hasControlPending())
816
+ return;
677
817
  this.fail(new SocketClosedError(`liveness probe convicted a half-open socket: no inbound frame for ${this.opts.livenessProbeWindowMs}ms after a channel-0 Ping (deadline-no-drop settles preceded this); closing so the next call reconnects`));
678
818
  sock.close();
679
819
  })().finally(() => {
@@ -814,6 +954,13 @@ export class SubcClient {
814
954
  // post-deadline liveness probe reads this stamp. Stamped after readLoop's
815
955
  // generation gate, so a stale socket's late frames never vouch for the
816
956
  // live one.
957
+ //
958
+ // PLACEMENT IS LOAD-BEARING: every inbound frame passes this one point
959
+ // before demux, which is the whole reason a single stamp works. A future
960
+ // fast path or drain-and-dispatch refactor that routes frames around
961
+ // dispatch() makes the stamp skippable, and the liveness watermark
962
+ // quietly stops meaning "the link delivered bytes" -- the cheapest
963
+ // correctness property here is also the easiest to lose in a refactor.
817
964
  this.lastInboundAtMs = Date.now();
818
965
  if (frame.header.channel === 0 && frame.header.ty === FrameType.Push) {
819
966
  // Daemon-originated control push (route.closing / route.closed / future
@@ -949,7 +1096,7 @@ export class SubcClient {
949
1096
  errorFromFrame(frame) {
950
1097
  try {
951
1098
  const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8"));
952
- return new SubcError(parsed.message ?? "subc error", parsed.code);
1099
+ return new SubcError(parsed.message ?? "subc error", parsed.code, parsed.detail);
953
1100
  }
954
1101
  catch {
955
1102
  return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
@@ -1034,6 +1181,10 @@ export class SubcClient {
1034
1181
  encode(value) {
1035
1182
  return Buffer.from(JSON.stringify(value), "utf8");
1036
1183
  }
1184
+ /** Decode a terminal response according to the representation on the wire. */
1185
+ decodeReply(frame) {
1186
+ return hasBinary(frame.header.flags) ? frame.body : this.parseJson(frame);
1187
+ }
1037
1188
  parseJson(frame) {
1038
1189
  const b = frame.body;
1039
1190
  return JSON.parse(Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString("utf8"));
@@ -1068,13 +1219,17 @@ export function isConsumerReconnectTransient(err) {
1068
1219
  * is booting, mid-reload, transiently absent, or the bind relay timed out. A
1069
1220
  * daemon-rejected route.open is provably pre-send (no data frame ever left the
1070
1221
  * client), so these classify as not_sent; the managed path retries them in-place
1071
- * within ROUTE_OPEN_RETRY_DEADLINE_MS. Permanent rejections (bad_consumer_identity,
1072
- * config_divergence, unknown_target, ...) are excluded — they are pre-send but
1073
- * would never succeed, so retrying them would only storm the daemon. Kept
1074
- * byte-identical to subc-client-rs is_retryable_route_open_code for cross-client
1222
+ * within ROUTE_OPEN_RETRY_DEADLINE_MS. Permanent rejections (module_removed,
1223
+ * bad_consumer_identity, config_divergence, unknown_target, ...) are excluded — they are pre-send but
1224
+ * would never succeed, so retrying them would only storm the daemon. In
1225
+ * particular, capability_forbidden is an explicit non-retryable policy refusal.
1226
+ * Kept byte-identical to subc-client-rs is_retryable_route_open_code for cross-client
1075
1227
  * classification parity.
1076
1228
  */
1077
1229
  export function isRetryableRouteOpenCode(code) {
1230
+ // A capability deny is policy, never a transient target-availability failure.
1231
+ if (code === "capability_forbidden")
1232
+ return false;
1078
1233
  return (code === "unknown_module" ||
1079
1234
  code === "module_reloading" ||
1080
1235
  code === "module_warming" ||
@@ -1098,11 +1253,32 @@ function normalizeConnectOptions(opts) {
1098
1253
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
1099
1254
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
1100
1255
  sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1256
+ routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
1101
1257
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
1102
1258
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
1103
1259
  onControlPush: opts.onControlPush,
1104
1260
  };
1105
1261
  }
1262
+ /** Validate the exact capability-grammar identifier before catalog I/O. */
1263
+ export function isValidCapabilityIdentifier(identifier) {
1264
+ if (/\s/u.test(identifier))
1265
+ return false;
1266
+ const separator = identifier.indexOf("/v");
1267
+ if (separator <= 0 || identifier.indexOf("/v", separator + 1) !== -1)
1268
+ return false;
1269
+ const name = identifier.slice(0, separator);
1270
+ const version = identifier.slice(separator + 2);
1271
+ if (new TextEncoder().encode(name).byteLength > 64 || version.length === 0)
1272
+ return false;
1273
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/u.test(name) && !/^[a-z]$/u.test(name))
1274
+ return false;
1275
+ if (name.includes("--"))
1276
+ return false;
1277
+ if (!/^\d+$/u.test(version) || (version.length > 1 && version.startsWith("0")))
1278
+ return false;
1279
+ const numericVersion = Number(version);
1280
+ return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 0xffff_ffff;
1281
+ }
1106
1282
  function routeCacheKey(target, identity, consumerIdentity) {
1107
1283
  const consumerPart = consumerIdentity
1108
1284
  ? `${consumerIdentity.module_id}\0${consumerIdentity.launch_nonce}`