@mycadet/gateway 0.12.7 → 0.12.8

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 (167) hide show
  1. package/README.md +3 -3
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  3. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  4. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  5. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  6. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  7. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  8. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  9. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  10. package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
  11. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  12. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  13. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  14. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  15. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  16. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  17. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  18. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  19. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  20. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  21. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  22. package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
  23. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
  24. package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
  25. package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
  26. package/node_modules/@mycadet/service-contracts/package.json +1 -1
  27. package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  28. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  29. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  30. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  31. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  32. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  33. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  34. package/package.json +1 -1
  35. package/src/__tests__/actor-token-revocation.test.ts +0 -1
  36. package/src/__tests__/{backup-worker-platform-mode.test.ts → backup-worker-cloud-mode.test.ts} +4 -4
  37. package/src/__tests__/channel-verification-session-proxy.test.ts +0 -1
  38. package/src/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +72 -86
  39. package/src/__tests__/config.test.ts +0 -24
  40. package/src/__tests__/contacts-control-plane-proxy.test.ts +0 -1
  41. package/src/__tests__/credential-requests.test.ts +6 -6
  42. package/src/__tests__/{credential-watcher-managed-bootstrap.test.ts → credential-watcher-cloud-bootstrap.test.ts} +1 -1
  43. package/src/__tests__/edge-auth.test.ts +18 -18
  44. package/src/__tests__/edge-guardian-auth.test.ts +10 -10
  45. package/src/__tests__/guardian-init-lockfile.test.ts +6 -8
  46. package/src/__tests__/guardian-pin.test.ts +8 -8
  47. package/src/__tests__/handle-inbound-admission.test.ts +0 -1
  48. package/src/__tests__/handle-inbound-app-context.test.ts +0 -1
  49. package/src/__tests__/handle-inbound-invite-intercept.test.ts +0 -1
  50. package/src/__tests__/handle-inbound-trust-verdict.test.ts +0 -1
  51. package/src/__tests__/inbound-contact-seed.test.ts +1 -1
  52. package/src/__tests__/live-voice-websocket.test.ts +12 -12
  53. package/src/__tests__/load-guards.test.ts +0 -1
  54. package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -1
  55. package/src/__tests__/oauth-callback.test.ts +0 -1
  56. package/src/__tests__/remote-feature-flag-sync.test.ts +35 -45
  57. package/src/__tests__/resolve-assistant.test.ts +0 -1
  58. package/src/__tests__/route-schema-guard.test.ts +4 -1
  59. package/src/__tests__/runtime-client.test.ts +1 -2
  60. package/src/__tests__/runtime-health-proxy.test.ts +0 -1
  61. package/src/__tests__/runtime-proxy-auth.test.ts +3 -2
  62. package/src/__tests__/runtime-proxy-device-stamp.test.ts +0 -1
  63. package/src/__tests__/runtime-proxy-error-body.test.ts +0 -1
  64. package/src/__tests__/runtime-proxy.test.ts +0 -1
  65. package/src/__tests__/runtime-stream-test-utils.ts +0 -1
  66. package/src/__tests__/shared-invite-registration.test.ts +15 -32
  67. package/src/__tests__/slack-control-plane-proxy.test.ts +0 -1
  68. package/src/__tests__/slack-display-name.test.ts +0 -1
  69. package/src/__tests__/slack-normalize.test.ts +0 -1
  70. package/src/__tests__/slack-reaction-normalize.test.ts +0 -1
  71. package/src/__tests__/slack-socket-mode-catchup.test.ts +0 -1
  72. package/src/__tests__/slack-socket-mode-group-dm.test.ts +0 -1
  73. package/src/__tests__/slack-socket-mode-liveness.test.ts +0 -1
  74. package/src/__tests__/slack-socket-mode-outbound-root.test.ts +0 -1
  75. package/src/__tests__/slack-socket-mode-thread-tracking.test.ts +0 -1
  76. package/src/__tests__/speech-relay-websocket.test.ts +16 -14
  77. package/src/__tests__/stt-stream-websocket.test.ts +5 -5
  78. package/src/__tests__/telegram-control-plane-proxy.test.ts +0 -1
  79. package/src/__tests__/telegram-webhook-drop-visibility.test.ts +0 -1
  80. package/src/__tests__/telegram-webhook-handler.test.ts +0 -1
  81. package/src/__tests__/telegram-webhook-manager.test.ts +79 -80
  82. package/src/__tests__/twilio-media-websocket.test.ts +0 -1
  83. package/src/__tests__/twilio-webhooks.test.ts +6 -7
  84. package/src/__tests__/whatsapp-download.test.ts +0 -1
  85. package/src/__tests__/whatsapp-webhook.test.ts +0 -1
  86. package/src/attachments/ingest.test.ts +0 -1
  87. package/src/auth/guardian-bootstrap.ts +17 -15
  88. package/src/auth/scopes.ts +1 -1
  89. package/src/auth-fallback-reporter.ts +1 -1
  90. package/src/backup/backup-worker.ts +2 -2
  91. package/src/channels/inbound-event.ts +3 -3
  92. package/src/channels/ingress-inbound-vendors.test.ts +2 -2
  93. package/src/channels/ingress-inbound.ts +1 -1
  94. package/src/channels/ingress-verification.test.ts +2 -2
  95. package/src/channels/ingress-verification.ts +4 -4
  96. package/src/channels/plugin-ingress-approvals.test.ts +1 -1
  97. package/src/channels/plugin-ingress-approvals.ts +1 -1
  98. package/src/channels/plugin-ingress.test.ts +3 -3
  99. package/src/channels/plugin-ingress.ts +3 -3
  100. package/src/channels/room-admission.ts +2 -2
  101. package/src/{platform-identity.ts → cloud-identity.ts} +60 -68
  102. package/src/cloud-url.ts +14 -0
  103. package/src/{platform-user-id.ts → cloud-user-id.ts} +4 -4
  104. package/src/config.ts +2 -4
  105. package/src/credential-reader.ts +2 -2
  106. package/src/db/assistant-db-proxy.ts +1 -1
  107. package/src/discord/forward.ts +1 -1
  108. package/src/email/normalize.ts +4 -4
  109. package/src/email/register-callback.test.ts +27 -34
  110. package/src/email/register-callback.ts +21 -26
  111. package/src/feature-flag-registry.json +5 -5
  112. package/src/feature-flag-remote-store.ts +2 -2
  113. package/src/feature-flag-resolver.ts +5 -5
  114. package/src/http/cadet-signature.ts +1 -1
  115. package/src/http/edge-forwarded-header.ts +1 -1
  116. package/src/http/loopback-guard.ts +1 -1
  117. package/src/http/middleware/auth.ts +22 -22
  118. package/src/http/router.ts +1 -1
  119. package/src/http/routes/cadet-identity.ts +2 -2
  120. package/src/http/routes/channel-ingress-routes.ts +1 -1
  121. package/src/http/routes/channel-ingress.test.ts +13 -2
  122. package/src/http/routes/channel-verification-session-proxy.ts +9 -9
  123. package/src/http/routes/{platform-push-proxy.test.ts → cloud-push-proxy.test.ts} +21 -22
  124. package/src/http/routes/{platform-push-proxy.ts → cloud-push-proxy.ts} +13 -14
  125. package/src/http/routes/contacts-control-plane-proxy.ts +2 -2
  126. package/src/http/routes/email-webhook.test.ts +2 -3
  127. package/src/http/routes/email-webhook.ts +4 -4
  128. package/src/http/routes/guardian-channel-create.ts +3 -3
  129. package/src/http/routes/guardian-pin.ts +9 -9
  130. package/src/http/routes/inbound-register.ts +2 -2
  131. package/src/http/routes/ingress-domains.test.ts +129 -0
  132. package/src/http/routes/ingress-domains.ts +73 -0
  133. package/src/http/routes/live-voice-websocket.ts +2 -2
  134. package/src/http/routes/log-export.test.ts +0 -1
  135. package/src/http/routes/log-tail.test.ts +0 -1
  136. package/src/http/routes/migration-proxy.ts +1 -1
  137. package/src/http/routes/plugin-webhook-websocket.test.ts +3 -3
  138. package/src/http/routes/plugin-webhook.test.ts +9 -8
  139. package/src/http/routes/plugin-webhook.ts +1 -1
  140. package/src/http/routes/speech-relay-websocket.ts +8 -11
  141. package/src/http/routes/stt-stream-websocket.ts +1 -1
  142. package/src/http/routes/telegram-webhook.test.ts +0 -1
  143. package/src/http/routes/twilio-voice-verify-callback.ts +3 -3
  144. package/src/http/routes/twilio-voice-webhook.test.ts +3 -4
  145. package/src/http/routes/twilio-voice-webhook.ts +3 -3
  146. package/src/http/routes/whatsapp-webhook.test.ts +0 -1
  147. package/src/index.ts +57 -56
  148. package/src/ipc/credential-request-handlers.ts +4 -4
  149. package/src/ipc/debug-export-handlers.test.ts +0 -1
  150. package/src/ipc/log-tail-handlers.test.ts +0 -1
  151. package/src/ipc/risk-classification-handlers.test.ts +4 -4
  152. package/src/ipc/risk-classification-handlers.ts +2 -2
  153. package/src/remote-feature-flag-sync.ts +16 -18
  154. package/src/risk/command-registry/commands/assistant.ts +2 -2
  155. package/src/risk/skill-risk-classifier.test.ts +24 -24
  156. package/src/risk/skill-risk-classifier.ts +10 -10
  157. package/src/runtime/client.ts +17 -17
  158. package/src/schema.ts +5 -5
  159. package/src/telegram/send.test.ts +0 -1
  160. package/src/telegram/webhook-manager.ts +33 -38
  161. package/src/tunnel/allowed-paths.test.ts +3 -3
  162. package/src/tunnel/allowed-paths.ts +1 -1
  163. package/src/tunnel/client.test.ts +20 -21
  164. package/src/tunnel/client.ts +17 -17
  165. package/src/verification/shared-invite-registration.ts +22 -17
  166. package/src/version.ts +1 -1
  167. package/src/platform-url.ts +0 -24
@@ -24,13 +24,13 @@ mock.module("../../fetch.js", () => ({
24
24
  fetchImpl: (...args: Parameters<FetchFn>) => fetchMock(...args),
25
25
  }));
26
26
 
27
- const { createPlatformPushProxyHandler } =
28
- await import("./platform-push-proxy.js");
29
- const { applyPlatformIdentityIds, _resetPlatformIdentityForTests } =
30
- await import("../../platform-identity.js");
27
+ const { createCloudPushProxyHandler } =
28
+ await import("./cloud-push-proxy.js");
29
+ const { applyCloudIdentityIds, _resetCloudIdentityForTests } =
30
+ await import("../../cloud-identity.js");
31
31
 
32
32
  const PLATFORM_ASSISTANT_ID = "11111111-1111-4111-8111-111111111111";
33
- const PLATFORM_BASE_URL = "https://platform.example.com";
33
+ const PLATFORM_BASE_URL = "https://test-cloud.mycadet.ai";
34
34
  const ASSISTANT_API_KEY = "assistant-api-key-123";
35
35
 
36
36
  function makeKeyedCredentials(
@@ -42,15 +42,14 @@ function makeKeyedCredentials(
42
42
  }
43
43
 
44
44
  function registeredCredentials(): CredentialCache {
45
- applyPlatformIdentityIds({
45
+ applyCloudIdentityIds({
46
46
  assistantId: PLATFORM_ASSISTANT_ID,
47
47
  organizationId: "",
48
48
  userId: "",
49
49
  });
50
+ process.env.CADET_CLOUD_URL = PLATFORM_BASE_URL;
50
51
  return makeKeyedCredentials({
51
- [credentialKey("cadet", "platform_base_url")]: PLATFORM_BASE_URL,
52
52
  [credentialKey("cadet", "assistant_api_key")]: ASSISTANT_API_KEY,
53
- [credentialKey("cadet", "platform_assistant_id")]: PLATFORM_ASSISTANT_ID,
54
53
  });
55
54
  }
56
55
 
@@ -84,27 +83,27 @@ function capturedFetch(): {
84
83
  }
85
84
 
86
85
  describe("platform push proxy", () => {
87
- const savedPlatformUrl = process.env.CADET_PLATFORM_URL;
86
+ const savedCloudUrl = process.env.CADET_CLOUD_URL;
88
87
 
89
88
  beforeAll(() => {
90
- delete process.env.CADET_PLATFORM_URL;
89
+ delete process.env.CADET_CLOUD_URL;
91
90
  });
92
91
 
93
92
  afterAll(() => {
94
- if (savedPlatformUrl !== undefined) {
95
- process.env.CADET_PLATFORM_URL = savedPlatformUrl;
93
+ if (savedCloudUrl !== undefined) {
94
+ process.env.CADET_CLOUD_URL = savedCloudUrl;
96
95
  } else {
97
- delete process.env.CADET_PLATFORM_URL;
96
+ delete process.env.CADET_CLOUD_URL;
98
97
  }
99
98
  });
100
99
 
101
100
  afterEach(() => {
102
101
  fetchMock = mock(async () => new Response());
103
- _resetPlatformIdentityForTests();
102
+ _resetCloudIdentityForTests();
104
103
  });
105
104
 
106
105
  test("returns 503 and does not fetch when platform credentials are missing", async () => {
107
- const handler = createPlatformPushProxyHandler(makeKeyedCredentials({}));
106
+ const handler = createCloudPushProxyHandler(makeKeyedCredentials({}));
108
107
 
109
108
  const res = await handler.handleUpsertPushToken(
110
109
  new Request("http://localhost:7830/v1/assistants/self/push-tokens/", {
@@ -131,7 +130,7 @@ describe("platform push proxy", () => {
131
130
  headers: { "content-type": "application/json" },
132
131
  }),
133
132
  );
134
- const handler = createPlatformPushProxyHandler(registeredCredentials());
133
+ const handler = createCloudPushProxyHandler(registeredCredentials());
135
134
  const payload = {
136
135
  token: "device-token",
137
136
  platform: "ios",
@@ -166,7 +165,7 @@ describe("platform push proxy", () => {
166
165
 
167
166
  test("preserves bundle_id on push-token delete and ignores the URL assistant id", async () => {
168
167
  fetchMock = mock(async () => new Response(null, { status: 204 }));
169
- const handler = createPlatformPushProxyHandler(registeredCredentials());
168
+ const handler = createCloudPushProxyHandler(registeredCredentials());
170
169
 
171
170
  const res = await handler.handleDeletePushToken(
172
171
  new Request(
@@ -195,7 +194,7 @@ describe("platform push proxy", () => {
195
194
  headers: { "content-type": "application/json" },
196
195
  }),
197
196
  );
198
- const handler = createPlatformPushProxyHandler(registeredCredentials());
197
+ const handler = createCloudPushProxyHandler(registeredCredentials());
199
198
 
200
199
  const res = await handler.handleUpsertLiveActivityToken(
201
200
  new Request(
@@ -217,7 +216,7 @@ describe("platform push proxy", () => {
217
216
 
218
217
  test("forwards a Live Activity token delete to Django", async () => {
219
218
  fetchMock = mock(async () => new Response(null, { status: 204 }));
220
- const handler = createPlatformPushProxyHandler(registeredCredentials());
219
+ const handler = createCloudPushProxyHandler(registeredCredentials());
221
220
 
222
221
  const res = await handler.handleDeleteLiveActivityToken(
223
222
  new Request(
@@ -242,7 +241,7 @@ describe("platform push proxy", () => {
242
241
  headers: { "content-type": "application/json" },
243
242
  }),
244
243
  );
245
- const handler = createPlatformPushProxyHandler(registeredCredentials());
244
+ const handler = createCloudPushProxyHandler(registeredCredentials());
246
245
 
247
246
  const res = await handler.handleDeletePushToken(
248
247
  new Request(
@@ -256,11 +255,11 @@ describe("platform push proxy", () => {
256
255
  expect(await res.json()).toEqual({ detail: "bundle_id is required." });
257
256
  });
258
257
 
259
- test("returns 502 when the platform request fails", async () => {
258
+ test("returns 502 when the cloud request fails", async () => {
260
259
  fetchMock = mock(async () => {
261
260
  throw new Error("network down");
262
261
  });
263
- const handler = createPlatformPushProxyHandler(registeredCredentials());
262
+ const handler = createCloudPushProxyHandler(registeredCredentials());
264
263
 
265
264
  const res = await handler.handleUpsertPushToken(
266
265
  new Request("http://localhost:7830/v1/assistants/self/push-tokens/", {
@@ -16,32 +16,31 @@ import type { CredentialCache } from "../../credential-cache.js";
16
16
  import { credentialKey } from "../../credential-key.js";
17
17
  import { fetchImpl } from "../../fetch.js";
18
18
  import { getLogger } from "../../logger.js";
19
- import { resolvePlatformAssistantIdOrUndefined } from "../../platform-identity.js";
20
- import { getPlatformBaseUrl } from "../../platform-url.js";
19
+ import { resolveCloudAssistantIdOrUndefined } from "../../cloud-identity.js";
20
+ import { getCloudBaseUrl } from "../../cloud-url.js";
21
21
  import { errorResponse } from "../loopback-guard.js";
22
22
 
23
- const log = getLogger("platform-push-proxy");
23
+ const log = getLogger("cloud-push-proxy");
24
24
 
25
25
  const UPSTREAM_TIMEOUT_MS = 10_000;
26
26
 
27
27
  interface PlatformTarget {
28
- platformBaseUrl: string;
28
+ cloudBaseUrl: string;
29
29
  assistantApiKey: string;
30
30
  assistantId: string;
31
31
  }
32
32
 
33
- export function createPlatformPushProxyHandler(credentials: CredentialCache) {
33
+ export function createCloudPushProxyHandler(credentials: CredentialCache) {
34
34
  async function resolvePlatformTarget(): Promise<PlatformTarget | Response> {
35
- const [platformBaseUrl, assistantApiKeyRaw, assistantId] =
36
- await Promise.all([
37
- getPlatformBaseUrl(credentials),
38
- credentials.get(credentialKey("cadet", "assistant_api_key")),
39
- resolvePlatformAssistantIdOrUndefined(),
40
- ]);
35
+ const [assistantApiKeyRaw, assistantId] = await Promise.all([
36
+ credentials.get(credentialKey("cadet", "assistant_api_key")),
37
+ resolveCloudAssistantIdOrUndefined(),
38
+ ]);
41
39
 
40
+ const cloudBaseUrl = getCloudBaseUrl();
42
41
  const assistantApiKey = assistantApiKeyRaw?.trim() || undefined;
43
42
 
44
- if (!platformBaseUrl || !assistantApiKey || !assistantId) {
43
+ if (!cloudBaseUrl || !assistantApiKey || !assistantId) {
45
44
  return errorResponse(
46
45
  "PLATFORM_UNAVAILABLE",
47
46
  "This assistant is not registered with Cadet Cloud, so push notifications are unavailable.",
@@ -49,7 +48,7 @@ export function createPlatformPushProxyHandler(credentials: CredentialCache) {
49
48
  );
50
49
  }
51
50
 
52
- return { platformBaseUrl, assistantApiKey, assistantId };
51
+ return { cloudBaseUrl, assistantApiKey, assistantId };
53
52
  }
54
53
 
55
54
  async function forward(
@@ -62,7 +61,7 @@ export function createPlatformPushProxyHandler(credentials: CredentialCache) {
62
61
  }
63
62
 
64
63
  const search = new URL(req.url).search;
65
- const upstreamUrl = `${target.platformBaseUrl}/v1/assistants/${target.assistantId}${resourcePath}${search}`;
64
+ const upstreamUrl = `${target.cloudBaseUrl}/v1/assistants/${target.assistantId}${resourcePath}${search}`;
66
65
 
67
66
  const headers = new Headers();
68
67
  headers.set("Authorization", `Api-Key ${target.assistantApiKey}`);
@@ -286,11 +286,11 @@ function sharedInviteRegistrationError(
286
286
  * daemon-owned and layered on by each create transport exactly once: the
287
287
  * daemon's create relay composes in-process; the gateway HTTP handler
288
288
  * composes via the `invites_compose_presentation` daemon IPC.
289
- * A `cadet-shared` invite is registered with the platform before the row is
289
+ * A `cadet-shared` invite is registered with the cloud before the row is
290
290
  * written (see `registerSharedInvite`).
291
291
  *
292
292
  * Throws InviteNativeError(404) when the contact is unknown, (400) on invalid
293
- * voice parameters, (409 / 429 / 502 / 503) when the platform registration
293
+ * voice parameters, (409 / 429 / 502 / 503) when the cloud registration
294
294
  * does not succeed, and (500) when the gateway write fails.
295
295
  */
296
296
  export async function createInviteNative(
@@ -71,7 +71,6 @@ const baseConfig: GatewayConfig = {
71
71
  runtimeMaxRetries: 2,
72
72
  runtimeProxyRequireAuth: true,
73
73
  runtimeTimeoutMs: 30000,
74
- shutdownDrainMs: 5000,
75
74
  trustProxy: false,
76
75
  };
77
76
 
@@ -381,7 +380,7 @@ describe("email-webhook", () => {
381
380
  expect(status).toBe(false);
382
381
  });
383
382
 
384
- // Intercept response-shape pins. The platform email relay
383
+ // Intercept response-shape pins. The cloud email relay
385
384
  // (mycadet-platform django/app/email/views.py) gates only on
386
385
  // `denied` in the gateway body and returns everything else verbatim — it
387
386
  // reads neither intercept flag nor `replyText`. The flags name which
@@ -571,7 +570,7 @@ describe("email-webhook", () => {
571
570
  const res = await handler(postRequest(body));
572
571
  expect(res.status).toBe(500);
573
572
  expect(handleInboundMock).not.toHaveBeenCalled();
574
- // Unreserved so the platform's retry delivery can be processed.
573
+ // Unreserved so the cloud's retry delivery can be processed.
575
574
  expect(dedupCache.reserve("<transient-upload-fail@example.com>")).toBe(
576
575
  true,
577
576
  );
@@ -154,7 +154,7 @@ export function createEmailWebhookHandler(
154
154
 
155
155
  // Ingest inline base64 attachments into the assistant's attachment store
156
156
  // (which stores them in the conversation workspace) and forward the ids.
157
- // A transient upload failure surfaces as 500 so the platform retries.
157
+ // A transient upload failure surfaces as 500 so the cloud retries.
158
158
  let attachmentIds: string[] | undefined;
159
159
  try {
160
160
  const ingested = await ingestEmailAttachments(config, attachments, tlog);
@@ -222,7 +222,7 @@ export function createEmailWebhookHandler(
222
222
  "Gateway intercept — returning reply text to platform",
223
223
  );
224
224
  // replyText contract: the gateway cannot send email on this path — the
225
- // reply rides the HTTP response for the platform (vembda) to deliver,
225
+ // reply rides the HTTP response for the cloud (vembda) to deliver,
226
226
  // and delivery is tracked platform-side.
227
227
  return Response.json({
228
228
  ok: true,
@@ -269,9 +269,9 @@ export function createEmailWebhookHandler(
269
269
  }
270
270
 
271
271
  // Propagate the runtime's full response (including denied/reason/replyText)
272
- // so the platform can decide whether to persist the email and how to respond
272
+ // so the cloud can decide whether to persist the email and how to respond
273
273
  // to the sender. Same replyText contract as above: the gateway cannot send
274
- // email here — the platform (vembda) delivers any reply and tracks delivery.
274
+ // email here — the cloud (vembda) delivers any reply and tracks delivery.
275
275
  const runtimeBody = result.runtimeResponse ?? {};
276
276
  return Response.json({ ok: true, ...runtimeBody });
277
277
  } catch (err) {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * POST /v1/contacts/guardian/channel — create a guardian channel binding
3
- * via platform auto-verify.
3
+ * via cloud auto-verify.
4
4
  *
5
- * This route is called by the platform's `assistant email register` flow
5
+ * This route is called by the cloud's `assistant email register` flow
6
6
  * to auto-verify the owner's email channel. It creates a guardian email
7
7
  * channel binding directly in both the assistant and gateway databases.
8
8
  *
@@ -138,7 +138,7 @@ export function createGuardianChannelHandler() {
138
138
 
139
139
  log.info(
140
140
  { channel: body.type, address: body.address },
141
- "Auto-verified guardian channel via platform auto-register",
141
+ "Auto-verified guardian channel via cloud auto-register",
142
142
  );
143
143
 
144
144
  return Response.json({
@@ -21,7 +21,7 @@
21
21
  * user who traversed tunnel, so it is cross-checked against the bound
22
22
  * platform user id by {@link requireManagedGuardian}. A gateway takes
23
23
  * this path when {@link acceptsTunnelAttestation} says it has a tunnel
24
- * at all: a managed pod, or a locally hosted assistant whose gateway dialed
24
+ * at all: a cloud pod, or a locally hosted assistant whose gateway dialed
25
25
  * tunnel so the mobile app can reach it.
26
26
  * - **Everything else.** The caller presents an actor edge JWT and its
27
27
  * principal is compared against the binding by {@link requireBoundGuardian}.
@@ -39,7 +39,7 @@ import { authorizeRuntimeAudioStream } from "./runtime-audio-stream.js";
39
39
  import { findCadetGuardian } from "../../auth/guardian-bootstrap.js";
40
40
  import type { GatewayConfig } from "../../config.js";
41
41
  import { requestHasTunnelBridgeAuth } from "../../tunnel/bridge-auth.js";
42
- import { readStoredPlatformUserId } from "../../platform-user-id.js";
42
+ import { readStoredCloudUserId } from "../../cloud-user-id.js";
43
43
 
44
44
  const TUNNEL_USER_ID_HEADER = "x-tunnel-user-id";
45
45
  const TUNNEL_ORG_ID_HEADER = "x-tunnel-org-id";
@@ -47,16 +47,16 @@ const TUNNEL_ACTOR_HEADER = "x-tunnel-actor";
47
47
 
48
48
  /**
49
49
  * True when the gateway runs in managed/cloud mode (vembda + tunnel ingress).
50
- * Mirrors the `IS_PLATFORM` check used by the gateway's HTTP edge auth
50
+ * Mirrors the `IS_CLOUD` check used by the gateway's HTTP edge auth
51
51
  * (`src/http/middleware/auth.ts`) and feature-flag resolver.
52
52
  */
53
53
  export function isPlatformManaged(): boolean {
54
- const v = process.env.IS_PLATFORM?.trim().toLowerCase();
54
+ const v = process.env.IS_CLOUD?.trim().toLowerCase();
55
55
  return v === "1" || v === "true";
56
56
  }
57
57
 
58
58
  /**
59
- * True when a tunnel-attested caller can reach this gateway at all: a managed
59
+ * True when a tunnel-attested caller can reach this gateway at all: a cloud
60
60
  * pod, or any gateway started with a tunnel (`TUNNEL_BASE_URL`), which is
61
61
  * how a locally hosted assistant is reachable from the mobile app.
62
62
  *
@@ -101,7 +101,7 @@ export function extractTunnelAttestedContext(
101
101
  /**
102
102
  * Managed-mode guardian check: cross-check a tunnel-attested caller's platform
103
103
  * user id against the bound platform user id, the same guard the
104
- * edge-auth middleware applies to guardian routes under the platform bypass.
104
+ * edge-auth middleware applies to guardian routes under the cloud bypass.
105
105
  *
106
106
  * Returns null when the caller is the guardian, else a 403/503 Response.
107
107
  */
@@ -112,7 +112,7 @@ export async function requireManagedGuardian(
112
112
  let storedUserId: string | undefined;
113
113
  let unreachable = false;
114
114
  try {
115
- const result = await readStoredPlatformUserId();
115
+ const result = await readStoredCloudUserId();
116
116
  storedUserId = result.userId;
117
117
  unreachable = result.unreachable;
118
118
  } catch (err) {
@@ -172,8 +172,8 @@ export async function authorizeGuardianStream(
172
172
  config: GatewayConfig,
173
173
  log: Logger,
174
174
  ): Promise<Response | null> {
175
- // Checked here as well as in the shared gate, because the managed path
176
- // below skips that gate entirely: without this, a managed caller sending a
175
+ // Checked here as well as in the shared gate, because the cloud path
176
+ // below skips that gate entirely: without this, a cloud caller sending a
177
177
  // plain request would fall through to `server.upgrade` and get a 500.
178
178
  if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
179
179
  return new Response("Upgrade Required", { status: 426 });
@@ -2,11 +2,11 @@
2
2
  * POST /inbound/register — auto-verify the guardian's email channel when
3
3
  * an email provider is registered.
4
4
  *
5
- * The platform provides `guardian_email` in the request body. For BYO
5
+ * The cloud provides `guardian_email` in the request body. For BYO
6
6
  * providers with an identity API (Mailgun), the gateway cross-verifies
7
7
  * the email against the API response. For BYO providers without one
8
8
  * (Resend), it validates that the stored API key is functional (proving
9
- * account ownership) and trusts the provided email. For platform-managed
9
+ * account ownership) and trusts the provided email. For cloud-managed
10
10
  * email (type "cadet"), the route's edge-scoped auth is sufficient.
11
11
  *
12
12
  * On success, creates a guardian email channel binding directly in both
@@ -0,0 +1,129 @@
1
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
5
+ import { z } from "zod";
6
+
7
+ import { ConfigFileCache } from "../../config-file-cache.js";
8
+ import { handleSaveIngressDomain } from "./ingress-domains.js";
9
+
10
+ // The handler's write path resolves the workspace config through
11
+ // getWorkspaceDir; the mock redirects it into a per-test temp dir.
12
+ let workspaceDir = "";
13
+
14
+ mock.module("../../credential-reader.js", () => ({
15
+ getWorkspaceDir: () => workspaceDir,
16
+ }));
17
+
18
+ const SavedIngressSchema = z.looseObject({
19
+ ingress: z
20
+ .looseObject({
21
+ publicBaseUrl: z.string().optional(),
22
+ publicBaseUrlManagedBy: z.string().optional(),
23
+ })
24
+ .optional(),
25
+ });
26
+
27
+ let testDir = "";
28
+
29
+ beforeEach(() => {
30
+ testDir = mkdtempSync(join(tmpdir(), "gateway-ingress-domains-test-"));
31
+ workspaceDir = testDir;
32
+ });
33
+
34
+ afterEach(() => {
35
+ rmSync(testDir, { recursive: true, force: true });
36
+ });
37
+
38
+ function request(body?: unknown): Request {
39
+ return new Request("http://127.0.0.1/v1/ingress/domains", {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: body === undefined ? undefined : JSON.stringify(body),
43
+ });
44
+ }
45
+
46
+ function readSavedIngress(): z.infer<typeof SavedIngressSchema>["ingress"] {
47
+ const raw = SavedIngressSchema.parse(
48
+ JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8")),
49
+ );
50
+ return raw.ingress;
51
+ }
52
+
53
+ describe("POST /v1/ingress/domains", () => {
54
+ test("records the tunnel URL as tunnel-managed in the gateway config", async () => {
55
+ const res = await handleSaveIngressDomain(
56
+ request({ publicBaseUrl: "https://foo.ngrok.app" }),
57
+ "127.0.0.1",
58
+ new ConfigFileCache(),
59
+ );
60
+
61
+ expect(res.status).toBe(200);
62
+ expect(readSavedIngress()).toMatchObject({
63
+ publicBaseUrl: "https://foo.ngrok.app",
64
+ publicBaseUrlManagedBy: "tunnel",
65
+ });
66
+ });
67
+
68
+ test("normalizes the URL the same way every other public base URL is", async () => {
69
+ await handleSaveIngressDomain(
70
+ request({ publicBaseUrl: "https://foo.ngrok.app/" }),
71
+ "127.0.0.1",
72
+ new ConfigFileCache(),
73
+ );
74
+
75
+ expect(readSavedIngress()?.publicBaseUrl).toBe("https://foo.ngrok.app");
76
+ });
77
+
78
+ test("is a no-op write when the URL is already recorded as tunnel-managed", async () => {
79
+ const configFile = new ConfigFileCache();
80
+ await handleSaveIngressDomain(
81
+ request({ publicBaseUrl: "https://foo.ngrok.app" }),
82
+ "127.0.0.1",
83
+ configFile,
84
+ );
85
+ const afterFirst = readFileSync(join(workspaceDir, "config.json"), "utf-8");
86
+
87
+ await handleSaveIngressDomain(
88
+ request({ publicBaseUrl: "https://foo.ngrok.app" }),
89
+ "127.0.0.1",
90
+ configFile,
91
+ );
92
+ const afterSecond = readFileSync(
93
+ join(workspaceDir, "config.json"),
94
+ "utf-8",
95
+ );
96
+
97
+ expect(afterSecond).toBe(afterFirst);
98
+ });
99
+
100
+ test("rejects non-loopback callers", async () => {
101
+ const res = await handleSaveIngressDomain(
102
+ request({ publicBaseUrl: "https://foo.ngrok.app" }),
103
+ "10.0.0.1",
104
+ new ConfigFileCache(),
105
+ );
106
+
107
+ expect(res.status).toBe(403);
108
+ });
109
+
110
+ test("rejects a body without a usable public URL", async () => {
111
+ const res = await handleSaveIngressDomain(
112
+ request({}),
113
+ "127.0.0.1",
114
+ new ConfigFileCache(),
115
+ );
116
+
117
+ expect(res.status).toBe(400);
118
+ });
119
+
120
+ test("rejects non-POST methods", async () => {
121
+ const res = await handleSaveIngressDomain(
122
+ new Request("http://127.0.0.1/v1/ingress/domains", { method: "GET" }),
123
+ "127.0.0.1",
124
+ new ConfigFileCache(),
125
+ );
126
+
127
+ expect(res.status).toBe(405);
128
+ });
129
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Loopback-guarded recording of a tunnel's public URL.
3
+ *
4
+ * POST /v1/ingress/domains — the CLI (its ngrok agent, today) tells the
5
+ * gateway the public URL its tunnel published; the gateway writes
6
+ * `ingress.publicBaseUrl` + `publicBaseUrlManagedBy: "tunnel"` in its own
7
+ * config via the same machinery its own tunnel uses, so both publishers
8
+ * record through one path and the gateway's existing loss handling clears
9
+ * it uniformly. Write-only: reads keep flowing through the config the
10
+ * gateway already serves. While we ship one bundle the gateway still hosts
11
+ * the ngrok-related logic; once the bundle splits, the gateway manages its
12
+ * own tunnel (mycadet.ai vs <assistant>.mycadet.bot) and this route is what
13
+ * the CLI-side publishers migrate onto.
14
+ */
15
+
16
+ import { z } from "zod";
17
+
18
+ import { normalizeHttpPublicBaseUrlWithoutTrailingSlash } from "@mycadet/service-contracts/ingress";
19
+
20
+ import type { ConfigFileCache } from "../../config-file-cache.js";
21
+ import { writeManagedPublicBaseUrl } from "../../tunnel/client.js";
22
+ import {
23
+ enforceLoopbackOnly,
24
+ errorResponse,
25
+ rejectBrowserOrigin,
26
+ } from "../loopback-guard.js";
27
+
28
+ const SaveIngressDomainRequestSchema = z.object({
29
+ publicBaseUrl: z.string(),
30
+ });
31
+
32
+ export async function handleSaveIngressDomain(
33
+ req: Request,
34
+ clientIp: string,
35
+ configFile: ConfigFileCache,
36
+ ): Promise<Response> {
37
+ if (req.method !== "POST") {
38
+ return new Response("method not allowed", {
39
+ status: 405,
40
+ headers: { Allow: "POST" },
41
+ });
42
+ }
43
+
44
+ const guardError = enforceLoopbackOnly(req, clientIp, "ingress-domains");
45
+ if (guardError) return guardError;
46
+ const originError = rejectBrowserOrigin(req, clientIp, "ingress-domains");
47
+ if (originError) return originError;
48
+
49
+ let body: unknown;
50
+ try {
51
+ body = await req.json();
52
+ } catch {
53
+ body = {};
54
+ }
55
+ const parsed = SaveIngressDomainRequestSchema.safeParse(body);
56
+ if (!parsed.success) {
57
+ return errorResponse("BAD_REQUEST", "publicBaseUrl is required", 400);
58
+ }
59
+ const publicUrl = normalizeHttpPublicBaseUrlWithoutTrailingSlash(
60
+ parsed.data.publicBaseUrl,
61
+ );
62
+ if (!publicUrl) {
63
+ return errorResponse(
64
+ "BAD_REQUEST",
65
+ "publicBaseUrl must be an absolute http(s) URL",
66
+ 400,
67
+ );
68
+ }
69
+
70
+ // No-op (no write) when the URL is already recorded as tunnel-managed.
71
+ await writeManagedPublicBaseUrl(publicUrl, configFile);
72
+ return Response.json({ ok: true });
73
+ }
@@ -119,7 +119,7 @@ async function checkLiveVoiceAuth(
119
119
  // the caller is *a* platform user who traversed tunnel, not that they
120
120
  // are THIS assistant's guardian, so cross-check the tunnel user id
121
121
  // against the bound platform user id (the same guardian check the
122
- // edge-auth middleware applies to guardian routes under the managed
122
+ // edge-auth middleware applies to guardian routes under the cloud
123
123
  // bypass). Without it, any tunnel-authorized org user reaching this
124
124
  // assistant would be stamped guardian downstream.
125
125
  const guardianError = await requireManagedGuardian(
@@ -127,7 +127,7 @@ async function checkLiveVoiceAuth(
127
127
  log,
128
128
  );
129
129
  if (guardianError) return { response: guardianError };
130
- // Tunnel attests a platform user, not an actor principal, so the
130
+ // Tunnel attests a cloud user, not an actor principal, so the
131
131
  // principal comes from the gateway's own binding rather than from
132
132
  // anything the request carried.
133
133
  const guardian = await findCadetGuardian();
@@ -112,7 +112,6 @@ const baseConfig: GatewayConfig = {
112
112
  runtimeMaxRetries: 2,
113
113
  runtimeProxyRequireAuth: true,
114
114
  runtimeTimeoutMs: 30000,
115
- shutdownDrainMs: 5000,
116
115
  trustProxy: false,
117
116
  };
118
117
 
@@ -79,7 +79,6 @@ function makeConfig(dir: string | undefined): GatewayConfig {
79
79
  runtimeMaxRetries: 2,
80
80
  runtimeProxyRequireAuth: true,
81
81
  runtimeTimeoutMs: 30000,
82
- shutdownDrainMs: 5000,
83
82
  trustProxy: false,
84
83
  } as GatewayConfig;
85
84
  }
@@ -452,7 +452,7 @@ async function handleSyncBytesImport(
452
452
  *
453
453
  * Returns the job's current status on 200, or 404 if the jobId is unknown
454
454
  * (never issued, or pruned after the TTL). The response shape deliberately
455
- * mirrors the platform's existing `/v1/migrations/import/:jobId/status/`
455
+ * mirrors the cloud's existing `/v1/migrations/import/:jobId/status/`
456
456
  * endpoint so `PlatformMigrationClient.pollImportStatus` works against
457
457
  * either surface with no changes.
458
458
  */
@@ -176,7 +176,7 @@ describe("upgrade gate", () => {
176
176
  });
177
177
 
178
178
  it("upgrades a cadet-signed declaration nobody approved", async () => {
179
- // Only a caller holding the platform secret can open it, and the user
179
+ // Only a caller holding the cloud secret can open it, and the user
180
180
  // extended that trust when they connected their account.
181
181
  const handle = makeHandler({
182
182
  resolve: () =>
@@ -203,7 +203,7 @@ describe("upgrade gate", () => {
203
203
  expect(upgrades).toHaveLength(1);
204
204
  });
205
205
 
206
- it("still demands the platform signature on an unapproved cadet route", async () => {
206
+ it("still demands the cloud signature on an unapproved cadet route", async () => {
207
207
  // Skipping approval did not skip authentication.
208
208
  const handle = makeHandler({
209
209
  resolve: () =>
@@ -378,7 +378,7 @@ describe("handshake signature", () => {
378
378
  expect(upgrades).toEqual([]);
379
379
  });
380
380
 
381
- it("verifies a cadet-signed route against the platform secret", async () => {
381
+ it("verifies a cadet-signed route against the cloud secret", async () => {
382
382
  const handle = makeHandler({
383
383
  resolve: () => approvedWith([{ ...ROUTE, signer: "cadet" as const }]),
384
384
  });