@mycadet/gateway 0.12.8 → 0.12.9
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/ces-client/package.json +1 -1
- package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/openapi.json +17 -17
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +1 -1
- package/src/auth/guardian-bootstrap.ts +2 -3
- package/src/cli/contacts.ts +3 -1
- package/src/cli/index.ts +1 -1
- package/src/cloud-url.ts +15 -4
- package/src/http/routes/auto-approve-thresholds-routes.ts +1 -1
- package/src/http/routes/channel-admission-policy-routes.ts +2 -2
- package/src/http/routes/channel-ingress-routes.ts +2 -2
- package/src/http/routes/channel-permission-overrides-routes.ts +3 -3
- package/src/http/routes/channel-verification-session-proxy.ts +4 -18
- package/src/http/routes/contacts-control-plane-proxy.ts +1 -1
- package/src/http/routes/contacts-control-plane-routes.ts +5 -5
- package/src/http/routes/feature-flags.ts +1 -1
- package/src/http/routes/trust-rules-routes.ts +2 -2
- package/src/remote-feature-flag-sync.ts +1 -1
- package/src/schema.ts +24 -24
- package/src/tunnel/client.ts +2 -3
|
@@ -141,7 +141,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
141
141
|
Promise.resolve(
|
|
142
142
|
Response.json({ account: "test-acct", value: "secret-123" }),
|
|
143
143
|
),
|
|
144
|
-
) as typeof fetch;
|
|
144
|
+
) as unknown as typeof fetch;
|
|
145
145
|
|
|
146
146
|
const client = createCesHttpCredentialClient(
|
|
147
147
|
createTestConfig(),
|
|
@@ -156,7 +156,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
156
156
|
test("get returns not-found on 404", async () => {
|
|
157
157
|
globalThis.fetch = mock(() =>
|
|
158
158
|
Promise.resolve(new Response(null, { status: 404 })),
|
|
159
|
-
) as typeof fetch;
|
|
159
|
+
) as unknown as typeof fetch;
|
|
160
160
|
|
|
161
161
|
const client = createCesHttpCredentialClient(
|
|
162
162
|
createTestConfig(),
|
|
@@ -171,7 +171,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
171
171
|
test("get returns unreachable on 500", async () => {
|
|
172
172
|
globalThis.fetch = mock(() =>
|
|
173
173
|
Promise.resolve(new Response("internal error", { status: 500 })),
|
|
174
|
-
) as typeof fetch;
|
|
174
|
+
) as unknown as typeof fetch;
|
|
175
175
|
|
|
176
176
|
const client = createCesHttpCredentialClient(
|
|
177
177
|
createTestConfig(),
|
|
@@ -186,7 +186,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
186
186
|
test("get returns unreachable on network error", async () => {
|
|
187
187
|
globalThis.fetch = mock(() =>
|
|
188
188
|
Promise.reject(new Error("ECONNREFUSED")),
|
|
189
|
-
) as typeof fetch;
|
|
189
|
+
) as unknown as typeof fetch;
|
|
190
190
|
|
|
191
191
|
const client = createCesHttpCredentialClient(
|
|
192
192
|
createTestConfig(),
|
|
@@ -201,7 +201,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
201
201
|
test("delete returns deleted on 200", async () => {
|
|
202
202
|
globalThis.fetch = mock(() =>
|
|
203
203
|
Promise.resolve(Response.json({ ok: true, account: "acct" })),
|
|
204
|
-
) as typeof fetch;
|
|
204
|
+
) as unknown as typeof fetch;
|
|
205
205
|
|
|
206
206
|
const client = createCesHttpCredentialClient(
|
|
207
207
|
createTestConfig(),
|
|
@@ -215,7 +215,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
215
215
|
test("delete returns not-found on 404", async () => {
|
|
216
216
|
globalThis.fetch = mock(() =>
|
|
217
217
|
Promise.resolve(new Response(null, { status: 404 })),
|
|
218
|
-
) as typeof fetch;
|
|
218
|
+
) as unknown as typeof fetch;
|
|
219
219
|
|
|
220
220
|
const client = createCesHttpCredentialClient(
|
|
221
221
|
createTestConfig(),
|
|
@@ -229,7 +229,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
229
229
|
test("delete returns error on network failure", async () => {
|
|
230
230
|
globalThis.fetch = mock(() =>
|
|
231
231
|
Promise.reject(new Error("network down")),
|
|
232
|
-
) as typeof fetch;
|
|
232
|
+
) as unknown as typeof fetch;
|
|
233
233
|
|
|
234
234
|
const client = createCesHttpCredentialClient(
|
|
235
235
|
createTestConfig(),
|
|
@@ -243,7 +243,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
243
243
|
test("list returns accounts on 200", async () => {
|
|
244
244
|
globalThis.fetch = mock(() =>
|
|
245
245
|
Promise.resolve(Response.json({ accounts: ["a", "b", "c"] })),
|
|
246
|
-
) as typeof fetch;
|
|
246
|
+
) as unknown as typeof fetch;
|
|
247
247
|
|
|
248
248
|
const client = createCesHttpCredentialClient(
|
|
249
249
|
createTestConfig(),
|
|
@@ -258,7 +258,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
258
258
|
test("list returns unreachable on error", async () => {
|
|
259
259
|
globalThis.fetch = mock(() =>
|
|
260
260
|
Promise.reject(new Error("timeout")),
|
|
261
|
-
) as typeof fetch;
|
|
261
|
+
) as unknown as typeof fetch;
|
|
262
262
|
|
|
263
263
|
const client = createCesHttpCredentialClient(
|
|
264
264
|
createTestConfig(),
|
|
@@ -273,7 +273,7 @@ describe("CesHttpCredentialClient", () => {
|
|
|
273
273
|
test("bulkSet returns all-failed when request fails", async () => {
|
|
274
274
|
globalThis.fetch = mock(() =>
|
|
275
275
|
Promise.reject(new Error("network")),
|
|
276
|
-
) as typeof fetch;
|
|
276
|
+
) as unknown as typeof fetch;
|
|
277
277
|
|
|
278
278
|
const client = createCesHttpCredentialClient(
|
|
279
279
|
createTestConfig(),
|
|
@@ -311,7 +311,7 @@ describe("fetchCesLogExport", () => {
|
|
|
311
311
|
headers: { "Content-Type": "application/gzip" },
|
|
312
312
|
}),
|
|
313
313
|
),
|
|
314
|
-
) as typeof fetch;
|
|
314
|
+
) as unknown as typeof fetch;
|
|
315
315
|
|
|
316
316
|
const result = await fetchCesLogExport({
|
|
317
317
|
baseUrl: "http://ces-test:8090",
|
|
@@ -327,7 +327,7 @@ describe("fetchCesLogExport", () => {
|
|
|
327
327
|
test("returns error on non-OK status", async () => {
|
|
328
328
|
globalThis.fetch = mock(() =>
|
|
329
329
|
Promise.resolve(new Response("forbidden", { status: 403 })),
|
|
330
|
-
) as typeof fetch;
|
|
330
|
+
) as unknown as typeof fetch;
|
|
331
331
|
|
|
332
332
|
const result = await fetchCesLogExport({
|
|
333
333
|
baseUrl: "http://ces-test:8090",
|
|
@@ -343,7 +343,7 @@ describe("fetchCesLogExport", () => {
|
|
|
343
343
|
test("returns error on network failure", async () => {
|
|
344
344
|
globalThis.fetch = mock(() =>
|
|
345
345
|
Promise.reject(new Error("connection refused")),
|
|
346
|
-
) as typeof fetch;
|
|
346
|
+
) as unknown as typeof fetch;
|
|
347
347
|
|
|
348
348
|
const result = await fetchCesLogExport({
|
|
349
349
|
baseUrl: "http://ces-test:8090",
|
|
@@ -361,7 +361,7 @@ describe("fetchCesLogExport", () => {
|
|
|
361
361
|
globalThis.fetch = mock((url: string | URL | Request) => {
|
|
362
362
|
capturedUrl = typeof url === "string" ? url : url.toString();
|
|
363
363
|
return Promise.resolve(new Response(new ArrayBuffer(0), { status: 200 }));
|
|
364
|
-
}) as typeof fetch;
|
|
364
|
+
}) as unknown as typeof fetch;
|
|
365
365
|
|
|
366
366
|
await fetchCesLogExport(
|
|
367
367
|
{ baseUrl: "http://ces-test:8090", serviceToken: "tok" },
|
package/openapi.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Cadet Gateway API",
|
|
5
|
-
"version": "0.12.
|
|
5
|
+
"version": "0.12.8",
|
|
6
6
|
"description": "Auto-generated OpenAPI specification for the Cadet Gateway HTTP endpoints."
|
|
7
7
|
},
|
|
8
|
-
"servers": [{ "url": "", "description": "Same-origin
|
|
8
|
+
"servers": [{ "url": "", "description": "Same-origin — the gateway" }],
|
|
9
9
|
"paths": {
|
|
10
10
|
"/v1/permissions/thresholds": {
|
|
11
11
|
"get": {
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"operationId": "conversationThresholdGet",
|
|
106
106
|
"summary": "Get a conversation's threshold override",
|
|
107
107
|
"tags": ["permissions"],
|
|
108
|
-
"description": "Returns { threshold: null } when no override exists.
|
|
108
|
+
"description": "Returns { threshold: null } when no override exists. Gateways predating that behavior returned 404 for the same condition; clients tolerate both during rollout.",
|
|
109
109
|
"parameters": [
|
|
110
110
|
{
|
|
111
111
|
"name": "conversation_id",
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"operationId": "channelAdmissionPolicyList",
|
|
210
210
|
"summary": "List per-channel admission floors",
|
|
211
211
|
"tags": ["channel-admission-policy"],
|
|
212
|
-
"description": "Returns one entry per enforced channel
|
|
212
|
+
"description": "Returns one entry per enforced channel, excluding exempt and hidden channels, seeded with defaults for channels without a stored row.",
|
|
213
213
|
"responses": {
|
|
214
214
|
"200": {
|
|
215
215
|
"description": "Successful response",
|
|
@@ -272,7 +272,7 @@
|
|
|
272
272
|
"in": "path",
|
|
273
273
|
"required": true,
|
|
274
274
|
"schema": { "type": "string" },
|
|
275
|
-
"description": "The channel type
|
|
275
|
+
"description": "The channel type, e.g. slack"
|
|
276
276
|
}
|
|
277
277
|
],
|
|
278
278
|
"requestBody": {
|
|
@@ -452,7 +452,7 @@
|
|
|
452
452
|
"in": "path",
|
|
453
453
|
"required": true,
|
|
454
454
|
"schema": { "type": "string" },
|
|
455
|
-
"description": "The declaring ingress source
|
|
455
|
+
"description": "The declaring ingress source, today a plugin name"
|
|
456
456
|
}
|
|
457
457
|
],
|
|
458
458
|
"requestBody": {
|
|
@@ -502,7 +502,7 @@
|
|
|
502
502
|
"in": "path",
|
|
503
503
|
"required": true,
|
|
504
504
|
"schema": { "type": "string" },
|
|
505
|
-
"description": "The declaring ingress source
|
|
505
|
+
"description": "The declaring ingress source, today a plugin name"
|
|
506
506
|
}
|
|
507
507
|
],
|
|
508
508
|
"responses": {
|
|
@@ -530,7 +530,7 @@
|
|
|
530
530
|
"operationId": "channelPermissionOverridesList",
|
|
531
531
|
"summary": "List channel-permission matrix cells",
|
|
532
532
|
"tags": ["channel-permission-overrides"],
|
|
533
|
-
"description": "Returns every persisted cell
|
|
533
|
+
"description": "Returns every persisted cell in the cascade selector × contact-type → RiskThreshold map. Unset cells fall through the cascade; the list contains only explicit overrides.",
|
|
534
534
|
"responses": {
|
|
535
535
|
"200": {
|
|
536
536
|
"description": "Successful response",
|
|
@@ -854,7 +854,7 @@
|
|
|
854
854
|
"operationId": "channelPermissionResolve",
|
|
855
855
|
"summary": "Resolve the effective channel-permission threshold",
|
|
856
856
|
"tags": ["channel-permission-overrides"],
|
|
857
|
-
"description": "Read-only cascade resolution for one coordinate: walks channel → channel_type → adapter → workspace for the given selector keys and contact-type, returning the winning cell's threshold and scope, or null when no cell matches
|
|
857
|
+
"description": "Read-only cascade resolution for one coordinate: walks channel → channel_type → adapter → workspace for the given selector keys and contact-type, returning the winning cell's threshold and scope, or null when no cell matches, in which case the caller falls through to the global thresholds. Same resolver the runtime evaluator uses over IPC.",
|
|
858
858
|
"requestBody": {
|
|
859
859
|
"required": true,
|
|
860
860
|
"content": {
|
|
@@ -931,7 +931,7 @@
|
|
|
931
931
|
"operationId": "channelPermissionOverrideDelete",
|
|
932
932
|
"summary": "Delete a channel-permission matrix cell",
|
|
933
933
|
"tags": ["channel-permission-overrides"],
|
|
934
|
-
"description": "Removes one cell by its composite key
|
|
934
|
+
"description": "Removes one cell by its composite key, selector × contact-type, letting the next cascade tier up win. Returns whether a cell was removed.",
|
|
935
935
|
"requestBody": {
|
|
936
936
|
"required": true,
|
|
937
937
|
"content": {
|
|
@@ -1027,7 +1027,7 @@
|
|
|
1027
1027
|
"operationId": "contactsUpsert",
|
|
1028
1028
|
"summary": "Create or update a contact",
|
|
1029
1029
|
"tags": ["contacts"],
|
|
1030
|
-
"description": "Gateway-native contact upsert
|
|
1030
|
+
"description": "Gateway-native contact upsert that dual-writes the gateway ACL store and the assistant info mirror. Matches by id, then by the provided type and address channel, else creates.",
|
|
1031
1031
|
"requestBody": {
|
|
1032
1032
|
"required": true,
|
|
1033
1033
|
"content": {
|
|
@@ -1085,7 +1085,7 @@
|
|
|
1085
1085
|
"address": { "type": "string" },
|
|
1086
1086
|
"isPrimary": { "type": "boolean" },
|
|
1087
1087
|
"externalChatId": {
|
|
1088
|
-
"description": "Delivery chat id. Omit to preserve the stored value. A legacy explicit null is accepted for compatibility and treated as omitted; clearing is not supported
|
|
1088
|
+
"description": "Delivery chat id. Omit to preserve the stored value. A legacy explicit null is accepted for compatibility and treated as omitted; clearing is not supported: a stored null is indistinguishable from never-learned, so no consumer could tell a clear from a gap.",
|
|
1089
1089
|
"type": "string"
|
|
1090
1090
|
},
|
|
1091
1091
|
"status": { "type": "string" },
|
|
@@ -1141,7 +1141,7 @@
|
|
|
1141
1141
|
},
|
|
1142
1142
|
{ "type": "null" }
|
|
1143
1143
|
],
|
|
1144
|
-
"description": "Per-contact auto-approve ceiling. Null means unset
|
|
1144
|
+
"description": "Per-contact auto-approve ceiling. Null means unset, inheriting the cascade."
|
|
1145
1145
|
},
|
|
1146
1146
|
"assistantMetadata": {
|
|
1147
1147
|
"anyOf": [
|
|
@@ -1184,7 +1184,7 @@
|
|
|
1184
1184
|
},
|
|
1185
1185
|
"externalUserId": {
|
|
1186
1186
|
"type": "string",
|
|
1187
|
-
"description": "Compat alias for address
|
|
1187
|
+
"description": "Compat alias for address, for older macOS clients"
|
|
1188
1188
|
},
|
|
1189
1189
|
"status": {
|
|
1190
1190
|
"anyOf": [
|
|
@@ -1479,7 +1479,7 @@
|
|
|
1479
1479
|
"operationId": "contactChannelVerify",
|
|
1480
1480
|
"summary": "Manually verify a contact channel",
|
|
1481
1481
|
"tags": ["contacts"],
|
|
1482
|
-
"description": "Guardian-only manual attestation: marks the channel active/verified in the gateway store
|
|
1482
|
+
"description": "Guardian-only manual attestation: marks the channel active/verified in the gateway store, the source of truth, with a best-effort assistant mirror. Idempotent.",
|
|
1483
1483
|
"parameters": [
|
|
1484
1484
|
{
|
|
1485
1485
|
"name": "channel_id",
|
|
@@ -1828,7 +1828,7 @@
|
|
|
1828
1828
|
"in": "query",
|
|
1829
1829
|
"required": false,
|
|
1830
1830
|
"schema": { "type": "string" },
|
|
1831
|
-
"description": "Filter by origin
|
|
1831
|
+
"description": "Filter by origin: default or user_defined"
|
|
1832
1832
|
},
|
|
1833
1833
|
{
|
|
1834
1834
|
"name": "tool",
|
|
@@ -1929,7 +1929,7 @@
|
|
|
1929
1929
|
},
|
|
1930
1930
|
"description": { "type": "string", "minLength": 1 },
|
|
1931
1931
|
"scope": {
|
|
1932
|
-
"description": "Compatibility field. Trust rules apply workspace-wide: the engine matches on
|
|
1932
|
+
"description": "Compatibility field. Trust rules apply workspace-wide: the engine matches on tool and pattern only, so a narrower scope cannot be honored and any value other than \"everywhere\" is rejected rather than stored broader than the consent it records.",
|
|
1933
1933
|
"type": "string",
|
|
1934
1934
|
"const": "everywhere"
|
|
1935
1935
|
}
|
package/package.json
CHANGED
|
@@ -126,7 +126,7 @@ function buildSpec(version: string) {
|
|
|
126
126
|
description:
|
|
127
127
|
"Auto-generated OpenAPI specification for the Cadet Gateway HTTP endpoints.",
|
|
128
128
|
},
|
|
129
|
-
servers: [{ url: "", description: "Same-origin
|
|
129
|
+
servers: [{ url: "", description: "Same-origin — the gateway" }],
|
|
130
130
|
paths,
|
|
131
131
|
});
|
|
132
132
|
}
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { CURRENT_POLICY_EPOCH } from "./policy.js";
|
|
42
42
|
import { mintToken } from "./token-service.js";
|
|
43
43
|
import type { ScopeProfile } from "./types.js";
|
|
44
|
+
import { getIsCloud } from "../cloud-url.js";
|
|
44
45
|
|
|
45
46
|
const log = getLogger("guardian-bootstrap");
|
|
46
47
|
|
|
@@ -1046,9 +1047,7 @@ async function fetchPlatformOwnerDisplayName(): Promise<string | null> {
|
|
|
1046
1047
|
return null;
|
|
1047
1048
|
}
|
|
1048
1049
|
|
|
1049
|
-
const isCloud =
|
|
1050
|
-
process.env.IS_CLOUD?.trim().toLowerCase() === "true" ||
|
|
1051
|
-
process.env.IS_CLOUD?.trim() === "1";
|
|
1050
|
+
const isCloud = getIsCloud();
|
|
1052
1051
|
if (!isCloud) return null;
|
|
1053
1052
|
|
|
1054
1053
|
const assistantApiKey = await readCredential(
|
package/src/cli/contacts.ts
CHANGED
|
@@ -204,7 +204,9 @@ export async function executeContactsCommand(
|
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
console.log("");
|
|
207
|
-
console.log(
|
|
207
|
+
console.log(
|
|
208
|
+
`${contacts.length} ${contacts.length === 1 ? "contact" : "contacts"}`,
|
|
209
|
+
);
|
|
208
210
|
return 0;
|
|
209
211
|
}
|
|
210
212
|
|
package/src/cli/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ function printUsage(): void {
|
|
|
15
15
|
console.log("Operator commands for gateway-owned state.");
|
|
16
16
|
console.log("");
|
|
17
17
|
console.log("Commands:");
|
|
18
|
-
console.log(" contacts Manage contact ACL
|
|
18
|
+
console.log(" contacts Manage the contact ACL with list, get, set-risk-threshold");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const args = process.argv.slice(2);
|
package/src/cloud-url.ts
CHANGED
|
@@ -6,9 +6,20 @@
|
|
|
6
6
|
* can safely append path segments.
|
|
7
7
|
*/
|
|
8
8
|
export function getCloudBaseUrl(): string {
|
|
9
|
-
const url = (process.env.CADET_CLOUD_URL?.trim() || "").replace(
|
|
10
|
-
/\/+$/,
|
|
11
|
-
"",
|
|
12
|
-
);
|
|
9
|
+
const url = (process.env.CADET_CLOUD_URL?.trim() || "").replace(/\/+$/, "");
|
|
13
10
|
return url || "https://cloud.mycadet.ai";
|
|
14
11
|
}
|
|
12
|
+
|
|
13
|
+
function str(name: string): string | undefined {
|
|
14
|
+
const v = process.env[name]?.trim();
|
|
15
|
+
return v || undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function flag(name: string): boolean {
|
|
19
|
+
const raw = str(name)?.toLowerCase();
|
|
20
|
+
return raw === "true" || raw === "1";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getIsCloud(): boolean {
|
|
24
|
+
return flag("IS_CLOUD");
|
|
25
|
+
}
|
|
@@ -55,7 +55,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
55
55
|
operationId: "conversationThresholdGet",
|
|
56
56
|
summary: "Get a conversation's threshold override",
|
|
57
57
|
description:
|
|
58
|
-
"Returns { threshold: null } when no override exists.
|
|
58
|
+
"Returns { threshold: null } when no override exists. Gateways predating that behavior returned 404 for the same condition; clients tolerate both during rollout.",
|
|
59
59
|
tags: ["permissions"],
|
|
60
60
|
pathParameters: [
|
|
61
61
|
{ name: "conversation_id", description: "The conversation id" },
|
|
@@ -44,7 +44,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
44
44
|
operationId: "channelAdmissionPolicyList",
|
|
45
45
|
summary: "List per-channel admission floors",
|
|
46
46
|
description:
|
|
47
|
-
"Returns one entry per enforced channel
|
|
47
|
+
"Returns one entry per enforced channel, excluding exempt and hidden channels, seeded with defaults for channels without a stored row.",
|
|
48
48
|
tags: ["channel-admission-policy"],
|
|
49
49
|
responseBody: z.object({
|
|
50
50
|
policies: z.array(ChannelPolicyViewSchema),
|
|
@@ -59,7 +59,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
59
59
|
"Upserts the channel's admission policy. Exempt and hidden channels return 403.",
|
|
60
60
|
tags: ["channel-admission-policy"],
|
|
61
61
|
pathParameters: [
|
|
62
|
-
{ name: "channel_type", description: "The channel type
|
|
62
|
+
{ name: "channel_type", description: "The channel type, e.g. slack" },
|
|
63
63
|
],
|
|
64
64
|
requestBody: SetChannelPolicyRequestSchema,
|
|
65
65
|
responseBody: z.object({ policy: ChannelPolicyViewSchema }),
|
|
@@ -110,7 +110,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
110
110
|
pathParameters: [
|
|
111
111
|
{
|
|
112
112
|
name: "source",
|
|
113
|
-
description: "The declaring ingress source
|
|
113
|
+
description: "The declaring ingress source, today a plugin name",
|
|
114
114
|
},
|
|
115
115
|
],
|
|
116
116
|
requestBody: ApproveChannelIngressRequestSchema,
|
|
@@ -127,7 +127,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
127
127
|
pathParameters: [
|
|
128
128
|
{
|
|
129
129
|
name: "source",
|
|
130
|
-
description: "The declaring ingress source
|
|
130
|
+
description: "The declaring ingress source, today a plugin name",
|
|
131
131
|
},
|
|
132
132
|
],
|
|
133
133
|
responseBody: z.object({ source: z.string(), revoked: z.boolean() }),
|
|
@@ -43,7 +43,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
43
43
|
operationId: "channelPermissionOverridesList",
|
|
44
44
|
summary: "List channel-permission matrix cells",
|
|
45
45
|
description:
|
|
46
|
-
"Returns every persisted cell
|
|
46
|
+
"Returns every persisted cell in the cascade selector × contact-type → RiskThreshold map. Unset cells fall through the cascade; the list contains only explicit overrides.",
|
|
47
47
|
tags: ["channel-permission-overrides"],
|
|
48
48
|
responseBody: z.object({
|
|
49
49
|
cells: z.array(ChannelPermissionCellRowSchema),
|
|
@@ -66,7 +66,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
66
66
|
operationId: "channelPermissionResolve",
|
|
67
67
|
summary: "Resolve the effective channel-permission threshold",
|
|
68
68
|
description:
|
|
69
|
-
"Read-only cascade resolution for one coordinate: walks channel → channel_type → adapter → workspace for the given selector keys and contact-type, returning the winning cell's threshold and scope, or null when no cell matches
|
|
69
|
+
"Read-only cascade resolution for one coordinate: walks channel → channel_type → adapter → workspace for the given selector keys and contact-type, returning the winning cell's threshold and scope, or null when no cell matches, in which case the caller falls through to the global thresholds. Same resolver the runtime evaluator uses over IPC.",
|
|
70
70
|
tags: ["channel-permission-overrides"],
|
|
71
71
|
requestBody: ResolveChannelPermissionRequestSchema,
|
|
72
72
|
responseBody: z.object({
|
|
@@ -84,7 +84,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
84
84
|
operationId: "channelPermissionOverrideDelete",
|
|
85
85
|
summary: "Delete a channel-permission matrix cell",
|
|
86
86
|
description:
|
|
87
|
-
"Removes one cell by its composite key
|
|
87
|
+
"Removes one cell by its composite key, selector × contact-type, letting the next cascade tier up win. Returns whether a cell was removed.",
|
|
88
88
|
tags: ["channel-permission-overrides"],
|
|
89
89
|
requestBody: ChannelPermissionCellKeySchema,
|
|
90
90
|
responseBody: z.object({ removed: z.boolean() }),
|
|
@@ -19,6 +19,7 @@ import { getLogger } from "../../logger.js";
|
|
|
19
19
|
import { isLoopbackAddress } from "../../util/is-loopback-address.js";
|
|
20
20
|
import { TUNNEL_FORWARDED_HEADER } from "../../tunnel/bridge-utils.js";
|
|
21
21
|
import { requestArrivedViaEdgeProxy } from "../edge-forwarded-header.js";
|
|
22
|
+
import { getIsCloud } from "../../cloud-url.js";
|
|
22
23
|
|
|
23
24
|
const log = getLogger("channel-verification-session-proxy");
|
|
24
25
|
|
|
@@ -190,15 +191,8 @@ export function createChannelVerificationSessionProxyHandler(
|
|
|
190
191
|
// Auth model for /v1/guardian/init — three deployment modes
|
|
191
192
|
// ─────────────────────────────────────────────────────────────────────
|
|
192
193
|
//
|
|
193
|
-
// 1.
|
|
194
|
-
// Auth is enforced UPSTREAM
|
|
195
|
-
// a request ever reaches this gateway pod. The pod is not directly
|
|
196
|
-
// reachable from the public internet — only vembda's authenticated
|
|
197
|
-
// proxy can route to it. GUARDIAN_BOOTSTRAP_SECRET is intentionally
|
|
198
|
-
// NOT provisioned in managed deployments (no Helm chart, vembda
|
|
199
|
-
// StatefulSet env, or k8s_resources.py provisions it) and there is
|
|
200
|
-
// no plan to add it. The bootstrap-secret mechanism is a laptop /
|
|
201
|
-
// Docker hatch artifact only.
|
|
194
|
+
// 1. Cadet Cloud:
|
|
195
|
+
// Auth is enforced UPSTREAM before a request ever reaches this gateway
|
|
202
196
|
//
|
|
203
197
|
// 2. Docker / laptop hatch (GUARDIAN_BOOTSTRAP_SECRET set):
|
|
204
198
|
// The CLI's hatch.ts mints a bootstrap secret and forwards it into
|
|
@@ -209,15 +203,7 @@ export function createChannelVerificationSessionProxyHandler(
|
|
|
209
203
|
// The lockfile + loopback guard is the sole defense — see below.
|
|
210
204
|
// Long-term direction: replace with an explicit pairing mechanism
|
|
211
205
|
// and retire GUARDIAN_BOOTSTRAP_SECRET entirely.
|
|
212
|
-
|
|
213
|
-
// NOTE TO SCANNERS / Codex: managed mode reaching this handler with no
|
|
214
|
-
// bootstrap secret is the EXPECTED state in production. It is not an
|
|
215
|
-
// auth bypass — vembda authenticates the caller before forwarding.
|
|
216
|
-
// Adding a fail-closed guard here on `IS_CLOUD=true && !secret`
|
|
217
|
-
// would brick every platform deployment.
|
|
218
|
-
const isCloud =
|
|
219
|
-
process.env.IS_CLOUD?.trim().toLowerCase() === "true" ||
|
|
220
|
-
process.env.IS_CLOUD?.trim() === "1";
|
|
206
|
+
const isCloud = getIsCloud();
|
|
221
207
|
|
|
222
208
|
// Bare-metal mode: restrict to loopback callers only. Without a
|
|
223
209
|
// bootstrap secret the lockfile is the sole guard, so a remote
|
|
@@ -364,7 +364,7 @@ export async function createInviteNative(
|
|
|
364
364
|
}
|
|
365
365
|
if (!isValidE164(input.expectedExternalUserId)) {
|
|
366
366
|
throw new InviteNativeError(
|
|
367
|
-
"expectedExternalUserId must be in E.164 format
|
|
367
|
+
"expectedExternalUserId must be in E.164 format, e.g. +15551234567",
|
|
368
368
|
400,
|
|
369
369
|
"BAD_REQUEST",
|
|
370
370
|
);
|
|
@@ -36,7 +36,7 @@ const ContactChannelPayloadSchema = z.object({
|
|
|
36
36
|
externalChatId: z.string().nullable(),
|
|
37
37
|
externalUserId: z
|
|
38
38
|
.string()
|
|
39
|
-
.describe("Compat alias for address
|
|
39
|
+
.describe("Compat alias for address, for older macOS clients"),
|
|
40
40
|
status: z.string().nullable(),
|
|
41
41
|
policy: z.string().nullable(),
|
|
42
42
|
verifiedAt: z.number().nullable(),
|
|
@@ -69,7 +69,7 @@ const ContactPayloadSchema = z.object({
|
|
|
69
69
|
interactionCount: z.number(),
|
|
70
70
|
lastInteraction: z.number().nullable(),
|
|
71
71
|
autoApproveThreshold: RiskThresholdSchema.nullable().describe(
|
|
72
|
-
"Per-contact auto-approve ceiling. Null means unset
|
|
72
|
+
"Per-contact auto-approve ceiling. Null means unset, inheriting the cascade.",
|
|
73
73
|
),
|
|
74
74
|
assistantMetadata: AssistantContactMetadataSchema.nullable(),
|
|
75
75
|
channels: z.array(ContactChannelPayloadSchema),
|
|
@@ -92,7 +92,7 @@ const UpsertContactChannelInputSchema = z.object({
|
|
|
92
92
|
.string()
|
|
93
93
|
.optional()
|
|
94
94
|
.describe(
|
|
95
|
-
"Delivery chat id. Omit to preserve the stored value. A legacy explicit null is accepted for compatibility and treated as omitted; clearing is not supported
|
|
95
|
+
"Delivery chat id. Omit to preserve the stored value. A legacy explicit null is accepted for compatibility and treated as omitted; clearing is not supported: a stored null is indistinguishable from never-learned, so no consumer could tell a clear from a gap.",
|
|
96
96
|
),
|
|
97
97
|
status: z.string().optional(),
|
|
98
98
|
policy: z.string().optional(),
|
|
@@ -200,7 +200,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
200
200
|
operationId: "contactsUpsert",
|
|
201
201
|
summary: "Create or update a contact",
|
|
202
202
|
description:
|
|
203
|
-
"Gateway-native contact upsert
|
|
203
|
+
"Gateway-native contact upsert that dual-writes the gateway ACL store and the assistant info mirror. Matches by id, then by the provided type and address channel, else creates.",
|
|
204
204
|
tags: ["contacts"],
|
|
205
205
|
requestBody: UpsertContactRequestSchema,
|
|
206
206
|
responseBody: z.object({
|
|
@@ -265,7 +265,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
265
265
|
operationId: "contactChannelVerify",
|
|
266
266
|
summary: "Manually verify a contact channel",
|
|
267
267
|
description:
|
|
268
|
-
"Guardian-only manual attestation: marks the channel active/verified in the gateway store
|
|
268
|
+
"Guardian-only manual attestation: marks the channel active/verified in the gateway store, the source of truth, with a best-effort assistant mirror. Idempotent.",
|
|
269
269
|
tags: ["contacts"],
|
|
270
270
|
pathParameters: [
|
|
271
271
|
{ name: "channel_id", description: "The contact-channel id" },
|
|
@@ -126,7 +126,7 @@ export function createFeatureFlagsPatchHandler(onFlagChanged?: () => void) {
|
|
|
126
126
|
return Response.json(
|
|
127
127
|
{
|
|
128
128
|
error:
|
|
129
|
-
"Invalid flag key format. Must be a simple kebab-case string
|
|
129
|
+
"Invalid flag key format. Must be a simple kebab-case string, e.g. 'browser' or 'contacts'",
|
|
130
130
|
},
|
|
131
131
|
{ status: 400 },
|
|
132
132
|
);
|
|
@@ -47,7 +47,7 @@ const CreateTrustRuleRequestSchema = z.object({
|
|
|
47
47
|
.literal("everywhere")
|
|
48
48
|
.optional()
|
|
49
49
|
.describe(
|
|
50
|
-
'Compatibility field. Trust rules apply workspace-wide: the engine matches on
|
|
50
|
+
'Compatibility field. Trust rules apply workspace-wide: the engine matches on tool and pattern only, so a narrower scope cannot be honored and any value other than "everywhere" is rejected rather than stored broader than the consent it records.',
|
|
51
51
|
),
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -116,7 +116,7 @@ export const ROUTES: GatewayRouteDefinition[] = [
|
|
|
116
116
|
queryParameters: [
|
|
117
117
|
{
|
|
118
118
|
name: "origin",
|
|
119
|
-
description: "Filter by origin
|
|
119
|
+
description: "Filter by origin: default or user_defined",
|
|
120
120
|
},
|
|
121
121
|
{ name: "tool", description: "Filter by tool name" },
|
|
122
122
|
{
|
|
@@ -423,7 +423,7 @@ export class RemoteFeatureFlagSync {
|
|
|
423
423
|
if (value === false && registry[key]?.defaultEnabled === true) {
|
|
424
424
|
log.debug(
|
|
425
425
|
{ key },
|
|
426
|
-
"Normalizing remote false for GA flag to true
|
|
426
|
+
"Normalizing remote false for GA flag to true — defaultEnabled is true",
|
|
427
427
|
);
|
|
428
428
|
values[key] = true;
|
|
429
429
|
continue;
|
package/src/schema.ts
CHANGED
|
@@ -302,7 +302,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
302
302
|
},
|
|
303
303
|
},
|
|
304
304
|
"405": {
|
|
305
|
-
description: "Method not allowed
|
|
305
|
+
description: "Method not allowed, only POST accepted",
|
|
306
306
|
content: {
|
|
307
307
|
"application/json": {
|
|
308
308
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
@@ -368,7 +368,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
368
368
|
},
|
|
369
369
|
},
|
|
370
370
|
"405": {
|
|
371
|
-
description: "Method not allowed
|
|
371
|
+
description: "Method not allowed, only POST accepted",
|
|
372
372
|
content: {
|
|
373
373
|
"application/json": {
|
|
374
374
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
@@ -822,7 +822,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
822
822
|
"application/json": {
|
|
823
823
|
schema: {
|
|
824
824
|
type: "object",
|
|
825
|
-
description: "Mailgun webhook payload
|
|
825
|
+
description: "Mailgun webhook payload as JSON.",
|
|
826
826
|
},
|
|
827
827
|
},
|
|
828
828
|
},
|
|
@@ -945,7 +945,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
945
945
|
get: {
|
|
946
946
|
summary: "Twilio Media Stream WebSocket",
|
|
947
947
|
description:
|
|
948
|
-
"Accepts a WebSocket upgrade from Twilio Media Streams and bidirectionally proxies frames to the assistant runtime's /v1/calls/media-stream endpoint. Handshake metadata
|
|
948
|
+
"Accepts a WebSocket upgrade from Twilio Media Streams and bidirectionally proxies frames to the assistant runtime's /v1/calls/media-stream endpoint. Handshake metadata, the callSessionId and auth token, is carried in URL path segments, e.g. /webhooks/twilio/media-stream/<callSessionId>/<token>, because Twilio Media Streams does not reliably preserve query parameters across the WebSocket upgrade. Legacy query-parameter-based handshake is still supported as a fallback.",
|
|
949
949
|
operationId: "twilioMediaStreamWebsocket",
|
|
950
950
|
parameters: [
|
|
951
951
|
{
|
|
@@ -1003,7 +1003,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1003
1003
|
required: false,
|
|
1004
1004
|
schema: { type: "string" },
|
|
1005
1005
|
description:
|
|
1006
|
-
"Optional STT provider identifier
|
|
1006
|
+
"Optional STT provider identifier, e.g. 'deepgram' or 'google-gemini'. Forwarded as compatibility metadata: the runtime resolves the transcriber from config (`services.stt.roles.dictation`, else `services.stt.provider`), not from this parameter. When supplied and it disagrees with the provider that resolves to, the runtime logs a mismatch warning.",
|
|
1007
1007
|
},
|
|
1008
1008
|
{
|
|
1009
1009
|
name: "mimeType",
|
|
@@ -1011,7 +1011,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1011
1011
|
required: true,
|
|
1012
1012
|
schema: { type: "string" },
|
|
1013
1013
|
description:
|
|
1014
|
-
"MIME type of the audio being streamed
|
|
1014
|
+
"MIME type of the audio being streamed, e.g. 'audio/webm;codecs=opus'.",
|
|
1015
1015
|
},
|
|
1016
1016
|
{
|
|
1017
1017
|
name: "sampleRate",
|
|
@@ -1035,7 +1035,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1035
1035
|
"WebSocket upgrade successful — bidirectional STT audio/transcription frame proxying begins.",
|
|
1036
1036
|
},
|
|
1037
1037
|
"400": {
|
|
1038
|
-
description: "Missing required query parameter
|
|
1038
|
+
description: "Missing required query parameter: mimeType",
|
|
1039
1039
|
content: {
|
|
1040
1040
|
"text/plain": {
|
|
1041
1041
|
schema: { type: "string" },
|
|
@@ -1084,7 +1084,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1084
1084
|
required: true,
|
|
1085
1085
|
schema: { type: "string" },
|
|
1086
1086
|
description:
|
|
1087
|
-
"MIME type of the narration audio being streamed
|
|
1087
|
+
"MIME type of the narration audio being streamed, e.g. 'audio/pcm'.",
|
|
1088
1088
|
},
|
|
1089
1089
|
{
|
|
1090
1090
|
name: "sampleRate",
|
|
@@ -1124,7 +1124,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1124
1124
|
"WebSocket upgrade successful - bidirectional watch stream frame proxying begins.",
|
|
1125
1125
|
},
|
|
1126
1126
|
"400": {
|
|
1127
|
-
description: "Missing required query parameter
|
|
1127
|
+
description: "Missing required query parameter: mimeType",
|
|
1128
1128
|
content: {
|
|
1129
1129
|
"text/plain": {
|
|
1130
1130
|
schema: { type: "string" },
|
|
@@ -1739,7 +1739,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1739
1739
|
"404": { description: "Channel not found" },
|
|
1740
1740
|
"409": {
|
|
1741
1741
|
description:
|
|
1742
|
-
"Invalid state transition
|
|
1742
|
+
"Invalid state transition, e.g. revoking a blocked channel",
|
|
1743
1743
|
},
|
|
1744
1744
|
"503": { description: "Bearer token not configured" },
|
|
1745
1745
|
"502": { description: "Failed to reach assistant runtime" },
|
|
@@ -1983,7 +1983,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1983
1983
|
required: false,
|
|
1984
1984
|
schema: { type: "string" },
|
|
1985
1985
|
description:
|
|
1986
|
-
"Optional verification purpose
|
|
1986
|
+
"Optional verification purpose, e.g. guardian or trusted-contact.",
|
|
1987
1987
|
},
|
|
1988
1988
|
{
|
|
1989
1989
|
name: "contactChannelId",
|
|
@@ -2126,7 +2126,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
2126
2126
|
"Loopback-only endpoint that removes the guardian-init lock file so that /v1/guardian/init can be called again. When bootstrap secrets are configured, callers must provide a valid x-bootstrap-secret header. Used by the desktop app to recover from a lost actor token.",
|
|
2127
2127
|
operationId: "guardianResetBootstrap",
|
|
2128
2128
|
responses: {
|
|
2129
|
-
"200": { description: "Lock file removed
|
|
2129
|
+
"200": { description: "Lock file removed, or already absent" },
|
|
2130
2130
|
"403": {
|
|
2131
2131
|
description:
|
|
2132
2132
|
"Forbidden — non-loopback origin or invalid bootstrap secret",
|
|
@@ -2183,7 +2183,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
2183
2183
|
responses: {
|
|
2184
2184
|
"200": { description: "New access token returned" },
|
|
2185
2185
|
"400": {
|
|
2186
|
-
description: "Missing required field
|
|
2186
|
+
description: "Missing required field: refreshToken or deviceId",
|
|
2187
2187
|
},
|
|
2188
2188
|
"401": { description: "Unauthorized — invalid token" },
|
|
2189
2189
|
"403": {
|
|
@@ -3314,11 +3314,11 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3314
3314
|
properties: {
|
|
3315
3315
|
startTime: {
|
|
3316
3316
|
type: "number",
|
|
3317
|
-
description: "Start of time range
|
|
3317
|
+
description: "Start of time range, in epoch ms",
|
|
3318
3318
|
},
|
|
3319
3319
|
endTime: {
|
|
3320
3320
|
type: "number",
|
|
3321
|
-
description: "End of time range
|
|
3321
|
+
description: "End of time range, in epoch ms",
|
|
3322
3322
|
},
|
|
3323
3323
|
auditLimit: {
|
|
3324
3324
|
type: "number",
|
|
@@ -3381,7 +3381,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3381
3381
|
enum: ["trace", "debug", "info", "warn", "error", "fatal"],
|
|
3382
3382
|
default: "info",
|
|
3383
3383
|
},
|
|
3384
|
-
description: "Minimum pino level name
|
|
3384
|
+
description: "Minimum pino level name, defaults to info",
|
|
3385
3385
|
},
|
|
3386
3386
|
{
|
|
3387
3387
|
name: "module",
|
|
@@ -3427,7 +3427,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3427
3427
|
get: {
|
|
3428
3428
|
summary: "List trust rules",
|
|
3429
3429
|
description:
|
|
3430
|
-
"Authenticated gateway endpoint that lists trust rules from the SQLite-backed store. By default returns user-relevant rules
|
|
3430
|
+
"Authenticated gateway endpoint that lists trust rules from the SQLite-backed store. By default returns user-relevant rules: user_defined + user-modified defaults. Supports `origin`, `tool`, and `include_deleted` query filters.",
|
|
3431
3431
|
operationId: "trustRulesGet",
|
|
3432
3432
|
security: [{ BearerAuth: [] }],
|
|
3433
3433
|
parameters: [
|
|
@@ -3752,7 +3752,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3752
3752
|
post: {
|
|
3753
3753
|
summary: "Resolve the effective channel-permission threshold",
|
|
3754
3754
|
description:
|
|
3755
|
-
"Authenticated gateway endpoint that resolves the cascade for one coordinate
|
|
3755
|
+
"Authenticated gateway endpoint that resolves the cascade for one coordinate — adapter, optional channelType/channelExternalId, contact-type — and returns the winning cell's threshold and scope, or null when no cell matches so the caller falls through to the global thresholds. Read-only; a POST verb path because the composite query travels in the body, same rationale as /delete.",
|
|
3756
3756
|
operationId: "channelPermissionResolve",
|
|
3757
3757
|
security: [{ BearerAuth: [] }],
|
|
3758
3758
|
requestBody: {
|
|
@@ -3764,7 +3764,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3764
3764
|
},
|
|
3765
3765
|
},
|
|
3766
3766
|
responses: {
|
|
3767
|
-
"200": { description: "Resolution returned
|
|
3767
|
+
"200": { description: "Resolution returned — resolved or null" },
|
|
3768
3768
|
"400": { description: "Invalid request payload or adapter" },
|
|
3769
3769
|
"401": {
|
|
3770
3770
|
description: "Unauthorized — missing or invalid bearer token",
|
|
@@ -3777,7 +3777,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
3777
3777
|
post: {
|
|
3778
3778
|
summary: "Delete a channel-permission matrix cell",
|
|
3779
3779
|
description:
|
|
3780
|
-
"Authenticated gateway endpoint that removes one channel-permission cell by its composite key
|
|
3780
|
+
"Authenticated gateway endpoint that removes one channel-permission cell by its composite key, selector × contact-type, letting the next cascade tier up win. A POST verb path because cells have no row id and DELETE request bodies are unreliable through proxies.",
|
|
3781
3781
|
operationId: "channelPermissionOverrideDelete",
|
|
3782
3782
|
security: [{ BearerAuth: [] }],
|
|
3783
3783
|
requestBody: {
|
|
@@ -4152,7 +4152,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
4152
4152
|
post: {
|
|
4153
4153
|
summary: "Kick off an async export-to-GCS job",
|
|
4154
4154
|
description:
|
|
4155
|
-
"Transparent proxy to the assistant daemon's `POST /v1/migrations/export-to-gcs` endpoint. The daemon schedules a background export job that streams the workspace backup to the supplied signed GCS upload URL and returns `202 Accepted` with a `job_id`. Callers poll `GET /v1/migrations/jobs/{jobId}` for progress. Registered explicitly
|
|
4155
|
+
"Transparent proxy to the assistant daemon's `POST /v1/migrations/export-to-gcs` endpoint. The daemon schedules a background export job that streams the workspace backup to the supplied signed GCS upload URL and returns `202 Accepted` with a `job_id`. Callers poll `GET /v1/migrations/jobs/{jobId}` for progress. Registered explicitly, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
|
|
4156
4156
|
operationId: "migrationExportToGcs",
|
|
4157
4157
|
security: [{ BearerAuth: [] }],
|
|
4158
4158
|
requestBody: {
|
|
@@ -4191,7 +4191,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
4191
4191
|
post: {
|
|
4192
4192
|
summary: "Kick off an async import-from-GCS job",
|
|
4193
4193
|
description:
|
|
4194
|
-
"Transparent proxy to the assistant daemon's `POST /v1/migrations/import-from-gcs` endpoint. The daemon schedules a background import job that fetches a `.vbundle` archive at the supplied `bundle_url` and streams it through the importer, returning `202 Accepted` with a `job_id`. Callers poll `GET /v1/migrations/jobs/{jobId}` for progress. Registered explicitly
|
|
4194
|
+
"Transparent proxy to the assistant daemon's `POST /v1/migrations/import-from-gcs` endpoint. The daemon schedules a background import job that fetches a `.vbundle` archive at the supplied `bundle_url` and streams it through the importer, returning `202 Accepted` with a `job_id`. Callers poll `GET /v1/migrations/jobs/{jobId}` for progress. Registered explicitly, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
|
|
4195
4195
|
operationId: "migrationImportFromGcs",
|
|
4196
4196
|
security: [{ BearerAuth: [] }],
|
|
4197
4197
|
requestBody: {
|
|
@@ -4241,7 +4241,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
4241
4241
|
get: {
|
|
4242
4242
|
summary: "Poll unified migration job status",
|
|
4243
4243
|
description:
|
|
4244
|
-
"Transparent proxy to the assistant daemon's `GET /v1/migrations/jobs/{job_id}` endpoint. Returns the current status of an async export-to-GCS or import-from-GCS job tracked by the daemon's migration job registry. Registered explicitly
|
|
4244
|
+
"Transparent proxy to the assistant daemon's `GET /v1/migrations/jobs/{job_id}` endpoint. Returns the current status of an async export-to-GCS or import-from-GCS job tracked by the daemon's migration job registry. Registered explicitly, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
|
|
4245
4245
|
operationId: "migrationJobStatus",
|
|
4246
4246
|
security: [{ BearerAuth: [] }],
|
|
4247
4247
|
parameters: [
|
|
@@ -4360,7 +4360,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
4360
4360
|
type: {
|
|
4361
4361
|
type: "string",
|
|
4362
4362
|
description:
|
|
4363
|
-
"Email provider type
|
|
4363
|
+
"Email provider type, e.g. resend, mailgun, cadet",
|
|
4364
4364
|
},
|
|
4365
4365
|
guardian_email: {
|
|
4366
4366
|
type: "string",
|
package/src/tunnel/client.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
TUNNEL_BINARY_WEBSOCKET_HEADER,
|
|
34
34
|
} from "./binary-websocket.js";
|
|
35
35
|
import { TunnelWebSocketBridge } from "./websocket-bridge.js";
|
|
36
|
+
import { getIsCloud } from "../cloud-url.js";
|
|
36
37
|
|
|
37
38
|
const log = getLogger("tunnel-client");
|
|
38
39
|
|
|
@@ -793,9 +794,7 @@ export function createTunnelClient(
|
|
|
793
794
|
},
|
|
794
795
|
): TunnelClient | undefined {
|
|
795
796
|
if (!config.tunnelBaseUrl) {
|
|
796
|
-
const isCloud =
|
|
797
|
-
process.env.IS_CLOUD?.trim().toLowerCase() === "true" ||
|
|
798
|
-
process.env.IS_CLOUD?.trim() === "1";
|
|
797
|
+
const isCloud = getIsCloud();
|
|
799
798
|
if (isCloud) {
|
|
800
799
|
log.warn(
|
|
801
800
|
"TUNNEL_BASE_URL is not configured on a cloud pod — the assistant tunnel will not be established and inbound webhook delivery will fail",
|