@capybara-hq/kernel 0.1.0-rc.22 → 0.1.0-rc.24
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.
- package/bootstrap.json +25 -25
- package/dist/admin/admin.controller.d.ts +3 -3
- package/dist/admin/admin.controller.js +6 -2
- package/dist/admin/admin.module.d.ts +2 -1
- package/dist/admin/admin.module.js +7 -3
- package/dist/admin/admin.schemas.d.ts +13 -0
- package/dist/admin/outbox-dead.controller.d.ts +23 -0
- package/dist/admin/outbox-dead.controller.js +62 -0
- package/dist/admin/outbox-dead.schemas.d.ts +8 -0
- package/dist/admin/outbox-dead.schemas.js +18 -0
- package/dist/admin/outbox-dead.service.d.ts +66 -0
- package/dist/admin/outbox-dead.service.js +179 -0
- package/dist/admin/overview.service.d.ts +40 -5
- package/dist/admin/overview.service.js +41 -7
- package/dist/agents/from-template.js +1 -1
- package/dist/background/finish.d.ts +5 -2
- package/dist/background/finish.js +14 -3
- package/dist/background/launch.d.ts +44 -5
- package/dist/background/launch.js +92 -9
- package/dist/background/session.d.ts +14 -2
- package/dist/background/session.js +26 -4
- package/dist/background/status.d.ts +1 -1
- package/dist/background/status.js +3 -3
- package/dist/background/stop.d.ts +0 -2
- package/dist/background/stop.js +1 -21
- package/dist/background/store.d.ts +8 -0
- package/dist/background/store.js +24 -0
- package/dist/catalog/admin-catalog.controller.d.ts +21 -2
- package/dist/catalog/admin-catalog.controller.js +45 -6
- package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
- package/dist/catalog/admin-catalog.schemas.js +8 -0
- package/dist/catalog/catalog-content.d.ts +5 -0
- package/dist/catalog/catalog-content.js +1 -0
- package/dist/catalog/catalog.module.d.ts +2 -1
- package/dist/catalog/catalog.module.js +5 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/platform-import-update.d.ts +50 -0
- package/dist/catalog/platform-import-update.js +116 -0
- package/dist/catalog/plugin-import.d.ts +36 -1
- package/dist/catalog/plugin-import.js +23 -1
- package/dist/catalog/plugin-upgrade.d.ts +18 -0
- package/dist/catalog/plugin-upgrade.js +19 -0
- package/dist/catalog/requests.service.js +2 -2
- package/dist/channels/channels.module.d.ts +5 -2
- package/dist/channels/channels.module.js +9 -2
- package/dist/channels/deliver-job.d.ts +25 -0
- package/dist/channels/deliver-job.js +25 -0
- package/dist/channels/inbound.d.ts +1 -1
- package/dist/channels/inbound.js +2 -15
- package/dist/channels/jobs.d.ts +28 -18
- package/dist/channels/jobs.js +63 -26
- package/dist/config/schema.d.ts +3 -6
- package/dist/config/schema.js +20 -27
- package/dist/conversations/agent-tree.d.ts +16 -0
- package/dist/conversations/agent-tree.js +36 -0
- package/dist/conversations/conversations.service.d.ts +44 -14
- package/dist/conversations/conversations.service.js +52 -38
- package/dist/conversations/event-log.d.ts +7 -10
- package/dist/conversations/event-log.js +24 -15
- package/dist/conversations/events.js +2 -1
- package/dist/conversations/queue.service.d.ts +8 -3
- package/dist/conversations/queue.service.js +37 -13
- package/dist/conversations/titles.d.ts +21 -8
- package/dist/conversations/titles.js +42 -15
- package/dist/db/kernel-schema.d.ts +26 -8
- package/dist/devices/devices.service.d.ts +12 -3
- package/dist/devices/devices.service.js +30 -9
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +15 -0
- package/dist/evals/run-replay.js +14 -8
- package/dist/extensions/extensions.module.js +3 -0
- package/dist/extensions/lifecycle.d.ts +6 -1
- package/dist/extensions/lifecycle.js +9 -2
- package/dist/extensions/usage-subscribers.d.ts +43 -0
- package/dist/extensions/usage-subscribers.js +108 -0
- package/dist/http/api.module.js +2 -0
- package/dist/identity/settings.d.ts +53 -3
- package/dist/identity/settings.js +92 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/cli/kernel-cli.module.js +5 -0
- package/dist/infra/config/kernel-config.service.d.ts +7 -0
- package/dist/infra/config/kernel-config.service.js +20 -0
- package/dist/infra/events/kernel-events.d.ts +9 -35
- package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
- package/dist/infra/outbox/outbox-relay.module.js +18 -0
- package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
- package/dist/infra/outbox/outbox.dispatcher.js +310 -0
- package/dist/infra/outbox/outbox.module.d.ts +24 -0
- package/dist/infra/outbox/outbox.module.js +50 -0
- package/dist/infra/outbox/outbox.relay.d.ts +31 -0
- package/dist/infra/outbox/outbox.relay.js +76 -0
- package/dist/infra/outbox/outbox.service.d.ts +51 -0
- package/dist/infra/outbox/outbox.service.js +131 -0
- package/dist/infra/outbox/registry.d.ts +120 -0
- package/dist/infra/outbox/registry.js +108 -0
- package/dist/infra/queue/queue-names.d.ts +8 -0
- package/dist/infra/queue/queue-names.js +16 -0
- package/dist/kernel.module.js +3 -0
- package/dist/local-devices/device-policy.service.d.ts +4 -0
- package/dist/local-devices/device-policy.service.js +61 -3
- package/dist/local-devices/local-device-gateway.d.ts +4 -2
- package/dist/local-devices/local-device-gateway.js +39 -14
- package/dist/local-devices/machines.d.ts +25 -1
- package/dist/local-devices/machines.js +91 -15
- package/dist/local-devices/policy-summary.d.ts +44 -1
- package/dist/local-devices/policy-summary.js +69 -0
- package/dist/models/embeddings.d.ts +9 -9
- package/dist/models/embeddings.js +11 -18
- package/dist/models/models.module.js +3 -0
- package/dist/orgs/orgs.service.js +7 -0
- package/dist/orgs/settings.controller.js +2 -2
- package/dist/orgs/settings.service.d.ts +11 -5
- package/dist/orgs/settings.service.js +28 -14
- package/dist/overrides.d.ts +20 -3
- package/dist/overrides.js +10 -2
- package/dist/platform/exclusions.js +5 -0
- package/dist/platform/ops/admin.ops.js +50 -9
- package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
- package/dist/platform/ops/catalog-admin.ops.js +32 -4
- package/dist/platform/ops/org-settings.ops.js +3 -3
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/outbox.ops.d.ts +21 -0
- package/dist/platform/ops/outbox.ops.js +125 -0
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/schedules.ops.js +40 -1
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +3 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +9 -4
- package/dist/runtime/langgraph-loop.js +13 -5
- package/dist/runtime/limits.d.ts +15 -0
- package/dist/runtime/limits.js +25 -0
- package/dist/runtime/loop.d.ts +8 -3
- package/dist/runtime/loop.js +2 -1
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -2
- package/dist/runtime/model-call.d.ts +14 -16
- package/dist/runtime/model-call.js +14 -24
- package/dist/runtime/queue-lost-jobs.d.ts +24 -0
- package/dist/runtime/queue-lost-jobs.js +51 -0
- package/dist/runtime/run-control.d.ts +42 -5
- package/dist/runtime/run-control.js +66 -12
- package/dist/runtime/run-finish.d.ts +39 -12
- package/dist/runtime/run-finish.js +97 -14
- package/dist/runtime/run-jobs.d.ts +95 -13
- package/dist/runtime/run-jobs.js +199 -28
- package/dist/runtime/run-orphan-check.job.d.ts +142 -0
- package/dist/runtime/run-orphan-check.job.js +339 -0
- package/dist/runtime/run-processors.d.ts +16 -9
- package/dist/runtime/run-processors.js +1 -1
- package/dist/runtime/run-queue.d.ts +64 -55
- package/dist/runtime/run-queue.js +147 -284
- package/dist/runtime/run-steps.d.ts +1 -2
- package/dist/runtime/run-steps.js +8 -11
- package/dist/runtime/run-worker.d.ts +6 -17
- package/dist/runtime/run-worker.js +32 -34
- package/dist/runtime/runtime.module.d.ts +2 -3
- package/dist/runtime/runtime.module.js +5 -6
- package/dist/runtime/step-log.d.ts +16 -4
- package/dist/runtime/step-log.js +26 -6
- package/dist/runtime/stream.controller.d.ts +2 -4
- package/dist/runtime/stream.controller.js +5 -11
- package/dist/runtime/subagents/child.js +1 -1
- package/dist/runtime/subagents/definition.d.ts +2 -1
- package/dist/runtime/subagents/definition.js +4 -2
- package/dist/runtime/subagents/deliveries.d.ts +6 -3
- package/dist/runtime/subagents/deliveries.js +16 -7
- package/dist/runtime/subagents/dispatch.d.ts +12 -12
- package/dist/runtime/subagents/dispatch.js +13 -38
- package/dist/runtime/subagents/messages.d.ts +5 -1
- package/dist/runtime/subagents/messages.js +11 -3
- package/dist/runtime/subagents/skill-fork.d.ts +3 -3
- package/dist/runtime/subagents/skill-fork.js +5 -5
- package/dist/runtime/subagents/subagents.service.js +1 -1
- package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
- package/dist/runtime/subagents/tree-jobs.js +86 -0
- package/dist/runtime/usage-report-jobs.d.ts +23 -48
- package/dist/runtime/usage-report-jobs.js +67 -119
- package/dist/runtime/usage-report.d.ts +28 -77
- package/dist/runtime/usage-report.js +35 -213
- package/dist/runtime/waits.d.ts +48 -198
- package/dist/runtime/waits.js +103 -805
- package/dist/sandbox/platform-tiers.d.ts +86 -0
- package/dist/sandbox/platform-tiers.js +97 -0
- package/dist/sandbox/sandbox.service.d.ts +18 -5
- package/dist/sandbox/sandbox.service.js +75 -20
- package/dist/sandbox/tiers.d.ts +14 -2
- package/dist/sandbox/tiers.js +8 -0
- package/dist/schedules/calendar.d.ts +134 -0
- package/dist/schedules/calendar.js +410 -0
- package/dist/schedules/fire.d.ts +1 -3
- package/dist/schedules/fire.js +5 -10
- package/dist/schedules/run-tracker.d.ts +0 -5
- package/dist/schedules/run-tracker.js +0 -13
- package/dist/schedules/scan.job.js +15 -20
- package/dist/schedules/schedule-math.d.ts +42 -0
- package/dist/schedules/schedule-math.js +52 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
- package/dist/schedules/scheduled-tasks.service.js +53 -0
- package/dist/schedules/schedules.controller.d.ts +5 -1
- package/dist/schedules/schedules.controller.js +15 -1
- package/dist/schedules/schedules.schemas.d.ts +35 -0
- package/dist/schedules/schedules.schemas.js +27 -0
- package/dist/schedules/store.d.ts +5 -0
- package/dist/schedules/store.js +20 -0
- package/dist/tools/approvals.service.d.ts +26 -7
- package/dist/tools/approvals.service.js +88 -10
- package/dist/tools/builtin-names.d.ts +5 -3
- package/dist/tools/builtin-names.js +12 -4
- package/dist/tools/builtins/builtin-tools.d.ts +1 -2
- package/dist/tools/builtins/builtin-tools.js +21 -17
- package/dist/tools/builtins/sandbox/bash.js +9 -3
- package/dist/tools/builtins/sandbox/common.d.ts +18 -1
- package/dist/tools/builtins/sandbox/common.js +14 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -1
- package/dist/tools/builtins/sandbox/index.js +4 -1
- package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
- package/dist/tools/builtins/sandbox/task-list.js +130 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
- package/dist/tools/builtins/sandbox/task-output.js +21 -3
- package/dist/tools/builtins/web/extract.d.ts +2 -4
- package/dist/tools/builtins/web/extract.js +2 -5
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/pipeline.js +1 -1
- package/dist/tools/questions.service.d.ts +15 -6
- package/dist/tools/questions.service.js +20 -8
- package/dist/tools/registry.d.ts +4 -2
- package/dist/tools/registry.js +4 -3
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0118_device_policy_ops.sql +6 -0
- package/src/migrations/0119_outbox.sql +22 -0
- package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
|
@@ -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)
|
|
@@ -207,19 +210,7 @@ let MachinesService = class MachinesService {
|
|
|
207
210
|
.execute();
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
|
-
const
|
|
211
|
-
const isolation = summary?.isolation ?? isolationOf(rawPolicy);
|
|
212
|
-
const policy = summary ?? (isolation === null ? null : { isolation });
|
|
213
|
-
const before = isolationOf(self.local_policy) ?? "sandbox";
|
|
214
|
-
if (isolation !== null && isolation !== before) {
|
|
215
|
-
await audit(this.db.db, {
|
|
216
|
-
actorAccountId: self.account_id,
|
|
217
|
-
orgId: self.org_id,
|
|
218
|
-
action: "device.isolation_reported",
|
|
219
|
-
target: self.device_id,
|
|
220
|
-
detail: { machineId, isolation, previous: before },
|
|
221
|
-
});
|
|
222
|
-
}
|
|
213
|
+
const policy = await this.reportedPolicy(machineId, self, hello.policy, rawPolicy);
|
|
223
214
|
await this.db
|
|
224
215
|
.updateTable("device_machines")
|
|
225
216
|
.set({
|
|
@@ -237,6 +228,88 @@ let MachinesService = class MachinesService {
|
|
|
237
228
|
.where("revoked_at", "is", null)
|
|
238
229
|
.execute();
|
|
239
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
|
|
233
|
+
* its policy file changed on the machine, its localhost proxy guard flipped, a new tier
|
|
234
|
+
* ceiling), read and audited as the hello's ({@link recordHello}): an unreadable summary keeps
|
|
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`.
|
|
237
|
+
* The next sandbox call then records a new `device_paths` event when the facts the model is told
|
|
238
|
+
* changed. A revoked machine is left alone.
|
|
239
|
+
*/
|
|
240
|
+
async recordPolicy(machineId, summary, rawPolicy = summary) {
|
|
241
|
+
const self = await this.db
|
|
242
|
+
.selectFrom("device_machines")
|
|
243
|
+
.select(["device_id", "org_id", "account_id", "local_policy", "pending_policy_ops"])
|
|
244
|
+
.where("id", "=", machineId)
|
|
245
|
+
.where("revoked_at", "is", null)
|
|
246
|
+
.forUpdate()
|
|
247
|
+
.executeTakeFirst();
|
|
248
|
+
if (self === undefined)
|
|
249
|
+
return;
|
|
250
|
+
const policy = await this.reportedPolicy(machineId, self, summary, rawPolicy);
|
|
251
|
+
await this.db
|
|
252
|
+
.updateTable("device_machines")
|
|
253
|
+
.set({ local_policy: jsonOrNull(policy), last_seen_at: sql `now()` })
|
|
254
|
+
.where("id", "=", machineId)
|
|
255
|
+
.where("revoked_at", "is", null)
|
|
256
|
+
.execute();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The summary to store for one a program reported (hello or `policy` frame): the parsed one,
|
|
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.
|
|
268
|
+
*/
|
|
269
|
+
async reportedPolicy(machineId, self, reported, rawPolicy) {
|
|
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
|
+
}
|
|
300
|
+
const isolation = summary?.isolation ?? isolationOf(rawPolicy);
|
|
301
|
+
const before = isolationOf(self.local_policy) ?? "sandbox";
|
|
302
|
+
if (isolation !== null && isolation !== before) {
|
|
303
|
+
await audit(this.db.db, {
|
|
304
|
+
actorAccountId: self.account_id,
|
|
305
|
+
orgId: self.org_id,
|
|
306
|
+
action: "device.isolation_reported",
|
|
307
|
+
target: self.device_id,
|
|
308
|
+
detail: { machineId, isolation, previous: before },
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return summary ?? (isolation === null ? null : { isolation });
|
|
312
|
+
}
|
|
240
313
|
/** Marks the machine seen (a ping); writes at most once every 60 seconds. */
|
|
241
314
|
async touch(machineId) {
|
|
242
315
|
await this.db
|
|
@@ -385,6 +458,9 @@ __decorate([
|
|
|
385
458
|
__decorate([
|
|
386
459
|
Transactional()
|
|
387
460
|
], MachinesService.prototype, "recordHello", null);
|
|
461
|
+
__decorate([
|
|
462
|
+
Transactional()
|
|
463
|
+
], MachinesService.prototype, "recordPolicy", null);
|
|
388
464
|
__decorate([
|
|
389
465
|
Transactional()
|
|
390
466
|
], MachinesService.prototype, "revoke", null);
|
|
@@ -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
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
-
/**
|
|
46
|
-
private readonly
|
|
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
|
-
/**
|
|
52
|
-
|
|
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
|
|
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
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
|
|
59
|
+
usage;
|
|
61
60
|
log;
|
|
62
61
|
config;
|
|
63
62
|
constructor(kdb, txHost, purposes, providers,
|
|
64
|
-
/**
|
|
65
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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],
|
|
@@ -235,6 +235,13 @@ let OrgsService = class OrgsService {
|
|
|
235
235
|
.set({ deleted_at: sql `now()` })
|
|
236
236
|
.where("id", "=", orgId)
|
|
237
237
|
.execute();
|
|
238
|
+
// the org's owed jobs go with it; dead letters stay for the overview, and the deletion's own
|
|
239
|
+
// cleanup work carries no org_id (transactional outbox spec §9)
|
|
240
|
+
await this.db
|
|
241
|
+
.deleteFrom("outbox")
|
|
242
|
+
.where("org_id", "=", orgId)
|
|
243
|
+
.where("dead_at", "is", null)
|
|
244
|
+
.execute();
|
|
238
245
|
// after the org is marked deleted, so it counts as no user of what it shared (P3-18)
|
|
239
246
|
await this.ownedItems.onOrgDeleted(orgId, actor.id, {
|
|
240
247
|
...(actorTokenId !== undefined && { actorTokenId }),
|
|
@@ -23,7 +23,7 @@ let OrgSettingsController = class OrgSettingsController {
|
|
|
23
23
|
async get(org) {
|
|
24
24
|
return {
|
|
25
25
|
settings: await this.settings.get(org.orgId),
|
|
26
|
-
deployment: this.settings.deployment(),
|
|
26
|
+
deployment: await this.settings.deployment(),
|
|
27
27
|
sandboxLimits: await this.settings.sandboxLimits(org.orgId),
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -32,7 +32,7 @@ let OrgSettingsController = class OrgSettingsController {
|
|
|
32
32
|
return {
|
|
33
33
|
// an API token may narrow the sandbox, never widen it (sandbox dev tiers spec §2)
|
|
34
34
|
settings: await this.settings.update(org.orgId, body, p.account.id, patTokenId(p), undefined, { onlyNarrows: patTokenId(p) !== undefined }),
|
|
35
|
-
deployment: this.settings.deployment(),
|
|
35
|
+
deployment: await this.settings.deployment(),
|
|
36
36
|
sandboxLimits: await this.settings.sandboxLimits(org.orgId),
|
|
37
37
|
};
|
|
38
38
|
}
|
|
@@ -5,6 +5,7 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
|
5
5
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
6
6
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
7
7
|
import type { KernelLogger } from "../log.js";
|
|
8
|
+
import { type PlatformTiers } from "../sandbox/platform-tiers.js";
|
|
8
9
|
import { type EffectiveSandboxPolicy } from "../sandbox/tiers.js";
|
|
9
10
|
import { OrgLock } from "./org-lock.js";
|
|
10
11
|
/**
|
|
@@ -296,13 +297,15 @@ export declare class OrgSettingsService {
|
|
|
296
297
|
config: Pick<KernelConfigService, "get">, log?: KernelLogger | undefined);
|
|
297
298
|
/** Parses stored settings, warning when an invalid `sandboxTier` narrows to `{ max: "strict" }`. */
|
|
298
299
|
private parseStored;
|
|
300
|
+
/** The platform's effective sandbox tiers (the startup config over the admin console's choice). */
|
|
301
|
+
platformTiers(): Promise<PlatformTiers>;
|
|
299
302
|
/** 部署默认值(设置页展示「跟随部署」时的实际取值)。 */
|
|
300
|
-
deployment(): OrgSettingsDeployment
|
|
303
|
+
deployment(): Promise<OrgSettingsDeployment>;
|
|
301
304
|
/** 组织实际生效的沙箱 cpu / 内存(只读;由超级管理员在部署设置里覆盖)。 */
|
|
302
305
|
sandboxLimits(orgId: string): Promise<OrgSandboxLimits>;
|
|
303
306
|
get(orgId: string): Promise<OrgSettings>;
|
|
304
307
|
/** The refusals of {@link update} that need no read of the org (the Platform tool's precheck). */
|
|
305
|
-
check(patch: OrgSettingsPatch): void
|
|
308
|
+
check(patch: OrgSettingsPatch): Promise<void>;
|
|
306
309
|
update(orgId: string, patch: OrgSettingsPatch, actorAccountId: string, actorTokenId?: string,
|
|
307
310
|
/** Merged into the audit detail (the Platform tool's `via: "agent"`). */
|
|
308
311
|
auditDetail?: Record<string, unknown>,
|
|
@@ -313,8 +316,11 @@ export declare class OrgSettingsService {
|
|
|
313
316
|
opts?: {
|
|
314
317
|
onlyNarrows?: boolean;
|
|
315
318
|
}): Promise<OrgSettings>;
|
|
316
|
-
/**
|
|
317
|
-
|
|
319
|
+
/**
|
|
320
|
+
* The effective sandbox policy for the org's settings and a device's own tier (null = none);
|
|
321
|
+
* `tiers`: the platform's tiers when the caller already read them.
|
|
322
|
+
*/
|
|
323
|
+
sandboxPolicy(settings: OrgSettings, deviceTier: SandboxTier | null, tiers?: PlatformTiers): Promise<EffectiveSandboxPolicy>;
|
|
318
324
|
/**
|
|
319
325
|
* Refuses a patch that widens the org's sandbox (sandbox dev tiers spec §2, §7.2: only a person
|
|
320
326
|
* widens it, never the Platform tool or an API token):
|
|
@@ -327,7 +333,7 @@ export declare class OrgSettingsService {
|
|
|
327
333
|
* - `sandboxEgress`: the base list grows: from none to anything else, or to hosts (the
|
|
328
334
|
* deployment's when the org gives none) not all in the current list.
|
|
329
335
|
*/
|
|
330
|
-
assertOnlyNarrows(before: OrgSettings, patch: OrgSettingsPatch): void
|
|
336
|
+
assertOnlyNarrows(before: OrgSettings, patch: OrgSettingsPatch): Promise<void>;
|
|
331
337
|
private updateIn;
|
|
332
338
|
}
|
|
333
339
|
/**
|
|
@@ -18,6 +18,7 @@ import { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
18
18
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
19
19
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
20
20
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
21
|
+
import { readPlatformTiers } from "../sandbox/platform-tiers.js";
|
|
21
22
|
import { effectiveSandboxPolicy } from "../sandbox/tiers.js";
|
|
22
23
|
import { assertThemeOnIn } from "../themes/theme-state.js";
|
|
23
24
|
import { IanaTimeZone } from "../time-zone.js";
|
|
@@ -255,9 +256,14 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
255
256
|
}
|
|
256
257
|
return parseOrgSettings(raw);
|
|
257
258
|
}
|
|
259
|
+
/** The platform's effective sandbox tiers (the startup config over the admin console's choice). */
|
|
260
|
+
platformTiers() {
|
|
261
|
+
return readPlatformTiers(this.db.db, this.config, this.log);
|
|
262
|
+
}
|
|
258
263
|
/** 部署默认值(设置页展示「跟随部署」时的实际取值)。 */
|
|
259
|
-
deployment() {
|
|
260
|
-
const
|
|
264
|
+
async deployment() {
|
|
265
|
+
const lim = this.config.get().sandbox.limits;
|
|
266
|
+
const tiers = await this.platformTiers();
|
|
261
267
|
return {
|
|
262
268
|
sandboxEgress: { mode: lim.egress, allowHosts: [...lim.allowHosts] },
|
|
263
269
|
sandboxTiers: {
|
|
@@ -290,14 +296,14 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
290
296
|
return this.parseStored(orgId, row.settings);
|
|
291
297
|
}
|
|
292
298
|
/** The refusals of {@link update} that need no read of the org (the Platform tool's precheck). */
|
|
293
|
-
check(patch) {
|
|
299
|
+
async check(patch) {
|
|
294
300
|
if (patch.sandboxEgress?.mode === "allowlist" &&
|
|
295
|
-
this.
|
|
301
|
+
this.config.get().sandbox.limits.egress === "none") {
|
|
296
302
|
throw new ApiError("org.sandbox_egress_not_allowed", "This deployment disables sandbox network access (sandbox.limits.egress is none); an organization can only set sandboxEgress to none");
|
|
297
303
|
}
|
|
298
304
|
const t = patch.sandboxTier;
|
|
299
305
|
if (t != null) {
|
|
300
|
-
const platform = this.
|
|
306
|
+
const platform = await this.platformTiers();
|
|
301
307
|
if (t.max !== undefined && !tierAtMost(t.max, platform.max)) {
|
|
302
308
|
throw sandboxTierNotAllowed(`The platform allows sandbox tiers up to ${platform.max}; sandboxTier.max cannot be ${t.max}`);
|
|
303
309
|
}
|
|
@@ -318,12 +324,19 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
318
324
|
* change that widens (see {@link assertOnlyNarrows}); only a person in a session widens.
|
|
319
325
|
*/
|
|
320
326
|
opts) {
|
|
321
|
-
this.check(patch);
|
|
327
|
+
await this.check(patch);
|
|
322
328
|
return this.updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail, opts);
|
|
323
329
|
}
|
|
324
|
-
/**
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
/**
|
|
331
|
+
* The effective sandbox policy for the org's settings and a device's own tier (null = none);
|
|
332
|
+
* `tiers`: the platform's tiers when the caller already read them.
|
|
333
|
+
*/
|
|
334
|
+
async sandboxPolicy(settings, deviceTier, tiers) {
|
|
335
|
+
const deployment = {
|
|
336
|
+
limits: this.config.get().sandbox.limits,
|
|
337
|
+
tiers: tiers ?? (await this.platformTiers()),
|
|
338
|
+
};
|
|
339
|
+
return effectiveSandboxPolicy(deployment, settings, deviceTier);
|
|
327
340
|
}
|
|
328
341
|
/**
|
|
329
342
|
* Refuses a patch that widens the org's sandbox (sandbox dev tiers spec §2, §7.2: only a person
|
|
@@ -337,12 +350,13 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
337
350
|
* - `sandboxEgress`: the base list grows: from none to anything else, or to hosts (the
|
|
338
351
|
* deployment's when the org gives none) not all in the current list.
|
|
339
352
|
*/
|
|
340
|
-
assertOnlyNarrows(before, patch) {
|
|
353
|
+
async assertOnlyNarrows(before, patch) {
|
|
341
354
|
if (patch.sandboxTier !== undefined) {
|
|
342
355
|
const next = patch.sandboxTier ?? undefined;
|
|
343
356
|
const prev = before.sandboxTier;
|
|
344
|
-
const
|
|
345
|
-
const
|
|
357
|
+
const tiers = await this.platformTiers();
|
|
358
|
+
const b = await this.sandboxPolicy(before, null, tiers);
|
|
359
|
+
const a = await this.sandboxPolicy({ ...before, sandboxTier: next }, null, tiers);
|
|
346
360
|
// a cleared default follows the platform, capped by the ceiling: `max` bounds it then
|
|
347
361
|
const nextDefault = next?.default === undefined && next?.max !== undefined ? next.max : next?.default;
|
|
348
362
|
if (!tierAtMost(a.ceiling, b.ceiling) ||
|
|
@@ -381,10 +395,10 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
381
395
|
const stored = asObject(row.settings);
|
|
382
396
|
const before = this.parseStored(orgId, stored);
|
|
383
397
|
// again under the org lock: a hot config reload may have lowered the platform since
|
|
384
|
-
this.check(patch);
|
|
398
|
+
await this.check(patch);
|
|
385
399
|
// under the org lock, so the comparison is with the value the write replaces
|
|
386
400
|
if (opts?.onlyNarrows)
|
|
387
|
-
this.assertOnlyNarrows(before, patch);
|
|
401
|
+
await this.assertOnlyNarrows(before, patch);
|
|
388
402
|
const changes = Object.fromEntries(Object.entries(patch)
|
|
389
403
|
.filter(([, v]) => v !== undefined)
|
|
390
404
|
// null clears sandboxEgress / wakeOnBackgroundTask: follow the deployment default again
|