@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
@@ -0,0 +1,217 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { ToolError, validateAllowHost, } from "@capybara-hq/plugin-sdk";
11
+ import { Inject, Injectable } from "@nestjs/common";
12
+ import { ApiError } from "../errors.js";
13
+ import { audit } from "../identity/audit.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { currentStore, TransactionHost } from "../infra/database/transaction-host.js";
16
+ import { Transactional } from "../infra/database/transactional.decorator.js";
17
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
18
+ import { deviceNotFound } from "../sandbox/sandbox.service.js";
19
+ import { MachinesService } from "./machines.js";
20
+ import { isolationOf } from "./policy-summary.js";
21
+ import { LocalDeviceRelay, ON_MACHINE_PREFIX } from "./relay.js";
22
+ /** How long the machine may take to apply a policy change. */
23
+ export const POLICY_DEADLINE_MS = 30_000;
24
+ /** The prefix the relay puts before a machine's `policy`-kind refusal. */
25
+ const REFUSED_PREFIX = "Refused by the machine's local policy: ";
26
+ const OFFLINE_MESSAGE = "The device's machine is offline, so its policy was not changed. Start capybara-device on it.";
27
+ const UNSUPPORTED_MESSAGE = "The device's capybara-device program cannot change its policy from the platform. Update capybara-device on the machine.";
28
+ const errMsg = (e) => (e instanceof Error ? e.message : String(e));
29
+ /**
30
+ * Changes a local device's policy on its machine (network ask-to-allow spec §5.2; sandbox dev
31
+ * tiers spec §7.3: its hosts, its `tier`, and `isolation` back to `sandbox`): the kernel→device
32
+ * `policy` op over the {@link LocalDeviceRelay}. The machine is the authority: it
33
+ * refuses widening unless its owner allows remote grants, validates every host and answers with
34
+ * the policy as written, which the kernel then keeps on the active machine's row (§5.4) and
35
+ * audits as `device.policy_changed`.
36
+ *
37
+ * The op is sent outside any transaction (a decision records its outcome afterwards, in its own
38
+ * transaction); only the summary write and the audit row are transactional. Callers decide who
39
+ * may change a device's policy (the device owner, in a signed-in session); this service checks
40
+ * only that the device is a live device of the org.
41
+ */
42
+ let DevicePolicyService = class DevicePolicyService {
43
+ db;
44
+ txHost;
45
+ relay;
46
+ machines;
47
+ log;
48
+ constructor(db, txHost, relay, machines, log) {
49
+ this.db = db;
50
+ this.txHost = txHost;
51
+ this.relay = relay;
52
+ this.machines = machines;
53
+ this.log = log;
54
+ }
55
+ /**
56
+ * Sends the `policy` op (no transaction may be open), stores the returned summary and audits
57
+ * `device.policy_changed`. Never throws for an offline machine, a refusal or a program without
58
+ * the `policyOp` feature (nothing is sent to it); a device that is not a live device of `orgId`
59
+ * is `device.not_found`.
60
+ */
61
+ async apply(input) {
62
+ if (currentStore()) {
63
+ throw new Error("DevicePolicyService.apply() must not run inside a transaction: send the policy op " +
64
+ "first, then record the decision in a transaction.");
65
+ }
66
+ assertHosts(input.change, input.source.approvalId !== undefined);
67
+ const device = await this.db
68
+ .selectFrom("devices as e")
69
+ .innerJoin("orgs as o", "o.id", "e.org_id")
70
+ .select("e.source")
71
+ .where("e.id", "=", input.deviceId)
72
+ .where("e.org_id", "=", input.orgId)
73
+ .where("e.deleted_at", "is", null)
74
+ .where("o.deleted_at", "is", null)
75
+ .executeTakeFirst();
76
+ if (!device)
77
+ throw deviceNotFound();
78
+ if (device.source !== "local") {
79
+ return { applied: false, reason: "unsupported", message: UNSUPPORTED_MESSAGE };
80
+ }
81
+ const machine = await this.machines.activeFor(input.deviceId);
82
+ if (machine === null)
83
+ return { applied: false, reason: "offline", message: OFFLINE_MESSAGE };
84
+ if (!machine.features.includes("policyOp")) {
85
+ return { applied: false, reason: "unsupported", message: UNSUPPORTED_MESSAGE };
86
+ }
87
+ // not connected: nothing is sent (a queued op would wait for a reconnect nobody watches)
88
+ if (!(await this.relay.holderLive(input.deviceId))) {
89
+ return { applied: false, reason: "offline", message: OFFLINE_MESSAGE };
90
+ }
91
+ const args = { ...input.change, source: input.source };
92
+ let policy;
93
+ try {
94
+ ({ policy } = await this.relay.call(input.deviceId, "policy", args, {
95
+ deadlineMs: POLICY_DEADLINE_MS,
96
+ // the relay gives up by itself (deadline plus the reconnect grace)
97
+ signal: new AbortController().signal,
98
+ }));
99
+ }
100
+ catch (e) {
101
+ if (e instanceof ApiError && e.code === "device.offline") {
102
+ // the op was sent and the wait ran out: the machine may still apply it, so the attempt is
103
+ // audited unconfirmed; its next hello reports the policy as it is
104
+ await this.record({ ...input, machineId: machine.id, policy: null });
105
+ }
106
+ return this.notApplied(input.deviceId, e);
107
+ }
108
+ await this.record({ ...input, machineId: machine.id, policy });
109
+ return { applied: true, policy };
110
+ }
111
+ /** The active machine's features and policy summary (null when no machine is connected). */
112
+ async summary(deviceId) {
113
+ const m = await this.machines.activeFor(deviceId);
114
+ return m === null ? null : { features: m.features, policy: m.localPolicy };
115
+ }
116
+ /**
117
+ * Keeps the policy the machine wrote on its row and audits the change (`confirmed`); with no
118
+ * policy (the answer never came) only audits the attempt, unconfirmed. The detail's `isolation`
119
+ * is the one asked for, or the one the machine now reports when it changed (a tier change turns
120
+ * the sandbox back on).
121
+ */
122
+ async record(input) {
123
+ // the isolation the machine reports now, when it differs from the stored one: a tier change
124
+ // turns an unsandboxed device's sandbox back on without asking for it (spec §5.4, §10.4)
125
+ let isolationNow;
126
+ if (input.policy !== null) {
127
+ const prev = await this.db
128
+ .selectFrom("device_machines")
129
+ .select("local_policy")
130
+ .where("id", "=", input.machineId)
131
+ .where("revoked_at", "is", null)
132
+ .forUpdate()
133
+ .executeTakeFirst();
134
+ const before = isolationOf(prev?.local_policy) ?? "sandbox";
135
+ if (input.policy.isolation !== before)
136
+ isolationNow = input.policy.isolation;
137
+ await this.db
138
+ .updateTable("device_machines")
139
+ .set({ local_policy: JSON.stringify(input.policy) })
140
+ .where("id", "=", input.machineId)
141
+ .where("revoked_at", "is", null)
142
+ .execute();
143
+ }
144
+ const { change, source } = input;
145
+ await audit(this.db.db, {
146
+ actorAccountId: source.accountId,
147
+ ...(input.actorTokenId !== undefined && { actorTokenId: input.actorTokenId }),
148
+ orgId: input.orgId,
149
+ action: "device.policy_changed",
150
+ target: input.deviceId,
151
+ detail: {
152
+ add: change.add ?? [],
153
+ remove: change.remove ?? [],
154
+ // only when set (null: follow the org's default); the device page lists the change
155
+ ...(change.tier !== undefined && { tier: change.tier }),
156
+ ...((isolationNow ?? change.isolation) !== undefined && {
157
+ isolation: isolationNow ?? change.isolation,
158
+ }),
159
+ by: source.accountId,
160
+ via: source.via,
161
+ approvalId: source.approvalId ?? null,
162
+ confirmed: input.policy !== null,
163
+ },
164
+ });
165
+ }
166
+ /** The outcome of a failed op: offline, or the machine's refusal; a deleted device is thrown. */
167
+ notApplied(deviceId, e) {
168
+ if (e instanceof ApiError) {
169
+ if (e.code === "device.not_found")
170
+ throw e;
171
+ // device.offline, or device.forbidden (the machine was disconnected meanwhile)
172
+ return { applied: false, reason: "offline", message: OFFLINE_MESSAGE };
173
+ }
174
+ if (e instanceof ToolError && e.message.startsWith(REFUSED_PREFIX)) {
175
+ return { applied: false, reason: "refused", message: e.message.slice(REFUSED_PREFIX.length) };
176
+ }
177
+ // anything else the machine answered (its own error, an invalid result): not applied
178
+ this.log.warn("a device policy change failed on the machine", { deviceId, error: errMsg(e) });
179
+ const message = errMsg(e);
180
+ return {
181
+ applied: false,
182
+ reason: "refused",
183
+ message: message.startsWith(ON_MACHINE_PREFIX)
184
+ ? message.slice(ON_MACHINE_PREFIX.length)
185
+ : message,
186
+ };
187
+ }
188
+ };
189
+ __decorate([
190
+ Transactional()
191
+ ], DevicePolicyService.prototype, "record", null);
192
+ DevicePolicyService = __decorate([
193
+ Injectable(),
194
+ __param(0, Inject(KernelDb)),
195
+ __param(1, Inject(TransactionHost)),
196
+ __param(2, Inject(LocalDeviceRelay)),
197
+ __param(3, Inject(MachinesService)),
198
+ __param(4, Inject(KERNEL_LOGGER))
199
+ ], DevicePolicyService);
200
+ export { DevicePolicyService };
201
+ /**
202
+ * Hosts the platform may send: an approval's `add` takes exact hosts only (a card never grants a
203
+ * wildcard); the owner's own change on the device page (no approval) may add a wildcard (spec §7);
204
+ * `remove` takes any valid entry (narrowing, a wildcard too).
205
+ */
206
+ function assertHosts(change, fromApproval) {
207
+ const bad = (h) => typeof h !== "string" || validateAllowHost(h).length > 0;
208
+ for (const h of change.add ?? []) {
209
+ if (bad(h) || (fromApproval && h.startsWith("*."))) {
210
+ throw new Error(`not an exact host to add to a device policy: ${JSON.stringify(h)}`);
211
+ }
212
+ }
213
+ for (const h of change.remove ?? []) {
214
+ if (bad(h))
215
+ throw new Error(`not a host to remove from a device policy: ${JSON.stringify(h)}`);
216
+ }
217
+ }
@@ -55,6 +55,8 @@ declare class DeviceConnection {
55
55
  private chain;
56
56
  /** Resolves once the lease is handed back (or was never held). */
57
57
  private released;
58
+ /** The hello's record, then each policy frame's, one after another (never rejects). */
59
+ private recorded;
58
60
  constructor(gw: LocalDeviceGateway, socket: WebSocket, identity: MachineIdentity, credential: string);
59
61
  get deviceId(): string;
60
62
  start(): void;
@@ -101,7 +103,7 @@ declare class DeviceConnection {
101
103
  * sockets are this process's own; everything other replicas need is in Redis.
102
104
  */
103
105
  export declare class LocalDeviceGateway {
104
- readonly machines: Pick<MachinesService, "authenticate" | "recordHello" | "touch">;
106
+ readonly machines: Pick<MachinesService, "authenticate" | "recordHello" | "recordPolicy" | "touch">;
105
107
  readonly locks: Pick<LockService, "hold">;
106
108
  readonly bus: ExtensionContext["bus"];
107
109
  readonly redis: Redis;
@@ -112,7 +114,7 @@ export declare class LocalDeviceGateway {
112
114
  private stopping;
113
115
  /** How often connections are pinged ({@link PING_INTERVAL_MS}; a test seam). */
114
116
  pingIntervalMs: number;
115
- constructor(machines: Pick<MachinesService, "authenticate" | "recordHello" | "touch">, locks: Pick<LockService, "hold">, bus: ExtensionContext["bus"], redis: Redis, config: KernelConfigService, log: Pick<KernelLogger, "info" | "warn">);
117
+ constructor(machines: Pick<MachinesService, "authenticate" | "recordHello" | "recordPolicy" | "touch">, locks: Pick<LockService, "hold">, bus: ExtensionContext["bus"], redis: Redis, config: KernelConfigService, log: Pick<KernelLogger, "info" | "warn">);
116
118
  graceMs(): number;
117
119
  leaseTtlMs(): number;
118
120
  maxFrameBytes(): number;
@@ -114,6 +114,8 @@ class DeviceConnection {
114
114
  chain = Promise.resolve();
115
115
  /** Resolves once the lease is handed back (or was never held). */
116
116
  released;
117
+ /** The hello's record, then each policy frame's, one after another (never rejects). */
118
+ recorded = Promise.resolve();
117
119
  constructor(gw, socket, identity, credential) {
118
120
  this.gw = gw;
119
121
  this.socket = socket;
@@ -174,17 +176,43 @@ class DeviceConnection {
174
176
  }
175
177
  clearTimeout(this.helloTimer);
176
178
  this.state = "claiming";
177
- if (!(await this.claim()))
178
- return;
179
- try {
180
- await this.gw.machines.recordHello(this.identity.machineId, frame);
181
- }
182
- catch (e) {
183
- this.gw.log.warn("could not record a device hello", {
184
- deviceId: this.deviceId,
185
- error: errMsg(e),
186
- });
187
- }
179
+ // the raw summary too: the frame parser drops one it cannot read whole, and its
180
+ // isolation must still reach the kernel
181
+ const rawPolicy = JSON.parse(text).policy;
182
+ // set before the first await: a policy frame that follows is recorded after the hello
183
+ this.recorded = (async () => {
184
+ if (!(await this.claim()))
185
+ return;
186
+ try {
187
+ await this.gw.machines.recordHello(this.identity.machineId, frame, rawPolicy);
188
+ }
189
+ catch (e) {
190
+ this.gw.log.warn("could not record a device hello", {
191
+ deviceId: this.deviceId,
192
+ error: errMsg(e),
193
+ });
194
+ }
195
+ })();
196
+ await this.recorded;
197
+ return;
198
+ }
199
+ if (frame.type === "policy" && this.state !== "hello") {
200
+ // after the hello's record and the earlier policy frames, in the order they came
201
+ const rawPolicy = JSON.parse(text).policy;
202
+ this.recorded = this.recorded.then(async () => {
203
+ if (this.state === "closed")
204
+ return;
205
+ try {
206
+ await this.gw.machines.recordPolicy(this.identity.machineId, frame.policy, rawPolicy);
207
+ }
208
+ catch (e) {
209
+ this.gw.log.warn("could not record a device policy change", {
210
+ deviceId: this.deviceId,
211
+ error: errMsg(e),
212
+ });
213
+ }
214
+ });
215
+ await this.recorded;
188
216
  return;
189
217
  }
190
218
  if (frame.type !== "res" || this.state === "hello") {
@@ -1,6 +1,8 @@
1
- import type { ExecResult, FileSystemHandle, SandboxHandle, SandboxLimits, SandboxProvider, SandboxRoots } from "@capybara-hq/plugin-sdk";
1
+ import { type ExecResult, type FileSystemHandle, type SandboxHandle, type SandboxLimits, type SandboxProvider, type SandboxRoots } from "@capybara-hq/plugin-sdk";
2
2
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
3
+ import type { KernelLogger } from "../log.js";
3
4
  import { SandboxService } from "../sandbox/sandbox.service.js";
5
+ import { NetworkGrants } from "./network-grants.js";
4
6
  import { LocalDeviceRelay } from "./relay.js";
5
7
  import { DeviceTargets } from "./targets.js";
6
8
  /** Extra time the machine gets beyond a command's own timeout (spawn, output, the trip back). */
@@ -15,6 +17,11 @@ export declare const OP_DEADLINE_MS = 30000;
15
17
  export declare const FRAME_ENVELOPE_BYTES: number;
16
18
  /** The most raw bytes whose base64 plus {@link FRAME_ENVELOPE_BYTES} fit in `maxFrameBytes`. */
17
19
  export declare const maxReadBytes: (maxFrameBytes: number) => number;
20
+ /**
21
+ * The tool error of a device that runs commands without a sandbox the org does not allow (§4.3);
22
+ * also what a machine answers when it refuses such a command itself.
23
+ */
24
+ export declare const UNSANDBOXED_NOT_ALLOWED_ERROR = "This device runs commands without a sandbox, which your organization does not allow. Its owner can turn the sandbox back on with `capybara-device policy set isolation sandbox`.";
18
25
  /**
19
26
  * The handle of an instance on a machine: the device id (ruling R1: the relay is keyed by
20
27
  * device), the roots the machine reported at `create`, and the org's effective limits at
@@ -42,9 +49,11 @@ export declare class LocalDeviceProvider implements SandboxProvider {
42
49
  private readonly targets;
43
50
  private readonly sandbox;
44
51
  private readonly config;
52
+ private readonly grants;
53
+ private readonly log;
45
54
  readonly concurrentExec: true;
46
55
  readonly sessionIsolation = false;
47
- constructor(relay: LocalDeviceRelay, targets: Pick<DeviceTargets, "orgOf" | "machineRoots">, sandbox: Pick<SandboxService, "orgLimits">, config: Pick<KernelConfigService, "get">);
56
+ constructor(relay: LocalDeviceRelay, targets: Pick<DeviceTargets, "orgOf" | "machineRoots">, sandbox: Pick<SandboxService, "devicePolicy" | "deviceLimits">, config: Pick<KernelConfigService, "get">, grants: Pick<NetworkGrants, "forExec" | "unattendedTier" | "unsandboxed" | "recordMissing">, log: Pick<KernelLogger, "warn">);
48
57
  createFileSystem(args: {
49
58
  name: string;
50
59
  limits: SandboxLimits;
@@ -71,6 +80,23 @@ export declare class LocalDeviceProvider implements SandboxProvider {
71
80
  destroyByName(_name: string, signal: AbortSignal): Promise<void>;
72
81
  killAll(h: SandboxHandle, signal: AbortSignal): Promise<void>;
73
82
  killTagged(h: SandboxHandle, tags: string[], signal: AbortSignal): Promise<void>;
83
+ /**
84
+ * Runs the command on the machine with the org's limits now (ruling R27) and, for a command of a
85
+ * run, the hosts the kernel allows that run on this device beyond the local policy (network
86
+ * ask-to-allow spec §6.3; only those the org's limits admit, sent only when there are some). The device's report of refused hosts is re-layered
87
+ * against the limits this exec carried: the kernel never takes the device's word that the org
88
+ * allows a host (spec §4.1).
89
+ *
90
+ * The limits are the device's **ceiling** (sandbox dev tiers spec §4.2): `limits.tier` is the
91
+ * highest tier the org allows, its network that tier's, `defaultTier` the org's default (the
92
+ * device runs at `min(local tier ?? defaultTier, tier)`) and `devHosts` the dev hosts. A run of
93
+ * a scheduled task without a person and without the task's `tier:open` grant is capped at `dev`
94
+ * (spec §11.4). Before relaying, a machine that last reported running commands without a sandbox
95
+ * is refused when the org does not allow it, or for such a run (`device.unsandboxed_not_allowed`,
96
+ * spec §4.3). The kernel knows the local policy only from the last hello or policy op, so the
97
+ * same decision also travels in `limits.allowUnsandboxed` and the machine enforces it itself;
98
+ * its refusal ({@link UNSANDBOXED_NOT_ALLOWED_MESSAGE}) becomes the same error.
99
+ */
74
100
  exec(h: SandboxHandle, args: Parameters<SandboxProvider["exec"]>[1]): Promise<ExecResult>;
75
101
  readFile(h: SandboxHandle, path: string, opts: {
76
102
  maxBytes: number;
@@ -84,6 +110,17 @@ export declare class LocalDeviceProvider implements SandboxProvider {
84
110
  size: number;
85
111
  isDir: boolean;
86
112
  } | null>;
87
- /** The org's effective limits now (ruling R27); a deleted device is not found. */
113
+ /**
114
+ * The device's org and the limits of one exec now (ruling R27): the ceiling, capped at `dev` for
115
+ * an unattended run without the `tier:open` grant (`cap`, set only when the platform and the org
116
+ * would otherwise allow an unsandboxed command); `allowUnsandboxed` whether this command may run
117
+ * without a sandbox. A deleted device is not found. Refuses an unsandboxed machine the org does
118
+ * not allow (or such a run), see {@link exec}.
119
+ */
88
120
  private currentLimits;
121
+ /**
122
+ * The `device.unsandboxed_not_allowed` error (spec §4.3); for an unattended run refused only for
123
+ * its missing `tier:open` grant, the run's text, with the missing grant recorded on the task run.
124
+ */
125
+ private unsandboxedRefusal;
89
126
  }
@@ -7,9 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
+ import { minTier, ToolError, UNSANDBOXED_NOT_ALLOWED_MESSAGE, } from "@capybara-hq/plugin-sdk";
10
11
  import { Inject, Injectable } from "@nestjs/common";
12
+ import { ApiError } from "../errors.js";
11
13
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
14
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
12
15
  import { deviceNotFound, SandboxService } from "../sandbox/sandbox.service.js";
16
+ import { TIER_OPEN_GRANT_KEY, UNATTENDED_UNSANDBOXED_ERROR } from "../schedules/unattended.js";
17
+ import { grantsWithin, NetworkGrants, relayer } from "./network-grants.js";
13
18
  import { deviceOffline, LocalDeviceRelay } from "./relay.js";
14
19
  import { DeviceTargets, sameRoots } from "./targets.js";
15
20
  /** Extra time the machine gets beyond a command's own timeout (spawn, output, the trip back). */
@@ -25,6 +30,16 @@ export const FRAME_ENVELOPE_BYTES = 64 * 1024;
25
30
  /** The most raw bytes whose base64 plus {@link FRAME_ENVELOPE_BYTES} fit in `maxFrameBytes`. */
26
31
  export const maxReadBytes = (maxFrameBytes) => Math.max(0, Math.floor((maxFrameBytes - FRAME_ENVELOPE_BYTES) / 4) * 3);
27
32
  const NAME_PREFIX = "cap-device-";
33
+ /**
34
+ * The tool error of a device that runs commands without a sandbox the org does not allow (§4.3);
35
+ * also what a machine answers when it refuses such a command itself.
36
+ */
37
+ export const UNSANDBOXED_NOT_ALLOWED_ERROR = UNSANDBOXED_NOT_ALLOWED_MESSAGE;
38
+ /**
39
+ * The relay's error for a machine that refused a command with {@link UNSANDBOXED_NOT_ALLOWED_MESSAGE}
40
+ * (a `policy` error: the relay prefixes the machine's message, `relay.ts`).
41
+ */
42
+ const MACHINE_UNSANDBOXED_REFUSAL = `Refused by the machine's local policy: ${UNSANDBOXED_NOT_ALLOWED_MESSAGE}`;
28
43
  /** The device id of a `cap-device-<deviceId>` name; undefined for any other name. */
29
44
  export const deviceIdOfName = (name) => name.startsWith(NAME_PREFIX) && name.length > NAME_PREFIX.length
30
45
  ? name.slice(NAME_PREFIX.length)
@@ -46,13 +61,17 @@ let LocalDeviceProvider = class LocalDeviceProvider {
46
61
  targets;
47
62
  sandbox;
48
63
  config;
64
+ grants;
65
+ log;
49
66
  concurrentExec = true;
50
67
  sessionIsolation = false;
51
- constructor(relay, targets, sandbox, config) {
68
+ constructor(relay, targets, sandbox, config, grants, log) {
52
69
  this.relay = relay;
53
70
  this.targets = targets;
54
71
  this.sandbox = sandbox;
55
72
  this.config = config;
73
+ this.grants = grants;
74
+ this.log = log;
56
75
  }
57
76
  async createFileSystem(args) {
58
77
  args.signal.throwIfAborted();
@@ -103,13 +122,36 @@ let LocalDeviceProvider = class LocalDeviceProvider {
103
122
  async killTagged(h, tags, signal) {
104
123
  await this.relay.call(h.id, "killTagged", { tags }, { deadlineMs: OP_DEADLINE_MS, signal });
105
124
  }
125
+ /**
126
+ * Runs the command on the machine with the org's limits now (ruling R27) and, for a command of a
127
+ * run, the hosts the kernel allows that run on this device beyond the local policy (network
128
+ * ask-to-allow spec §6.3; only those the org's limits admit, sent only when there are some). The device's report of refused hosts is re-layered
129
+ * against the limits this exec carried: the kernel never takes the device's word that the org
130
+ * allows a host (spec §4.1).
131
+ *
132
+ * The limits are the device's **ceiling** (sandbox dev tiers spec §4.2): `limits.tier` is the
133
+ * highest tier the org allows, its network that tier's, `defaultTier` the org's default (the
134
+ * device runs at `min(local tier ?? defaultTier, tier)`) and `devHosts` the dev hosts. A run of
135
+ * a scheduled task without a person and without the task's `tier:open` grant is capped at `dev`
136
+ * (spec §11.4). Before relaying, a machine that last reported running commands without a sandbox
137
+ * is refused when the org does not allow it, or for such a run (`device.unsandboxed_not_allowed`,
138
+ * spec §4.3). The kernel knows the local policy only from the last hello or policy op, so the
139
+ * same decision also travels in `limits.allowUnsandboxed` and the machine enforces it itself;
140
+ * its refusal ({@link UNSANDBOXED_NOT_ALLOWED_MESSAGE}) becomes the same error.
141
+ */
106
142
  async exec(h, args) {
107
143
  const { signal, ...rest } = args;
108
144
  signal.throwIfAborted();
109
- const limits = await this.currentLimits(h.id);
145
+ const runId = rest.actor?.runId;
146
+ const { orgId, limits, cap } = await this.currentLimits(h.id, runId);
147
+ // the run's grants for this device, cut to what the org allows now
148
+ const grants = runId !== undefined
149
+ ? grantsWithin(await this.grants.forExec({ orgId, runId, deviceId: h.id }), limits)
150
+ : undefined;
110
151
  signal.throwIfAborted();
152
+ let result;
111
153
  try {
112
- return await this.relay.call(h.id, "exec", { ...rest, limits }, { deadlineMs: args.timeoutMs + EXEC_DEADLINE_MARGIN_MS, signal });
154
+ result = await this.relay.call(h.id, "exec", { ...rest, limits, ...(grants !== undefined && { grants }) }, { deadlineMs: args.timeoutMs + EXEC_DEADLINE_MARGIN_MS, signal });
113
155
  }
114
156
  catch (e) {
115
157
  // the contract: an exec aborted after it was sent resolves with exitCode null (the relay
@@ -117,8 +159,19 @@ let LocalDeviceProvider = class LocalDeviceProvider {
117
159
  if (signal.aborted && e === signal.reason) {
118
160
  return { exitCode: null, stdout: "", stderr: "", truncated: false, timedOut: false };
119
161
  }
162
+ // the machine refused to run it unsandboxed (its local policy changed since it reported)
163
+ if (e instanceof ToolError && e.message === MACHINE_UNSANDBOXED_REFUSAL) {
164
+ this.log.warn("the machine refused a command without a sandbox", {
165
+ deviceId: h.id,
166
+ reason: cap !== undefined ? "unattended" : "not_allowed",
167
+ });
168
+ throw await this.unsandboxedRefusal(h.id, cap);
169
+ }
120
170
  throw e;
121
171
  }
172
+ if (result.network?.blocked === undefined)
173
+ return result;
174
+ return { ...result, network: { blocked: relayer(result.network.blocked, limits) } };
122
175
  }
123
176
  async readFile(h, path, opts) {
124
177
  // the answer must fit one frame: a larger file comes back truncated (Deliver: too large)
@@ -133,12 +186,59 @@ let LocalDeviceProvider = class LocalDeviceProvider {
133
186
  async stat(h, path, signal) {
134
187
  return this.relay.call(h.id, "stat", { path }, { deadlineMs: OP_DEADLINE_MS, signal });
135
188
  }
136
- /** The org's effective limits now (ruling R27); a deleted device is not found. */
137
- async currentLimits(deviceId) {
189
+ /**
190
+ * The device's org and the limits of one exec now (ruling R27): the ceiling, capped at `dev` for
191
+ * an unattended run without the `tier:open` grant (`cap`, set only when the platform and the org
192
+ * would otherwise allow an unsandboxed command); `allowUnsandboxed` whether this command may run
193
+ * without a sandbox. A deleted device is not found. Refuses an unsandboxed machine the org does
194
+ * not allow (or such a run), see {@link exec}.
195
+ */
196
+ async currentLimits(deviceId, runId) {
138
197
  const orgId = await this.targets.orgOf(deviceId);
139
198
  if (orgId === null)
140
199
  throw deviceNotFound();
141
- return this.sandbox.orgLimits(orgId);
200
+ const [policy, unattended, unsandboxed] = await Promise.all([
201
+ this.sandbox.devicePolicy({ orgId, deviceId }),
202
+ runId === undefined ? null : this.grants.unattendedTier({ runId, deviceId }),
203
+ this.grants.unsandboxed(deviceId),
204
+ ]);
205
+ const capped = unattended !== null && !unattended.openGranted;
206
+ // what an unsandboxed refusal reports: the run's missing grant only when it alone refused
207
+ const cap = capped && policy.allowUnsandboxed ? { taskRunId: unattended.taskRunId } : undefined;
208
+ if (unsandboxed && (!policy.allowUnsandboxed || capped)) {
209
+ this.log.warn("command refused on a device that runs without a sandbox", {
210
+ deviceId,
211
+ reason: policy.allowUnsandboxed ? "unattended" : "not_allowed",
212
+ });
213
+ throw await this.unsandboxedRefusal(deviceId, cap);
214
+ }
215
+ const tier = capped ? minTier(policy.ceiling, "dev") : policy.ceiling;
216
+ const limits = await this.sandbox.deviceLimits({ orgId, deviceId }, { policy, tier });
217
+ return {
218
+ orgId,
219
+ limits: {
220
+ ...limits,
221
+ defaultTier: minTier(policy.defaultTier, tier),
222
+ // the platform and the org allow it (which needs an open ceiling) and no cap applies
223
+ allowUnsandboxed: policy.allowUnsandboxed && !capped,
224
+ },
225
+ cap,
226
+ };
227
+ }
228
+ /**
229
+ * The `device.unsandboxed_not_allowed` error (spec §4.3); for an unattended run refused only for
230
+ * its missing `tier:open` grant, the run's text, with the missing grant recorded on the task run.
231
+ */
232
+ async unsandboxedRefusal(deviceId, cap) {
233
+ if (cap === undefined) {
234
+ return new ApiError("device.unsandboxed_not_allowed", UNSANDBOXED_NOT_ALLOWED_ERROR);
235
+ }
236
+ await this.grants
237
+ .recordMissing(cap.taskRunId, TIER_OPEN_GRANT_KEY)
238
+ .catch((err) => this.log.warn("cannot record a missing task grant", { deviceId, err }));
239
+ return new ApiError("device.unsandboxed_not_allowed", UNATTENDED_UNSANDBOXED_ERROR, [
240
+ { reason: "unattended" },
241
+ ]);
142
242
  }
143
243
  };
144
244
  LocalDeviceProvider = __decorate([
@@ -146,6 +246,8 @@ LocalDeviceProvider = __decorate([
146
246
  __param(0, Inject(LocalDeviceRelay)),
147
247
  __param(1, Inject(DeviceTargets)),
148
248
  __param(2, Inject(SandboxService)),
149
- __param(3, Inject(KernelConfigService))
249
+ __param(3, Inject(KernelConfigService)),
250
+ __param(4, Inject(NetworkGrants)),
251
+ __param(5, Inject(KERNEL_LOGGER))
150
252
  ], LocalDeviceProvider);
151
253
  export { LocalDeviceProvider };
@@ -4,7 +4,8 @@ import { LocalDeviceProvider } from "./local-device-provider.js";
4
4
  /**
5
5
  * Connected machines (environments spec §6, §13): the caller side of the device relay and the
6
6
  * kernel-internal `@local` sandbox provider, registered with {@link SandboxProviders} on init, and
7
- * the machines and their credentials ({@link MachinesService}).
7
+ * the machines and their credentials ({@link MachinesService}), changes to a device's network
8
+ * policy ({@link DevicePolicyService}) and the run grants sent with each exec ({@link NetworkGrants}).
8
9
  * Imported on every role: any replica may run a sandbox operation on a local device
9
10
  * (the socket itself is held by the gateway role).
10
11
  */
@@ -10,14 +10,17 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Inject, Module } from "@nestjs/common";
11
11
  import { LOCAL_PROVIDER, SandboxProviders } from "../sandbox/providers.js";
12
12
  import { SandboxModule } from "../sandbox/sandbox.module.js";
13
+ import { DevicePolicyService } from "./device-policy.service.js";
13
14
  import { LocalDeviceProvider } from "./local-device-provider.js";
14
15
  import { MachinesService } from "./machines.js";
16
+ import { NetworkGrants } from "./network-grants.js";
15
17
  import { LocalDeviceRelay } from "./relay.js";
16
18
  import { DeviceTargets } from "./targets.js";
17
19
  /**
18
20
  * Connected machines (environments spec §6, §13): the caller side of the device relay and the
19
21
  * kernel-internal `@local` sandbox provider, registered with {@link SandboxProviders} on init, and
20
- * the machines and their credentials ({@link MachinesService}).
22
+ * the machines and their credentials ({@link MachinesService}), changes to a device's network
23
+ * policy ({@link DevicePolicyService}) and the run grants sent with each exec ({@link NetworkGrants}).
21
24
  * Imported on every role: any replica may run a sandbox operation on a local device
22
25
  * (the socket itself is held by the gateway role).
23
26
  */
@@ -35,8 +38,21 @@ let LocalDevicesModule = class LocalDevicesModule {
35
38
  LocalDevicesModule = __decorate([
36
39
  Module({
37
40
  imports: [SandboxModule],
38
- providers: [DeviceTargets, LocalDeviceRelay, LocalDeviceProvider, MachinesService],
39
- exports: [LocalDeviceRelay, LocalDeviceProvider, MachinesService],
41
+ providers: [
42
+ DeviceTargets,
43
+ LocalDeviceRelay,
44
+ LocalDeviceProvider,
45
+ MachinesService,
46
+ DevicePolicyService,
47
+ NetworkGrants,
48
+ ],
49
+ exports: [
50
+ LocalDeviceRelay,
51
+ LocalDeviceProvider,
52
+ MachinesService,
53
+ DevicePolicyService,
54
+ NetworkGrants,
55
+ ],
40
56
  }),
41
57
  __param(0, Inject(SandboxProviders)),
42
58
  __param(1, Inject(LocalDeviceProvider))