@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
@@ -1,4 +1,4 @@
1
- import type { DeviceHello, DeviceStopArgs, ExtensionContext } from "@capybara-hq/plugin-sdk";
1
+ import type { DeviceFeature, DeviceHello, DeviceLocalPolicySummary, DeviceStopArgs, ExtensionContext } from "@capybara-hq/plugin-sdk";
2
2
  import { KernelDb } from "../infra/database/kernel-db.js";
3
3
  import { TransactionHost } from "../infra/database/transaction-host.js";
4
4
  import type { KernelLogger } from "../log.js";
@@ -23,6 +23,10 @@ export interface ActiveMachine {
23
23
  programVersion: string | null;
24
24
  lastSeenAt: Date | null;
25
25
  createdAt: Date;
26
+ /** The features its program reported in its last hello (known ones only). */
27
+ features: DeviceFeature[];
28
+ /** Its local network policy as last reported (hello or `policy` op); null when unknown. */
29
+ localPolicy: DeviceLocalPolicySummary | null;
26
30
  }
27
31
  /**
28
32
  * Connected machines and their credentials (environments spec §13.2). One active machine per
@@ -83,9 +87,32 @@ export declare class MachinesService {
83
87
  * {@link issue} already counted the reset) but serves another directory or mode (other roots,
84
88
  * ruling R23), the device's files are other files: `fs_resets` moves on (maintainer
85
89
  * decision 2026-11-08), so conversations get the files-reset notice. A previous machine that
86
- * never said hello has no known roots: nothing to compare.
90
+ * never said hello has no known roots: nothing to compare. The reported features (known ones
91
+ * only) and local policy summary (null when absent or malformed) replace the stored ones, so a
92
+ * program downgraded to one without them is no longer sent what it cannot do. A summary the
93
+ * kernel cannot read (`hello.policy` dropped by the frame parser) is kept as its isolation
94
+ * alone, read from `rawPolicy` (the frame's raw `policy`; {@link isolationOf}: anything but
95
+ * `sandbox` is `none`), so it never hides that commands run without a sandbox. A reported
96
+ * isolation that differs from the stored one (none stored counts as `sandbox`) is audited
97
+ * `device.isolation_reported` (sandbox dev tiers spec §10.4), once per change.
87
98
  */
88
- recordHello(machineId: string, hello: DeviceHello): Promise<void>;
99
+ recordHello(machineId: string, hello: DeviceHello, rawPolicy?: unknown): Promise<void>;
100
+ /**
101
+ * Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
102
+ * its policy file changed on the machine, its localhost proxy guard flipped, a new tier
103
+ * ceiling), read and audited as the hello's ({@link recordHello}): an unreadable summary keeps
104
+ * its isolation alone (`rawPolicy`), a changed isolation is audited `device.isolation_reported`.
105
+ * The next sandbox call then records a new `device_paths` event when the facts the model is told
106
+ * changed. A revoked machine is left alone.
107
+ */
108
+ recordPolicy(machineId: string, summary: DeviceLocalPolicySummary | undefined, rawPolicy?: unknown): Promise<void>;
109
+ /**
110
+ * The summary to store for one a program reported (hello or `policy` frame): the parsed one,
111
+ * else its raw isolation alone, else null. Audits `device.isolation_reported` when the isolation
112
+ * differs from the stored one (none stored counts as `sandbox`). Runs in the caller's
113
+ * transaction, with the machine's row locked.
114
+ */
115
+ private reportedPolicy;
89
116
  /** Marks the machine seen (a ping); writes at most once every 60 seconds. */
90
117
  touch(machineId: string): Promise<void>;
91
118
  /**
@@ -20,6 +20,7 @@ import { KERNEL_BUS, KERNEL_LOGGER } from "../infra/tokens.js";
20
20
  import { LOCAL_PROVIDER } from "../sandbox/providers.js";
21
21
  import { deviceNotFound } from "../sandbox/sandbox.service.js";
22
22
  import { deviceRevokedTopic } from "./keys.js";
23
+ import { deviceFeaturesOf, isolationOf, localPolicyOf } from "./policy-summary.js";
23
24
  import { LocalDeviceRelay } from "./relay.js";
24
25
  /** The machine credential's prefix (`cap_dev_` plus 32 random bytes, base64url). */
25
26
  export const MACHINE_CREDENTIAL_PREFIX = "cap_dev_";
@@ -154,12 +155,29 @@ let MachinesService = class MachinesService {
154
155
  * {@link issue} already counted the reset) but serves another directory or mode (other roots,
155
156
  * ruling R23), the device's files are other files: `fs_resets` moves on (maintainer
156
157
  * decision 2026-11-08), so conversations get the files-reset notice. A previous machine that
157
- * never said hello has no known roots: nothing to compare.
158
+ * never said hello has no known roots: nothing to compare. The reported features (known ones
159
+ * only) and local policy summary (null when absent or malformed) replace the stored ones, so a
160
+ * program downgraded to one without them is no longer sent what it cannot do. A summary the
161
+ * kernel cannot read (`hello.policy` dropped by the frame parser) is kept as its isolation
162
+ * alone, read from `rawPolicy` (the frame's raw `policy`; {@link isolationOf}: anything but
163
+ * `sandbox` is `none`), so it never hides that commands run without a sandbox. A reported
164
+ * isolation that differs from the stored one (none stored counts as `sandbox`) is audited
165
+ * `device.isolation_reported` (sandbox dev tiers spec §10.4), once per change.
158
166
  */
159
- async recordHello(machineId, hello) {
167
+ async recordHello(machineId, hello, rawPolicy = hello.policy) {
160
168
  const self = await this.db
161
169
  .selectFrom("device_machines")
162
- .select(["device_id", "name", "os", "arch", "roots", "created_at"])
170
+ .select([
171
+ "device_id",
172
+ "org_id",
173
+ "account_id",
174
+ "name",
175
+ "os",
176
+ "arch",
177
+ "roots",
178
+ "created_at",
179
+ "local_policy",
180
+ ])
163
181
  .where("id", "=", machineId)
164
182
  .where("revoked_at", "is", null)
165
183
  .forUpdate()
@@ -189,6 +207,7 @@ let MachinesService = class MachinesService {
189
207
  .execute();
190
208
  }
191
209
  }
210
+ const policy = await this.reportedPolicy(machineId, self, hello.policy, rawPolicy);
192
211
  await this.db
193
212
  .updateTable("device_machines")
194
213
  .set({
@@ -198,12 +217,61 @@ let MachinesService = class MachinesService {
198
217
  mode: hello.mode,
199
218
  roots: JSON.stringify(hello.roots),
200
219
  session_isolation: hello.sessionIsolation,
220
+ features: deviceFeaturesOf(hello.features),
221
+ local_policy: jsonOrNull(policy),
201
222
  last_seen_at: sql `now()`,
202
223
  })
203
224
  .where("id", "=", machineId)
204
225
  .where("revoked_at", "is", null)
205
226
  .execute();
206
227
  }
228
+ /**
229
+ * Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
230
+ * its policy file changed on the machine, its localhost proxy guard flipped, a new tier
231
+ * ceiling), read and audited as the hello's ({@link recordHello}): an unreadable summary keeps
232
+ * its isolation alone (`rawPolicy`), a changed isolation is audited `device.isolation_reported`.
233
+ * The next sandbox call then records a new `device_paths` event when the facts the model is told
234
+ * changed. A revoked machine is left alone.
235
+ */
236
+ async recordPolicy(machineId, summary, rawPolicy = summary) {
237
+ const self = await this.db
238
+ .selectFrom("device_machines")
239
+ .select(["device_id", "org_id", "account_id", "local_policy"])
240
+ .where("id", "=", machineId)
241
+ .where("revoked_at", "is", null)
242
+ .forUpdate()
243
+ .executeTakeFirst();
244
+ if (self === undefined)
245
+ return;
246
+ const policy = await this.reportedPolicy(machineId, self, summary, rawPolicy);
247
+ await this.db
248
+ .updateTable("device_machines")
249
+ .set({ local_policy: jsonOrNull(policy), last_seen_at: sql `now()` })
250
+ .where("id", "=", machineId)
251
+ .where("revoked_at", "is", null)
252
+ .execute();
253
+ }
254
+ /**
255
+ * The summary to store for one a program reported (hello or `policy` frame): the parsed one,
256
+ * else its raw isolation alone, else null. Audits `device.isolation_reported` when the isolation
257
+ * differs from the stored one (none stored counts as `sandbox`). Runs in the caller's
258
+ * transaction, with the machine's row locked.
259
+ */
260
+ async reportedPolicy(machineId, self, reported, rawPolicy) {
261
+ const summary = localPolicyOf(reported);
262
+ const isolation = summary?.isolation ?? isolationOf(rawPolicy);
263
+ const before = isolationOf(self.local_policy) ?? "sandbox";
264
+ if (isolation !== null && isolation !== before) {
265
+ await audit(this.db.db, {
266
+ actorAccountId: self.account_id,
267
+ orgId: self.org_id,
268
+ action: "device.isolation_reported",
269
+ target: self.device_id,
270
+ detail: { machineId, isolation, previous: before },
271
+ });
272
+ }
273
+ return summary ?? (isolation === null ? null : { isolation });
274
+ }
207
275
  /** Marks the machine seen (a ping); writes at most once every 60 seconds. */
208
276
  async touch(machineId) {
209
277
  await this.db
@@ -302,6 +370,8 @@ let MachinesService = class MachinesService {
302
370
  "program_version",
303
371
  "last_seen_at",
304
372
  "created_at",
373
+ "features",
374
+ "local_policy",
305
375
  ])
306
376
  .where("device_id", "=", deviceId)
307
377
  .where("revoked_at", "is", null)
@@ -317,6 +387,8 @@ let MachinesService = class MachinesService {
317
387
  programVersion: r.program_version,
318
388
  lastSeenAt: r.last_seen_at,
319
389
  createdAt: r.created_at,
390
+ features: deviceFeaturesOf(r.features),
391
+ localPolicy: localPolicyOf(r.local_policy),
320
392
  }
321
393
  : null;
322
394
  }
@@ -348,6 +420,9 @@ __decorate([
348
420
  __decorate([
349
421
  Transactional()
350
422
  ], MachinesService.prototype, "recordHello", null);
423
+ __decorate([
424
+ Transactional()
425
+ ], MachinesService.prototype, "recordPolicy", null);
351
426
  __decorate([
352
427
  Transactional()
353
428
  ], MachinesService.prototype, "revoke", null);
@@ -360,6 +435,7 @@ MachinesService = __decorate([
360
435
  __param(4, Inject(KERNEL_LOGGER))
361
436
  ], MachinesService);
362
437
  export { MachinesService };
438
+ const jsonOrNull = (v) => (v === null ? null : JSON.stringify(v));
363
439
  /** Two machine rows (or a login) name the same machine: same name, OS and arch. */
364
440
  function sameMachine(a, b) {
365
441
  return a.name === b.name && a.os === b.os && a.arch === b.arch;
@@ -0,0 +1,104 @@
1
+ import { type DeviceExecGrants, type NetworkBlockedHost, type SandboxLimits } from "@capybara-hq/plugin-sdk";
2
+ import { KernelDb } from "../infra/database/kernel-db.js";
3
+ /** The most hosts one exec's grants carry (the device protocol's limit). */
4
+ export declare const MAX_GRANT_HOSTS = 100;
5
+ /** What a run tree's network asks add up to (network ask-to-allow spec §6.8). */
6
+ export interface NetworkRunState {
7
+ /** Hosts allowed for the rest of the run tree (「这次允许」). */
8
+ granted: Set<string>;
9
+ /** Hosts refused in the run tree (denied, expired, skipped, cancelled, or unchecked): never asked again. */
10
+ refused: Set<string>;
11
+ /** Network asks of this run (the per-run cap counts them). */
12
+ asks: number;
13
+ /** A network ask of the run tree is still pending (at most one at a time). */
14
+ pending: boolean;
15
+ /**
16
+ * Hosts the scheduled task the run serves approves on this device (spec §8.1; unattended runs
17
+ * only, read only with a `deviceId`): never asked about nor recorded as missing. Absent: none.
18
+ */
19
+ taskGranted?: Set<string>;
20
+ }
21
+ /**
22
+ * Network hosts the kernel allows a run beyond a device's local policy (network ask-to-allow spec
23
+ * §6.3): the hosts of the run tree's approved `network` asks with scope `once` (「这次允许」), read
24
+ * from PG for every exec, so a grant decided on one replica reaches a command on any other. A run
25
+ * tree is the runs of one budget window (`runs.budget_run_id` lineage): a sub-agent's run sees the
26
+ * root's grants, another run tree never does. The hosts a decision allowed are its
27
+ * `detail.grantedHosts`; a decision without them grants nothing. In an unattended run of a
28
+ * scheduled task, the task's `network:<host>` grants (spec §8.1) join them.
29
+ */
30
+ export declare class NetworkGrants {
31
+ private readonly db;
32
+ constructor(db: KernelDb);
33
+ /**
34
+ * Hosts allowed for this exec on `deviceId`: the run tree's once-approved network asks in
35
+ * `orgId` about that device, while the tree's root run has not ended (a grant ends with its run;
36
+ * a later run of the same budget window does not inherit it), with the root run as their source
37
+ * and the latest decider as its account; and in an unattended run of a scheduled task, the
38
+ * task's network grants (spec §8.1) when the device is its creator's, with the task as their
39
+ * source and the creator as its account. Both together are one grant: the hosts of both, the
40
+ * task as the source. Undefined when none. The device applies them only when its owner allows
41
+ * remote grants and only within the org's limits.
42
+ */
43
+ forExec(input: {
44
+ orgId: string;
45
+ runId: string;
46
+ deviceId: string;
47
+ }): Promise<DeviceExecGrants | undefined>;
48
+ /**
49
+ * The run tree's network asks (spec §6.8): what was granted and refused, the run's asks, a
50
+ * pending ask. With `deviceId`, granted and refused count only the asks about that device (a
51
+ * host allowed on one device is not allowed on another); the cap and the pending ask count all.
52
+ */
53
+ runState(runId: string, deviceId?: string): Promise<NetworkRunState>;
54
+ /** The run tree's once-approved hosts as exec grants (source: the root run). */
55
+ private runGrants;
56
+ /**
57
+ * A scheduled task's network grants for an unattended run (spec §8.1): the task's
58
+ * `network:<host>` grants when the run serves a task run that is no trial run and no person
59
+ * joined (`taskGrantsRunOf`, inherited by its sub-agents), the task is of `orgId` (when given)
60
+ * and `deviceId` is a live device of the task's org owned by the task's creator (a creator
61
+ * approves domains for their own devices only). Exact valid hosts only.
62
+ */
63
+ private taskGrants;
64
+ /**
65
+ * The tier rule of an unattended run on `deviceId` (sandbox dev tiers spec §11.4), read fresh:
66
+ * null for an attended run; otherwise the task run and whether the task grants `tier:open` on
67
+ * this device (`unattendedTierOf`).
68
+ */
69
+ unattendedTier(input: {
70
+ runId: string;
71
+ deviceId: string;
72
+ }): Promise<{
73
+ taskRunId: string;
74
+ openGranted: boolean;
75
+ } | null>;
76
+ /** Records `key` in the task run's `missing_grants` (once). */
77
+ recordMissing(taskRunId: string, key: string): Promise<void>;
78
+ /**
79
+ * Whether the device's active machine runs commands without a sandbox
80
+ * (`device_machines.local_policy.isolation`, read fresh at each exec, spec §4.3), read from the
81
+ * stored JSON's isolation field alone ({@link isolationOf}). Fails closed for a program that
82
+ * applies tiers (feature `tiers`, which always sends a summary): no summary or any isolation but
83
+ * `sandbox` counts as unsandboxed. False without a machine, or for an older program without
84
+ * tiers (it has no unsandboxed mode).
85
+ */
86
+ unsandboxed(deviceId: string): Promise<boolean>;
87
+ /** The runs of the tree whose budget run is `root`. */
88
+ private treeOf;
89
+ }
90
+ /** Whether the org's effective egress lets a command reach `host`. */
91
+ export declare function orgAllows(host: string, limits: Pick<SandboxLimits, "egress" | "allowHosts">): boolean;
92
+ /**
93
+ * The kernel's own layer for a device's report (spec §4.1 item 3): it trusts the device's word that
94
+ * a host was refused, never its word that the org allows it. A host the device calls `device` that
95
+ * the org's effective egress (`limits`, read for this exec) does not allow becomes `org`; one that
96
+ * is no valid host name becomes `address`. Other layers are kept.
97
+ */
98
+ export declare function relayer(blocked: NetworkBlockedHost[], limits: Pick<SandboxLimits, "egress" | "allowHosts">): NetworkBlockedHost[];
99
+ /**
100
+ * `grants` cut to the hosts the org's effective egress (`limits`, read for this exec) admits, or
101
+ * undefined when none is left. The device intersects them with the limits too; the kernel does
102
+ * it first so a grant never reaches a device beyond what the org allows.
103
+ */
104
+ export declare function grantsWithin(grants: DeviceExecGrants | undefined, limits: Pick<SandboxLimits, "egress" | "allowHosts">): DeviceExecGrants | undefined;
@@ -0,0 +1,288 @@
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 { validateAllowHost, } from "@capybara-hq/plugin-sdk";
11
+ import { Inject, Injectable } from "@nestjs/common";
12
+ import { sql } from "kysely";
13
+ import { KernelDb } from "../infra/database/kernel-db.js";
14
+ import { budgetRunOf } from "../runtime/subagents/limits.js";
15
+ import { appendMissingGrant, taskGrantsRunOf, unattendedTierOf } from "../schedules/unattended.js";
16
+ import { deviceFeaturesOf, isolationOf } from "./policy-summary.js";
17
+ /** The most hosts one exec's grants carry (the device protocol's limit). */
18
+ export const MAX_GRANT_HOSTS = 100;
19
+ /** The key prefix of a scheduled task's network grant (`schedules/grant-rules.ts`). */
20
+ const NETWORK_KEY_PREFIX = "network:";
21
+ /** The statuses of a run that has not ended: its run grants still apply. */
22
+ const LIVE_RUN = ["queued", "running", "waiting"];
23
+ /** The statuses of a network ask that refuse its hosts for the rest of the run tree (§6.8). */
24
+ const REFUSING = new Set(["denied", "expired", "skipped", "cancelled"]);
25
+ /** An exact host a grant may carry: no wildcard, valid. */
26
+ const exactHost = (h) => typeof h === "string" && !h.startsWith("*.") && validateAllowHost(h).length === 0;
27
+ /** `detail.deviceId` of a network ask: the device it asked about. */
28
+ function deviceOf(detail) {
29
+ const d = detail?.deviceId;
30
+ return typeof d === "string" ? d : undefined;
31
+ }
32
+ /** `detail.grantedHosts` of a decided network ask: what the decision allowed (absent: none). */
33
+ function grantedOf(detail) {
34
+ const g = detail?.grantedHosts;
35
+ return Array.isArray(g) ? g.filter(exactHost) : [];
36
+ }
37
+ /** `detail.hosts[].host` of a network ask: what it asked about. */
38
+ function askedOf(detail) {
39
+ const hs = detail?.hosts;
40
+ if (!Array.isArray(hs))
41
+ return [];
42
+ return hs.flatMap((h) => {
43
+ const host = h?.host;
44
+ return typeof host === "string" ? [host] : [];
45
+ });
46
+ }
47
+ /**
48
+ * Network hosts the kernel allows a run beyond a device's local policy (network ask-to-allow spec
49
+ * §6.3): the hosts of the run tree's approved `network` asks with scope `once` (「这次允许」), read
50
+ * from PG for every exec, so a grant decided on one replica reaches a command on any other. A run
51
+ * tree is the runs of one budget window (`runs.budget_run_id` lineage): a sub-agent's run sees the
52
+ * root's grants, another run tree never does. The hosts a decision allowed are its
53
+ * `detail.grantedHosts`; a decision without them grants nothing. In an unattended run of a
54
+ * scheduled task, the task's `network:<host>` grants (spec §8.1) join them.
55
+ */
56
+ let NetworkGrants = class NetworkGrants {
57
+ db;
58
+ constructor(db) {
59
+ this.db = db;
60
+ }
61
+ /**
62
+ * Hosts allowed for this exec on `deviceId`: the run tree's once-approved network asks in
63
+ * `orgId` about that device, while the tree's root run has not ended (a grant ends with its run;
64
+ * a later run of the same budget window does not inherit it), with the root run as their source
65
+ * and the latest decider as its account; and in an unattended run of a scheduled task, the
66
+ * task's network grants (spec §8.1) when the device is its creator's, with the task as their
67
+ * source and the creator as its account. Both together are one grant: the hosts of both, the
68
+ * task as the source. Undefined when none. The device applies them only when its owner allows
69
+ * remote grants and only within the org's limits.
70
+ */
71
+ async forExec(input) {
72
+ const run = await this.runGrants(input);
73
+ const task = await this.taskGrants(input);
74
+ if (task === undefined)
75
+ return run;
76
+ if (run === undefined)
77
+ return task;
78
+ // one exec frame carries one source: the task's, the standing approval behind an unattended
79
+ // run (the run grants of an unattended run come from asks only in `wait` mode); the device
80
+ // logs it with the command, and the kernel's approvals keep the per-host record
81
+ const hosts = [...new Set([...task.hosts, ...run.hosts])].slice(0, MAX_GRANT_HOSTS);
82
+ return { hosts, source: task.source };
83
+ }
84
+ /**
85
+ * The run tree's network asks (spec §6.8): what was granted and refused, the run's asks, a
86
+ * pending ask. With `deviceId`, granted and refused count only the asks about that device (a
87
+ * host allowed on one device is not allowed on another); the cap and the pending ask count all.
88
+ */
89
+ async runState(runId, deviceId) {
90
+ const task = deviceId === undefined ? undefined : await this.taskGrants({ runId, deviceId });
91
+ const root = await budgetRunOf(this.db.db, runId);
92
+ const rows = await this.db
93
+ .selectFrom("approvals")
94
+ .select(["run_id", "status", "scope", "detail"])
95
+ .where("kind", "=", "network")
96
+ .where("run_id", "in", this.treeOf(root))
97
+ .execute();
98
+ const state = {
99
+ granted: new Set(),
100
+ refused: new Set(),
101
+ asks: 0,
102
+ pending: false,
103
+ };
104
+ for (const r of rows) {
105
+ if (r.run_id === runId)
106
+ state.asks++;
107
+ if (r.status === "pending") {
108
+ state.pending = true;
109
+ }
110
+ else if (deviceId !== undefined && deviceOf(r.detail) !== deviceId) {
111
+ // another device's decision says nothing about this one
112
+ }
113
+ else if (r.status === "approved") {
114
+ const granted = grantedOf(r.detail);
115
+ if (r.scope === "once")
116
+ for (const h of granted)
117
+ state.granted.add(h);
118
+ // the hosts left unchecked in a partial decision count as denied (P-D5)
119
+ for (const h of askedOf(r.detail))
120
+ if (!granted.includes(h))
121
+ state.refused.add(h);
122
+ }
123
+ else if (REFUSING.has(r.status)) {
124
+ for (const h of askedOf(r.detail))
125
+ state.refused.add(h);
126
+ }
127
+ }
128
+ if (task !== undefined)
129
+ state.taskGranted = new Set(task.hosts);
130
+ return state;
131
+ }
132
+ /** The run tree's once-approved hosts as exec grants (source: the root run). */
133
+ async runGrants(input) {
134
+ const root = await budgetRunOf(this.db.db, input.runId);
135
+ const live = await this.db
136
+ .selectFrom("runs")
137
+ .select("id")
138
+ .where("id", "=", root)
139
+ .where("status", "in", LIVE_RUN)
140
+ .executeTakeFirst();
141
+ if (live === undefined)
142
+ return undefined;
143
+ const rows = await this.db
144
+ .selectFrom("approvals")
145
+ .select(["decided_by", "detail"])
146
+ .where("kind", "=", "network")
147
+ .where("org_id", "=", input.orgId)
148
+ .where("status", "=", "approved")
149
+ .where("scope", "=", "once")
150
+ .where("decided_by", "is not", null)
151
+ .where(sql `detail->>'deviceId'`, "=", input.deviceId)
152
+ .where("run_id", "in", this.treeOf(root))
153
+ .orderBy("decided_at", "desc")
154
+ .orderBy("id", "desc")
155
+ .execute();
156
+ const hosts = [...new Set(rows.flatMap((r) => grantedOf(r.detail)))].slice(0, MAX_GRANT_HOSTS);
157
+ const latest = rows.find((r) => grantedOf(r.detail).length > 0);
158
+ if (hosts.length === 0 || latest?.decided_by == null)
159
+ return undefined;
160
+ return { hosts, source: { kind: "run", id: root, accountId: latest.decided_by } };
161
+ }
162
+ /**
163
+ * A scheduled task's network grants for an unattended run (spec §8.1): the task's
164
+ * `network:<host>` grants when the run serves a task run that is no trial run and no person
165
+ * joined (`taskGrantsRunOf`, inherited by its sub-agents), the task is of `orgId` (when given)
166
+ * and `deviceId` is a live device of the task's org owned by the task's creator (a creator
167
+ * approves domains for their own devices only). Exact valid hosts only.
168
+ */
169
+ async taskGrants(input) {
170
+ const task = await taskGrantsRunOf(this.db.db, input.runId);
171
+ if (task === null || (input.orgId !== undefined && task.orgId !== input.orgId)) {
172
+ return undefined;
173
+ }
174
+ const device = await this.db
175
+ .selectFrom("devices")
176
+ .select("id")
177
+ .where("id", "=", input.deviceId)
178
+ .where("org_id", "=", task.orgId)
179
+ .where("owner_account_id", "=", task.accountId)
180
+ .where("deleted_at", "is", null)
181
+ .executeTakeFirst();
182
+ if (device === undefined)
183
+ return undefined;
184
+ const rows = await this.db
185
+ .selectFrom("scheduled_task_grants")
186
+ .select("tool_key")
187
+ .where("task_id", "=", task.taskId)
188
+ .where("tool_key", "like", `${NETWORK_KEY_PREFIX}%`)
189
+ .orderBy("tool_key")
190
+ .execute();
191
+ const hosts = rows
192
+ .map((r) => r.tool_key.slice(NETWORK_KEY_PREFIX.length))
193
+ .filter(exactHost)
194
+ .slice(0, MAX_GRANT_HOSTS);
195
+ if (hosts.length === 0)
196
+ return undefined;
197
+ return { hosts, source: { kind: "task", id: task.taskId, accountId: task.accountId } };
198
+ }
199
+ /**
200
+ * The tier rule of an unattended run on `deviceId` (sandbox dev tiers spec §11.4), read fresh:
201
+ * null for an attended run; otherwise the task run and whether the task grants `tier:open` on
202
+ * this device (`unattendedTierOf`).
203
+ */
204
+ async unattendedTier(input) {
205
+ return await unattendedTierOf(this.db.db, input.runId, input.deviceId);
206
+ }
207
+ /** Records `key` in the task run's `missing_grants` (once). */
208
+ async recordMissing(taskRunId, key) {
209
+ await appendMissingGrant(this.db.db, taskRunId, key);
210
+ }
211
+ /**
212
+ * Whether the device's active machine runs commands without a sandbox
213
+ * (`device_machines.local_policy.isolation`, read fresh at each exec, spec §4.3), read from the
214
+ * stored JSON's isolation field alone ({@link isolationOf}). Fails closed for a program that
215
+ * applies tiers (feature `tiers`, which always sends a summary): no summary or any isolation but
216
+ * `sandbox` counts as unsandboxed. False without a machine, or for an older program without
217
+ * tiers (it has no unsandboxed mode).
218
+ */
219
+ async unsandboxed(deviceId) {
220
+ const row = await this.db
221
+ .selectFrom("device_machines")
222
+ .select(["local_policy", "features"])
223
+ .where("device_id", "=", deviceId)
224
+ .where("revoked_at", "is", null)
225
+ .executeTakeFirst();
226
+ if (row === undefined)
227
+ return false;
228
+ const isolation = isolationOf(row.local_policy);
229
+ if (isolation !== null)
230
+ return isolation === "none";
231
+ return deviceFeaturesOf(row.features).includes("tiers");
232
+ }
233
+ /** The runs of the tree whose budget run is `root`. */
234
+ treeOf(root) {
235
+ return this.db
236
+ .selectFrom("runs")
237
+ .select("id")
238
+ .where((eb) => eb.or([eb("id", "=", root), eb("budget_run_id", "=", root)]));
239
+ }
240
+ };
241
+ NetworkGrants = __decorate([
242
+ Injectable(),
243
+ __param(0, Inject(KernelDb))
244
+ ], NetworkGrants);
245
+ export { NetworkGrants };
246
+ const normalize = (host) => {
247
+ const h = host.toLowerCase();
248
+ return h.endsWith(".") ? h.slice(0, -1) : h;
249
+ };
250
+ /** Whether the allowlist entry `pattern` admits `host` (`*.x.com`: subdomains, never the apex). */
251
+ const covers = (pattern, host) => pattern === host || (pattern.startsWith("*.") && host.endsWith(pattern.slice(1)));
252
+ /** Whether the org's effective egress lets a command reach `host`. */
253
+ export function orgAllows(host, limits) {
254
+ if (limits.egress === "none")
255
+ return false;
256
+ if (limits.egress === "open")
257
+ return true;
258
+ const h = normalize(host);
259
+ return limits.allowHosts.some((p) => covers(p.toLowerCase(), h));
260
+ }
261
+ /**
262
+ * The kernel's own layer for a device's report (spec §4.1 item 3): it trusts the device's word that
263
+ * a host was refused, never its word that the org allows it. A host the device calls `device` that
264
+ * the org's effective egress (`limits`, read for this exec) does not allow becomes `org`; one that
265
+ * is no valid host name becomes `address`. Other layers are kept.
266
+ */
267
+ export function relayer(blocked, limits) {
268
+ return blocked.map((b) => {
269
+ if (b.layer !== "device")
270
+ return b;
271
+ const h = normalize(b.host);
272
+ if (validateAllowHost(h).length > 0 || h.startsWith("*.")) {
273
+ return { ...b, layer: "address" };
274
+ }
275
+ return orgAllows(b.host, limits) ? b : { ...b, layer: "org" };
276
+ });
277
+ }
278
+ /**
279
+ * `grants` cut to the hosts the org's effective egress (`limits`, read for this exec) admits, or
280
+ * undefined when none is left. The device intersects them with the limits too; the kernel does
281
+ * it first so a grant never reaches a device beyond what the org allows.
282
+ */
283
+ export function grantsWithin(grants, limits) {
284
+ if (grants === undefined)
285
+ return undefined;
286
+ const hosts = grants.hosts.filter((h) => orgAllows(h, limits));
287
+ return hosts.length === 0 ? undefined : { ...grants, hosts };
288
+ }
@@ -0,0 +1,20 @@
1
+ import { type DeviceFeature, type DeviceLocalPolicySummary } from "@capybara-hq/plugin-sdk";
2
+ /**
3
+ * The known features in `v` (a hello's `features` or the stored column), in a fixed order and
4
+ * deduped; anything else (unknown names, a non-array) is dropped.
5
+ */
6
+ export declare function deviceFeaturesOf(v: unknown): DeviceFeature[];
7
+ /**
8
+ * `v` as a local policy summary (network ask-to-allow spec §5.4), or null when it is absent or not
9
+ * a valid one. The device is the authority on its policy: the kernel only keeps what it last said,
10
+ * for the device page and to pick a card's options, never to decide what a command may reach.
11
+ */
12
+ export declare function localPolicyOf(v: unknown): DeviceLocalPolicySummary | null;
13
+ /**
14
+ * The isolation a policy summary states, read from its `isolation` field alone (a hello's raw
15
+ * `policy` or the stored column), so a summary the kernel cannot otherwise read (a newer program's
16
+ * new value elsewhere, a broken one) still tells whether commands run without a sandbox: `sandbox`
17
+ * only when it says exactly that, `none` for any other present summary (fail closed), null when
18
+ * there is none.
19
+ */
20
+ export declare function isolationOf(v: unknown): "sandbox" | "none" | null;