@mycadet/gateway 0.12.10 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  3. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  5. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  6. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  7. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  8. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  9. package/openapi.json +1 -1
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-revocation.test.ts +41 -34
  12. package/src/__tests__/contact-store-mirror-op-missing.test.ts +33 -70
  13. package/src/__tests__/contacts-mirror-op-reporter.test.ts +33 -89
  14. package/src/__tests__/credential-requests.test.ts +4 -4
  15. package/src/__tests__/devices-mine.test.ts +187 -0
  16. package/src/__tests__/edge-auth.test.ts +1 -20
  17. package/src/__tests__/edge-guardian-auth.test.ts +1 -17
  18. package/src/__tests__/feature-flag-resolver.test.ts +2 -30
  19. package/src/__tests__/feature-flag-watcher-callback.test.ts +0 -22
  20. package/src/__tests__/feature-flags-route.test.ts +2 -191
  21. package/src/__tests__/guardian-bootstrap-lookups.test.ts +22 -29
  22. package/src/__tests__/guardian-init-lockfile.test.ts +2 -2
  23. package/src/__tests__/guardian-integrity-reporter.test.ts +51 -82
  24. package/src/__tests__/guardian-integrity.test.ts +16 -20
  25. package/src/__tests__/handle-inbound-trust-verdict.test.ts +1 -14
  26. package/src/__tests__/ipc-feature-flag-routes.test.ts +0 -56
  27. package/src/__tests__/lum-2783-recovery-e2e.test.ts +0 -12
  28. package/src/__tests__/post-assistant-ready-deferred.test.ts +0 -18
  29. package/src/__tests__/refresh-device-binding.test.ts +0 -12
  30. package/src/__tests__/remote-web-pairing-token.test.ts +5 -15
  31. package/src/__tests__/runtime-client.test.ts +7 -7
  32. package/src/__tests__/speech-relay-websocket.test.ts +4 -4
  33. package/src/__tests__/trust-verdict-resolver-guardian-integrity.test.ts +30 -36
  34. package/src/__tests__/twilio-webhooks.test.ts +2 -2
  35. package/src/auth/actor-token-revocation.ts +34 -9
  36. package/src/auth/guardian-bootstrap.ts +1 -1
  37. package/src/cli/__tests__/pairing.test.ts +95 -0
  38. package/src/cli/index.ts +1 -1
  39. package/src/cli/pairing.ts +81 -6
  40. package/src/contacts-mirror-op-reporter.ts +19 -36
  41. package/src/db/connection.ts +0 -10
  42. package/src/feature-flag-resolver.ts +2 -7
  43. package/src/feature-flag-watcher.ts +1 -14
  44. package/src/guardian-integrity-reporter.ts +19 -47
  45. package/src/http/middleware/auth.ts +33 -27
  46. package/src/http/routes/auth-token.ts +1 -1
  47. package/src/http/routes/devices-mine.ts +103 -0
  48. package/src/http/routes/feature-flags.ts +1 -8
  49. package/src/http/routes/guardian-pin.ts +2 -11
  50. package/src/http/routes/ipc-runtime-proxy.ts +1 -1
  51. package/src/http/routes/live-voice-websocket.ts +1 -1
  52. package/src/http/routes/runtime-audio-stream.ts +1 -1
  53. package/src/http/routes/runtime-proxy.ts +2 -2
  54. package/src/http/routes/speech-relay-websocket.ts +2 -2
  55. package/src/http/routes/twilio-voice-webhook.test.ts +13 -13
  56. package/src/index.ts +11 -198
  57. package/src/ipc/feature-flag-handlers.ts +2 -9
  58. package/src/remote-web/pairing-challenge-store.ts +15 -0
  59. package/src/risk/command-registry/commands/assistant.ts +0 -24
  60. package/src/risk/command-registry.test.ts +2 -4
  61. package/src/schema.ts +44 -372
  62. package/src/__tests__/auth-fallback-count-tracker.test.ts +0 -92
  63. package/src/__tests__/auth-fallback-reporter.test.ts +0 -101
  64. package/src/__tests__/backup-worker-cloud-mode.test.ts +0 -44
  65. package/src/__tests__/backup-worker-export-timeout.test.ts +0 -164
  66. package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -216
  67. package/src/__tests__/remote-feature-flag-sync.test.ts +0 -961
  68. package/src/auth-fallback-count-tracker.ts +0 -113
  69. package/src/auth-fallback-reporter.ts +0 -127
  70. package/src/backup/backup-key.ts +0 -138
  71. package/src/backup/backup-routes.ts +0 -161
  72. package/src/backup/backup-worker.ts +0 -425
  73. package/src/backup/list-snapshots.ts +0 -97
  74. package/src/backup/local-writer.ts +0 -87
  75. package/src/backup/offsite-writer.ts +0 -182
  76. package/src/backup/paths.ts +0 -113
  77. package/src/backup/stream-crypt.ts +0 -258
  78. package/src/device-id.ts +0 -18
  79. package/src/feature-flag-remote-store.ts +0 -166
  80. package/src/http/routes/migration-proxy.ts +0 -636
  81. package/src/internal-telemetry-client.ts +0 -39
  82. package/src/ipc/debug-export-handlers.test.ts +0 -102
  83. package/src/ipc/debug-export-handlers.ts +0 -81
  84. package/src/remote-feature-flag-sync.ts +0 -440
  85. package/src/watchdog-reporter.ts +0 -110
@@ -11,7 +11,6 @@ import { z } from "zod";
11
11
 
12
12
  import { loadFeatureFlagDefaults } from "../feature-flag-defaults.js";
13
13
  import { readEnvFeatureFlagOverrides } from "../feature-flag-env-overrides.js";
14
- import { readRemoteFeatureFlags } from "../feature-flag-remote-store.js";
15
14
  import { readPersistedFeatureFlags } from "../feature-flag-store.js";
16
15
  import type { IpcRoute } from "./server.js";
17
16
 
@@ -20,24 +19,18 @@ const GetFeatureFlagParamsSchema = z.object({
20
19
  });
21
20
 
22
21
  /**
23
- * Compute the merged feature flag state: defaults < remote < persisted.
22
+ * Compute the merged feature flag state: defaults < persisted.
24
23
  * Returns a `Record<string, boolean | string>` keyed by flag name.
25
24
  */
26
25
  export function getMergedFeatureFlags(): FeatureFlagsIpcResponse {
27
26
  const defaults = loadFeatureFlagDefaults();
28
27
  const persisted = readPersistedFeatureFlags();
29
- const remote = readRemoteFeatureFlags();
30
28
 
31
29
  const result: Record<string, boolean | string> = {};
32
30
  for (const [key, def] of Object.entries(defaults)) {
33
31
  const persistedValue = persisted[key];
34
- const remoteValue = remote[key];
35
32
  result[key] =
36
- persistedValue !== undefined
37
- ? persistedValue
38
- : remoteValue !== undefined
39
- ? remoteValue
40
- : def.defaultEnabled;
33
+ persistedValue !== undefined ? persistedValue : def.defaultEnabled;
41
34
  }
42
35
 
43
36
  const envOverrides = readEnvFeatureFlagOverrides();
@@ -331,6 +331,21 @@ export function listPendingRemoteWebPairingChallenges(): RemoteWebPairingRequest
331
331
  }));
332
332
  }
333
333
 
334
+ /**
335
+ * Every persisted pairing challenge — pending, approved, exchanging,
336
+ * consumed, or past expiry — newest first. Unlike the pending-list route
337
+ * this deletes nothing: expired challenges linger until their own
338
+ * verification/exchange paths clean them up, and this list is how the
339
+ * operator sees that tail state. Liveness (active vs expired) is the
340
+ * caller's call — it compares expiresAtMs against its own clock.
341
+ */
342
+ export function listPersistedRemoteWebPairingChallenges(): PendingRemoteWebPairingChallenge[] {
343
+ loadFromDisk();
344
+ return [...challengesByUserCodeHash.values()].sort(
345
+ (a, b) => b.createdAtMs - a.createdAtMs,
346
+ );
347
+ }
348
+
334
349
  export function approveRemoteWebPairingChallengeById(
335
350
  id: string,
336
351
  ): ApproveRemoteWebPairingChallengeResult {
@@ -33,19 +33,6 @@ const ASSISTANT_SUPPORTED_COMMAND_PATHS = [
33
33
  "avatar character update",
34
34
  "avatar character components",
35
35
  "avatar character ascii",
36
- "backup",
37
- "backup enable",
38
- "backup disable",
39
- "backup destinations",
40
- "backup destinations list",
41
- "backup destinations add",
42
- "backup destinations remove",
43
- "backup destinations set-encrypt",
44
- "backup status",
45
- "backup list",
46
- "backup create",
47
- "backup restore",
48
- "backup verify",
49
36
  "bash",
50
37
  "browser",
51
38
  "browser navigate",
@@ -449,11 +436,6 @@ const riskOverrides: AssistantRiskOverride[] = [
449
436
  reason: "Deletes API key material",
450
437
  },
451
438
  // Destructive assistant-state operations
452
- {
453
- path: "backup restore",
454
- risk: "high",
455
- reason: "Restores backup and overwrites workspace state",
456
- },
457
439
  {
458
440
  path: "conversations clear",
459
441
  risk: "medium",
@@ -471,12 +453,6 @@ const riskOverrides: AssistantRiskOverride[] = [
471
453
  { path: "avatar set", risk: "low" },
472
454
  { path: "avatar remove", risk: "low" },
473
455
  { path: "avatar character update", risk: "low" },
474
- { path: "backup enable", risk: "low" },
475
- { path: "backup disable", risk: "high" },
476
- { path: "backup destinations add", risk: "high" },
477
- { path: "backup destinations remove", risk: "high" },
478
- { path: "backup destinations set-encrypt", risk: "high" },
479
- { path: "backup create", risk: "low" },
480
456
  { path: "cache set", risk: "low" },
481
457
  { path: "cache delete", risk: "low" },
482
458
  { path: "channel-verification-sessions create", risk: "high" },
@@ -525,8 +525,8 @@ describe("command-registry", () => {
525
525
  expect(assistantSubs.platform.baseRisk).toBe("low");
526
526
  });
527
527
 
528
- test("assistant backup is low risk", () => {
529
- expect(assistantSubs.backup.baseRisk).toBe("low");
528
+ test("assistant backup is not registered", () => {
529
+ expect(assistantSubs.backup).toBeUndefined();
530
530
  });
531
531
 
532
532
  test("assistant help is low risk", () => {
@@ -585,7 +585,6 @@ describe("command-registry", () => {
585
585
  "audit",
586
586
  "auth",
587
587
  "avatar",
588
- "backup",
589
588
  "bash",
590
589
  "browser",
591
590
  "cache",
@@ -649,7 +648,6 @@ describe("command-registry", () => {
649
648
  expect(getAssistantPath("email send").baseRisk).toBe("high");
650
649
  expect(getAssistantPath("domain register").baseRisk).toBe("medium");
651
650
  expect(getAssistantPath("conversations clear").baseRisk).toBe("medium");
652
- expect(getAssistantPath("backup restore").baseRisk).toBe("high");
653
651
  expect(getAssistantPath("inference session open").baseRisk).toBe("low");
654
652
  expect(getAssistantPath("inference session close").baseRisk).toBe("low");
655
653
  expect(getAssistantPath("inference session list").baseRisk).toBe("low");
package/src/schema.ts CHANGED
@@ -3989,309 +3989,6 @@ export function buildSchema(): Record<string, unknown> {
3989
3989
  },
3990
3990
  },
3991
3991
  },
3992
- "/v1/migrations/export": {
3993
- post: {
3994
- summary: "Export workspace backup",
3995
- description:
3996
- "Proxies a migration export request to the assistant daemon. Returns a binary .vbundle backup of the workspace. Authenticated with an edge JWT. Timeout is 120 seconds to accommodate large workspaces.",
3997
- operationId: "migrationExport",
3998
- security: [{ BearerAuth: [] }],
3999
- requestBody: {
4000
- required: false,
4001
- content: {
4002
- "application/json": {
4003
- schema: { type: "object", additionalProperties: true },
4004
- },
4005
- },
4006
- },
4007
- responses: {
4008
- "200": {
4009
- description: "Binary .vbundle backup file",
4010
- content: {
4011
- "application/octet-stream": {
4012
- schema: { type: "string", format: "binary" },
4013
- },
4014
- },
4015
- },
4016
- "401": {
4017
- description: "Unauthorized — missing or invalid bearer token",
4018
- },
4019
- "502": { description: "Failed to reach assistant daemon" },
4020
- "504": { description: "Assistant daemon request timed out" },
4021
- },
4022
- },
4023
- },
4024
- "/v1/migrations/import": {
4025
- post: {
4026
- summary: "Import workspace backup",
4027
- description:
4028
- "Proxies a migration import request to the assistant. Request shapes:\n" +
4029
- "\n" +
4030
- " - `application/octet-stream`: raw .vbundle body. The request is proxied synchronously and the caller's connection stays open for the full import duration (returns 200 on success).\n" +
4031
- ' - `application/json` with `{ "url": "<signed GCS URL>" }` or `{ "path": "<staged workspace path>" }`: the gateway generates a jobId, kicks off the upstream assistant call in the background, and returns `202 Accepted` with `{ job_id, status: "pending" }` immediately. Callers poll `GET /v1/migrations/import/{jobId}/status` for progress. The path form points at a `.vbundle` staged under the assistant workspace `.restore-staging` directory.\n' +
4032
- "\n" +
4033
- "Authenticated with an edge JWT. Synchronous-path timeout is 60 minutes to accommodate large 8 GB backups; the async path returns immediately.",
4034
- operationId: "migrationImport",
4035
- security: [{ BearerAuth: [] }],
4036
- requestBody: {
4037
- required: true,
4038
- content: {
4039
- "application/octet-stream": {
4040
- schema: { type: "string", format: "binary" },
4041
- },
4042
- "application/json": {
4043
- schema: {
4044
- oneOf: [
4045
- {
4046
- type: "object",
4047
- required: ["url"],
4048
- properties: {
4049
- url: {
4050
- type: "string",
4051
- format: "uri",
4052
- description:
4053
- "Signed GCS URL pointing at a .vbundle archive.",
4054
- },
4055
- },
4056
- },
4057
- {
4058
- type: "object",
4059
- required: ["path"],
4060
- properties: {
4061
- path: {
4062
- type: "string",
4063
- minLength: 1,
4064
- description:
4065
- "Workspace-relative or absolute path to a staged .vbundle under .restore-staging.",
4066
- },
4067
- },
4068
- },
4069
- ],
4070
- },
4071
- },
4072
- },
4073
- },
4074
- responses: {
4075
- "200": {
4076
- description:
4077
- "Backup imported successfully (synchronous byte path).",
4078
- },
4079
- "202": {
4080
- description:
4081
- "Import accepted for async processing (JSON url or path body). Poll `/v1/migrations/import/{jobId}/status` for progress.",
4082
- content: {
4083
- "application/json": {
4084
- schema: {
4085
- type: "object",
4086
- required: ["job_id", "status"],
4087
- properties: {
4088
- job_id: { type: "string" },
4089
- status: { type: "string", enum: ["pending"] },
4090
- },
4091
- },
4092
- },
4093
- },
4094
- },
4095
- "401": {
4096
- description: "Unauthorized — missing or invalid bearer token",
4097
- },
4098
- "502": { description: "Failed to reach assistant daemon" },
4099
- "504": { description: "Assistant daemon request timed out" },
4100
- },
4101
- },
4102
- },
4103
- "/v1/migrations/import/{jobId}/status": {
4104
- get: {
4105
- summary: "Poll async import job status",
4106
- description:
4107
- "Returns the current status of an async `.vbundle` import kicked off by `POST /v1/migrations/import` with a JSON `{url}` or `{path}` body. Finished jobs remain queryable for 30 minutes before being pruned.",
4108
- operationId: "migrationImportStatus",
4109
- security: [{ BearerAuth: [] }],
4110
- parameters: [
4111
- {
4112
- in: "path",
4113
- name: "jobId",
4114
- required: true,
4115
- schema: { type: "string" },
4116
- description:
4117
- "The `job_id` returned by the 202 response from `POST /v1/migrations/import`.",
4118
- },
4119
- ],
4120
- responses: {
4121
- "200": {
4122
- description: "Current job status.",
4123
- content: {
4124
- "application/json": {
4125
- schema: {
4126
- type: "object",
4127
- required: ["job_id", "status"],
4128
- properties: {
4129
- job_id: { type: "string" },
4130
- status: {
4131
- type: "string",
4132
- enum: ["pending", "processing", "complete", "failed"],
4133
- },
4134
- error: { type: "string" },
4135
- result: {},
4136
- },
4137
- },
4138
- },
4139
- },
4140
- },
4141
- "401": {
4142
- description: "Unauthorized — missing or invalid bearer token",
4143
- },
4144
- "404": {
4145
- description:
4146
- "Unknown job — never issued, or pruned after 30-minute TTL.",
4147
- },
4148
- },
4149
- },
4150
- },
4151
- "/v1/migrations/export-to-gcs": {
4152
- post: {
4153
- summary: "Kick off an async export-to-GCS job",
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, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
4156
- operationId: "migrationExportToGcs",
4157
- security: [{ BearerAuth: [] }],
4158
- requestBody: {
4159
- required: true,
4160
- content: {
4161
- "application/json": {
4162
- schema: { type: "object", additionalProperties: true },
4163
- },
4164
- },
4165
- },
4166
- responses: {
4167
- "202": {
4168
- description: "Export job accepted.",
4169
- content: {
4170
- "application/json": {
4171
- schema: {
4172
- type: "object",
4173
- required: ["job_id"],
4174
- properties: { job_id: { type: "string" } },
4175
- },
4176
- },
4177
- },
4178
- },
4179
- "401": {
4180
- description: "Unauthorized — missing or invalid bearer token",
4181
- },
4182
- "409": {
4183
- description: "Another export is already in flight.",
4184
- },
4185
- "502": { description: "Failed to reach assistant daemon" },
4186
- "504": { description: "Assistant daemon request timed out" },
4187
- },
4188
- },
4189
- },
4190
- "/v1/migrations/import-from-gcs": {
4191
- post: {
4192
- summary: "Kick off an async import-from-GCS job",
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, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
4195
- operationId: "migrationImportFromGcs",
4196
- security: [{ BearerAuth: [] }],
4197
- requestBody: {
4198
- required: true,
4199
- content: {
4200
- "application/json": {
4201
- schema: {
4202
- type: "object",
4203
- required: ["bundle_url"],
4204
- properties: {
4205
- bundle_url: {
4206
- type: "string",
4207
- format: "uri",
4208
- description:
4209
- "Signed GCS URL pointing at a .vbundle archive.",
4210
- },
4211
- },
4212
- },
4213
- },
4214
- },
4215
- },
4216
- responses: {
4217
- "202": {
4218
- description: "Import job accepted.",
4219
- content: {
4220
- "application/json": {
4221
- schema: {
4222
- type: "object",
4223
- required: ["job_id"],
4224
- properties: { job_id: { type: "string" } },
4225
- },
4226
- },
4227
- },
4228
- },
4229
- "401": {
4230
- description: "Unauthorized — missing or invalid bearer token",
4231
- },
4232
- "409": {
4233
- description: "Another import is already in flight.",
4234
- },
4235
- "502": { description: "Failed to reach assistant daemon" },
4236
- "504": { description: "Assistant daemon request timed out" },
4237
- },
4238
- },
4239
- },
4240
- "/v1/migrations/jobs/{jobId}": {
4241
- get: {
4242
- summary: "Poll unified migration job status",
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, not via the runtime-proxy catch-all, for dedicated auth and timeout handling.",
4245
- operationId: "migrationJobStatus",
4246
- security: [{ BearerAuth: [] }],
4247
- parameters: [
4248
- {
4249
- in: "path",
4250
- name: "jobId",
4251
- required: true,
4252
- schema: { type: "string" },
4253
- description:
4254
- "The `job_id` returned by the 202 response from `POST /v1/migrations/export-to-gcs` or `POST /v1/migrations/import-from-gcs`.",
4255
- },
4256
- ],
4257
- responses: {
4258
- "200": {
4259
- description: "Current job status.",
4260
- content: {
4261
- "application/json": {
4262
- schema: {
4263
- type: "object",
4264
- required: ["job_id", "type", "status"],
4265
- properties: {
4266
- job_id: { type: "string" },
4267
- type: {
4268
- type: "string",
4269
- enum: ["export", "import"],
4270
- },
4271
- status: {
4272
- type: "string",
4273
- enum: ["processing", "complete", "failed"],
4274
- },
4275
- result: {},
4276
- error: { type: "string" },
4277
- error_code: { type: "string" },
4278
- upstream_status: { type: "integer" },
4279
- },
4280
- },
4281
- },
4282
- },
4283
- },
4284
- "401": {
4285
- description: "Unauthorized — missing or invalid bearer token",
4286
- },
4287
- "404": {
4288
- description: "Unknown job_id.",
4289
- },
4290
- "502": { description: "Failed to reach assistant daemon" },
4291
- "504": { description: "Assistant daemon request timed out" },
4292
- },
4293
- },
4294
- },
4295
3992
  "/v1/admin/workspace-commit": {
4296
3993
  post: {
4297
3994
  summary: "Commit workspace changes",
@@ -4431,50 +4128,37 @@ export function buildSchema(): Record<string, unknown> {
4431
4128
  },
4432
4129
  },
4433
4130
  },
4434
- "/v1/backups": {
4131
+ "/v1/devices/mine": {
4435
4132
  get: {
4436
- summary: "List backup snapshots",
4133
+ summary: "The caller's own paired-device row",
4437
4134
  description:
4438
- "Lists local and offsite backup snapshots. The gateway owns the backup encryption key and performs all encrypt/decrypt operations.",
4439
- operationId: "backupsList",
4135
+ "Returns the paired-device row matching the presenting bearer token, so the paired web UI can surface 'this device' for matching against the host-side `cadet devices` list. Browser-reachable: the bearer is the credential, and the route answers only for the token presenting it. Not available on cloud-hosted assistants (they have no paired-device rows).",
4136
+ operationId: "getMyDevice",
4440
4137
  security: [{ BearerAuth: [] }],
4441
4138
  responses: {
4442
4139
  "200": {
4443
- description: "Backup snapshots listed",
4140
+ description: "The caller's paired-device row",
4444
4141
  content: {
4445
4142
  "application/json": {
4446
4143
  schema: {
4447
4144
  type: "object",
4448
- required: ["local", "offsite"],
4145
+ required: ["device"],
4449
4146
  properties: {
4450
- local: {
4147
+ device: {
4451
4148
  type: "object",
4452
- required: ["directory", "snapshots"],
4149
+ required: ["hashedDeviceId", "platform", "issuedAt"],
4453
4150
  properties: {
4454
- directory: { type: "string" },
4455
- snapshots: {
4456
- type: "array",
4457
- items: {
4458
- $ref: "#/components/schemas/BackupSnapshot",
4459
- },
4151
+ hashedDeviceId: { type: "string" },
4152
+ platform: { type: "string" },
4153
+ pairingUserAgent: {
4154
+ type: ["string", "null"],
4460
4155
  },
4461
- },
4462
- },
4463
- offsite: {
4464
- type: "array",
4465
- items: {
4466
- type: "object",
4467
- required: ["directory", "encrypted", "snapshots"],
4468
- properties: {
4469
- directory: { type: "string" },
4470
- encrypted: { type: "boolean" },
4471
- snapshots: {
4472
- type: "array",
4473
- items: {
4474
- $ref: "#/components/schemas/BackupSnapshot",
4475
- },
4476
- },
4156
+ clientReportedName: {
4157
+ type: ["string", "null"],
4477
4158
  },
4159
+ issuedAt: { type: "string" },
4160
+ expiresAt: { type: ["string", "null"] },
4161
+ lastUsedAt: { type: ["string", "null"] },
4478
4162
  },
4479
4163
  },
4480
4164
  },
@@ -4483,51 +4167,22 @@ export function buildSchema(): Record<string, unknown> {
4483
4167
  },
4484
4168
  },
4485
4169
  "401": {
4486
- description: "Unauthorized — missing or invalid bearer token",
4487
- },
4488
- "403": { description: "Insufficient scope" },
4489
- "500": { description: "Internal server error" },
4490
- },
4491
- },
4492
- },
4493
- "/v1/backups/create": {
4494
- post: {
4495
- summary: "Create backup snapshot",
4496
- description:
4497
- "Triggers a manual backup snapshot. The gateway exports a plaintext vbundle from the daemon, writes it locally, and encrypts + mirrors to offsite destinations.",
4498
- operationId: "backupsCreate",
4499
- security: [{ BearerAuth: [] }],
4500
- responses: {
4501
- "200": {
4502
- description: "Backup snapshot created",
4170
+ description: "Missing or invalid bearer token",
4503
4171
  content: {
4504
4172
  "application/json": {
4505
- schema: {
4506
- type: "object",
4507
- required: ["success", "local", "offsite", "duration_ms"],
4508
- properties: {
4509
- success: { type: "boolean" },
4510
- local: {
4511
- $ref: "#/components/schemas/BackupSnapshot",
4512
- },
4513
- offsite: {
4514
- type: "array",
4515
- items: { type: "object", additionalProperties: true },
4516
- },
4517
- duration_ms: { type: "number" },
4518
- },
4519
- },
4173
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
4520
4174
  },
4521
4175
  },
4522
4176
  },
4523
- "401": {
4524
- description: "Unauthorized — missing or invalid bearer token",
4525
- },
4526
- "403": { description: "Insufficient scope" },
4527
- "409": {
4528
- description: "A backup snapshot is already in progress",
4177
+ "404": {
4178
+ description:
4179
+ "No paired-device row for this token, or the gateway is cloud-hosted",
4180
+ content: {
4181
+ "application/json": {
4182
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
4183
+ },
4184
+ },
4529
4185
  },
4530
- "500": { description: "Internal server error" },
4531
4186
  },
4532
4187
  },
4533
4188
  },
@@ -4628,6 +4283,23 @@ export function buildSchema(): Record<string, unknown> {
4628
4283
  },
4629
4284
  },
4630
4285
  },
4286
+ "404": {
4287
+ description: "Route not found on upstream runtime",
4288
+ content: {
4289
+ "application/json": {
4290
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
4291
+ },
4292
+ },
4293
+ },
4294
+ "500": {
4295
+ description:
4296
+ "Server misconfigured (proxy auth enabled without token)",
4297
+ content: {
4298
+ "application/json": {
4299
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
4300
+ },
4301
+ },
4302
+ },
4631
4303
  "502": {
4632
4304
  description: "Upstream connection failed",
4633
4305
  content: {