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

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 (293) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.module.d.ts +2 -1
  3. package/dist/admin/admin.module.js +9 -3
  4. package/dist/admin/health.service.d.ts +2 -2
  5. package/dist/admin/health.service.js +6 -2
  6. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  7. package/dist/admin/outbox-dead.controller.js +62 -0
  8. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  9. package/dist/admin/outbox-dead.schemas.js +18 -0
  10. package/dist/admin/outbox-dead.service.d.ts +66 -0
  11. package/dist/admin/outbox-dead.service.js +179 -0
  12. package/dist/admin/overview.service.d.ts +40 -5
  13. package/dist/admin/overview.service.js +41 -7
  14. package/dist/agents/agents.module.js +3 -0
  15. package/dist/agents/agents.service.d.ts +6 -1
  16. package/dist/agents/agents.service.js +11 -2
  17. package/dist/agents/from-template.js +1 -1
  18. package/dist/background/finish.d.ts +5 -2
  19. package/dist/background/finish.js +14 -3
  20. package/dist/background/launch.d.ts +2 -1
  21. package/dist/background/launch.js +5 -2
  22. package/dist/background/store.d.ts +8 -0
  23. package/dist/background/store.js +24 -0
  24. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  25. package/dist/catalog/admin-catalog.controller.js +45 -6
  26. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  27. package/dist/catalog/admin-catalog.schemas.js +8 -0
  28. package/dist/catalog/catalog-import.module.js +0 -4
  29. package/dist/catalog/catalog-scope.service.d.ts +63 -26
  30. package/dist/catalog/catalog-scope.service.js +146 -71
  31. package/dist/catalog/catalog.module.d.ts +2 -1
  32. package/dist/catalog/catalog.module.js +9 -4
  33. package/dist/catalog/catalog.service.d.ts +11 -4
  34. package/dist/catalog/catalog.service.js +21 -5
  35. package/dist/catalog/cli.d.ts +4 -3
  36. package/dist/catalog/cli.js +8 -13
  37. package/dist/catalog/job-ids.d.ts +8 -20
  38. package/dist/catalog/job-ids.js +11 -16
  39. package/dist/catalog/marketplaces.service.d.ts +1 -1
  40. package/dist/catalog/org-plugin-uploads.d.ts +11 -4
  41. package/dist/catalog/org-plugin-uploads.js +21 -6
  42. package/dist/catalog/owned-items.d.ts +6 -13
  43. package/dist/catalog/owned-items.js +11 -29
  44. package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
  45. package/dist/catalog/owned-plugin-cleanup.js +12 -66
  46. package/dist/catalog/platform-components.d.ts +56 -34
  47. package/dist/catalog/platform-components.js +68 -78
  48. package/dist/catalog/platform-import-update.d.ts +50 -0
  49. package/dist/catalog/platform-import-update.js +116 -0
  50. package/dist/catalog/plugin-import.d.ts +64 -26
  51. package/dist/catalog/plugin-import.js +113 -77
  52. package/dist/catalog/plugin-upgrade.d.ts +22 -4
  53. package/dist/catalog/plugin-upgrade.js +26 -19
  54. package/dist/catalog/scope-jobs.d.ts +59 -0
  55. package/dist/catalog/scope-jobs.js +65 -0
  56. package/dist/catalog/scope-sync-workers.d.ts +22 -0
  57. package/dist/catalog/scope-sync-workers.js +58 -2
  58. package/dist/catalog/scope-sync.d.ts +40 -65
  59. package/dist/catalog/scope-sync.js +114 -122
  60. package/dist/channels/channels.module.d.ts +5 -2
  61. package/dist/channels/channels.module.js +9 -2
  62. package/dist/channels/deliver-job.d.ts +25 -0
  63. package/dist/channels/deliver-job.js +25 -0
  64. package/dist/channels/inbound.d.ts +1 -1
  65. package/dist/channels/inbound.js +4 -17
  66. package/dist/channels/jobs.d.ts +28 -18
  67. package/dist/channels/jobs.js +63 -26
  68. package/dist/channels/threads.d.ts +4 -3
  69. package/dist/channels/threads.js +4 -3
  70. package/dist/config/schema.d.ts +0 -3
  71. package/dist/config/schema.js +11 -20
  72. package/dist/conversations/agent-tree.d.ts +16 -0
  73. package/dist/conversations/agent-tree.js +36 -0
  74. package/dist/conversations/conversations.controller.js +4 -2
  75. package/dist/conversations/conversations.service.d.ts +61 -19
  76. package/dist/conversations/conversations.service.js +69 -43
  77. package/dist/conversations/event-log.d.ts +7 -10
  78. package/dist/conversations/event-log.js +24 -15
  79. package/dist/conversations/events.d.ts +21 -0
  80. package/dist/conversations/events.js +26 -2
  81. package/dist/conversations/queue.service.d.ts +8 -3
  82. package/dist/conversations/queue.service.js +37 -13
  83. package/dist/conversations/source.d.ts +10 -0
  84. package/dist/conversations/source.js +17 -0
  85. package/dist/conversations/titles.d.ts +21 -8
  86. package/dist/conversations/titles.js +42 -15
  87. package/dist/conversations/tree.d.ts +10 -0
  88. package/dist/conversations/tree.js +35 -0
  89. package/dist/db/kernel-schema.d.ts +35 -22
  90. package/dist/devices/devices.service.d.ts +23 -11
  91. package/dist/devices/devices.service.js +34 -26
  92. package/dist/errors.d.ts +11 -0
  93. package/dist/errors.js +11 -0
  94. package/dist/evals/run-replay.d.ts +13 -2
  95. package/dist/evals/run-replay.js +42 -9
  96. package/dist/evals/snapshot.d.ts +7 -1
  97. package/dist/evals/snapshot.js +2 -1
  98. package/dist/extensions/extensions.module.js +3 -0
  99. package/dist/extensions/hooks.d.ts +22 -3
  100. package/dist/extensions/hooks.js +15 -1
  101. package/dist/extensions/lifecycle-hooks.d.ts +99 -0
  102. package/dist/extensions/lifecycle-hooks.js +273 -0
  103. package/dist/extensions/lifecycle.d.ts +6 -1
  104. package/dist/extensions/lifecycle.js +9 -2
  105. package/dist/extensions/org-config.d.ts +13 -1
  106. package/dist/extensions/org-config.js +19 -2
  107. package/dist/extensions/permission-view.d.ts +9 -5
  108. package/dist/extensions/permission-view.js +9 -4
  109. package/dist/extensions/point-defaults.d.ts +28 -1
  110. package/dist/extensions/point-defaults.js +44 -13
  111. package/dist/extensions/usage-subscribers.d.ts +43 -0
  112. package/dist/extensions/usage-subscribers.js +108 -0
  113. package/dist/http/api.module.js +2 -0
  114. package/dist/inbox/inbox.service.d.ts +6 -20
  115. package/dist/inbox/inbox.service.js +0 -3
  116. package/dist/inbox/org-notices.d.ts +1 -1
  117. package/dist/inbox/platform-inbox.service.d.ts +3 -32
  118. package/dist/inbox/platform-inbox.service.js +5 -54
  119. package/dist/index.d.ts +1 -0
  120. package/dist/index.js +1 -0
  121. package/dist/infra/cli/kernel-cli.module.js +5 -0
  122. package/dist/infra/events/kernel-events.d.ts +9 -35
  123. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  124. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  125. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  126. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  127. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  128. package/dist/infra/outbox/outbox.module.js +50 -0
  129. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  130. package/dist/infra/outbox/outbox.relay.js +76 -0
  131. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  132. package/dist/infra/outbox/outbox.service.js +131 -0
  133. package/dist/infra/outbox/registry.d.ts +244 -0
  134. package/dist/infra/outbox/registry.js +234 -0
  135. package/dist/infra/queue/queue-names.d.ts +8 -0
  136. package/dist/infra/queue/queue-names.js +17 -0
  137. package/dist/kernel.module.js +3 -0
  138. package/dist/local-devices/device-policy.service.d.ts +4 -0
  139. package/dist/local-devices/device-policy.service.js +61 -3
  140. package/dist/local-devices/machines.d.ts +13 -5
  141. package/dist/local-devices/machines.js +45 -7
  142. package/dist/local-devices/policy-summary.d.ts +44 -1
  143. package/dist/local-devices/policy-summary.js +69 -0
  144. package/dist/models/embeddings.d.ts +9 -9
  145. package/dist/models/embeddings.js +11 -18
  146. package/dist/models/models.module.js +3 -0
  147. package/dist/orgs/directory-sync.js +1 -1
  148. package/dist/orgs/members.service.js +1 -1
  149. package/dist/orgs/offboarding.d.ts +50 -57
  150. package/dist/orgs/offboarding.js +117 -162
  151. package/dist/orgs/orgs.module.js +3 -0
  152. package/dist/orgs/orgs.service.d.ts +11 -5
  153. package/dist/orgs/orgs.service.js +22 -6
  154. package/dist/overrides.d.ts +23 -3
  155. package/dist/overrides.js +12 -2
  156. package/dist/platform/exclusions.js +5 -0
  157. package/dist/platform/ops/admin.ops.js +18 -2
  158. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  159. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  160. package/dist/platform/ops/inbox.ops.js +4 -20
  161. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  162. package/dist/platform/ops/outbox.ops.js +125 -0
  163. package/dist/platform/ops/platform.ops.d.ts +1 -0
  164. package/dist/platform/ops/platform.ops.js +6 -3
  165. package/dist/platform/ops/schedules.ops.js +40 -1
  166. package/dist/platform/platform-ops.module.d.ts +2 -1
  167. package/dist/platform/platform-ops.module.js +3 -0
  168. package/dist/platform/route-schemas.js +3 -0
  169. package/dist/roles/worker-role.d.ts +10 -5
  170. package/dist/roles/worker-role.js +19 -9
  171. package/dist/roles/worker.module.js +11 -8
  172. package/dist/runtime/context/build.d.ts +3 -0
  173. package/dist/runtime/context/build.js +15 -0
  174. package/dist/runtime/context/plugin-context.d.ts +20 -0
  175. package/dist/runtime/context/plugin-context.js +30 -0
  176. package/dist/runtime/langgraph-loop.js +17 -5
  177. package/dist/runtime/limits.d.ts +15 -0
  178. package/dist/runtime/limits.js +25 -0
  179. package/dist/runtime/loop.d.ts +16 -3
  180. package/dist/runtime/loop.js +2 -1
  181. package/dist/runtime/maintenance.d.ts +7 -3
  182. package/dist/runtime/maintenance.js +52 -12
  183. package/dist/runtime/model-call.d.ts +20 -17
  184. package/dist/runtime/model-call.js +15 -24
  185. package/dist/runtime/plugin-context.service.d.ts +69 -0
  186. package/dist/runtime/plugin-context.service.js +208 -0
  187. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  188. package/dist/runtime/queue-lost-jobs.js +51 -0
  189. package/dist/runtime/run-control.d.ts +47 -5
  190. package/dist/runtime/run-control.js +79 -12
  191. package/dist/runtime/run-finish.d.ts +39 -12
  192. package/dist/runtime/run-finish.js +97 -14
  193. package/dist/runtime/run-jobs.d.ts +95 -13
  194. package/dist/runtime/run-jobs.js +199 -28
  195. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  196. package/dist/runtime/run-orphan-check.job.js +339 -0
  197. package/dist/runtime/run-processors.d.ts +16 -9
  198. package/dist/runtime/run-processors.js +1 -1
  199. package/dist/runtime/run-queue.d.ts +75 -53
  200. package/dist/runtime/run-queue.js +189 -263
  201. package/dist/runtime/run-steps.d.ts +1 -2
  202. package/dist/runtime/run-steps.js +8 -11
  203. package/dist/runtime/run-worker.d.ts +26 -16
  204. package/dist/runtime/run-worker.js +132 -37
  205. package/dist/runtime/runtime.module.d.ts +2 -3
  206. package/dist/runtime/runtime.module.js +7 -6
  207. package/dist/runtime/step-log.d.ts +16 -4
  208. package/dist/runtime/step-log.js +26 -6
  209. package/dist/runtime/stream.controller.d.ts +2 -4
  210. package/dist/runtime/stream.controller.js +5 -11
  211. package/dist/runtime/subagents/child.js +1 -1
  212. package/dist/runtime/subagents/definition.d.ts +2 -1
  213. package/dist/runtime/subagents/definition.js +4 -2
  214. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  215. package/dist/runtime/subagents/deliveries.js +16 -7
  216. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  217. package/dist/runtime/subagents/dispatch.js +14 -38
  218. package/dist/runtime/subagents/messages.d.ts +5 -1
  219. package/dist/runtime/subagents/messages.js +11 -3
  220. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  221. package/dist/runtime/subagents/skill-fork.js +8 -5
  222. package/dist/runtime/subagents/subagents.service.js +1 -1
  223. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  224. package/dist/runtime/subagents/tree-jobs.js +86 -0
  225. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  226. package/dist/runtime/usage-report-jobs.js +67 -119
  227. package/dist/runtime/usage-report.d.ts +28 -77
  228. package/dist/runtime/usage-report.js +35 -213
  229. package/dist/runtime/waits.d.ts +48 -198
  230. package/dist/runtime/waits.js +103 -805
  231. package/dist/sandbox/jobs.d.ts +20 -10
  232. package/dist/sandbox/jobs.js +23 -17
  233. package/dist/sandbox/sandbox.service.d.ts +17 -4
  234. package/dist/sandbox/sandbox.service.js +55 -14
  235. package/dist/schedules/calendar.d.ts +134 -0
  236. package/dist/schedules/calendar.js +410 -0
  237. package/dist/schedules/fire.d.ts +26 -7
  238. package/dist/schedules/fire.js +91 -22
  239. package/dist/schedules/job-ids.d.ts +66 -17
  240. package/dist/schedules/job-ids.js +81 -13
  241. package/dist/schedules/run-tracker.d.ts +0 -5
  242. package/dist/schedules/run-tracker.js +2 -14
  243. package/dist/schedules/scan.job.d.ts +13 -32
  244. package/dist/schedules/scan.job.js +50 -164
  245. package/dist/schedules/schedule-math.d.ts +42 -0
  246. package/dist/schedules/schedule-math.js +52 -0
  247. package/dist/schedules/scheduled-tasks.service.d.ts +22 -6
  248. package/dist/schedules/scheduled-tasks.service.js +70 -14
  249. package/dist/schedules/schedules.controller.d.ts +5 -1
  250. package/dist/schedules/schedules.controller.js +15 -1
  251. package/dist/schedules/schedules.module.js +0 -4
  252. package/dist/schedules/schedules.schemas.d.ts +35 -0
  253. package/dist/schedules/schedules.schemas.js +27 -0
  254. package/dist/schedules/store.d.ts +5 -0
  255. package/dist/schedules/store.js +20 -0
  256. package/dist/skills/changes.controller.d.ts +1 -1
  257. package/dist/skills/changes.controller.js +1 -1
  258. package/dist/tools/approvals.service.d.ts +41 -11
  259. package/dist/tools/approvals.service.js +108 -14
  260. package/dist/tools/builtin-names.d.ts +5 -3
  261. package/dist/tools/builtin-names.js +12 -4
  262. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  263. package/dist/tools/builtins/builtin-tools.js +21 -17
  264. package/dist/tools/builtins/sandbox/bash.js +9 -3
  265. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  266. package/dist/tools/builtins/sandbox/common.js +14 -1
  267. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  268. package/dist/tools/builtins/sandbox/index.js +4 -1
  269. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  270. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  271. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  272. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  273. package/dist/tools/builtins/web/extract.d.ts +2 -4
  274. package/dist/tools/builtins/web/extract.js +2 -5
  275. package/dist/tools/pipeline.d.ts +12 -3
  276. package/dist/tools/pipeline.js +6 -1
  277. package/dist/tools/questions.service.d.ts +30 -10
  278. package/dist/tools/questions.service.js +41 -27
  279. package/dist/tools/registry.d.ts +12 -3
  280. package/dist/tools/registry.js +13 -3
  281. package/dist/tools/tools.module.js +4 -3
  282. package/package.json +3 -3
  283. package/src/migrations/0118_device_policy_ops.sql +6 -0
  284. package/src/migrations/0119_outbox.sql +22 -0
  285. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
  286. package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
  287. package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
  288. package/src/migrations/0123_pgvector.sql +27 -0
  289. package/src/migrations/0124_conversation_source.sql +36 -0
  290. package/dist/catalog/platform-migration.d.ts +0 -115
  291. package/dist/catalog/platform-migration.js +0 -583
  292. package/dist/infra/queue/add-job-once.d.ts +0 -14
  293. package/dist/infra/queue/add-job-once.js +0 -16
@@ -7,8 +7,10 @@ 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 { randomUUID } from "node:crypto";
10
11
  import { ToolError, validateAllowHost, } from "@capybara-hq/plugin-sdk";
11
12
  import { Inject, Injectable } from "@nestjs/common";
13
+ import { sql } from "kysely";
12
14
  import { ApiError } from "../errors.js";
13
15
  import { audit } from "../identity/audit.js";
14
16
  import { KernelDb } from "../infra/database/kernel-db.js";
@@ -21,6 +23,11 @@ import { isolationOf } from "./policy-summary.js";
21
23
  import { LocalDeviceRelay, ON_MACHINE_PREFIX } from "./relay.js";
22
24
  /** How long the machine may take to apply a policy change. */
23
25
  export const POLICY_DEADLINE_MS = 30_000;
26
+ /**
27
+ * How long a sent op may still explain a summary the machine reports by itself: its deadline and
28
+ * the reconnect grace, with room for an answer that comes only with the next hello.
29
+ */
30
+ const PENDING_OP_MS = POLICY_DEADLINE_MS + 5 * 60_000;
24
31
  /** The prefix the relay puts before a machine's `policy`-kind refusal. */
25
32
  const REFUSED_PREFIX = "Refused by the machine's local policy: ";
26
33
  const OFFLINE_MESSAGE = "The device's machine is offline, so its policy was not changed. Start capybara-device on it.";
@@ -89,6 +96,17 @@ let DevicePolicyService = class DevicePolicyService {
89
96
  return { applied: false, reason: "offline", message: OFFLINE_MESSAGE };
90
97
  }
91
98
  const args = { ...input.change, source: input.source };
99
+ // noted before it is sent: the machine may report the change by itself (a `policy` frame)
100
+ // before its answer is recorded, and the device page lists it once, as the platform's
101
+ const opId = randomUUID();
102
+ await this.notePending(machine.id, {
103
+ id: opId,
104
+ add: input.change.add ?? [],
105
+ remove: input.change.remove ?? [],
106
+ ...(input.change.tier !== undefined && { tier: input.change.tier }),
107
+ ...(input.change.isolation !== undefined && { isolation: input.change.isolation }),
108
+ until: Date.now() + PENDING_OP_MS,
109
+ });
92
110
  let policy;
93
111
  try {
94
112
  ({ policy } = await this.relay.call(input.deviceId, "policy", args, {
@@ -101,11 +119,15 @@ let DevicePolicyService = class DevicePolicyService {
101
119
  if (e instanceof ApiError && e.code === "device.offline") {
102
120
  // the op was sent and the wait ran out: the machine may still apply it, so the attempt is
103
121
  // audited unconfirmed; its next hello reports the policy as it is
104
- await this.record({ ...input, machineId: machine.id, policy: null });
122
+ await this.record({ ...input, machineId: machine.id, opId, policy: null });
123
+ }
124
+ else {
125
+ // refused or failed: nothing changed, so it explains no later change
126
+ await this.dropPending(machine.id, opId);
105
127
  }
106
128
  return this.notApplied(input.deviceId, e);
107
129
  }
108
- await this.record({ ...input, machineId: machine.id, policy });
130
+ await this.record({ ...input, machineId: machine.id, opId, policy });
109
131
  return { applied: true, policy };
110
132
  }
111
133
  /** The active machine's features and policy summary (null when no machine is connected). */
@@ -113,6 +135,27 @@ let DevicePolicyService = class DevicePolicyService {
113
135
  const m = await this.machines.activeFor(deviceId);
114
136
  return m === null ? null : { features: m.features, policy: m.localPolicy };
115
137
  }
138
+ /** Adds `op` to the machine's pending ops, dropping the expired ones. */
139
+ async notePending(machineId, op) {
140
+ await this.db
141
+ .updateTable("device_machines")
142
+ .set({
143
+ pending_policy_ops: sql `(
144
+ SELECT coalesce(jsonb_agg(e), '[]'::jsonb) FROM jsonb_array_elements(pending_policy_ops) e
145
+ WHERE (e->>'until')::bigint > ${Date.now()}
146
+ ) || jsonb_build_array(${JSON.stringify(op)}::jsonb)`,
147
+ })
148
+ .where("id", "=", machineId)
149
+ .execute();
150
+ }
151
+ /** Removes the pending op `opId` from the machine's row. */
152
+ async dropPending(machineId, opId) {
153
+ await this.db
154
+ .updateTable("device_machines")
155
+ .set({ pending_policy_ops: withoutOp(opId) })
156
+ .where("id", "=", machineId)
157
+ .execute();
158
+ }
116
159
  /**
117
160
  * Keeps the policy the machine wrote on its row and audits the change (`confirmed`); with no
118
161
  * policy (the answer never came) only audits the attempt, unconfirmed. The detail's `isolation`
@@ -136,7 +179,11 @@ let DevicePolicyService = class DevicePolicyService {
136
179
  isolationNow = input.policy.isolation;
137
180
  await this.db
138
181
  .updateTable("device_machines")
139
- .set({ local_policy: JSON.stringify(input.policy) })
182
+ // answered: the op no longer explains a summary the machine reports
183
+ .set({
184
+ local_policy: JSON.stringify(input.policy),
185
+ pending_policy_ops: withoutOp(input.opId),
186
+ })
140
187
  .where("id", "=", input.machineId)
141
188
  .where("revoked_at", "is", null)
142
189
  .execute();
@@ -186,6 +233,12 @@ let DevicePolicyService = class DevicePolicyService {
186
233
  };
187
234
  }
188
235
  };
236
+ __decorate([
237
+ Transactional()
238
+ ], DevicePolicyService.prototype, "notePending", null);
239
+ __decorate([
240
+ Transactional()
241
+ ], DevicePolicyService.prototype, "dropPending", null);
189
242
  __decorate([
190
243
  Transactional()
191
244
  ], DevicePolicyService.prototype, "record", null);
@@ -198,6 +251,11 @@ DevicePolicyService = __decorate([
198
251
  __param(4, Inject(KERNEL_LOGGER))
199
252
  ], DevicePolicyService);
200
253
  export { DevicePolicyService };
254
+ /** The machine's pending ops without `opId` (an expired one left for the next note to drop). */
255
+ const withoutOp = (opId) => sql `(
256
+ SELECT coalesce(jsonb_agg(e), '[]'::jsonb) FROM jsonb_array_elements(pending_policy_ops) e
257
+ WHERE e->>'id' <> ${opId}
258
+ )`;
201
259
  /**
202
260
  * Hosts the platform may send: an approval's `add` takes exact hosts only (a card never grants a
203
261
  * wildcard); the owner's own change on the device page (no approval) may add a wildcard (spec §7);
@@ -94,23 +94,31 @@ export declare class MachinesService {
94
94
  * alone, read from `rawPolicy` (the frame's raw `policy`; {@link isolationOf}: anything but
95
95
  * `sandbox` is `none`), so it never hides that commands run without a sandbox. A reported
96
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.
97
+ * `device.isolation_reported` (sandbox dev tiers spec §10.4), once per change; a change of what
98
+ * the owner sets, made on the machine while it was away, is audited `device.policy_changed`
99
+ * ({@link reportedPolicy}).
98
100
  */
99
101
  recordHello(machineId: string, hello: DeviceHello, rawPolicy?: unknown): Promise<void>;
100
102
  /**
101
103
  * Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
102
104
  * its policy file changed on the machine, its localhost proxy guard flipped, a new tier
103
105
  * 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`.
106
+ * its isolation alone (`rawPolicy`); a change the owner made on the machine is audited
107
+ * `device.policy_changed` (`via: "device"`), else a changed isolation `device.isolation_reported`.
105
108
  * The next sandbox call then records a new `device_paths` event when the facts the model is told
106
109
  * changed. A revoked machine is left alone.
107
110
  */
108
111
  recordPolicy(machineId: string, summary: DeviceLocalPolicySummary | undefined, rawPolicy?: unknown): Promise<void>;
109
112
  /**
110
113
  * 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
+ * else its raw isolation alone, else null. When both the stored and the reported summary are
115
+ * readable and what the owner sets changed ({@link localPolicyDiff}) by no `policy` op the
116
+ * platform sent ({@link opExplains}: its answer may come after the machine's own report), the
117
+ * owner changed it on the machine (`capybara-device policy ...`): audited
118
+ * `device.policy_changed` with `via: "device"`, the isolation with it, so the device page lists
119
+ * it beside the platform's changes; one an op explains is not audited here (the op's answer
120
+ * is). Otherwise a reported isolation that differs from the stored one (none stored counts as
121
+ * `sandbox`) is audited `device.isolation_reported`. A summary reported again is no change. Runs in the caller's transaction, with the machine's row locked.
114
122
  */
115
123
  private reportedPolicy;
116
124
  /** Marks the machine seen (a ping); writes at most once every 60 seconds. */
@@ -20,7 +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
+ import { deviceFeaturesOf, isolationOf, localPolicyDiff, localPolicyOf, opExplains, pendingPolicyOpsOf, } from "./policy-summary.js";
24
24
  import { LocalDeviceRelay } from "./relay.js";
25
25
  /** The machine credential's prefix (`cap_dev_` plus 32 random bytes, base64url). */
26
26
  export const MACHINE_CREDENTIAL_PREFIX = "cap_dev_";
@@ -162,7 +162,9 @@ let MachinesService = class MachinesService {
162
162
  * alone, read from `rawPolicy` (the frame's raw `policy`; {@link isolationOf}: anything but
163
163
  * `sandbox` is `none`), so it never hides that commands run without a sandbox. A reported
164
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.
165
+ * `device.isolation_reported` (sandbox dev tiers spec §10.4), once per change; a change of what
166
+ * the owner sets, made on the machine while it was away, is audited `device.policy_changed`
167
+ * ({@link reportedPolicy}).
166
168
  */
167
169
  async recordHello(machineId, hello, rawPolicy = hello.policy) {
168
170
  const self = await this.db
@@ -177,6 +179,7 @@ let MachinesService = class MachinesService {
177
179
  "roots",
178
180
  "created_at",
179
181
  "local_policy",
182
+ "pending_policy_ops",
180
183
  ])
181
184
  .where("id", "=", machineId)
182
185
  .where("revoked_at", "is", null)
@@ -229,14 +232,15 @@ let MachinesService = class MachinesService {
229
232
  * Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
230
233
  * its policy file changed on the machine, its localhost proxy guard flipped, a new tier
231
234
  * 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`.
235
+ * its isolation alone (`rawPolicy`); a change the owner made on the machine is audited
236
+ * `device.policy_changed` (`via: "device"`), else a changed isolation `device.isolation_reported`.
233
237
  * The next sandbox call then records a new `device_paths` event when the facts the model is told
234
238
  * changed. A revoked machine is left alone.
235
239
  */
236
240
  async recordPolicy(machineId, summary, rawPolicy = summary) {
237
241
  const self = await this.db
238
242
  .selectFrom("device_machines")
239
- .select(["device_id", "org_id", "account_id", "local_policy"])
243
+ .select(["device_id", "org_id", "account_id", "local_policy", "pending_policy_ops"])
240
244
  .where("id", "=", machineId)
241
245
  .where("revoked_at", "is", null)
242
246
  .forUpdate()
@@ -253,12 +257,46 @@ let MachinesService = class MachinesService {
253
257
  }
254
258
  /**
255
259
  * 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.
260
+ * else its raw isolation alone, else null. When both the stored and the reported summary are
261
+ * readable and what the owner sets changed ({@link localPolicyDiff}) by no `policy` op the
262
+ * platform sent ({@link opExplains}: its answer may come after the machine's own report), the
263
+ * owner changed it on the machine (`capybara-device policy ...`): audited
264
+ * `device.policy_changed` with `via: "device"`, the isolation with it, so the device page lists
265
+ * it beside the platform's changes; one an op explains is not audited here (the op's answer
266
+ * is). Otherwise a reported isolation that differs from the stored one (none stored counts as
267
+ * `sandbox`) is audited `device.isolation_reported`. A summary reported again is no change. Runs in the caller's transaction, with the machine's row locked.
259
268
  */
260
269
  async reportedPolicy(machineId, self, reported, rawPolicy) {
261
270
  const summary = localPolicyOf(reported);
271
+ const prev = localPolicyOf(self.local_policy);
272
+ const diff = summary !== null && prev !== null ? localPolicyDiff(prev, summary) : null;
273
+ if (diff !== null) {
274
+ const now = Date.now();
275
+ const byOp = pendingPolicyOpsOf(self.pending_policy_ops).some((op) => op.until > now && opExplains(op, diff));
276
+ // the platform's op: its answer lists it (the isolation with it)
277
+ if (byOp)
278
+ return summary;
279
+ await audit(this.db.db, {
280
+ actorAccountId: self.account_id,
281
+ orgId: self.org_id,
282
+ action: "device.policy_changed",
283
+ target: self.device_id,
284
+ detail: {
285
+ add: diff.add,
286
+ remove: diff.remove,
287
+ ...(diff.tier !== undefined && { tier: diff.tier.to, tierFrom: diff.tier.from }),
288
+ ...(diff.localhost !== undefined && { localhost: diff.localhost }),
289
+ ...(diff.isolation !== undefined && { isolation: diff.isolation }),
290
+ ...(diff.remoteGrants !== undefined && { remoteGrants: diff.remoteGrants }),
291
+ by: self.account_id,
292
+ via: "device",
293
+ machineId,
294
+ approvalId: null,
295
+ confirmed: true,
296
+ },
297
+ });
298
+ return summary;
299
+ }
262
300
  const isolation = summary?.isolation ?? isolationOf(rawPolicy);
263
301
  const before = isolationOf(self.local_policy) ?? "sandbox";
264
302
  if (isolation !== null && isolation !== before) {
@@ -1,4 +1,4 @@
1
- import { type DeviceFeature, type DeviceLocalPolicySummary } from "@capybara-hq/plugin-sdk";
1
+ import { type DeviceFeature, type DeviceLocalPolicySummary, type SandboxTier } from "@capybara-hq/plugin-sdk";
2
2
  /**
3
3
  * The known features in `v` (a hello's `features` or the stored column), in a fixed order and
4
4
  * deduped; anything else (unknown names, a non-array) is dropped.
@@ -18,3 +18,46 @@ export declare function localPolicyOf(v: unknown): DeviceLocalPolicySummary | nu
18
18
  * there is none.
19
19
  */
20
20
  export declare function isolationOf(v: unknown): "sandbox" | "none" | null;
21
+ /**
22
+ * What changed between two local policy summaries, in the owner's terms (the device page's change
23
+ * list, sandbox dev tiers spec §10.4): hosts added to and removed from the allowlist, the chosen
24
+ * tier (`null`: follows the org's default), the localhost access, the isolation and whether the
25
+ * machine accepts the platform's changes. Facts the owner does not set (the effective tier, unix
26
+ * sockets) are left out. Each field is present only when it changed.
27
+ */
28
+ export interface LocalPolicyDiff {
29
+ add: string[];
30
+ remove: string[];
31
+ tier?: {
32
+ from: SandboxTier | null;
33
+ to: SandboxTier | null;
34
+ };
35
+ localhost?: DeviceLocalPolicySummary["localhost"];
36
+ isolation?: DeviceLocalPolicySummary["isolation"];
37
+ remoteGrants?: DeviceLocalPolicySummary["remoteGrants"];
38
+ }
39
+ /** The {@link LocalPolicyDiff} from `prev` to `next`; null when nothing the owner sets changed. */
40
+ export declare function localPolicyDiff(prev: DeviceLocalPolicySummary, next: DeviceLocalPolicySummary): LocalPolicyDiff | null;
41
+ /**
42
+ * A `policy` op the platform sent to a machine whose answer may not be recorded yet (kept on the
43
+ * machine's row, `pending_policy_ops`): the change asked for, and until when (epoch ms) a summary
44
+ * the machine reports by itself may still be that op's.
45
+ */
46
+ export interface PendingPolicyOp {
47
+ id: string;
48
+ add: string[];
49
+ remove: string[];
50
+ /** Present when the op set the tier (null: follow the org's default). */
51
+ tier?: SandboxTier | null;
52
+ isolation?: "sandbox";
53
+ until: number;
54
+ }
55
+ /** The pending ops stored on a machine row (malformed entries dropped). */
56
+ export declare function pendingPolicyOpsOf(v: unknown): PendingPolicyOp[];
57
+ /**
58
+ * Whether `diff` is what the platform's `op` did (its answer, or the machine's own report of it
59
+ * before the answer was recorded): hosts within the op's, the tier the op set, and only what a
60
+ * tier change moves with it (the isolation back to `sandbox`, the localhost access). The device
61
+ * page then lists the change once, as the platform's.
62
+ */
63
+ export declare function opExplains(op: PendingPolicyOp, diff: LocalPolicyDiff): boolean;
@@ -61,3 +61,72 @@ export function isolationOf(v) {
61
61
  }
62
62
  return "none";
63
63
  }
64
+ /** The {@link LocalPolicyDiff} from `prev` to `next`; null when nothing the owner sets changed. */
65
+ export function localPolicyDiff(prev, next) {
66
+ const before = new Set(prev.allowedDomains);
67
+ const after = new Set(next.allowedDomains);
68
+ const d = {
69
+ add: [...after].filter((h) => !before.has(h)).sort(),
70
+ remove: [...before].filter((h) => !after.has(h)).sort(),
71
+ };
72
+ const fromTier = prev.tier ?? null;
73
+ const toTier = next.tier ?? null;
74
+ if (fromTier !== toTier)
75
+ d.tier = { from: fromTier, to: toTier };
76
+ if (prev.localhost !== next.localhost)
77
+ d.localhost = next.localhost;
78
+ if (prev.isolation !== next.isolation)
79
+ d.isolation = next.isolation;
80
+ if (prev.remoteGrants !== next.remoteGrants)
81
+ d.remoteGrants = next.remoteGrants;
82
+ const changed = d.add.length > 0 ||
83
+ d.remove.length > 0 ||
84
+ d.tier !== undefined ||
85
+ d.localhost !== undefined ||
86
+ d.isolation !== undefined ||
87
+ d.remoteGrants !== undefined;
88
+ return changed ? d : null;
89
+ }
90
+ /** The pending ops stored on a machine row (malformed entries dropped). */
91
+ export function pendingPolicyOpsOf(v) {
92
+ if (!Array.isArray(v))
93
+ return [];
94
+ const hosts = (x) => Array.isArray(x) ? x.filter((h) => typeof h === "string") : [];
95
+ const out = [];
96
+ for (const e of v) {
97
+ if (typeof e !== "object" || e === null)
98
+ continue;
99
+ const o = e;
100
+ if (typeof o.id !== "string" || typeof o.until !== "number")
101
+ continue;
102
+ out.push({
103
+ id: o.id,
104
+ add: hosts(o.add),
105
+ remove: hosts(o.remove),
106
+ ...("tier" in o && { tier: isSandboxTier(o.tier) ? o.tier : null }),
107
+ ...(o.isolation === "sandbox" && { isolation: "sandbox" }),
108
+ until: o.until,
109
+ });
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Whether `diff` is what the platform's `op` did (its answer, or the machine's own report of it
115
+ * before the answer was recorded): hosts within the op's, the tier the op set, and only what a
116
+ * tier change moves with it (the isolation back to `sandbox`, the localhost access). The device
117
+ * page then lists the change once, as the platform's.
118
+ */
119
+ export function opExplains(op, diff) {
120
+ if (!diff.add.every((h) => op.add.includes(h)))
121
+ return false;
122
+ if (!diff.remove.every((h) => op.remove.includes(h)))
123
+ return false;
124
+ if (diff.tier !== undefined && (op.tier === undefined || diff.tier.to !== op.tier))
125
+ return false;
126
+ if (diff.isolation !== undefined && op.isolation === undefined && op.tier === undefined) {
127
+ return false;
128
+ }
129
+ if (diff.localhost !== undefined && op.tier === undefined)
130
+ return false;
131
+ return diff.remoteGrants === undefined;
132
+ }
@@ -2,9 +2,9 @@ import { type ExtensionEmbedRequest, type ExtensionEmbedResult, type ExtensionMo
2
2
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
3
3
  import { KernelDb } from "../infra/database/kernel-db.js";
4
4
  import { TransactionHost } from "../infra/database/transaction-host.js";
5
- import { KernelEvents } from "../infra/events/kernel-events.js";
6
5
  import { type HookRunner } from "../runtime/hook-calls.js";
7
6
  import { ProviderRegistry } from "../runtime/providers.js";
7
+ import { UsageReportJobs } from "../runtime/usage-report-jobs.js";
8
8
  import { ModelPurposesService } from "./purposes.js";
9
9
  /** An extension's `permissions.models` (what it declared, as approved in its manifest). */
10
10
  export type DeclaredModelPurposes = readonly NonNullable<ExtensionPermissions["models"]>[number][];
@@ -29,9 +29,9 @@ export declare const EMBED_MAX_INPUTS = 2048;
29
29
  * limited by `models.embedTimeoutMs` (`ModelError("timeout")`), checks each result ({@link validateEmbedResult}: a
30
30
  * wrong count, dimension or a non-finite number is a `ModelError` of kind `server`), and
31
31
  * concatenates the vectors; no retries (an extension retries itself if it wants to);
32
- * - writes one `model_usage` row (the sum of the batches' input tokens) and announces it after the
33
- * commit (`model_usage.recorded`: the usage reporter's `ModelUsageJobs` enqueues its
34
- * `usage.report` delivery; the usage sweep catches up a lost one). A call that fails after the
32
+ * - writes one `model_usage` row (the sum of the batches' input tokens) and, in the same
33
+ * transaction, owes its `usage.report` delivery while some plugin subscribes (`UsageReportJobs`,
34
+ * transactional outbox spec §8.1). A call that fails after the
35
35
  * provider answered some batches (an invalid result, a later batch failing) still records their
36
36
  * input tokens before the error reaches the extension; one that fails before any answer records
37
37
  * nothing.
@@ -42,14 +42,14 @@ export declare class ModelEmbeddings {
42
42
  readonly txHost: TransactionHost;
43
43
  private readonly purposes;
44
44
  private readonly providers;
45
- /** `model_usage.recorded`: the usage reporter enqueues the row's delivery. */
46
- private readonly events;
45
+ /** The row's `usage.report` delivery, owed with it. */
46
+ private readonly usage;
47
47
  private readonly log;
48
48
  /** `models.embedTimeoutMs` (hot). */
49
49
  private readonly config;
50
50
  constructor(kdb: KernelDb, txHost: TransactionHost, purposes: Pick<ModelPurposesService, "resolve">, providers: Pick<ProviderRegistry, "resolve">,
51
- /** `model_usage.recorded`: the usage reporter enqueues the row's delivery. */
52
- events: Pick<KernelEvents, "emit">, log: Pick<Logger, "warn">,
51
+ /** The row's `usage.report` delivery, owed with it. */
52
+ usage: Pick<UsageReportJobs, "oweModelUsage">, log: Pick<Logger, "warn">,
53
53
  /** `models.embedTimeoutMs` (hot). */
54
54
  config: Pick<KernelConfigService, "get">);
55
55
  /** Refuses a purpose `plugin` did not declare with kind `embedding`, or an org it is off in. */
@@ -77,6 +77,6 @@ export declare class ModelEmbeddings {
77
77
  orgId: string;
78
78
  purpose: string;
79
79
  }): Promise<ExtensionModelTarget | null>;
80
- /** One `model_usage` row; its `usage.report` delivery is enqueued after the commit. */
80
+ /** One `model_usage` row with its owed `usage.report` delivery. */
81
81
  private record;
82
82
  }
@@ -13,15 +13,14 @@ import { ApiError } from "../errors.js";
13
13
  import { isExtensionEnabled } from "../extensions/enabled.js";
14
14
  import { audit } from "../identity/audit.js";
15
15
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
16
- import { afterCommit } from "../infra/database/after-commit.js";
17
16
  import { KernelDb } from "../infra/database/kernel-db.js";
18
17
  import { TransactionHost } from "../infra/database/transaction-host.js";
19
18
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
- import { KernelEvents } from "../infra/events/kernel-events.js";
21
19
  import { KERNEL_LOGGER } from "../infra/tokens.js";
22
20
  import { embeddingDeniedAudit } from "../runtime/hook-calls.js";
23
21
  import { modelDeniedText } from "../runtime/model-call.js";
24
22
  import { ProviderRegistry } from "../runtime/providers.js";
23
+ import { UsageReportJobs } from "../runtime/usage-report-jobs.js";
25
24
  import { ModelPurposesService } from "./purposes.js";
26
25
  /** Most texts one `ctx.models.embed` call may carry (the kernel splits them by `maxBatch`). */
27
26
  export const EMBED_MAX_INPUTS = 2048;
@@ -44,9 +43,9 @@ export const EMBED_MAX_INPUTS = 2048;
44
43
  * limited by `models.embedTimeoutMs` (`ModelError("timeout")`), checks each result ({@link validateEmbedResult}: a
45
44
  * wrong count, dimension or a non-finite number is a `ModelError` of kind `server`), and
46
45
  * concatenates the vectors; no retries (an extension retries itself if it wants to);
47
- * - writes one `model_usage` row (the sum of the batches' input tokens) and announces it after the
48
- * commit (`model_usage.recorded`: the usage reporter's `ModelUsageJobs` enqueues its
49
- * `usage.report` delivery; the usage sweep catches up a lost one). A call that fails after the
46
+ * - writes one `model_usage` row (the sum of the batches' input tokens) and, in the same
47
+ * transaction, owes its `usage.report` delivery while some plugin subscribes (`UsageReportJobs`,
48
+ * transactional outbox spec §8.1). A call that fails after the
50
49
  * provider answered some batches (an invalid result, a later batch failing) still records their
51
50
  * input tokens before the error reaches the extension; one that fails before any answer records
52
51
  * nothing.
@@ -57,19 +56,19 @@ let ModelEmbeddings = class ModelEmbeddings {
57
56
  txHost;
58
57
  purposes;
59
58
  providers;
60
- events;
59
+ usage;
61
60
  log;
62
61
  config;
63
62
  constructor(kdb, txHost, purposes, providers,
64
- /** `model_usage.recorded`: the usage reporter enqueues the row's delivery. */
65
- events, log,
63
+ /** The row's `usage.report` delivery, owed with it. */
64
+ usage, log,
66
65
  /** `models.embedTimeoutMs` (hot). */
67
66
  config) {
68
67
  this.kdb = kdb;
69
68
  this.txHost = txHost;
70
69
  this.purposes = purposes;
71
70
  this.providers = providers;
72
- this.events = events;
71
+ this.usage = usage;
73
72
  this.log = log;
74
73
  this.config = config;
75
74
  }
@@ -243,7 +242,7 @@ let ModelEmbeddings = class ModelEmbeddings {
243
242
  throw e;
244
243
  }
245
244
  }
246
- /** One `model_usage` row; its `usage.report` delivery is enqueued after the commit. */
245
+ /** One `model_usage` row with its owed `usage.report` delivery. */
247
246
  async record(plugin, orgId, purpose, model, inputTokens) {
248
247
  const row = await this.kdb.db
249
248
  .insertInto("model_usage")
@@ -257,13 +256,7 @@ let ModelEmbeddings = class ModelEmbeddings {
257
256
  })
258
257
  .returning("id")
259
258
  .executeTakeFirstOrThrow();
260
- // fire and forget: the extension's call never waits on the enqueue (PG, Redis); a lost one is
261
- // caught up by the usage sweep
262
- afterCommit(() => {
263
- void this.events
264
- .emit("model_usage.recorded", { id: row.id })
265
- .catch((err) => this.log.warn("model usage event failed", { usageId: row.id, err }));
266
- });
259
+ await this.usage.oweModelUsage(row.id, orgId);
267
260
  }
268
261
  };
269
262
  __decorate([
@@ -275,7 +268,7 @@ ModelEmbeddings = __decorate([
275
268
  __param(1, Inject(TransactionHost)),
276
269
  __param(2, Inject(ModelPurposesService)),
277
270
  __param(3, Inject(ProviderRegistry)),
278
- __param(4, Inject(KernelEvents)),
271
+ __param(4, Inject(UsageReportJobs)),
279
272
  __param(5, Inject(KERNEL_LOGGER)),
280
273
  __param(6, Inject(KernelConfigService))
281
274
  ], ModelEmbeddings);
@@ -9,6 +9,7 @@ import { ExtensionRegistryModule } from "../extensions/registry.js";
9
9
  import { IdentityModule } from "../identity/identity.module.js";
10
10
  import { OrgLockModule } from "../orgs/org-lock.js";
11
11
  import { ProvidersModule } from "../runtime/providers.js";
12
+ import { UsageReportJobsModule } from "../runtime/usage-report-jobs.js";
12
13
  import { ModelEmbeddings } from "./embeddings.js";
13
14
  import { ModelEnablementModule } from "./enablement.js";
14
15
  import { PricesService } from "./prices.js";
@@ -28,6 +29,8 @@ ModelsModule = __decorate([
28
29
  OrgLockModule,
29
30
  ProvidersModule,
30
31
  ModelEnablementModule,
32
+ // an embed call's `usage.report` delivery, owed with its `model_usage` row (no queue)
33
+ UsageReportJobsModule,
31
34
  ],
32
35
  providers: [PricesService, ModelPurposesService, ModelEmbeddings],
33
36
  exports: [PricesService, ModelPurposesService, ModelEnablementModule, ModelEmbeddings],
@@ -611,7 +611,7 @@ let DirectorySync = class DirectorySync {
611
611
  if (row?.source !== source)
612
612
  return undefined;
613
613
  const result = await this.offboarding.offboard(orgId, accountId, "directory");
614
- // the markers, events and run stops follow the root commit (none after a rollback)
614
+ // the revocation markers and events follow the root commit (hints; none after a rollback)
615
615
  afterCommit(() => this.offboarding.afterOffboard(result));
616
616
  return result;
617
617
  }
@@ -151,7 +151,7 @@ let MembersService = class MembersService {
151
151
  }
152
152
  const actor = { actorAccountId, ...(actorTokenId !== undefined && { actorTokenId }) };
153
153
  const res = await this.offboarding.offboard(orgId, targetId, "removed", actor);
154
- // the markers, events and run stops follow the root commit (none after a rollback)
154
+ // the revocation markers and events follow the root commit (hints; none after a rollback)
155
155
  afterCommit(() => this.offboarding.afterOffboard(res));
156
156
  await audit(this.db.db, {
157
157
  ...actor,