@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.23

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 (263) hide show
  1. package/dist/admin/admin.controller.d.ts +3 -3
  2. package/dist/admin/admin.controller.js +6 -2
  3. package/dist/admin/admin.schemas.d.ts +13 -0
  4. package/dist/agents/admin-agents.d.ts +16 -3
  5. package/dist/agents/admin-agents.js +65 -7
  6. package/dist/agents/agents.schemas.d.ts +24 -6
  7. package/dist/agents/agents.service.d.ts +4 -3
  8. package/dist/agents/agents.service.js +39 -26
  9. package/dist/agents/definition.d.ts +44 -6
  10. package/dist/agents/definition.js +86 -18
  11. package/dist/agents/from-template.d.ts +6 -3
  12. package/dist/agents/from-template.js +57 -44
  13. package/dist/agents/org-templates.js +97 -41
  14. package/dist/agents/templates.d.ts +6 -0
  15. package/dist/agents/templates.js +15 -1
  16. package/dist/background/launch.d.ts +43 -5
  17. package/dist/background/launch.js +88 -8
  18. package/dist/background/session.d.ts +14 -2
  19. package/dist/background/session.js +26 -4
  20. package/dist/background/status.d.ts +1 -1
  21. package/dist/background/status.js +3 -3
  22. package/dist/background/stop.d.ts +0 -2
  23. package/dist/background/stop.js +1 -21
  24. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  25. package/dist/catalog/admin-catalog.controller.js +2 -6
  26. package/dist/catalog/catalog-content.d.ts +5 -0
  27. package/dist/catalog/catalog-content.js +1 -0
  28. package/dist/catalog/catalog.module.js +3 -0
  29. package/dist/catalog/claude/report.d.ts +9 -2
  30. package/dist/catalog/claude/report.js +1 -0
  31. package/dist/catalog/claude/translate.js +1 -1
  32. package/dist/catalog/component-promotion.js +1 -2
  33. package/dist/catalog/component-state.d.ts +13 -1
  34. package/dist/catalog/component-state.js +14 -5
  35. package/dist/catalog/marketplaces.service.d.ts +1 -1
  36. package/dist/catalog/marketplaces.service.js +2 -1
  37. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  38. package/dist/catalog/org-catalog.controller.js +26 -4
  39. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  40. package/dist/catalog/org-catalog.schemas.js +34 -0
  41. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  42. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  43. package/dist/catalog/org-plugin-uploads.js +0 -3
  44. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  45. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  46. package/dist/catalog/platform-components.d.ts +0 -1
  47. package/dist/catalog/platform-components.js +3 -8
  48. package/dist/catalog/plugin-import.d.ts +0 -8
  49. package/dist/catalog/plugin-import.js +4 -35
  50. package/dist/catalog/plugin-install.d.ts +2 -2
  51. package/dist/catalog/plugin-install.js +2 -2
  52. package/dist/catalog/plugin-package.d.ts +7 -0
  53. package/dist/catalog/plugin-package.js +27 -1
  54. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  55. package/dist/catalog/plugin-upgrade.js +5 -23
  56. package/dist/catalog/requests.service.d.ts +66 -10
  57. package/dist/catalog/requests.service.js +196 -15
  58. package/dist/channels/actions.d.ts +2 -0
  59. package/dist/channels/actions.js +19 -4
  60. package/dist/channels/cards.d.ts +16 -2
  61. package/dist/channels/cards.js +40 -7
  62. package/dist/channels/channels.module.js +3 -0
  63. package/dist/channels/outbound.d.ts +22 -1
  64. package/dist/channels/outbound.js +116 -7
  65. package/dist/channels/phrases.d.ts +25 -0
  66. package/dist/channels/phrases.js +19 -0
  67. package/dist/channels/threads.d.ts +12 -0
  68. package/dist/channels/threads.js +34 -0
  69. package/dist/config/schema.d.ts +23 -1
  70. package/dist/config/schema.js +107 -5
  71. package/dist/conversations/conversations.service.d.ts +42 -9
  72. package/dist/conversations/conversations.service.js +57 -25
  73. package/dist/conversations/events.d.ts +106 -11
  74. package/dist/conversations/events.js +121 -18
  75. package/dist/conversations/message-context.d.ts +6 -3
  76. package/dist/conversations/message-context.js +14 -10
  77. package/dist/conversations/queue.d.ts +15 -2
  78. package/dist/conversations/queue.js +22 -3
  79. package/dist/conversations/queue.service.d.ts +12 -6
  80. package/dist/conversations/queue.service.js +36 -13
  81. package/dist/db/kernel-schema.d.ts +105 -3
  82. package/dist/devices/conversation-devices.d.ts +8 -1
  83. package/dist/devices/conversation-devices.js +8 -0
  84. package/dist/devices/devices.controller.d.ts +21 -4
  85. package/dist/devices/devices.controller.js +46 -8
  86. package/dist/devices/devices.schemas.d.ts +9 -1
  87. package/dist/devices/devices.schemas.js +11 -3
  88. package/dist/devices/devices.service.d.ts +176 -4
  89. package/dist/devices/devices.service.js +347 -6
  90. package/dist/devices/dto.d.ts +56 -3
  91. package/dist/devices/dto.js +51 -1
  92. package/dist/devices/public-suffix.d.ts +6 -0
  93. package/dist/devices/public-suffix.js +79 -0
  94. package/dist/errors.d.ts +50 -5
  95. package/dist/errors.js +50 -5
  96. package/dist/evals/reconstruct.d.ts +18 -13
  97. package/dist/evals/reconstruct.js +50 -36
  98. package/dist/evals/recorded-tools.d.ts +7 -7
  99. package/dist/evals/recorded-tools.js +4 -3
  100. package/dist/evals/trajectory.d.ts +6 -1
  101. package/dist/evals/trajectory.js +7 -0
  102. package/dist/extensions/package.js +2 -2
  103. package/dist/http/api.module.js +5 -0
  104. package/dist/identity/settings.d.ts +53 -3
  105. package/dist/identity/settings.js +92 -9
  106. package/dist/inbox/inbox.module.js +2 -1
  107. package/dist/inbox/inbox.service.d.ts +19 -4
  108. package/dist/inbox/inbox.service.js +14 -3
  109. package/dist/infra/config/kernel-config.service.d.ts +7 -0
  110. package/dist/infra/config/kernel-config.service.js +31 -0
  111. package/dist/infra/events/kernel-events.d.ts +2 -1
  112. package/dist/local-devices/device-policy.service.d.ts +65 -0
  113. package/dist/local-devices/device-policy.service.js +217 -0
  114. package/dist/local-devices/local-device-gateway.d.ts +4 -2
  115. package/dist/local-devices/local-device-gateway.js +39 -11
  116. package/dist/local-devices/local-device-provider.d.ts +40 -3
  117. package/dist/local-devices/local-device-provider.js +109 -7
  118. package/dist/local-devices/local-devices.module.d.ts +2 -1
  119. package/dist/local-devices/local-devices.module.js +19 -3
  120. package/dist/local-devices/machines.d.ts +30 -3
  121. package/dist/local-devices/machines.js +79 -3
  122. package/dist/local-devices/network-grants.d.ts +104 -0
  123. package/dist/local-devices/network-grants.js +288 -0
  124. package/dist/local-devices/policy-summary.d.ts +20 -0
  125. package/dist/local-devices/policy-summary.js +63 -0
  126. package/dist/orgs/orgs.module.js +4 -10
  127. package/dist/orgs/roles.d.ts +1 -1
  128. package/dist/orgs/roles.js +4 -0
  129. package/dist/orgs/settings.controller.js +4 -3
  130. package/dist/orgs/settings.service.d.ts +172 -14
  131. package/dist/orgs/settings.service.js +229 -27
  132. package/dist/platform/exclusions.js +11 -1
  133. package/dist/platform/operation.js +4 -0
  134. package/dist/platform/ops/admin-items.ops.js +1 -1
  135. package/dist/platform/ops/admin.ops.js +32 -7
  136. package/dist/platform/ops/agent-skills.ops.js +2 -2
  137. package/dist/platform/ops/agent-templates.ops.js +2 -2
  138. package/dist/platform/ops/agents.ops.js +11 -15
  139. package/dist/platform/ops/devices.ops.js +3 -1
  140. package/dist/platform/ops/inbox.ops.js +19 -0
  141. package/dist/platform/ops/org-settings.ops.js +15 -6
  142. package/dist/platform/ops/orgs.ops.js +1 -0
  143. package/dist/platform/ops/platform.ops.d.ts +9 -1
  144. package/dist/platform/ops/platform.ops.js +34 -12
  145. package/dist/platform/ops/requests.ops.js +3 -2
  146. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  147. package/dist/platform/ops/schedules.ops.js +470 -0
  148. package/dist/platform/ops/skills.ops.js +3 -3
  149. package/dist/platform/platform-ops.module.d.ts +2 -1
  150. package/dist/platform/platform-ops.module.js +5 -0
  151. package/dist/platform/route-schemas.js +3 -0
  152. package/dist/roles/worker.module.js +3 -0
  153. package/dist/runtime/context/build.js +13 -4
  154. package/dist/runtime/context/compaction.js +6 -3
  155. package/dist/runtime/context/device-paths.d.ts +43 -21
  156. package/dist/runtime/context/device-paths.js +104 -21
  157. package/dist/runtime/context/run-context.d.ts +9 -0
  158. package/dist/runtime/context/run-context.js +8 -0
  159. package/dist/runtime/context/skills.d.ts +14 -1
  160. package/dist/runtime/context/skills.js +36 -5
  161. package/dist/runtime/maintenance.d.ts +4 -2
  162. package/dist/runtime/maintenance.js +16 -5
  163. package/dist/runtime/run-finish.d.ts +6 -1
  164. package/dist/runtime/run-finish.js +16 -2
  165. package/dist/runtime/run-queue.js +21 -2
  166. package/dist/runtime/run-worker.js +14 -33
  167. package/dist/runtime/runtime.module.js +3 -0
  168. package/dist/runtime/subagents/definition.d.ts +32 -14
  169. package/dist/runtime/subagents/definition.js +106 -109
  170. package/dist/runtime/subagents/dispatch.js +4 -3
  171. package/dist/runtime/subagents/preamble.d.ts +2 -1
  172. package/dist/runtime/subagents/preamble.js +2 -1
  173. package/dist/runtime/waits.js +14 -4
  174. package/dist/sandbox/platform-tiers.d.ts +86 -0
  175. package/dist/sandbox/platform-tiers.js +97 -0
  176. package/dist/sandbox/sandbox.service.d.ts +121 -11
  177. package/dist/sandbox/sandbox.service.js +351 -30
  178. package/dist/sandbox/tiers.d.ts +98 -0
  179. package/dist/sandbox/tiers.js +104 -0
  180. package/dist/schedules/effective-settings.d.ts +16 -0
  181. package/dist/schedules/effective-settings.js +17 -0
  182. package/dist/schedules/errors.d.ts +8 -0
  183. package/dist/schedules/errors.js +8 -0
  184. package/dist/schedules/fire.d.ts +118 -0
  185. package/dist/schedules/fire.js +625 -0
  186. package/dist/schedules/grant-rules.d.ts +94 -0
  187. package/dist/schedules/grant-rules.js +126 -0
  188. package/dist/schedules/job-ids.d.ts +32 -0
  189. package/dist/schedules/job-ids.js +27 -0
  190. package/dist/schedules/pause.d.ts +9 -0
  191. package/dist/schedules/pause.js +33 -0
  192. package/dist/schedules/run-tracker.d.ts +81 -0
  193. package/dist/schedules/run-tracker.js +333 -0
  194. package/dist/schedules/scan.job.d.ts +64 -0
  195. package/dist/schedules/scan.job.js +303 -0
  196. package/dist/schedules/schedule-math.d.ts +31 -0
  197. package/dist/schedules/schedule-math.js +223 -0
  198. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  199. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  200. package/dist/schedules/schedules.controller.d.ts +68 -0
  201. package/dist/schedules/schedules.controller.js +242 -0
  202. package/dist/schedules/schedules.module.d.ts +16 -0
  203. package/dist/schedules/schedules.module.js +65 -0
  204. package/dist/schedules/schedules.schemas.d.ts +316 -0
  205. package/dist/schedules/schedules.schemas.js +186 -0
  206. package/dist/schedules/store.d.ts +81 -0
  207. package/dist/schedules/store.js +390 -0
  208. package/dist/schedules/store.module.d.ts +6 -0
  209. package/dist/schedules/store.module.js +21 -0
  210. package/dist/schedules/types.d.ts +108 -0
  211. package/dist/schedules/types.js +1 -0
  212. package/dist/schedules/unattended.d.ts +117 -0
  213. package/dist/schedules/unattended.js +228 -0
  214. package/dist/setup/oauth-token-probe.js +8 -4
  215. package/dist/skills/agent-skills.service.d.ts +4 -8
  216. package/dist/skills/agent-skills.service.js +18 -53
  217. package/dist/skills/catalog.d.ts +65 -62
  218. package/dist/skills/catalog.js +109 -206
  219. package/dist/skills/conversation-catalog.d.ts +3 -3
  220. package/dist/skills/conversation-catalog.js +5 -12
  221. package/dist/skills/invoke.d.ts +2 -2
  222. package/dist/skills/skills.controller.d.ts +3 -1
  223. package/dist/skills/skills.service.d.ts +38 -37
  224. package/dist/skills/skills.service.js +168 -107
  225. package/dist/skills/usable.d.ts +57 -8
  226. package/dist/skills/usable.js +172 -38
  227. package/dist/tools/approvals.controller.d.ts +4 -2
  228. package/dist/tools/approvals.controller.js +17 -2
  229. package/dist/tools/approvals.schemas.d.ts +1 -0
  230. package/dist/tools/approvals.schemas.js +3 -1
  231. package/dist/tools/approvals.service.d.ts +182 -7
  232. package/dist/tools/approvals.service.js +597 -13
  233. package/dist/tools/builtin-names.d.ts +25 -4
  234. package/dist/tools/builtin-names.js +45 -4
  235. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  236. package/dist/tools/builtins/builtin-tools.js +23 -2
  237. package/dist/tools/builtins/sandbox/bash.js +16 -7
  238. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  239. package/dist/tools/builtins/sandbox/common.js +33 -10
  240. package/dist/tools/builtins/sandbox/edit.js +7 -3
  241. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  242. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  243. package/dist/tools/builtins/sandbox/write.js +8 -4
  244. package/dist/tools/builtins/schedules.d.ts +107 -0
  245. package/dist/tools/builtins/schedules.js +789 -0
  246. package/dist/tools/elicitation.d.ts +2 -0
  247. package/dist/tools/elicitation.js +1 -1
  248. package/dist/tools/fixed-ask.d.ts +7 -0
  249. package/dist/tools/fixed-ask.js +9 -0
  250. package/dist/tools/network-asks.d.ts +109 -0
  251. package/dist/tools/network-asks.js +221 -0
  252. package/dist/tools/pipeline.d.ts +53 -6
  253. package/dist/tools/pipeline.js +202 -13
  254. package/dist/tools/policy.d.ts +21 -8
  255. package/dist/tools/policy.js +63 -9
  256. package/dist/tools/registry.d.ts +10 -1
  257. package/dist/tools/tools.module.js +12 -0
  258. package/package.json +2 -2
  259. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  260. package/src/migrations/0114_network_asks.sql +23 -0
  261. package/src/migrations/0115_network_requests.sql +28 -0
  262. package/src/migrations/0116_skills_default_on.sql +37 -0
  263. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -8,6 +8,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { randomUUID } from "node:crypto";
11
+ import { minTier, tierAtMost, } from "@capybara-hq/plugin-sdk";
11
12
  import { Inject, Injectable } from "@nestjs/common";
12
13
  import { sql } from "kysely";
13
14
  import { ApiError } from "../errors.js";
@@ -20,12 +21,19 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
20
21
  import { Transactional } from "../infra/database/transactional.decorator.js";
21
22
  import { LockService } from "../infra/lock/lock.service.js";
22
23
  import { KERNEL_LOGGER } from "../infra/tokens.js";
24
+ import { DevicePolicyService } from "../local-devices/device-policy.service.js";
23
25
  import { deviceLeaseKey } from "../local-devices/keys.js";
24
26
  import { MachinesService } from "../local-devices/machines.js";
27
+ import { deviceFeaturesOf, localPolicyOf } from "../local-devices/policy-summary.js";
28
+ import { readOrgSettings } from "../orgs/settings.service.js";
25
29
  import { SandboxJobs } from "../sandbox/jobs.js";
26
30
  import { isInternalProvider, LOCAL_PROVIDER, SandboxProviders } from "../sandbox/providers.js";
27
31
  import { deviceNotFound, SandboxService } from "../sandbox/sandbox.service.js";
32
+ import { effectiveSandboxPolicy, readDeploymentSandboxPolicy, } from "../sandbox/tiers.js";
33
+ import { unattendedTierOf } from "../schedules/unattended.js";
28
34
  import { DeviceAccess, demotedProvider, deviceProvider, deviceUnavailable, inUseForDefault, } from "./access.js";
35
+ /** How many recent sandbox changes a device view lists. */
36
+ export const RECENT_POLICY_CHANGES = 10;
29
37
  const isUniqueViolation = (e, constraint) => typeof e === "object" &&
30
38
  e !== null &&
31
39
  e.code === "23505" &&
@@ -50,9 +58,10 @@ let DevicesService = class DevicesService {
50
58
  log;
51
59
  locks;
52
60
  machines;
61
+ policies;
53
62
  constructor(db, txHost, config, providers, sandbox, sandboxJobs, access, displayNames, log,
54
63
  /** Reads the device relay's lease (`online`). */
55
- locks, machines) {
64
+ locks, machines, policies) {
56
65
  this.db = db;
57
66
  this.txHost = txHost;
58
67
  this.config = config;
@@ -64,6 +73,7 @@ let DevicesService = class DevicesService {
64
73
  this.log = log;
65
74
  this.locks = locks;
66
75
  this.machines = machines;
76
+ this.policies = policies;
67
77
  }
68
78
  /**
69
79
  * The person's live devices in the org, the default first, then by creation. With a
@@ -236,6 +246,120 @@ let DevicesService = class DevicesService {
236
246
  }
237
247
  async renameIn(orgId, accountId, id, name, actorTokenId, auditDetail) {
238
248
  const before = await this.lockOwn(orgId, accountId, id);
249
+ await this.renameLocked(orgId, accountId, id, before, name, actorTokenId, auditDetail);
250
+ }
251
+ /**
252
+ * `PATCH /devices/:id` (sandbox dev tiers spec §7.2): renames one of the person's live devices
253
+ * ({@link rename}) and sets a cloud device's sandbox tier, in one transaction. The tier: null
254
+ * follows the org's default; above the device's ceiling (the org's or the platform's max) →
255
+ * `device.tier_not_allowed`; a local device → `device.tier_on_device` (its tier is its
256
+ * machine's, changed with {@link changePolicy}). A changed tier is audited `device.tier_changed`
257
+ * (`from` / `to`); the device's next use applies it (the instance is updated, or recreated on
258
+ * the kept files). Callers make sure only a person in a session sets the tier.
259
+ */
260
+ async update(orgId, accountId, id, patch, actorTokenId) {
261
+ await this.updateIn(orgId, accountId, id, patch, actorTokenId);
262
+ return this.get(orgId, accountId, id);
263
+ }
264
+ async updateIn(orgId, accountId, id, patch, actorTokenId) {
265
+ const before = await this.lockOwn(orgId, accountId, id);
266
+ if (patch.tier !== undefined) {
267
+ if (before.source !== "cloud") {
268
+ throw new ApiError("device.tier_on_device", "This device's sandbox level is kept on its connected machine: change it from the device page (sent to the machine) or run capybara-device policy set tier <strict|dev|open|default> there.");
269
+ }
270
+ const policy = await this.policyFor(orgId, patch.tier);
271
+ if (patch.tier !== null && !tierAtMost(patch.tier, policy.ceiling)) {
272
+ throw tierNotAllowed(patch.tier, policy);
273
+ }
274
+ if (patch.tier !== before.tier) {
275
+ await this.db
276
+ .updateTable("devices")
277
+ .set({ tier: patch.tier, updated_at: sql `now()` })
278
+ .where("id", "=", id)
279
+ .execute();
280
+ await audit(this.db.db, {
281
+ actorAccountId: accountId,
282
+ ...(actorTokenId !== undefined && { actorTokenId }),
283
+ orgId,
284
+ action: "device.tier_changed",
285
+ target: id,
286
+ detail: { from: before.tier, to: patch.tier },
287
+ });
288
+ }
289
+ }
290
+ if (patch.name !== undefined) {
291
+ await this.renameLocked(orgId, accountId, id, before, patch.name, actorTokenId, undefined);
292
+ }
293
+ }
294
+ /** The org's sandbox policy for a device choosing `tier` (null: the org's default). */
295
+ async policyFor(orgId, tier) {
296
+ const [deployment, settings] = await Promise.all([
297
+ readDeploymentSandboxPolicy(this.db.db, this.config),
298
+ readOrgSettings(this.db.db, orgId),
299
+ ]);
300
+ return effectiveSandboxPolicy(deployment, settings, tier);
301
+ }
302
+ /**
303
+ * The sandbox level a sandbox call in the device runs at now, for the model (`device_paths`,
304
+ * spec §11.5): see {@link sandboxStateOf}, plus
305
+ * - `network: "none"` when the org's or the platform's sandbox network is off;
306
+ * - a cloud device whose new limits wait for a file reset: the tier and network its instance
307
+ * still runs with (`limitsState(...).applied`; open with its network on when unrecorded);
308
+ * - a local device: its machine's localhost and unix socket facts; in a scheduled run without
309
+ * a person (`runId`, {@link unattendedTierOf}) and without the task's `tier:open` grant its
310
+ * tier is capped at `dev`, as the exec args cap it;
311
+ * - `unattended` in such a run: nobody can approve a domain.
312
+ * Null when the device is not a live device of the org.
313
+ */
314
+ async sandboxLevel(orgId, deviceId, runId) {
315
+ const row = await this.db
316
+ .selectFrom("devices as e")
317
+ .leftJoin("device_machines as m", (j) => j.onRef("m.device_id", "=", "e.id").on("m.revoked_at", "is", null))
318
+ .select(["e.source", "e.tier", "m.features", "m.local_policy"])
319
+ .where("e.id", "=", deviceId)
320
+ .where("e.org_id", "=", orgId)
321
+ .where("e.deleted_at", "is", null)
322
+ .executeTakeFirst();
323
+ if (row === undefined)
324
+ return null;
325
+ const [deployment, settings, unattended] = await Promise.all([
326
+ readDeploymentSandboxPolicy(this.db.db, this.config),
327
+ readOrgSettings(this.db.db, orgId),
328
+ runId === undefined ? null : unattendedTierOf(this.db.db, runId, deviceId),
329
+ ]);
330
+ const policyOf = (tier) => effectiveSandboxPolicy(deployment, settings, tier);
331
+ const summary = localPolicyOf(row.local_policy);
332
+ const s = sandboxStateOf(policyOf, {
333
+ source: row.source,
334
+ tier: row.tier,
335
+ features: deviceFeaturesOf(row.features),
336
+ policy: summary,
337
+ });
338
+ let tier = s.effectiveTier;
339
+ let networkOff = policyOf(s.tier).base.egress === "none";
340
+ if (row.source === "cloud") {
341
+ const state = await this.sandbox.limitsState({ orgId, deviceId }, { policy: policyOf(row.tier) });
342
+ if (state.pendingFilesReset) {
343
+ // an unknown record (an instance made before records) may be wider: it counts as open
344
+ // with its network on, as the unattended gate counts it (`mayRunOpen`)
345
+ tier = state.applied?.tier ?? "open";
346
+ networkOff = state.applied?.egress === "none";
347
+ }
348
+ }
349
+ else if (unattended !== null && !unattended.openGranted) {
350
+ tier = minTier(tier, "dev");
351
+ }
352
+ return {
353
+ tier,
354
+ isolation: tier === "open" ? s.isolation : "sandbox",
355
+ ...(networkOff && { network: "none" }),
356
+ ...(row.source === "local" &&
357
+ summary !== null && { localhost: summary.localhost, unixSockets: summary.unixSockets }),
358
+ ...(unattended !== null && { unattended: true }),
359
+ };
360
+ }
361
+ /** Renames the device locked by {@link lockOwn} (`before`); see {@link rename}. */
362
+ async renameLocked(orgId, accountId, id, before, name, actorTokenId, auditDetail) {
239
363
  if (before.name === name)
240
364
  return;
241
365
  // the same name in another case is this device's own: the index allows it
@@ -310,6 +434,55 @@ let DevicesService = class DevicesService {
310
434
  async revokeMachine(orgId, accountId, id, actorTokenId) {
311
435
  await this.machines.revoke(orgId, accountId, id, actorTokenId);
312
436
  }
437
+ /**
438
+ * The owner changes one of their own devices' local policy (network ask-to-allow spec §7, §13
439
+ * Q6; sandbox dev tiers spec §7.3): sent to the machine with the `policy` op, which is the
440
+ * authority and answers with the policy as written. Anyone but the owner gets
441
+ * `device.not_found`. A `tier` above the device's ceiling → `device.tier_not_allowed`. A widening
442
+ * change (`add`; a `tier` that may raise the machine's, judged as the machine judges it) is
443
+ * refused up front with `device.policy_refused` when the machine last reported
444
+ * `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
445
+ * `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
446
+ * program without the `policyOp` feature → `device.policy_unsupported`; the machine's refusal →
447
+ * `device.policy_refused` with its message. Callers are human sessions only (no personal access
448
+ * token, no service token, no Platform op).
449
+ */
450
+ async changePolicy(orgId, account, id, change) {
451
+ await this.access.usable(orgId, account.id, id);
452
+ if (change.tier !== undefined && change.tier !== null) {
453
+ const policy = await this.policyFor(orgId, change.tier);
454
+ if (!tierAtMost(change.tier, policy.ceiling))
455
+ throw tierNotAllowed(change.tier, policy);
456
+ }
457
+ const summary = await this.policies.summary(id);
458
+ const known = summary?.policy ?? null;
459
+ const widens = (change.add?.length ?? 0) > 0 || (known !== null && tierWidens(known.tier, change.tier));
460
+ // only a known "off" is refused here; an unknown summary, a machine that cannot take the op
461
+ // or none at all are left to apply (the machine decides, or unsupported / offline)
462
+ const canApply = summary?.features.includes("policyOp") === true;
463
+ if (widens && canApply && known?.remoteGrants === "off") {
464
+ throw new ApiError("device.policy_refused", remoteGrantsOffMessage(change));
465
+ }
466
+ const out = await this.policies.apply({
467
+ orgId,
468
+ deviceId: id,
469
+ change: {
470
+ ...(change.add !== undefined && { add: change.add }),
471
+ ...(change.remove !== undefined && { remove: change.remove }),
472
+ ...(change.tier !== undefined && { tier: change.tier }),
473
+ ...(change.isolation !== undefined && { isolation: change.isolation }),
474
+ },
475
+ source: { accountId: account.id, accountName: account.displayName, via: "web" },
476
+ });
477
+ if (out.applied)
478
+ return out.policy;
479
+ if (out.reason === "offline")
480
+ throw new ApiError("device.offline", out.message);
481
+ if (out.reason === "unsupported") {
482
+ throw new ApiError("device.policy_unsupported", out.message);
483
+ }
484
+ throw new ApiError("device.policy_refused", out.message);
485
+ }
313
486
  /**
314
487
  * Starts one of the person's devices over with empty files (after
315
488
  * `device.files_lost`): records the request (audit), then, after the commit, enqueues the
@@ -590,6 +763,7 @@ let DevicesService = class DevicesService {
590
763
  "e.source",
591
764
  "e.provider",
592
765
  "e.demoted_from_provider",
766
+ "e.tier",
593
767
  "e.created_at",
594
768
  "m.id as machine_id",
595
769
  "m.name as machine_name",
@@ -598,6 +772,8 @@ let DevicesService = class DevicesService {
598
772
  "m.program_version as machine_program_version",
599
773
  "m.last_seen_at as machine_last_seen_at",
600
774
  "m.session_isolation as machine_session_isolation",
775
+ "m.features as machine_features",
776
+ "m.local_policy as machine_local_policy",
601
777
  "i.provider as instance_provider",
602
778
  "i.fs_status",
603
779
  "i.handle",
@@ -636,10 +812,24 @@ let DevicesService = class DevicesService {
636
812
  const demotedProviders = [
637
813
  ...new Set(rows.flatMap((r) => (demoted.get(r.id) ? [demoted.get(r.id)] : []))),
638
814
  ];
639
- const [limits, names] = await Promise.all([
640
- this.sandbox.orgLimits(orgId),
815
+ const [names, recent, settings, deployment] = await Promise.all([
641
816
  this.displayNames.many(demotedProviders),
817
+ this.recentSandboxChanges(orgId, rows.map((r) => r.id)),
818
+ readOrgSettings(this.db.db, orgId),
819
+ readDeploymentSandboxPolicy(this.db.db, this.config),
642
820
  ]);
821
+ const policyOf = (tier) => effectiveSandboxPolicy(deployment, settings, tier);
822
+ // each device's limits (a cloud device's own tier) and its instance's pending change
823
+ const perDevice = new Map(await Promise.all(rows.map(async (r) => {
824
+ const policy = policyOf(r.source === "cloud" ? r.tier : null);
825
+ const [limits, state] = await Promise.all([
826
+ this.sandbox.deviceLimits({ orgId, deviceId: r.id }, { policy }),
827
+ r.source === "cloud"
828
+ ? this.sandbox.limitsState({ orgId, deviceId: r.id }, { policy })
829
+ : { pendingNextUse: false, pendingFilesReset: false },
830
+ ]);
831
+ return [r.id, { limits, state }];
832
+ })));
643
833
  const current = this.providers.cloud()?.plugin;
644
834
  // a local device is online while a replica holds its machine's connection lease
645
835
  const online = new Map(await Promise.all(rows
@@ -677,6 +867,23 @@ let DevicesService = class DevicesService {
677
867
  lastSeenAt: r.machine_last_seen_at?.toISOString() ?? null,
678
868
  }
679
869
  : null,
870
+ network: r.source === "local" && r.machine_id !== null
871
+ ? {
872
+ features: deviceFeaturesOf(r.machine_features),
873
+ policy: localPolicyOf(r.machine_local_policy),
874
+ }
875
+ : null,
876
+ sandbox: {
877
+ ...sandboxStateOf(policyOf, {
878
+ source: r.source,
879
+ tier: r.tier,
880
+ features: r.machine_id === null ? [] : deviceFeaturesOf(r.machine_features),
881
+ policy: r.machine_id === null ? null : localPolicyOf(r.machine_local_policy),
882
+ }),
883
+ pendingNextUse: perDevice.get(r.id)?.state.pendingNextUse ?? false,
884
+ pendingFilesReset: perDevice.get(r.id)?.state.pendingFilesReset ?? false,
885
+ recent: recent.get(r.id) ?? [],
886
+ },
680
887
  // a local device's machine decides it; cloud devices have none in phase 2
681
888
  sessionIsolation: r.machine_session_isolation ?? false,
682
889
  filesLost: r.fs_status === "lost",
@@ -686,11 +893,49 @@ let DevicesService = class DevicesService {
686
893
  lastUsedAt: r.last_used_at.toISOString(),
687
894
  }
688
895
  : null,
689
- limits: { ...limits, allowHosts: [...limits.allowHosts] },
896
+ limits: limitsView(perDevice.get(r.id)?.limits),
690
897
  createdAt: r.created_at.toISOString(),
691
898
  };
692
899
  });
693
900
  }
901
+ /**
902
+ * The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
903
+ * `device.policy_changed`, `device.tier_changed`, `device.isolation_reported`), newest first,
904
+ * with the deciding account's display name.
905
+ */
906
+ async recentSandboxChanges(orgId, deviceIds) {
907
+ const out = new Map();
908
+ if (deviceIds.length === 0)
909
+ return out;
910
+ const ranked = this.db
911
+ .selectFrom("audit_events as a")
912
+ .select([
913
+ "a.target",
914
+ "a.action",
915
+ "a.at",
916
+ "a.detail",
917
+ "a.actor_account_id",
918
+ sql `row_number() OVER (PARTITION BY a.target ORDER BY a.at DESC, a.id DESC)`.as("n"),
919
+ ])
920
+ .where("a.org_id", "=", orgId)
921
+ .where("a.action", "in", [...SANDBOX_CHANGE_ACTIONS])
922
+ .where("a.target", "in", deviceIds);
923
+ const rows = await this.db
924
+ .selectFrom(ranked.as("r"))
925
+ .leftJoin("accounts as ac", "ac.id", "r.actor_account_id")
926
+ .select(["r.target", "r.action", "r.at", "r.detail", "ac.display_name"])
927
+ .where("r.n", "<=", RECENT_POLICY_CHANGES)
928
+ .orderBy("r.at", "desc")
929
+ .execute();
930
+ for (const r of rows) {
931
+ if (r.target === null)
932
+ continue;
933
+ const list = out.get(r.target) ?? [];
934
+ list.push(sandboxChangeOf(r.action, r.at, (r.detail ?? {}), r.display_name ?? null));
935
+ out.set(r.target, list);
936
+ }
937
+ return out;
938
+ }
694
939
  /**
695
940
  * Serializes the person's device writes in the org: the org row (`FOR SHARE`), then their
696
941
  * membership row (`FOR NO KEY UPDATE`, or `FOR SHARE` with `mode` share: enough to keep the
@@ -722,7 +967,7 @@ let DevicesService = class DevicesService {
722
967
  await this.lockMember(orgId, accountId);
723
968
  const row = await this.db
724
969
  .selectFrom("devices")
725
- .select(["name", "is_default"])
970
+ .select(["name", "is_default", "source", "tier"])
726
971
  .where("id", "=", id)
727
972
  .where("org_id", "=", orgId)
728
973
  .where("owner_kind", "=", "personal")
@@ -756,6 +1001,9 @@ __decorate([
756
1001
  __decorate([
757
1002
  Transactional()
758
1003
  ], DevicesService.prototype, "renameIn", null);
1004
+ __decorate([
1005
+ Transactional()
1006
+ ], DevicesService.prototype, "updateIn", null);
759
1007
  __decorate([
760
1008
  Transactional()
761
1009
  ], DevicesService.prototype, "remove", null);
@@ -777,7 +1025,8 @@ DevicesService = __decorate([
777
1025
  __param(7, Inject(ExtensionDisplayNames)),
778
1026
  __param(8, Inject(KERNEL_LOGGER)),
779
1027
  __param(9, Inject(LockService)),
780
- __param(10, Inject(MachinesService))
1028
+ __param(10, Inject(MachinesService)),
1029
+ __param(11, Inject(DevicePolicyService))
781
1030
  ], DevicesService);
782
1031
  export { DevicesService };
783
1032
  /**
@@ -789,3 +1038,95 @@ function refuseDeviceReset(env) {
789
1038
  throw new ApiError("device.files_on_device", "This device's files are in a directory on its connected machine and cannot be reset from here. To start over, run capybara-device logout, then capybara-device login into this device, on that machine.");
790
1039
  }
791
1040
  }
1041
+ /** A copy of `l` for a view (its lists not shared). */
1042
+ const limitsView = (l) => ({
1043
+ ...l,
1044
+ allowHosts: [...l.allowHosts],
1045
+ devHosts: [...l.devHosts],
1046
+ });
1047
+ /** The audit actions a device's recent sandbox changes list. */
1048
+ const SANDBOX_CHANGE_ACTIONS = [
1049
+ "device.policy_changed",
1050
+ "device.tier_changed",
1051
+ "device.isolation_reported",
1052
+ ];
1053
+ const tierOrNull = (v) => v === "strict" || v === "dev" || v === "open" ? v : null;
1054
+ /** One audit row of {@link SANDBOX_CHANGE_ACTIONS} as a {@link DeviceSandboxChangeView}. */
1055
+ function sandboxChangeOf(action, at, d, by) {
1056
+ const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : [];
1057
+ const isolation = d.isolation === "sandbox" || d.isolation === "none" ? d.isolation : null;
1058
+ const base = { at: at.toISOString(), add: [], remove: [], tier: null, isolation: null, by };
1059
+ if (action === "device.tier_changed") {
1060
+ return { ...base, kind: "tier", tier: { to: tierOrNull(d.to) }, via: "web", confirmed: true };
1061
+ }
1062
+ if (action === "device.isolation_reported") {
1063
+ return { ...base, kind: "isolation", isolation, via: "device", confirmed: true };
1064
+ }
1065
+ return {
1066
+ ...base,
1067
+ kind: "policy",
1068
+ add: hosts(d.add),
1069
+ remove: hosts(d.remove),
1070
+ // the key is there only when the change set the tier (null: follow the org's default)
1071
+ tier: "tier" in d ? { to: tierOrNull(d.tier) } : null,
1072
+ isolation,
1073
+ via: d.via === "im" ? "im" : "web",
1074
+ confirmed: d.confirmed === true,
1075
+ };
1076
+ }
1077
+ /**
1078
+ * A device's sandbox level (spec §4.2, §4.4): its chosen tier (a cloud device's stored one, a
1079
+ * local device's reported one) under the org's policy (`policyOf`). A local device whose program
1080
+ * lacks the `tiers` feature runs `strict` (it ignores the tier the kernel sends); it runs without
1081
+ * a sandbox only when it reports `isolation: "none"` and its effective tier is `open` (the device
1082
+ * keeps the sandbox at any lower tier).
1083
+ */
1084
+ export function sandboxStateOf(policyOf, d) {
1085
+ const chosen = d.source === "cloud" ? d.tier : (d.policy?.tier ?? null);
1086
+ const p = policyOf(chosen);
1087
+ const tiersKnown = d.source === "cloud" || d.features.includes("tiers");
1088
+ const effectiveTier = tiersKnown ? p.tier : "strict";
1089
+ return {
1090
+ tier: chosen,
1091
+ effectiveTier,
1092
+ ceiling: p.ceiling,
1093
+ cappedBy: tiersKnown ? p.cappedBy : null,
1094
+ defaultTier: p.defaultTier,
1095
+ allowUnsandboxed: p.allowUnsandboxed,
1096
+ isolation: d.source === "local" && d.policy?.isolation === "none" && effectiveTier === "open"
1097
+ ? "none"
1098
+ : "sandbox",
1099
+ };
1100
+ }
1101
+ /**
1102
+ * Whether setting a local device's tier to `next` (null: follow the org's default) may raise it,
1103
+ * not knowing the org's default at the machine: a tier above the reported one, any tier but
1104
+ * `strict` when it reports none, and following the default when it reports `strict` or `dev`.
1105
+ */
1106
+ export function tierWidens(reported, next) {
1107
+ if (next === undefined || next === "strict")
1108
+ return false;
1109
+ if (next === null)
1110
+ return reported !== undefined && reported !== "open";
1111
+ return reported === undefined || !tierAtMost(next, reported);
1112
+ }
1113
+ /** A tier above the device's ceiling: which ceiling, and the highest tier allowed. */
1114
+ function tierNotAllowed(tier, policy) {
1115
+ const platform = policy.cappedBy === "platform";
1116
+ return new ApiError("device.tier_not_allowed", `${platform ? "The platform" : "Your organization"} allows sandbox levels up to ${policy.ceiling}; this device cannot be set to ${tier}.`);
1117
+ }
1118
+ /**
1119
+ * The refusal of a widening change on a device whose machine reported `remoteGrants: "off"`: the
1120
+ * command to run on the machine instead.
1121
+ */
1122
+ function remoteGrantsOffMessage(change) {
1123
+ const local = [
1124
+ ...(change.add !== undefined && change.add.length > 0
1125
+ ? [`capybara-device policy add allowedDomains ${change.add.join(",")}`]
1126
+ : []),
1127
+ ...(change.tier !== undefined
1128
+ ? [`capybara-device policy set tier ${change.tier ?? "default"}`]
1129
+ : []),
1130
+ ];
1131
+ return `This device does not accept widening changes from the deployment; change it on the machine: ${local.join("; ")}`;
1132
+ }
@@ -7,19 +7,72 @@ export declare const DeviceNameBody: z.ZodObject<{
7
7
  export declare const DeviceParams: z.ZodObject<{
8
8
  id: z.ZodString;
9
9
  }, z.core.$strip>;
10
+ /** A sandbox tier (sandbox dev tiers spec §5). */
11
+ export declare const DeviceTier: z.ZodEnum<{
12
+ dev: "dev";
13
+ open: "open";
14
+ strict: "strict";
15
+ }>;
16
+ /**
17
+ * `PATCH /devices/:id`: a new name, and for a cloud device its sandbox tier (null = follow the
18
+ * org's default; sandbox dev tiers spec §7.2). The tier is a person's choice in a web session:
19
+ * an API token gets `auth.session_required` and the Platform tool leaves the field out.
20
+ */
21
+ export declare const DevicePatchBody: z.ZodObject<{
22
+ name: z.ZodOptional<z.ZodString>;
23
+ tier: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
24
+ dev: "dev";
25
+ open: "open";
26
+ strict: "strict";
27
+ }>>>;
28
+ }, z.core.$strict>;
29
+ /**
30
+ * `POST /devices/:id/policy` (network ask-to-allow spec §7, sandbox dev tiers spec §7.3): the
31
+ * device owner's change of the machine's local policy. `add` and a higher `tier` widen it (the
32
+ * machine accepts them only while its local `remoteGrants` is `owner`); `remove` and a lower
33
+ * `tier` narrow it. `tier: null` follows the org's default. `isolation` only turns the sandbox
34
+ * back on: running without one is decided on the machine.
35
+ */
36
+ export declare const DevicePolicyBody: z.ZodObject<{
37
+ add: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
+ remove: z.ZodOptional<z.ZodArray<z.ZodString>>;
39
+ tier: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
40
+ dev: "dev";
41
+ open: "open";
42
+ strict: "strict";
43
+ }>>>;
44
+ isolation: z.ZodOptional<z.ZodLiteral<"sandbox">>;
45
+ }, z.core.$strict>;
10
46
  declare const DeviceCreateBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
11
47
  name: z.ZodString;
12
48
  }, z.core.$strict>>;
13
49
  export declare class DeviceCreateBodyDto extends DeviceCreateBodyDto_base {
14
50
  }
15
- declare const DeviceRenameBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
16
- name: z.ZodString;
51
+ declare const DevicePatchBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
52
+ name: z.ZodOptional<z.ZodString>;
53
+ tier: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
54
+ dev: "dev";
55
+ open: "open";
56
+ strict: "strict";
57
+ }>>>;
17
58
  }, z.core.$strict>>;
18
- export declare class DeviceRenameBodyDto extends DeviceRenameBodyDto_base {
59
+ export declare class DevicePatchBodyDto extends DevicePatchBodyDto_base {
19
60
  }
20
61
  declare const DeviceParamsDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
21
62
  id: z.ZodString;
22
63
  }, z.core.$strip>>;
23
64
  export declare class DeviceParamsDto extends DeviceParamsDto_base {
24
65
  }
66
+ declare const DevicePolicyBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
67
+ add: z.ZodOptional<z.ZodArray<z.ZodString>>;
68
+ remove: z.ZodOptional<z.ZodArray<z.ZodString>>;
69
+ tier: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
70
+ dev: "dev";
71
+ open: "open";
72
+ strict: "strict";
73
+ }>>>;
74
+ isolation: z.ZodOptional<z.ZodLiteral<"sandbox">>;
75
+ }, z.core.$strict>>;
76
+ export declare class DevicePolicyBodyDto extends DevicePolicyBodyDto_base {
77
+ }
25
78
  export {};
@@ -1,12 +1,62 @@
1
+ import { validateAllowHost } from "@capybara-hq/plugin-sdk";
1
2
  import { z } from "zod";
2
3
  import { createZodDto } from "../infra/validation/create-zod-dto.js";
4
+ import { publicSuffixWildcard } from "./public-suffix.js";
3
5
  /** A device's name: trimmed, 1-64 characters (unique per person, case-insensitive). */
4
6
  export const DeviceName = z.string().trim().min(1).max(64);
5
7
  export const DeviceNameBody = z.object({ name: DeviceName }).strict();
6
8
  export const DeviceParams = z.object({ id: z.string().min(1).max(100) });
9
+ /** A sandbox tier (sandbox dev tiers spec §5). */
10
+ export const DeviceTier = z.enum(["strict", "dev", "open"]);
11
+ /**
12
+ * `PATCH /devices/:id`: a new name, and for a cloud device its sandbox tier (null = follow the
13
+ * org's default; sandbox dev tiers spec §7.2). The tier is a person's choice in a web session:
14
+ * an API token gets `auth.session_required` and the Platform tool leaves the field out.
15
+ */
16
+ export const DevicePatchBody = z
17
+ .object({ name: DeviceName.optional(), tier: DeviceTier.nullable().optional() })
18
+ .strict()
19
+ .refine((b) => b.name !== undefined || b.tier !== undefined, {
20
+ message: "Give at least one of name or tier",
21
+ });
22
+ /** One host of a device's network allowlist (`validateAllowHost`: lowercase, `*.` allowed). */
23
+ const PolicyHost = z
24
+ .string()
25
+ .max(253)
26
+ .superRefine((h, ctx) => {
27
+ for (const message of validateAllowHost(h))
28
+ ctx.addIssue({ code: "custom", message });
29
+ });
30
+ /** A host to add: also no `*.` wildcard over a whole public suffix (`*.com`, `*.co.uk`). */
31
+ const AddHost = PolicyHost.superRefine((h, ctx) => {
32
+ const tooBroad = publicSuffixWildcard(h);
33
+ if (tooBroad !== null)
34
+ ctx.addIssue({ code: "custom", message: tooBroad });
35
+ });
36
+ /**
37
+ * `POST /devices/:id/policy` (network ask-to-allow spec §7, sandbox dev tiers spec §7.3): the
38
+ * device owner's change of the machine's local policy. `add` and a higher `tier` widen it (the
39
+ * machine accepts them only while its local `remoteGrants` is `owner`); `remove` and a lower
40
+ * `tier` narrow it. `tier: null` follows the org's default. `isolation` only turns the sandbox
41
+ * back on: running without one is decided on the machine.
42
+ */
43
+ export const DevicePolicyBody = z
44
+ .object({
45
+ add: z.array(AddHost).min(1).max(20).optional(),
46
+ remove: z.array(PolicyHost).min(1).max(100).optional(),
47
+ tier: DeviceTier.nullable().optional(),
48
+ isolation: z.literal("sandbox").optional(),
49
+ })
50
+ .strict()
51
+ .refine((b) => b.add !== undefined ||
52
+ b.remove !== undefined ||
53
+ b.tier !== undefined ||
54
+ b.isolation !== undefined, { message: "Give at least one of add, remove, tier or isolation" });
7
55
  export class DeviceCreateBodyDto extends createZodDto(DeviceNameBody, "DeviceCreateBody") {
8
56
  }
9
- export class DeviceRenameBodyDto extends createZodDto(DeviceNameBody, "DeviceRenameBody") {
57
+ export class DevicePatchBodyDto extends createZodDto(DevicePatchBody, "DevicePatchBody") {
10
58
  }
11
59
  export class DeviceParamsDto extends createZodDto(DeviceParams, "DeviceParams") {
12
60
  }
61
+ export class DevicePolicyBodyDto extends createZodDto(DevicePolicyBody, "DevicePolicyBody") {
62
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Why a `*.` wildcard is too broad to add from the device page: its remainder is a whole public
3
+ * suffix (one label such as `com`, or a known multi-label one such as `co.uk`, `github.io`);
4
+ * null for an exact host or a narrower wildcard.
5
+ */
6
+ export declare function publicSuffixWildcard(pattern: string): string | null;