borgmcp-server 0.1.1 → 0.1.5

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 (61) hide show
  1. package/README.md +70 -22
  2. package/THIRD_PARTY_NOTICES.md +1 -0
  3. package/dist/cli.js +62 -11
  4. package/dist/cli.js.map +1 -1
  5. package/dist/coordination-api.d.ts +3 -1
  6. package/dist/coordination-api.js +476 -78
  7. package/dist/coordination-api.js.map +1 -1
  8. package/dist/credentials.d.ts +13 -7
  9. package/dist/credentials.js +77 -15
  10. package/dist/credentials.js.map +1 -1
  11. package/dist/debug-log.d.ts +76 -0
  12. package/dist/debug-log.js +124 -0
  13. package/dist/debug-log.js.map +1 -0
  14. package/dist/enrollment.d.ts +3 -11
  15. package/dist/enrollment.js +21 -60
  16. package/dist/enrollment.js.map +1 -1
  17. package/dist/https-server.d.ts +5 -20
  18. package/dist/https-server.js +108 -50
  19. package/dist/https-server.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/message-taxonomy.d.ts +38 -0
  22. package/dist/message-taxonomy.js +218 -0
  23. package/dist/message-taxonomy.js.map +1 -0
  24. package/dist/migrations.d.ts +4 -0
  25. package/dist/migrations.js +99 -0
  26. package/dist/migrations.js.map +1 -1
  27. package/dist/operator-error.d.ts +2 -1
  28. package/dist/operator-error.js +23 -5
  29. package/dist/operator-error.js.map +1 -1
  30. package/dist/role-section.d.ts +14 -0
  31. package/dist/role-section.js +87 -0
  32. package/dist/role-section.js.map +1 -0
  33. package/dist/service.d.ts +14 -4
  34. package/dist/service.js +243 -25
  35. package/dist/service.js.map +1 -1
  36. package/dist/start-options.d.ts +5 -1
  37. package/dist/start-options.js +17 -3
  38. package/dist/start-options.js.map +1 -1
  39. package/dist/store.d.ts +106 -8
  40. package/dist/store.js +772 -120
  41. package/dist/store.js.map +1 -1
  42. package/npm-shrinkwrap.json +6 -7
  43. package/package.json +2 -2
  44. package/src/cli.ts +61 -11
  45. package/src/coordination-api.ts +490 -72
  46. package/src/credentials.ts +103 -19
  47. package/src/debug-log.ts +165 -0
  48. package/src/enrollment.ts +32 -78
  49. package/src/https-server.ts +113 -78
  50. package/src/index.ts +1 -1
  51. package/src/message-taxonomy.ts +284 -0
  52. package/src/migrations.ts +102 -0
  53. package/src/operator-error.ts +40 -6
  54. package/src/role-section.ts +108 -0
  55. package/src/service.ts +268 -27
  56. package/src/start-options.ts +21 -4
  57. package/src/store.ts +887 -142
  58. package/dist/protocol-draft.d.ts +0 -2
  59. package/dist/protocol-draft.js +0 -31
  60. package/dist/protocol-draft.js.map +0 -1
  61. package/src/protocol-draft.ts +0 -32
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-log.js","sourceRoot":"","sources":["../src/debug-log.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,8 @@
1
+ import { type EnrollmentExchangeRequest, type ProtocolEnvelope } from "borgmcp-shared/protocol";
1
2
  import type { CredentialAuthority } from "./credentials.js";
2
- interface EnrollmentEnvelope {
3
- readonly protocol_version: "1";
4
- readonly request_id: string;
5
- readonly payload: {
6
- readonly invitation: string;
7
- readonly retry_key: string;
8
- readonly client_credential: string;
9
- readonly client_name?: string;
10
- };
11
- }
3
+ type EnrollmentEnvelope = ProtocolEnvelope<EnrollmentExchangeRequest>;
12
4
  export declare function createEnrollmentExchange(authority: CredentialAuthority): (body: unknown) => Promise<{
13
- readonly status: 201 | 400 | 401 | 507;
5
+ readonly status: 201 | 400 | 401 | 426 | 507;
14
6
  readonly body?: unknown;
15
7
  }>;
16
8
  export declare function decodeEnrollmentEnvelope(value: unknown): EnrollmentEnvelope;
@@ -1,3 +1,4 @@
1
+ import { ErrorCode, PROTOCOL_VERSION, ProtocolContractError, createProtocolEnvelope, decodeEnrollmentExchangeRequestEnvelope, } from "borgmcp-shared/protocol";
1
2
  import { StorageCapacityError } from "./store.js";
2
3
  export function createEnrollmentExchange(authority) {
3
4
  return async (body) => {
@@ -5,7 +6,14 @@ export function createEnrollmentExchange(authority) {
5
6
  try {
6
7
  envelope = decodeEnrollmentEnvelope(body);
7
8
  }
8
- catch {
9
+ catch (error) {
10
+ if (error instanceof ProtocolContractError &&
11
+ error.code === ErrorCode.UNSUPPORTED_PROTOCOL_VERSION) {
12
+ return {
13
+ status: 426,
14
+ body: errorEnvelope(ErrorCode.UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version.", safeRequestId(body)),
15
+ };
16
+ }
9
17
  return {
10
18
  status: 400,
11
19
  body: errorEnvelope("INVALID_INPUT", "Invalid enrollment request.", safeRequestId(body)),
@@ -38,21 +46,17 @@ export function createEnrollmentExchange(authority) {
38
46
  }
39
47
  return {
40
48
  status: 201,
41
- body: {
42
- protocol_version: "1",
43
- request_id: envelope.request_id,
44
- payload: {
45
- purpose: response.purpose,
46
- client_id: response.clientId,
47
- server_capabilities: response.serverCapabilities,
48
- },
49
- },
49
+ body: createProtocolEnvelope(envelope.request_id, {
50
+ purpose: response.purpose,
51
+ client_id: response.clientId,
52
+ server_capabilities: response.serverCapabilities,
53
+ }),
50
54
  };
51
55
  };
52
56
  }
53
57
  function errorEnvelope(code, message, requestId) {
54
58
  return {
55
- protocol_version: "1",
59
+ protocol_version: PROTOCOL_VERSION,
56
60
  ...(requestId === undefined ? {} : { request_id: requestId }),
57
61
  error: { code, message },
58
62
  };
@@ -66,57 +70,14 @@ function safeRequestId(value) {
66
70
  : undefined;
67
71
  }
68
72
  export function decodeEnrollmentEnvelope(value) {
69
- const envelope = exactRecord(value, ["protocol_version", "request_id", "payload"]);
70
- if (envelope["protocol_version"] !== "1")
71
- throw new Error("Invalid enrollment request.");
72
- const requestId = envelope["request_id"];
73
- if (typeof requestId !== "string" || !/^[A-Za-z0-9._-]{8,128}$/u.test(requestId)) {
74
- throw new Error("Invalid enrollment request.");
75
- }
76
- const payload = exactRecord(envelope["payload"], ["invitation", "retry_key", "client_credential"], ["client_name"]);
77
- const invitation = payload["invitation"];
78
- if (typeof invitation !== "string" || !/^[A-Za-z0-9_-]{43,1024}$/u.test(invitation)) {
79
- throw new Error("Invalid enrollment request.");
80
- }
81
- const retryKey = payload["retry_key"];
82
- if (typeof retryKey !== "string" ||
83
- !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(retryKey)) {
84
- throw new Error("Invalid enrollment request.");
85
- }
86
- const clientCredential = payload["client_credential"];
87
- if (typeof clientCredential !== "string" ||
88
- !/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/u.test(clientCredential)) {
89
- throw new Error("Invalid enrollment request.");
90
- }
91
- const clientName = payload["client_name"];
92
- if (clientName !== undefined &&
93
- (typeof clientName !== "string" || Buffer.byteLength(clientName) > 120 ||
94
- !/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/u.test(clientName))) {
95
- throw new Error("Invalid enrollment request.");
96
- }
97
- return {
98
- protocol_version: "1",
99
- request_id: requestId,
100
- payload: clientName === undefined
101
- ? { invitation, retry_key: retryKey.toLowerCase(), client_credential: clientCredential }
102
- : {
103
- invitation,
104
- retry_key: retryKey.toLowerCase(),
105
- client_credential: clientCredential,
106
- client_name: clientName,
107
- },
108
- };
109
- }
110
- function exactRecord(value, required, optional = []) {
111
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
112
- throw new Error("Invalid enrollment request.");
73
+ try {
74
+ return decodeEnrollmentExchangeRequestEnvelope(value);
113
75
  }
114
- const record = value;
115
- const allowed = new Set([...required, ...optional]);
116
- if (Object.keys(record).some((key) => !allowed.has(key)) ||
117
- required.some((key) => !Object.hasOwn(record, key))) {
76
+ catch (error) {
77
+ if (error instanceof ProtocolContractError &&
78
+ error.code === ErrorCode.UNSUPPORTED_PROTOCOL_VERSION)
79
+ throw error;
118
80
  throw new Error("Invalid enrollment request.");
119
81
  }
120
- return record;
121
82
  }
122
83
  //# sourceMappingURL=enrollment.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../src/enrollment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../src/enrollment.ts"],"names":[],"mappings":"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"}
@@ -2,20 +2,7 @@ import type { Socket } from "node:net";
2
2
  import { type BindOptionsInput } from "./network-policy.js";
3
3
  import type { CoordinationRequest, CoordinationResponse } from "./coordination-api.js";
4
4
  import type { Principal } from "./principal.js";
5
- export interface ProtocolInfoDocument {
6
- readonly protocol_version: string;
7
- readonly package: {
8
- readonly name: "borgmcp-shared";
9
- readonly version: string;
10
- };
11
- readonly capabilities: readonly string[];
12
- readonly limits: {
13
- readonly max_request_bytes: number;
14
- readonly max_log_message_bytes: number;
15
- readonly max_read_page_size: number;
16
- readonly max_replay_page_size: number;
17
- };
18
- }
5
+ import { type DebugLogger } from "./debug-log.js";
19
6
  export interface ServiceLimits {
20
7
  readonly maxConnections: number;
21
8
  readonly maxConnectionsPerAddress: number;
@@ -36,14 +23,13 @@ export interface ServiceLimits {
36
23
  }
37
24
  export declare const DEFAULT_SERVICE_LIMITS: ServiceLimits;
38
25
  export interface RequestHandlerContext {
39
- readonly protocolInfo: ProtocolInfoDocument;
40
- readonly authorizeProtocol: (authorization: string | undefined, signal: AbortSignal) => Promise<boolean | "missing" | "invalid" | "revoked">;
41
26
  readonly exchangeEnrollment?: (body: unknown) => Promise<{
42
- readonly status: 201 | 400 | 401 | 507;
27
+ readonly status: 201 | 400 | 401 | 426 | 507;
43
28
  readonly body?: unknown;
44
29
  }>;
45
- readonly authorizeCoordination?: (authorization: string | undefined, signal: AbortSignal) => Promise<Principal | "missing" | "invalid" | "revoked">;
30
+ readonly authorizeCoordination?: (authorization: string | undefined, signal: AbortSignal) => Promise<Principal | "missing" | "invalid" | "revoked" | "evicted">;
46
31
  readonly handleCoordination?: (request: CoordinationRequest) => Promise<CoordinationResponse>;
32
+ readonly debugLogger: DebugLogger;
47
33
  }
48
34
  export interface HttpsServerOptions {
49
35
  readonly bind?: BindOptionsInput;
@@ -52,12 +38,11 @@ export interface HttpsServerOptions {
52
38
  readonly cert: string | Buffer;
53
39
  readonly ca?: string | Buffer;
54
40
  };
55
- readonly protocolInfo: ProtocolInfoDocument;
56
- readonly authorizeProtocol: RequestHandlerContext["authorizeProtocol"];
57
41
  readonly exchangeEnrollment?: RequestHandlerContext["exchangeEnrollment"];
58
42
  readonly authorizeCoordination?: RequestHandlerContext["authorizeCoordination"];
59
43
  readonly handleCoordination?: RequestHandlerContext["handleCoordination"];
60
44
  readonly limits?: ServiceLimits;
45
+ readonly debugLogger?: DebugLogger;
61
46
  readonly testHooks?: {
62
47
  readonly identifyRemoteAddress?: (socket: Socket) => string;
63
48
  };
@@ -1,6 +1,8 @@
1
1
  import { createServer } from "node:https";
2
2
  import { createHash, X509Certificate } from "node:crypto";
3
+ import { ATTACH_PATH, CUBES_PATH, ENROLLMENT_EXCHANGE_PATH, ErrorCode, HEALTH_PATH, PROTOCOL_INFO_PATH, PROTOCOL_VERSION, createProtocolTagPreflight, } from "borgmcp-shared/protocol";
3
4
  import { resolveBindOptions } from "./network-policy.js";
5
+ import { disabledDebugLogger } from "./debug-log.js";
4
6
  export const DEFAULT_SERVICE_LIMITS = {
5
7
  maxConnections: 100,
6
8
  maxConnectionsPerAddress: 25,
@@ -40,8 +42,14 @@ export async function startHttpsServer(options) {
40
42
  }, createRequestListener(handlerContext, limits, options.testHooks?.identifyRemoteAddress));
41
43
  const acceptedSockets = applyServerLimits(server, limits);
42
44
  server.on("secureConnection", (socket) => socket.disableRenegotiation());
43
- server.on("tlsClientError", (_error, socket) => socket.destroy());
44
- server.on("clientError", (_error, socket) => socket.end("HTTP/1.1 400 Bad Request\r\n\r\n"));
45
+ server.on("tlsClientError", (_error, socket) => {
46
+ handlerContext.debugLogger.emit({ event: "transport_rejection", reason: "tls_client_error" });
47
+ socket.destroy();
48
+ });
49
+ server.on("clientError", (_error, socket) => {
50
+ handlerContext.debugLogger.emit({ event: "transport_rejection", reason: "http_parser_error" });
51
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
52
+ });
45
53
  server.on("checkContinue", (_request, response) => sendEmpty(response, 417, true));
46
54
  try {
47
55
  await listen(server, bind.port, bind.host);
@@ -70,8 +78,6 @@ export async function startHttpsServer(options) {
70
78
  }
71
79
  export function createRequestHandlerContext(options) {
72
80
  return Object.freeze({
73
- protocolInfo: options.protocolInfo,
74
- authorizeProtocol: options.authorizeProtocol,
75
81
  ...(options.exchangeEnrollment === undefined
76
82
  ? {}
77
83
  : { exchangeEnrollment: options.exchangeEnrollment }),
@@ -81,6 +87,7 @@ export function createRequestHandlerContext(options) {
81
87
  ...(options.handleCoordination === undefined
82
88
  ? {}
83
89
  : { handleCoordination: options.handleCoordination }),
90
+ debugLogger: options.debugLogger ?? disabledDebugLogger,
84
91
  });
85
92
  }
86
93
  function createRequestListener(context, limits, identifyRemoteAddress = (socket) => socket.remoteAddress ?? "unknown") {
@@ -88,6 +95,33 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
88
95
  const credentialRateLimiter = new RequestRateLimiter(limits, limits.maxRequestsPerWindow);
89
96
  const streamQuota = new ConcurrentQuota(limits.maxStreamsPerCredential);
90
97
  return (request, response) => {
98
+ const startedAt = Date.now();
99
+ const trace = {
100
+ route: debugRoute(request.url),
101
+ method: debugMethod(request.method),
102
+ authentication: "not_required",
103
+ };
104
+ let debugEmitted = false;
105
+ const emitDebug = () => {
106
+ if (debugEmitted)
107
+ return;
108
+ debugEmitted = true;
109
+ const status = response.headersSent ? response.statusCode : 0;
110
+ context.debugLogger.emit({
111
+ event: "request",
112
+ route: trace.route,
113
+ method: trace.method,
114
+ authentication: trace.authentication,
115
+ authorization: trace.authentication === "accepted"
116
+ ? status === 403 || status === 404 ? "denied_or_not_found" : "accepted"
117
+ : "not_checked",
118
+ ...(trace.principal === undefined ? {} : { principal: trace.principal }),
119
+ status,
120
+ durationMs: Math.max(0, Date.now() - startedAt),
121
+ });
122
+ };
123
+ response.once("finish", emitDebug);
124
+ response.once("close", emitDebug);
91
125
  const controller = new AbortController();
92
126
  response.once("close", () => controller.abort());
93
127
  let timer;
@@ -98,7 +132,7 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
98
132
  }, limits.handlerTimeoutMs);
99
133
  timer.unref();
100
134
  });
101
- const handled = handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, controller.signal)
135
+ const handled = handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, controller.signal, trace)
102
136
  .then(() => "handled");
103
137
  void Promise.race([handled, deadline])
104
138
  .then((outcome) => {
@@ -112,7 +146,7 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
112
146
  });
113
147
  };
114
148
  }
115
- async function handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, signal) {
149
+ async function handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, signal, trace) {
116
150
  const addressIdentity = `address:${identifyRemoteAddress(request.socket)}`;
117
151
  const preAuthRetry = admissionLimiter.consume(addressIdentity);
118
152
  if (preAuthRetry !== null) {
@@ -136,42 +170,23 @@ async function handleRequest(request, response, context, limits, admissionLimite
136
170
  }
137
171
  return;
138
172
  }
139
- if (path === "/healthz") {
173
+ if (path === HEALTH_PATH) {
140
174
  if (requestBody.length !== 0)
141
175
  return sendEmpty(response, 400, true);
142
176
  sendEmpty(response, request.method === "GET" ? 204 : 405);
143
177
  return;
144
178
  }
145
- if (path === "/api/protocol") {
179
+ if (path === PROTOCOL_INFO_PATH) {
146
180
  if (requestBody.length !== 0)
147
181
  return sendEmpty(response, 400, true);
148
- const authorized = await context.authorizeProtocol(request.headers.authorization, signal);
149
- if (signal.aborted)
150
- return;
151
- if (authorized !== true) {
152
- const code = authorized === "revoked"
153
- ? "SESSION_REVOKED"
154
- : authorized === "missing" || request.headers.authorization === undefined
155
- ? "AUTH_MISSING"
156
- : "AUTH_INVALID";
157
- sendJson(response, 401, protocolError(code, "Authentication failed."));
158
- return;
159
- }
160
- const credentialRetry = consumeAuthenticatedRateLimit(request.headers.authorization, credentialRateLimiter);
161
- if (credentialRetry !== null)
162
- return sendRateLimited(response, credentialRetry);
163
182
  if (request.method !== "GET") {
164
183
  sendEmpty(response, 405);
165
184
  return;
166
185
  }
167
- sendJson(response, 200, {
168
- protocol_version: "1",
169
- request_id: "protocol-info",
170
- payload: context.protocolInfo,
171
- });
186
+ sendJson(response, 200, createProtocolTagPreflight());
172
187
  return;
173
188
  }
174
- if (path === "/api/enrollment/exchange") {
189
+ if (path === ENROLLMENT_EXCHANGE_PATH) {
175
190
  if (request.method !== "POST" || context.exchangeEnrollment === undefined) {
176
191
  sendEmpty(response, 405);
177
192
  return;
@@ -195,8 +210,9 @@ async function handleRequest(request, response, context, limits, admissionLimite
195
210
  sendEmpty(response, result.status, result.status === 400);
196
211
  else if (result.status === 400)
197
212
  sendJson(response, 400, result.body, true);
198
- else if (result.status === 401 || result.status === 507)
213
+ else if (result.status === 401 || result.status === 426 || result.status === 507) {
199
214
  sendJson(response, result.status, result.body);
215
+ }
200
216
  else
201
217
  sendJson(response, 201, result.body);
202
218
  return;
@@ -221,23 +237,29 @@ async function handleRequest(request, response, context, limits, admissionLimite
221
237
  return;
222
238
  }
223
239
  const authentication = await authorizeCoordination(authorization, signal);
240
+ trace.authentication = typeof authentication === "string" ? authentication : "accepted";
224
241
  if (signal.aborted)
225
242
  return;
226
243
  if (typeof authentication === "string") {
244
+ if (authentication === "evicted") {
245
+ sendJson(response, 410, protocolError(ErrorCode.DRONE_EVICTED, "Authentication failed."));
246
+ return;
247
+ }
227
248
  const code = authentication === "revoked"
228
249
  ? "SESSION_REVOKED"
229
250
  : authentication === "missing" || authorization === undefined ? "AUTH_MISSING" : "AUTH_INVALID";
230
251
  sendJson(response, 401, protocolError(code, "Authentication failed."));
231
252
  return;
232
253
  }
254
+ trace.principal = authentication;
233
255
  const clientIdentity = `client:${authentication.kind === "drone-session"
234
256
  ? authentication.clientId
235
257
  : authentication.id}`;
236
258
  const credentialRetry = credentialRateLimiter.consume(clientIdentity);
237
259
  if (credentialRetry !== null)
238
260
  return sendRateLimited(response, credentialRetry);
239
- const cursor = parseCursorParameter(request.url, path);
240
- if (cursor === INVALID_COORDINATION_QUERY) {
261
+ const query = parseCoordinationQuery(request.url, path);
262
+ if (query === INVALID_COORDINATION_QUERY) {
241
263
  sendJson(response, 400, protocolError("INVALID_INPUT", "Invalid query parameters."), true);
242
264
  return;
243
265
  }
@@ -246,7 +268,7 @@ async function handleRequest(request, response, context, limits, admissionLimite
246
268
  path,
247
269
  principal: authentication,
248
270
  ...(decoded === undefined ? {} : { body: decoded }),
249
- ...(cursor === undefined ? {} : { cursor }),
271
+ ...query,
250
272
  signal,
251
273
  });
252
274
  if (signal.aborted) {
@@ -273,6 +295,46 @@ async function handleRequest(request, response, context, limits, admissionLimite
273
295
  }
274
296
  sendEmpty(response, 404);
275
297
  }
298
+ function debugMethod(method) {
299
+ return method === "GET" || method === "POST" || method === "PUT" ||
300
+ method === "PATCH" || method === "DELETE" ? method : "OTHER";
301
+ }
302
+ function debugRoute(rawUrl) {
303
+ const path = parseRequestPath(rawUrl);
304
+ if (path === null)
305
+ return "unknown";
306
+ if (path === HEALTH_PATH)
307
+ return "health";
308
+ if (path === PROTOCOL_INFO_PATH)
309
+ return "protocol";
310
+ if (path === ENROLLMENT_EXCHANGE_PATH)
311
+ return "enrollment_exchange";
312
+ if (path === ATTACH_PATH)
313
+ return "client_attach";
314
+ if (path === "/api/cubes")
315
+ return "cubes";
316
+ if (/^\/api\/cubes\/[0-9a-f-]{36}$/iu.test(path))
317
+ return "cube";
318
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles$/iu.test(path))
319
+ return "cube_roles";
320
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles\/[0-9a-f-]{36}$/iu.test(path))
321
+ return "cube_role";
322
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles\/[0-9a-f-]{36}\/section-patch$/iu.test(path))
323
+ return "cube_role_section_patch";
324
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/taxonomy-patch$/iu.test(path))
325
+ return "cube_taxonomy_patch";
326
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/drones$/iu.test(path))
327
+ return "cube_drones";
328
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/logs$/iu.test(path))
329
+ return "cube_logs";
330
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/acks$/iu.test(path))
331
+ return "cube_acks";
332
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/decisions$/iu.test(path))
333
+ return "cube_decisions";
334
+ if (/^\/api\/cubes\/[0-9a-f-]{36}\/stream$/iu.test(path))
335
+ return "cube_stream";
336
+ return "unknown";
337
+ }
276
338
  async function closeRejectedStream(stream) {
277
339
  const iterator = stream[Symbol.asyncIterator]();
278
340
  try {
@@ -402,33 +464,33 @@ function waitForDrain(response) {
402
464
  });
403
465
  }
404
466
  function protocolError(code, message) {
405
- return { protocol_version: "1", error: { code, message } };
467
+ return { protocol_version: PROTOCOL_VERSION, error: { code, message } };
406
468
  }
407
469
  const INVALID_COORDINATION_QUERY = Symbol("invalid-coordination-query");
408
- function parseCursorParameter(value, path) {
470
+ function parseCoordinationQuery(value, path) {
409
471
  if (value === undefined)
410
- return undefined;
472
+ return {};
411
473
  try {
412
474
  const parsed = new URL(value, "https://local.invalid");
413
475
  const keys = [...parsed.searchParams.keys()];
414
- if (!path.endsWith("/stream")) {
415
- return keys.length === 0 ? undefined : INVALID_COORDINATION_QUERY;
416
- }
417
- if (keys.some((key) => key !== "cursor"))
476
+ const allowed = path.endsWith("/stream") ? "cursor" : path.endsWith("/drones") ? "since" : null;
477
+ if (allowed === null)
478
+ return keys.length === 0 ? {} : INVALID_COORDINATION_QUERY;
479
+ if (keys.some((key) => key !== allowed))
418
480
  return INVALID_COORDINATION_QUERY;
419
- const values = parsed.searchParams.getAll("cursor");
481
+ const values = parsed.searchParams.getAll(allowed);
420
482
  if (values.length === 0)
421
- return undefined;
422
- return values.length === 1 && values[0].length > 0
423
- ? values[0]
424
- : INVALID_COORDINATION_QUERY;
483
+ return {};
484
+ if (values.length !== 1 || values[0].length === 0)
485
+ return INVALID_COORDINATION_QUERY;
486
+ return allowed === "cursor" ? { cursor: values[0] } : { since: values[0] };
425
487
  }
426
488
  catch {
427
489
  return INVALID_COORDINATION_QUERY;
428
490
  }
429
491
  }
430
492
  function isCoordinationPath(path) {
431
- return path === "/api/client/attach" || path === "/api/cubes" ||
493
+ return path === ATTACH_PATH || path === CUBES_PATH ||
432
494
  path?.startsWith("/api/cubes/") === true;
433
495
  }
434
496
  function applyServerLimits(server, limits) {
@@ -773,10 +835,6 @@ function credentialIdentity(authorization) {
773
835
  return null;
774
836
  return `credential:${createHash("sha256").update(authorization).digest("base64url")}`;
775
837
  }
776
- function consumeAuthenticatedRateLimit(authorization, rateLimiter) {
777
- const identity = credentialIdentity(authorization);
778
- return identity === null ? null : rateLimiter.consume(identity);
779
- }
780
838
  function listen(server, port, host) {
781
839
  return new Promise((resolve, reject) => {
782
840
  const onError = (error) => {