@capybara-hq/kernel 0.1.0-rc.24 → 0.1.0-rc.26
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/dist/admin/admin.module.js +2 -0
- package/dist/admin/health.service.d.ts +2 -2
- package/dist/admin/health.service.js +6 -2
- package/dist/admin/outbox-dead.service.d.ts +7 -3
- package/dist/admin/outbox-dead.service.js +17 -6
- package/dist/admin/overview.service.d.ts +6 -6
- package/dist/admin/overview.service.js +7 -7
- package/dist/agents/agents.module.js +3 -0
- package/dist/agents/agents.service.d.ts +6 -1
- package/dist/agents/agents.service.js +11 -2
- package/dist/background/wake-rules.d.ts +5 -0
- package/dist/background/wake-rules.js +16 -5
- package/dist/catalog/catalog-import.module.js +0 -4
- package/dist/catalog/catalog-scope.service.d.ts +63 -26
- package/dist/catalog/catalog-scope.service.js +146 -71
- package/dist/catalog/catalog.module.js +4 -3
- package/dist/catalog/catalog.service.d.ts +11 -4
- package/dist/catalog/catalog.service.js +21 -5
- package/dist/catalog/cli.d.ts +4 -3
- package/dist/catalog/cli.js +8 -13
- package/dist/catalog/job-ids.d.ts +8 -20
- package/dist/catalog/job-ids.js +11 -16
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-plugin-uploads.d.ts +11 -4
- package/dist/catalog/org-plugin-uploads.js +21 -6
- package/dist/catalog/owned-items.d.ts +6 -13
- package/dist/catalog/owned-items.js +11 -29
- package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
- package/dist/catalog/owned-plugin-cleanup.js +12 -66
- package/dist/catalog/platform-components.d.ts +56 -34
- package/dist/catalog/platform-components.js +68 -78
- package/dist/catalog/plugin-import.d.ts +28 -25
- package/dist/catalog/plugin-import.js +90 -76
- package/dist/catalog/plugin-upgrade.d.ts +4 -4
- package/dist/catalog/plugin-upgrade.js +7 -19
- package/dist/catalog/scope-jobs.d.ts +59 -0
- package/dist/catalog/scope-jobs.js +65 -0
- package/dist/catalog/scope-sync-workers.d.ts +22 -0
- package/dist/catalog/scope-sync-workers.js +58 -2
- package/dist/catalog/scope-sync.d.ts +40 -65
- package/dist/catalog/scope-sync.js +114 -122
- package/dist/channels/channels.module.d.ts +9 -8
- package/dist/channels/channels.module.js +11 -10
- package/dist/channels/connections.service.d.ts +3 -3
- package/dist/channels/connections.service.js +6 -6
- package/dist/channels/deliver-job.d.ts +10 -3
- package/dist/channels/deliver-job.js +7 -3
- package/dist/channels/inbound.js +2 -2
- package/dist/channels/jobs.d.ts +11 -4
- package/dist/channels/jobs.js +18 -3
- package/dist/channels/outbound.d.ts +5 -2
- package/dist/channels/outbound.js +9 -2
- package/dist/channels/threads.d.ts +4 -3
- package/dist/channels/threads.js +4 -3
- package/dist/conversations/conversations.controller.d.ts +4 -2
- package/dist/conversations/conversations.controller.js +8 -4
- package/dist/conversations/conversations.service.d.ts +54 -9
- package/dist/conversations/conversations.service.js +94 -25
- package/dist/conversations/events.d.ts +27 -0
- package/dist/conversations/events.js +30 -1
- package/dist/conversations/source.d.ts +10 -0
- package/dist/conversations/source.js +17 -0
- package/dist/conversations/tree.d.ts +10 -0
- package/dist/conversations/tree.js +35 -0
- package/dist/db/kernel-schema.d.ts +19 -15
- package/dist/devices/devices.service.d.ts +25 -14
- package/dist/devices/devices.service.js +36 -30
- package/dist/evals/evals.module.js +5 -1
- package/dist/evals/jobs.d.ts +14 -6
- package/dist/evals/jobs.js +21 -19
- package/dist/evals/run-replay.d.ts +13 -2
- package/dist/evals/run-replay.js +28 -1
- package/dist/evals/runs.service.d.ts +5 -6
- package/dist/evals/runs.service.js +11 -33
- package/dist/evals/snapshot.d.ts +7 -1
- package/dist/evals/snapshot.js +2 -1
- package/dist/extensions/hooks.d.ts +22 -3
- package/dist/extensions/hooks.js +15 -1
- package/dist/extensions/lifecycle-hooks.d.ts +99 -0
- package/dist/extensions/lifecycle-hooks.js +273 -0
- package/dist/extensions/org-config.d.ts +13 -1
- package/dist/extensions/org-config.js +19 -2
- package/dist/extensions/permission-view.d.ts +9 -5
- package/dist/extensions/permission-view.js +9 -4
- package/dist/extensions/point-defaults.d.ts +28 -1
- package/dist/extensions/point-defaults.js +44 -13
- package/dist/inbox/inbox.service.d.ts +6 -20
- package/dist/inbox/inbox.service.js +0 -3
- package/dist/inbox/org-notices.d.ts +1 -1
- package/dist/inbox/platform-inbox.service.d.ts +3 -32
- package/dist/inbox/platform-inbox.service.js +5 -54
- package/dist/infra/outbox/outbox.service.d.ts +6 -0
- package/dist/infra/outbox/outbox.service.js +51 -17
- package/dist/infra/outbox/registry.d.ts +176 -0
- package/dist/infra/outbox/registry.js +183 -0
- package/dist/infra/queue/queue-names.d.ts +1 -1
- package/dist/infra/queue/queue-names.js +1 -0
- package/dist/local-devices/device-policy.service.d.ts +131 -26
- package/dist/local-devices/device-policy.service.js +341 -145
- package/dist/local-devices/machines.d.ts +17 -2
- package/dist/local-devices/machines.js +66 -5
- package/dist/local-devices/policy-op-jobs.d.ts +15 -0
- package/dist/local-devices/policy-op-jobs.js +12 -0
- package/dist/local-devices/policy-summary.d.ts +36 -3
- package/dist/local-devices/policy-summary.js +47 -1
- package/dist/orgs/directory-sync.js +1 -1
- package/dist/orgs/members.service.js +1 -1
- package/dist/orgs/offboarding.d.ts +50 -57
- package/dist/orgs/offboarding.js +117 -162
- package/dist/orgs/orgs.module.js +3 -0
- package/dist/orgs/orgs.service.d.ts +11 -5
- package/dist/orgs/orgs.service.js +19 -7
- package/dist/overrides.d.ts +7 -0
- package/dist/overrides.js +4 -0
- package/dist/platform/ops/conversations.ops.js +1 -1
- package/dist/platform/ops/inbox.ops.js +4 -20
- package/dist/platform/ops/platform.ops.d.ts +1 -0
- package/dist/platform/ops/platform.ops.js +6 -3
- package/dist/roles/worker-role.d.ts +10 -5
- package/dist/roles/worker-role.js +19 -9
- package/dist/roles/worker.module.js +2 -4
- package/dist/runtime/context/build.d.ts +3 -0
- package/dist/runtime/context/build.js +15 -0
- package/dist/runtime/context/plugin-context.d.ts +20 -0
- package/dist/runtime/context/plugin-context.js +30 -0
- package/dist/runtime/langgraph-loop.js +4 -0
- package/dist/runtime/loop.d.ts +8 -0
- package/dist/runtime/maintenance.d.ts +9 -4
- package/dist/runtime/maintenance.js +52 -13
- package/dist/runtime/model-call.d.ts +6 -1
- package/dist/runtime/model-call.js +1 -0
- package/dist/runtime/plugin-context.service.d.ts +75 -0
- package/dist/runtime/plugin-context.service.js +220 -0
- package/dist/runtime/run-context.service.d.ts +7 -3
- package/dist/runtime/run-context.service.js +12 -2
- package/dist/runtime/run-control.d.ts +6 -1
- package/dist/runtime/run-control.js +13 -0
- package/dist/runtime/run-finish.d.ts +2 -2
- package/dist/runtime/run-finish.js +11 -6
- package/dist/runtime/run-jobs.d.ts +21 -4
- package/dist/runtime/run-jobs.js +29 -6
- package/dist/runtime/run-orphan-check.job.d.ts +14 -11
- package/dist/runtime/run-orphan-check.job.js +16 -11
- package/dist/runtime/run-queue.d.ts +29 -24
- package/dist/runtime/run-queue.js +118 -57
- package/dist/runtime/run-status.d.ts +12 -0
- package/dist/runtime/run-status.js +21 -0
- package/dist/runtime/run-worker.d.ts +32 -3
- package/dist/runtime/run-worker.js +143 -21
- package/dist/runtime/runtime.module.js +2 -0
- package/dist/runtime/step-log.d.ts +6 -5
- package/dist/runtime/step-log.js +6 -5
- package/dist/runtime/subagents/dispatch.js +1 -0
- package/dist/runtime/subagents/skill-fork.js +3 -0
- package/dist/runtime/usage-report.d.ts +3 -1
- package/dist/runtime/usage-report.js +4 -2
- package/dist/runtime/waits.d.ts +16 -6
- package/dist/runtime/waits.js +37 -15
- package/dist/sandbox/jobs.d.ts +20 -10
- package/dist/sandbox/jobs.js +23 -17
- package/dist/schedules/fire.d.ts +25 -4
- package/dist/schedules/fire.js +87 -13
- package/dist/schedules/job-ids.d.ts +66 -17
- package/dist/schedules/job-ids.js +81 -13
- package/dist/schedules/run-tracker.js +2 -1
- package/dist/schedules/scan.job.d.ts +16 -35
- package/dist/schedules/scan.job.js +38 -147
- package/dist/schedules/scheduled-tasks.service.d.ts +6 -6
- package/dist/schedules/scheduled-tasks.service.js +17 -14
- package/dist/schedules/schedules.module.js +0 -4
- package/dist/skills/catalog.d.ts +7 -3
- package/dist/skills/catalog.js +12 -2
- package/dist/skills/changes.controller.d.ts +1 -1
- package/dist/skills/changes.controller.js +1 -1
- package/dist/tools/approvals.service.d.ts +111 -23
- package/dist/tools/approvals.service.js +326 -77
- package/dist/tools/builtins/sandbox/task-output.js +21 -6
- package/dist/tools/device-op-resolver.d.ts +40 -0
- package/dist/tools/device-op-resolver.js +121 -0
- package/dist/tools/network-asks.d.ts +1 -1
- package/dist/tools/network-asks.js +4 -3
- package/dist/tools/pipeline.d.ts +11 -2
- package/dist/tools/pipeline.js +5 -0
- package/dist/tools/questions.service.d.ts +15 -4
- package/dist/tools/questions.service.js +21 -19
- package/dist/tools/registry.d.ts +8 -1
- package/dist/tools/registry.js +9 -0
- package/dist/tools/tools.module.js +4 -0
- package/package.json +3 -3
- package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
- package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
- package/src/migrations/0123_pgvector.sql +27 -0
- package/src/migrations/0124_conversation_source.sql +36 -0
- package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
- package/dist/catalog/platform-migration.d.ts +0 -115
- package/dist/catalog/platform-migration.js +0 -583
- package/dist/infra/queue/add-job-once.d.ts +0 -14
- package/dist/infra/queue/add-job-once.js +0 -16
|
@@ -16,11 +16,14 @@ import { afterCommit } from "../infra/database/after-commit.js";
|
|
|
16
16
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
17
17
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
18
18
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
19
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
19
20
|
import { KERNEL_BUS, KERNEL_LOGGER } from "../infra/tokens.js";
|
|
21
|
+
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
20
22
|
import { LOCAL_PROVIDER } from "../sandbox/providers.js";
|
|
21
23
|
import { deviceNotFound } from "../sandbox/sandbox.service.js";
|
|
22
24
|
import { deviceRevokedTopic } from "./keys.js";
|
|
23
|
-
import {
|
|
25
|
+
import { DEVICE_OP_RESOLVE_JOB, deviceOpReportedJobId } from "./policy-op-jobs.js";
|
|
26
|
+
import { deviceFeaturesOf, isolationOf, localPolicyDiff, localPolicyOf, opExplains, opVisibleIn, pendingPolicyOpsOf, } from "./policy-summary.js";
|
|
24
27
|
import { LocalDeviceRelay } from "./relay.js";
|
|
25
28
|
/** The machine credential's prefix (`cap_dev_` plus 32 random bytes, base64url). */
|
|
26
29
|
export const MACHINE_CREDENTIAL_PREFIX = "cap_dev_";
|
|
@@ -42,12 +45,14 @@ let MachinesService = class MachinesService {
|
|
|
42
45
|
txHost;
|
|
43
46
|
relay;
|
|
44
47
|
bus;
|
|
48
|
+
outbox;
|
|
45
49
|
log;
|
|
46
|
-
constructor(db, txHost, relay, bus, log) {
|
|
50
|
+
constructor(db, txHost, relay, bus, outbox, log) {
|
|
47
51
|
this.db = db;
|
|
48
52
|
this.txHost = txHost;
|
|
49
53
|
this.relay = relay;
|
|
50
54
|
this.bus = bus;
|
|
55
|
+
this.outbox = outbox;
|
|
51
56
|
this.log = log;
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
@@ -164,7 +169,8 @@ let MachinesService = class MachinesService {
|
|
|
164
169
|
* isolation that differs from the stored one (none stored counts as `sandbox`) is audited
|
|
165
170
|
* `device.isolation_reported` (sandbox dev tiers spec §10.4), once per change; a change of what
|
|
166
171
|
* the owner sets, made on the machine while it was away, is audited `device.policy_changed`
|
|
167
|
-
* ({@link reportedPolicy}).
|
|
172
|
+
* ({@link reportedPolicy}). The outcomes of its recent policy ops (`recentOps`) backfill the
|
|
173
|
+
* platform's ops still pending on the row ({@link backfillOps}).
|
|
168
174
|
*/
|
|
169
175
|
async recordHello(machineId, hello, rawPolicy = hello.policy) {
|
|
170
176
|
const self = await this.db
|
|
@@ -227,6 +233,61 @@ let MachinesService = class MachinesService {
|
|
|
227
233
|
.where("id", "=", machineId)
|
|
228
234
|
.where("revoked_at", "is", null)
|
|
229
235
|
.execute();
|
|
236
|
+
if (hello.recentOps !== undefined) {
|
|
237
|
+
await this.backfillOps(machineId, self, hello.recentOps, localPolicyOf(hello.policy));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Hello backfill (transactional outbox spec §7): a pending op of the platform's whose outcome
|
|
242
|
+
* the hello reports (`applied`, `refused`), or that it does not list although the op can no
|
|
243
|
+
* longer reach the machine (`absent`: past its `resolveAt`), is marked `reported` and owes a
|
|
244
|
+
* `device-op-resolve` job (`device-op-reported-<opId>`) that records the outcome. An op the
|
|
245
|
+
* machine logged only as `started` (it stopped between the log and the answer) is judged once
|
|
246
|
+
* past its `resolveAt` by the policy the hello reports: its change there → `applied`, else
|
|
247
|
+
* `interrupted` (within the window, or with no readable policy, it is left). An expired entry
|
|
248
|
+
* (past `until`) is skipped: the machine's reports are no longer the op's. Runs in the caller's
|
|
249
|
+
* transaction, with the machine's row locked; an op already reported is not owed again.
|
|
250
|
+
*/
|
|
251
|
+
async backfillOps(machineId, self, recentOps, policy) {
|
|
252
|
+
const outcomes = new Map(recentOps.map((r) => [r.opId, r.outcome]));
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
const reported = new Map();
|
|
255
|
+
for (const op of pendingPolicyOpsOf(self.pending_policy_ops)) {
|
|
256
|
+
if (op.reported !== undefined || op.until <= now)
|
|
257
|
+
continue;
|
|
258
|
+
const outcome = outcomes.get(op.id);
|
|
259
|
+
if (outcome === "applied" || outcome === "refused")
|
|
260
|
+
reported.set(op.id, { outcome });
|
|
261
|
+
else if (now < op.resolveAt)
|
|
262
|
+
continue;
|
|
263
|
+
else if (outcome === undefined)
|
|
264
|
+
reported.set(op.id, { outcome: "absent" });
|
|
265
|
+
else if (policy !== null) {
|
|
266
|
+
reported.set(op.id, { outcome: opVisibleIn(op, policy) ? "applied" : "interrupted" });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (reported.size === 0)
|
|
270
|
+
return;
|
|
271
|
+
const ops = Array.isArray(self.pending_policy_ops) ? self.pending_policy_ops : [];
|
|
272
|
+
const next = ops.map((e) => {
|
|
273
|
+
const id = e?.id;
|
|
274
|
+
const r = typeof id === "string" ? reported.get(id) : undefined;
|
|
275
|
+
return r === undefined ? e : { ...e, reported: r };
|
|
276
|
+
});
|
|
277
|
+
await this.db
|
|
278
|
+
.updateTable("device_machines")
|
|
279
|
+
.set({ pending_policy_ops: JSON.stringify(next) })
|
|
280
|
+
.where("id", "=", machineId)
|
|
281
|
+
.execute();
|
|
282
|
+
for (const opId of reported.keys()) {
|
|
283
|
+
await this.outbox.add({
|
|
284
|
+
queue: MAINTENANCE_QUEUE_NAME,
|
|
285
|
+
name: DEVICE_OP_RESOLVE_JOB,
|
|
286
|
+
jobId: deviceOpReportedJobId(opId),
|
|
287
|
+
data: { machineId, opId },
|
|
288
|
+
orgId: self.org_id,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
230
291
|
}
|
|
231
292
|
/**
|
|
232
293
|
* Stores the local policy summary a connected program pushed after its hello (a `policy` frame:
|
|
@@ -292,7 +353,6 @@ let MachinesService = class MachinesService {
|
|
|
292
353
|
via: "device",
|
|
293
354
|
machineId,
|
|
294
355
|
approvalId: null,
|
|
295
|
-
confirmed: true,
|
|
296
356
|
},
|
|
297
357
|
});
|
|
298
358
|
return summary;
|
|
@@ -470,7 +530,8 @@ MachinesService = __decorate([
|
|
|
470
530
|
__param(1, Inject(TransactionHost)),
|
|
471
531
|
__param(2, Inject(LocalDeviceRelay)),
|
|
472
532
|
__param(3, Inject(KERNEL_BUS)),
|
|
473
|
-
__param(4, Inject(
|
|
533
|
+
__param(4, Inject(OutboxService)),
|
|
534
|
+
__param(5, Inject(KERNEL_LOGGER))
|
|
474
535
|
], MachinesService);
|
|
475
536
|
export { MachinesService };
|
|
476
537
|
const jsonOrNull = (v) => (v === null ? null : JSON.stringify(v));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settles a device policy op whose outcome was not recorded (transactional outbox spec §7): owed
|
|
3
|
+
* by the op's intent transaction for when the op can no longer reach the machine, and by a hello
|
|
4
|
+
* that reported its outcome. Consumed by `DeviceOpResolver` on the maintenance queue.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEVICE_OP_RESOLVE_JOB = "device-op-resolve";
|
|
7
|
+
/** The `device-op-resolve` job's data. */
|
|
8
|
+
export interface DeviceOpResolveData {
|
|
9
|
+
machineId: string;
|
|
10
|
+
opId: string;
|
|
11
|
+
}
|
|
12
|
+
/** The job id of the resolve job owed with an op's intent. */
|
|
13
|
+
export declare const deviceOpResolveJobId: (opId: string) => string;
|
|
14
|
+
/** The job id of the resolve job owed by a hello that reported the op's outcome. */
|
|
15
|
+
export declare const deviceOpReportedJobId: (opId: string) => string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The device policy op jobs' names and ids: a leaf module (no service imports), read by the
|
|
2
|
+
// producers (DevicePolicyService, MachinesService) and the maintenance processor.
|
|
3
|
+
/**
|
|
4
|
+
* Settles a device policy op whose outcome was not recorded (transactional outbox spec §7): owed
|
|
5
|
+
* by the op's intent transaction for when the op can no longer reach the machine, and by a hello
|
|
6
|
+
* that reported its outcome. Consumed by `DeviceOpResolver` on the maintenance queue.
|
|
7
|
+
*/
|
|
8
|
+
export const DEVICE_OP_RESOLVE_JOB = "device-op-resolve";
|
|
9
|
+
/** The job id of the resolve job owed with an op's intent. */
|
|
10
|
+
export const deviceOpResolveJobId = (opId) => `device-op-resolve-${opId}`;
|
|
11
|
+
/** The job id of the resolve job owed by a hello that reported the op's outcome. */
|
|
12
|
+
export const deviceOpReportedJobId = (opId) => `device-op-reported-${opId}`;
|
|
@@ -38,10 +38,28 @@ export interface LocalPolicyDiff {
|
|
|
38
38
|
}
|
|
39
39
|
/** The {@link LocalPolicyDiff} from `prev` to `next`; null when nothing the owner sets changed. */
|
|
40
40
|
export declare function localPolicyDiff(prev: DeviceLocalPolicySummary, next: DeviceLocalPolicySummary): LocalPolicyDiff | null;
|
|
41
|
+
/** Who asked for a `policy` op: the deciding account, where, and the network ask it answers. */
|
|
42
|
+
export interface PolicyOpSource {
|
|
43
|
+
accountId: string;
|
|
44
|
+
via: "web" | "im";
|
|
45
|
+
approvalId: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** What the machine reported of an op after its answer was lost (its hello's `recentOps`). */
|
|
48
|
+
export interface ReportedPolicyOp {
|
|
49
|
+
/**
|
|
50
|
+
* `absent`: not in the hello's outcomes although the op can no longer reach the machine;
|
|
51
|
+
* `interrupted`: logged `started` only, and its change is not in the policy the hello reports
|
|
52
|
+
* (one whose change is there is `applied`).
|
|
53
|
+
*/
|
|
54
|
+
outcome: "applied" | "refused" | "absent" | "interrupted";
|
|
55
|
+
}
|
|
41
56
|
/**
|
|
42
|
-
*
|
|
43
|
-
* machine's row
|
|
44
|
-
* the
|
|
57
|
+
* The intent of a `policy` op the platform sends to a machine (transactional outbox spec §7), kept
|
|
58
|
+
* on the machine's row (`pending_policy_ops`) from before the op is sent until its outcome is
|
|
59
|
+
* recorded: the change asked for, who asked, and until when (epoch ms) a summary the machine
|
|
60
|
+
* reports by itself may still be that op's. `sent`: on its way or in flight, the
|
|
61
|
+
* `device-op-resolve` job owed for `resolveAt`; `unknown`: no answer came, and only the machine's
|
|
62
|
+
* next hello (`recentOps`) or a later `policyStatus` can tell what became of it.
|
|
45
63
|
*/
|
|
46
64
|
export interface PendingPolicyOp {
|
|
47
65
|
id: string;
|
|
@@ -50,10 +68,25 @@ export interface PendingPolicyOp {
|
|
|
50
68
|
/** Present when the op set the tier (null: follow the org's default). */
|
|
51
69
|
tier?: SandboxTier | null;
|
|
52
70
|
isolation?: "sandbox";
|
|
71
|
+
source: PolicyOpSource;
|
|
72
|
+
actorTokenId?: string;
|
|
73
|
+
status: "sent" | "unknown";
|
|
74
|
+
/** When the intent was written (epoch ms). */
|
|
75
|
+
sentAt: number;
|
|
76
|
+
/** From when the op can no longer reach the machine (epoch ms): the resolve job's `runAt`. */
|
|
77
|
+
resolveAt: number;
|
|
53
78
|
until: number;
|
|
79
|
+
/** Set by a hello that reported the op's outcome (or its absence) before the op was resolved. */
|
|
80
|
+
reported?: ReportedPolicyOp;
|
|
54
81
|
}
|
|
55
82
|
/** The pending ops stored on a machine row (malformed entries dropped). */
|
|
56
83
|
export declare function pendingPolicyOpsOf(v: unknown): PendingPolicyOp[];
|
|
84
|
+
/**
|
|
85
|
+
* Whether the change of `op` is in `policy`: its hosts added and removed, its tier and isolation
|
|
86
|
+
* set. Tells an op the machine logged only as `started` (it stopped between the log and the
|
|
87
|
+
* answer) that was written from one that was not.
|
|
88
|
+
*/
|
|
89
|
+
export declare function opVisibleIn(op: PendingPolicyOp, policy: DeviceLocalPolicySummary): boolean;
|
|
57
90
|
/**
|
|
58
91
|
* Whether `diff` is what the platform's `op` did (its answer, or the machine's own report of it
|
|
59
92
|
* before the answer was recorded): hosts within the op's, the tier the op set, and only what a
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { isSandboxTier, } from "@capybara-hq/plugin-sdk";
|
|
2
|
-
const KNOWN_FEATURES = [
|
|
2
|
+
const KNOWN_FEATURES = [
|
|
3
|
+
"networkReport",
|
|
4
|
+
"policyOp",
|
|
5
|
+
"policyOpId",
|
|
6
|
+
"tiers",
|
|
7
|
+
];
|
|
3
8
|
/**
|
|
4
9
|
* The known features in `v` (a hello's `features` or the stored column), in a fixed order and
|
|
5
10
|
* deduped; anything else (unknown names, a non-array) is dropped.
|
|
@@ -99,17 +104,58 @@ export function pendingPolicyOpsOf(v) {
|
|
|
99
104
|
const o = e;
|
|
100
105
|
if (typeof o.id !== "string" || typeof o.until !== "number")
|
|
101
106
|
continue;
|
|
107
|
+
if (typeof o.sentAt !== "number" || typeof o.resolveAt !== "number")
|
|
108
|
+
continue;
|
|
109
|
+
if (o.status !== "sent" && o.status !== "unknown")
|
|
110
|
+
continue;
|
|
111
|
+
const src = o.source;
|
|
112
|
+
if (typeof src !== "object" || src === null || typeof src.accountId !== "string")
|
|
113
|
+
continue;
|
|
114
|
+
if (src.via !== "web" && src.via !== "im")
|
|
115
|
+
continue;
|
|
116
|
+
const rep = o.reported;
|
|
117
|
+
const outcome = typeof rep === "object" && rep !== null ? rep.outcome : undefined;
|
|
118
|
+
const reported = outcome === "applied" ||
|
|
119
|
+
outcome === "refused" ||
|
|
120
|
+
outcome === "absent" ||
|
|
121
|
+
outcome === "interrupted"
|
|
122
|
+
? { outcome }
|
|
123
|
+
: undefined;
|
|
102
124
|
out.push({
|
|
103
125
|
id: o.id,
|
|
104
126
|
add: hosts(o.add),
|
|
105
127
|
remove: hosts(o.remove),
|
|
106
128
|
...("tier" in o && { tier: isSandboxTier(o.tier) ? o.tier : null }),
|
|
107
129
|
...(o.isolation === "sandbox" && { isolation: "sandbox" }),
|
|
130
|
+
source: {
|
|
131
|
+
accountId: src.accountId,
|
|
132
|
+
via: src.via,
|
|
133
|
+
approvalId: typeof src.approvalId === "string" ? src.approvalId : null,
|
|
134
|
+
},
|
|
135
|
+
...(typeof o.actorTokenId === "string" && { actorTokenId: o.actorTokenId }),
|
|
136
|
+
status: o.status,
|
|
137
|
+
sentAt: o.sentAt,
|
|
138
|
+
resolveAt: o.resolveAt,
|
|
108
139
|
until: o.until,
|
|
140
|
+
...(reported !== undefined && { reported }),
|
|
109
141
|
});
|
|
110
142
|
}
|
|
111
143
|
return out;
|
|
112
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether the change of `op` is in `policy`: its hosts added and removed, its tier and isolation
|
|
147
|
+
* set. Tells an op the machine logged only as `started` (it stopped between the log and the
|
|
148
|
+
* answer) that was written from one that was not.
|
|
149
|
+
*/
|
|
150
|
+
export function opVisibleIn(op, policy) {
|
|
151
|
+
if (!op.add.every((h) => policy.allowedDomains.includes(h)))
|
|
152
|
+
return false;
|
|
153
|
+
if (op.remove.some((h) => policy.allowedDomains.includes(h)))
|
|
154
|
+
return false;
|
|
155
|
+
if (op.tier !== undefined && (policy.tier ?? null) !== op.tier)
|
|
156
|
+
return false;
|
|
157
|
+
return op.isolation === undefined || policy.isolation === op.isolation;
|
|
158
|
+
}
|
|
113
159
|
/**
|
|
114
160
|
* Whether `diff` is what the platform's `op` did (its answer, or the machine's own report of it
|
|
115
161
|
* before the answer was recorded): hosts within the op's, the tier the op set, and only what a
|
|
@@ -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
|
|
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
|
|
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,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { Redis } from "ioredis";
|
|
2
1
|
import { EventLog } from "../conversations/event-log.js";
|
|
2
|
+
import { LifecycleHooks } from "../extensions/lifecycle-hooks.js";
|
|
3
3
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
4
4
|
import { SessionService } from "../identity/sessions.js";
|
|
5
5
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
6
6
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
7
|
-
import {
|
|
7
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
8
8
|
import type { KernelLogger } from "../log.js";
|
|
9
|
-
import { SandboxJobs } from "../sandbox/jobs.js";
|
|
10
9
|
import { ApprovalsService } from "../tools/approvals.service.js";
|
|
11
10
|
import { Credentials } from "../tools/credentials.js";
|
|
12
11
|
import { QuestionsService } from "../tools/questions.service.js";
|
|
@@ -26,11 +25,11 @@ export interface OffboardResult {
|
|
|
26
25
|
revokedSessionIds: string[];
|
|
27
26
|
/** 写了 cancelled 事件的会话 → 最大 seq(提交后发布)。 */
|
|
28
27
|
notify: Map<string, number>;
|
|
29
|
-
/**
|
|
28
|
+
/** The person's active runs in the org (queued / running / waiting): a `run-stop` job each is owed. */
|
|
30
29
|
runs: InFlightRun[];
|
|
31
30
|
/** 负责的 agent 转交给的所有者;没有转交时为 null。 */
|
|
32
31
|
newOwnerId: string | null;
|
|
33
|
-
/** The person's devices in the org, soft-deleted here;
|
|
32
|
+
/** The person's devices in the org, soft-deleted here; a destroy job each is owed. */
|
|
34
33
|
devices: string[];
|
|
35
34
|
/** Conversations whose queued messages were deleted (`changed: queue` after commit). */
|
|
36
35
|
queueConversations: string[];
|
|
@@ -39,21 +38,28 @@ export interface OffboardResult {
|
|
|
39
38
|
export interface OffboardingRunControl {
|
|
40
39
|
abortWaiting(conversationId: string, runId: string, byAccountId: string | null): Promise<boolean>;
|
|
41
40
|
abortIfOrphaned(conversationId: string, runId: string, byAccountId: string | null): Promise<boolean>;
|
|
41
|
+
abortQueued(conversationId: string, runId: string, byAccountId: string | null): Promise<boolean>;
|
|
42
42
|
requestAbort(runId: string, byAccountId: string | null): Promise<void>;
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The stop of a departed member's run (transactional outbox spec §8.1), owed by the offboard
|
|
46
|
+
* transaction on the maintenance queue under {@link runStopJobId}; `MaintenanceProcessor` runs
|
|
47
|
+
* {@link Offboarding.stopRun}.
|
|
48
|
+
*/
|
|
49
|
+
export declare const RUN_STOP_JOB = "run-stop";
|
|
50
|
+
/** The `run-stop` job's data. */
|
|
51
|
+
export interface RunStopJobData {
|
|
52
|
+
conversationId: string;
|
|
53
|
+
runId: string;
|
|
54
|
+
}
|
|
55
|
+
/** The `run-stop` job id: a run is stopped once (no `:`). */
|
|
56
|
+
export declare const runStopJobId: (runId: string) => string;
|
|
53
57
|
/**
|
|
54
58
|
* 成员离职(spec 3.3):管理员移出、本人离开与通讯录同步共用这一条路径,全部写入在调用方的同一个事务内,
|
|
55
|
-
* 要么都生效要么都不生效。
|
|
56
|
-
*
|
|
59
|
+
* 要么都生效要么都不生效。The stops of the person's runs and the destruction of their devices are owed
|
|
60
|
+
* in that transaction (outbox jobs, transactional outbox spec §8.1); the Redis revocation markers
|
|
61
|
+
* and the event publishes after the commit are hints ({@link afterOffboard}: `revoked_at` in PG is
|
|
62
|
+
* the authority).
|
|
57
63
|
*
|
|
58
64
|
* 已接受的窄窗口:离职提交时正在进行的 OAuth token 刷新(`Credentials` 刷新锁内)可能在提交之后把此人的
|
|
59
65
|
* 个人凭据重新写回(upsert)。凭据只在此人以成员身份调用工具时使用,而其运行已被中止、成员关系已删除,
|
|
@@ -63,22 +69,25 @@ export declare class Offboarding {
|
|
|
63
69
|
private readonly db;
|
|
64
70
|
readonly txHost: TransactionHost;
|
|
65
71
|
private readonly sessions;
|
|
66
|
-
private readonly redis;
|
|
67
72
|
private readonly registry;
|
|
68
73
|
private readonly eventLog;
|
|
69
|
-
/**
|
|
70
|
-
private readonly
|
|
71
|
-
/** Stops the departed member's runs (`by: null`, a system action). */
|
|
74
|
+
/** Owes the run stops and device destroys in the offboard transaction. */
|
|
75
|
+
private readonly outbox;
|
|
76
|
+
/** Stops the departed member's runs (`by: null`, a system action) in the `run-stop` job. */
|
|
72
77
|
private readonly runs;
|
|
73
78
|
private readonly credentials;
|
|
74
79
|
private readonly approvals;
|
|
75
80
|
private readonly questions;
|
|
81
|
+
/** Owes `org.member_removed` to the subscribing plugins, in the offboard transaction. */
|
|
82
|
+
private readonly lifecycle;
|
|
76
83
|
readonly log: KernelLogger;
|
|
77
|
-
constructor(db: KernelDb, txHost: TransactionHost, sessions: Pick<SessionService, "markRevoked">,
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
/** Stops the departed member's runs (`by: null`, a system action). */
|
|
81
|
-
runs: OffboardingRunControl, credentials: Pick<Credentials, "clearAccount">, approvals: Pick<ApprovalsService, "cancelPendingFor">, questions: Pick<QuestionsService, "cancelPendingFor">,
|
|
84
|
+
constructor(db: KernelDb, txHost: TransactionHost, sessions: Pick<SessionService, "markRevoked">, registry: Pick<ExtensionRegistry, "list">, eventLog: Pick<EventLog, "notify" | "publishChanged">,
|
|
85
|
+
/** Owes the run stops and device destroys in the offboard transaction. */
|
|
86
|
+
outbox: Pick<OutboxService, "add">,
|
|
87
|
+
/** Stops the departed member's runs (`by: null`, a system action) in the `run-stop` job. */
|
|
88
|
+
runs: OffboardingRunControl, credentials: Pick<Credentials, "clearAccount">, approvals: Pick<ApprovalsService, "pendingConversationsFor" | "cancelPendingFor">, questions: Pick<QuestionsService, "pendingConversationsFor" | "cancelPendingFor">,
|
|
89
|
+
/** Owes `org.member_removed` to the subscribing plugins, in the offboard transaction. */
|
|
90
|
+
lifecycle: Pick<LifecycleHooks, "recordIn">, log: KernelLogger);
|
|
82
91
|
/**
|
|
83
92
|
* In the caller's transaction (`mandatory`: the caller holds the org row lock, order org row →
|
|
84
93
|
* membership, and did the "last owner" checks):
|
|
@@ -87,7 +96,8 @@ export declare class Offboarding {
|
|
|
87
96
|
* account (both are account-wide, so conservatively all); a person who leaves (`left`) keeps
|
|
88
97
|
* them, and the session's current org is cleared (the membership check ends the access);
|
|
89
98
|
* 3. 删除其在该组织所有连接器上的个人凭据;
|
|
90
|
-
* 4. 取消其作为处理人的 pending 审批与提问(写 cancelled
|
|
99
|
+
* 4. 取消其作为处理人的 pending 审批与提问(写 cancelled 事件); owes a `run-stop` job for each of the
|
|
100
|
+
* person's active runs in the org;
|
|
91
101
|
* 5. 其负责的该组织 agent 转给组织所有者(最早加入的 owner)并置 `needs_reassignment = true`;
|
|
92
102
|
* 6. 归档其在该组织各渠道连接上的 IM 话题行(重新加入后在 IM 里开始新的对话,不接着旧会话),
|
|
93
103
|
* 删除其在该组织的「以后都允许」工具授权(`tool_grants`);
|
|
@@ -95,9 +105,10 @@ export declare class Offboarding {
|
|
|
95
105
|
* 以及成员关系自身的 `directory:<插件>` 来源,不论此人当初是不是由通讯录加入(手动重新加入后再被移出也不会被同步加回);
|
|
96
106
|
* 同步不再把此人加回,直到有人手动重新加入({@link MembersService.add} 删除排除);
|
|
97
107
|
* 8. 审计 `org.member.offboarded`;
|
|
98
|
-
* 9. Soft-deletes the person's devices in the org and clears their default row
|
|
99
|
-
*
|
|
100
|
-
*
|
|
108
|
+
* 9. Soft-deletes the person's devices in the org and clears their default row, and owes the
|
|
109
|
+
* destruction of each (instance and files, `device-destroy-<deviceId>`);
|
|
110
|
+
* 10. Owes the `org.member_removed` lifecycle hook deliveries (outbox jobs, one per subscribing
|
|
111
|
+
* plugin).
|
|
101
112
|
*/
|
|
102
113
|
offboard(orgId: string, accountId: string, reason: OffboardReason, actor?: {
|
|
103
114
|
actorAccountId?: string;
|
|
@@ -107,36 +118,18 @@ export declare class Offboarding {
|
|
|
107
118
|
private exclusionPlugins;
|
|
108
119
|
/**
|
|
109
120
|
* After the commit (callers register it with the infra `afterCommit` inside their transaction):
|
|
110
|
-
* writes the session revocation markers
|
|
111
|
-
*
|
|
112
|
-
* warning.
|
|
121
|
+
* writes the session revocation markers and publishes the cancelled events and queue changes,
|
|
122
|
+
* all hints (the run stops and device destroys were owed in the transaction). A failing step
|
|
123
|
+
* only logs a warning.
|
|
113
124
|
*/
|
|
114
125
|
afterOffboard(r: OffboardResult): Promise<void>;
|
|
115
126
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
127
|
+
* The `run-stop` job (`MaintenanceProcessor`): stops a departed member's run the way the
|
|
128
|
+
* conversation's stop does (`POST .../abort`), by no account: a waiting run is aborted directly,
|
|
129
|
+
* a queued run is ended in PG (ruling R27: it never starts later, however long it was held), an
|
|
130
|
+
* orphan is finished directly, a running run is asked to stop through Redis (its replica ends it). A finished run is left alone. No
|
|
131
|
+
* new run follows, as after a person's stop: the departed member's queued messages were deleted.
|
|
132
|
+
* A failure throws (the job is retried).
|
|
119
133
|
*/
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 兜底扫描(维护队列每 10 分钟):进行中、且发起人已不是该会话所属组织成员的运行 → 中止
|
|
123
|
-
* (提交后的中止没做成时,如进程崩溃)。按 `(runs.created_at, runs.id)` 键集分页翻完(每页 500 条,
|
|
124
|
-
* 至多 20 页):经广播中止、尚未真正结束的运行仍是进行中,若只取前 500 条,超过 500 条卡住的运行会
|
|
125
|
-
* 一直占满这一页,后面的永远轮不到。已广播中止、仍带 `capk:offboard-abort:<runId>` 标记(30 分钟)的运行跳过,
|
|
126
|
-
* 不每轮重复广播;标记过期仍未结束的再处理一次。返回本次处理(未跳过)的运行数。
|
|
127
|
-
* 现任成员(含匿名访客这样的 guest 成员)发起的运行不受影响。
|
|
128
|
-
*/
|
|
129
|
-
sweep(batch?: number): Promise<number>;
|
|
130
|
-
private recentlyAborted;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* The offboarding safety-net sweep ({@link Offboarding.sweep}) every 10 minutes, once per tick
|
|
134
|
-
* across replicas; a worker-role job (`WorkerModule`).
|
|
135
|
-
*/
|
|
136
|
-
export declare class OffboardSweepJob {
|
|
137
|
-
private readonly offboarding;
|
|
138
|
-
readonly lockService: LockService;
|
|
139
|
-
readonly log: KernelLogger;
|
|
140
|
-
constructor(offboarding: Pick<Offboarding, "sweep">, lockService: LockService, log: KernelLogger);
|
|
141
|
-
run(): Promise<void>;
|
|
134
|
+
stopRun(data: RunStopJobData): Promise<void>;
|
|
142
135
|
}
|