@opengeni/core 1.0.1 → 2.2.0-canary.0
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/access/index.d.ts +37 -0
- package/dist/application/new-session-drafts.d.ts +8 -1
- package/dist/application/session-commands.d.ts +18 -11
- package/dist/application/session-tenancy.d.ts +18 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
- package/dist/chunk-H7X52RI2.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
- package/dist/chunk-VCZFFEPB.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
- package/dist/chunk-ZYCNLYGH.js.map +1 -0
- package/dist/dependencies.d.ts +12 -1
- package/dist/domain/automations.d.ts +51 -0
- package/dist/domain/company-brain-governed-writes.d.ts +70 -0
- package/dist/domain/company-profile-agent-admin.d.ts +24 -0
- package/dist/domain/connection-authority.d.ts +97 -0
- package/dist/domain/connection-authority.js +169 -0
- package/dist/domain/connection-authority.js.map +1 -0
- package/dist/domain/editable-artifacts/service.d.ts +1 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/governed-learning-activation.d.ts +16 -0
- package/dist/domain/governed-learning-evaluator.d.ts +6 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/memory-slack-delivery.d.ts +1 -4
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/packs.d.ts +5 -1
- package/dist/domain/personal-connection-delegations.d.ts +34 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +22 -1
- package/dist/domain/scheduled-tasks.d.ts +43 -2
- package/dist/domain/session-tool-policy.d.ts +1 -1
- package/dist/domain/sessions.d.ts +40 -6
- package/dist/editable-artifact-live/application.d.ts +10 -2
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +0 -1
- package/dist/editable-artifact-live/ticket.d.ts +0 -1
- package/dist/editable-artifact-live/types.d.ts +5 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +2 -4
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4827 -1164
- package/dist/index.js.map +1 -1
- package/dist/rigs/index.d.ts +22 -5
- package/dist/sandbox/fleet.d.ts +34 -6
- package/dist/sandbox/routing.d.ts +15 -1
- package/dist/sandbox/runtime-settings.d.ts +39 -0
- package/package.json +16 -11
- package/src/access/index.ts +157 -31
- package/src/application/new-session-drafts.ts +24 -2
- package/src/application/session-commands.ts +305 -71
- package/src/application/session-tenancy.ts +321 -0
- package/src/application/user-resource-grants.ts +140 -0
- package/src/billing/limits.ts +25 -15
- package/src/dependencies.ts +12 -1
- package/src/domain/automations.ts +196 -0
- package/src/domain/capabilities.ts +7 -1
- package/src/domain/company-brain-governed-writes.ts +330 -0
- package/src/domain/company-profile-agent-admin.ts +55 -0
- package/src/domain/connection-authority.ts +329 -0
- package/src/domain/editable-artifacts/agent-application.ts +17 -9
- package/src/domain/editable-artifacts/service.ts +12 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +16 -3
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/governed-learning-activation.ts +44 -0
- package/src/domain/governed-learning-evaluator.ts +49 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights.ts +9 -0
- package/src/domain/memory-slack-delivery.ts +0 -30
- package/src/domain/organization-membership-lifecycle.ts +9 -0
- package/src/domain/packs.ts +106 -3
- package/src/domain/personal-connection-delegations.ts +532 -51
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +131 -4
- package/src/domain/scheduled-tasks.ts +497 -76
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +613 -166
- package/src/editable-artifact-live/application.ts +21 -3
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +13 -7
- package/src/editable-artifact-live/ticket.ts +1 -5
- package/src/editable-artifact-live/types.ts +6 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +13 -1
- package/src/rigs/index.ts +165 -48
- package/src/sandbox/fleet.ts +376 -30
- package/src/sandbox/routing.ts +91 -29
- package/src/sandbox/runtime-settings.ts +274 -0
- package/src/session-authorization.ts +17 -49
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/src/domain/durable-learning-slack-publication.ts +0 -202
package/src/sandbox/routing.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { sandboxLifecycleTransitionWaitMs, type Settings } from "@opengeni/confi
|
|
|
15
15
|
import {
|
|
16
16
|
advanceWorkspaceGenerationForDirectRequest,
|
|
17
17
|
advanceWorkspaceGenerationForRetainedProcess,
|
|
18
|
-
|
|
18
|
+
getLiveEnrollmentConnection,
|
|
19
19
|
getRetainedProcess,
|
|
20
20
|
getSandbox,
|
|
21
21
|
markWarmLeaseInstanceLost,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type Database,
|
|
29
29
|
type SandboxWorkspaceMutationAdmission,
|
|
30
30
|
} from "@opengeni/db";
|
|
31
|
+
import { settleSessionBackgroundCommandForRetainedProcess } from "@opengeni/db/session-background-commands";
|
|
31
32
|
import { appendAndPublishEvents, type EventBus } from "@opengeni/events";
|
|
32
33
|
import {
|
|
33
34
|
isProviderSandboxGoneDuringRoutedOperation,
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
type RoutingRetainedProcessTerminalProof,
|
|
47
48
|
type RoutingSandboxOperationObserver,
|
|
48
49
|
type SelfhostedRelayConfig,
|
|
50
|
+
type SelfhostedConnectionBinding,
|
|
49
51
|
type SelfhostedOpStreamDeps,
|
|
50
52
|
} from "@opengeni/runtime/sandbox";
|
|
51
53
|
|
|
@@ -56,6 +58,34 @@ type PersistableMutationAdmission = {
|
|
|
56
58
|
> | null;
|
|
57
59
|
};
|
|
58
60
|
|
|
61
|
+
type DirectRetainedProcessRoute = {
|
|
62
|
+
providerSessionId: number;
|
|
63
|
+
providerBackend: string;
|
|
64
|
+
providerInstanceId: string;
|
|
65
|
+
leaseEpoch: number;
|
|
66
|
+
routeKind: "home" | "active";
|
|
67
|
+
routeTargetId: string | null;
|
|
68
|
+
routeEpoch: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** API-direct requests always use active-route authority. The default active
|
|
72
|
+
* pointer is represented by a null target id; it is not a home-route write. */
|
|
73
|
+
export function directRetainedProcessMatchesBackend(
|
|
74
|
+
durable: DirectRetainedProcessRoute,
|
|
75
|
+
process: RoutingRetainedProcess,
|
|
76
|
+
backend: ResolvedActiveBackend,
|
|
77
|
+
): boolean {
|
|
78
|
+
return (
|
|
79
|
+
durable.providerSessionId === process.providerSessionId &&
|
|
80
|
+
durable.providerBackend === backend.kind &&
|
|
81
|
+
durable.providerInstanceId === backend.providerInstanceId &&
|
|
82
|
+
durable.leaseEpoch === backend.leaseEpoch &&
|
|
83
|
+
durable.routeKind === "active" &&
|
|
84
|
+
durable.routeTargetId === backend.sandboxId &&
|
|
85
|
+
durable.routeEpoch === backend.activeEpoch
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
export type ChannelARoutingServices = {
|
|
60
90
|
db: Database;
|
|
61
91
|
settings: Settings;
|
|
@@ -118,24 +148,31 @@ function controlRpcFactory(bus: EventBus | undefined): () => ControlRpc {
|
|
|
118
148
|
});
|
|
119
149
|
}
|
|
120
150
|
|
|
121
|
-
async function
|
|
151
|
+
async function resolveSelfhostedConnection(
|
|
122
152
|
services: ChannelARoutingServices,
|
|
123
|
-
workspaceId: string,
|
|
153
|
+
access: string | { accountId: string; workspaceId: string; subjectId: string },
|
|
124
154
|
sandbox: RoutableSandbox,
|
|
125
|
-
): Promise<
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
155
|
+
): Promise<SelfhostedConnectionBinding | null> {
|
|
156
|
+
if (!sandbox.enrollmentId) return null;
|
|
157
|
+
const enrollment = await getLiveEnrollmentConnection(services.db, access, sandbox.enrollmentId);
|
|
158
|
+
if (!enrollment?.connectionInstanceId) return null;
|
|
159
|
+
const opStream: SelfhostedOpStreamDeps | undefined =
|
|
160
|
+
services.settings.agentOpStreamEnabled === true &&
|
|
161
|
+
services.bus?.getOpStreamConnection &&
|
|
162
|
+
enrollment.opStream === true
|
|
163
|
+
? {
|
|
164
|
+
transport: new NatsOpStreamTransport(
|
|
165
|
+
async () => services.bus?.getOpStreamConnection?.() ?? null,
|
|
166
|
+
),
|
|
167
|
+
}
|
|
168
|
+
: undefined;
|
|
135
169
|
return {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
),
|
|
170
|
+
connectionInstanceId: enrollment.connectionInstanceId,
|
|
171
|
+
workspaceRoot: enrollment.workspaceRoot,
|
|
172
|
+
...(opStream ? { opStream } : {}),
|
|
173
|
+
operationResourcePolicy: enrollment.operationPolicy,
|
|
174
|
+
operationResourcePolicySupported: enrollment.agentCapabilities.operationResourcePolicy === true,
|
|
175
|
+
operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
|
|
139
176
|
};
|
|
140
177
|
}
|
|
141
178
|
|
|
@@ -157,6 +194,7 @@ export function wrapChannelABoxWithRouting(
|
|
|
157
194
|
accountId: string;
|
|
158
195
|
workspaceId: string;
|
|
159
196
|
sessionId: string;
|
|
197
|
+
resourceSubjectId: string;
|
|
160
198
|
homeLease?: {
|
|
161
199
|
sandboxGroupId: string;
|
|
162
200
|
leaseEpoch: number;
|
|
@@ -209,6 +247,7 @@ export function wrapChannelABoxWithRouting(
|
|
|
209
247
|
sessionId: ids.sessionId,
|
|
210
248
|
requestId: ids.directRequest.requestId,
|
|
211
249
|
holderId: ids.directRequest.holderId,
|
|
250
|
+
initiatorSubjectId: ids.resourceSubjectId,
|
|
212
251
|
sandboxGroupId: homeLease.sandboxGroupId,
|
|
213
252
|
expectedEpoch: backend.leaseEpoch,
|
|
214
253
|
expectedInstanceId: backend.providerInstanceId,
|
|
@@ -267,10 +306,15 @@ export function wrapChannelABoxWithRouting(
|
|
|
267
306
|
admittedWorkspaceGeneration: exactAdmission.workspaceGeneration,
|
|
268
307
|
operation: op,
|
|
269
308
|
providerBinding: boundAdmission.providerBinding ?? null,
|
|
309
|
+
backgroundCommand: {
|
|
310
|
+
commandId: retainedProcess.id,
|
|
311
|
+
command: op,
|
|
312
|
+
},
|
|
270
313
|
owner: {
|
|
271
314
|
kind: "direct",
|
|
272
315
|
requestId: ids.directRequest.requestId,
|
|
273
316
|
holderId: ids.directRequest.holderId,
|
|
317
|
+
initiatorSubjectId: ids.resourceSubjectId,
|
|
274
318
|
sandboxGroupId: homeLease.sandboxGroupId,
|
|
275
319
|
expectedEpoch: backend.leaseEpoch,
|
|
276
320
|
expectedInstanceId: backend.providerInstanceId,
|
|
@@ -286,6 +330,7 @@ export function wrapChannelABoxWithRouting(
|
|
|
286
330
|
sessionId: ids.sessionId,
|
|
287
331
|
requestId: ids.directRequest.requestId,
|
|
288
332
|
holderId: ids.directRequest.holderId,
|
|
333
|
+
initiatorSubjectId: ids.resourceSubjectId,
|
|
289
334
|
sandboxGroupId: homeLease.sandboxGroupId,
|
|
290
335
|
expectedEpoch: backend.leaseEpoch,
|
|
291
336
|
expectedInstanceId: backend.providerInstanceId,
|
|
@@ -373,16 +418,7 @@ export function wrapChannelABoxWithRouting(
|
|
|
373
418
|
sessionId: ids.sessionId,
|
|
374
419
|
processId: process.id,
|
|
375
420
|
});
|
|
376
|
-
if (
|
|
377
|
-
!durable ||
|
|
378
|
-
durable.providerSessionId !== process.providerSessionId ||
|
|
379
|
-
durable.providerBackend !== backend.kind ||
|
|
380
|
-
durable.providerInstanceId !== backend.providerInstanceId ||
|
|
381
|
-
durable.leaseEpoch !== backend.leaseEpoch ||
|
|
382
|
-
durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
|
|
383
|
-
durable.routeTargetId !== backend.sandboxId ||
|
|
384
|
-
durable.routeEpoch !== backend.activeEpoch
|
|
385
|
-
) {
|
|
421
|
+
if (!durable || !directRetainedProcessMatchesBackend(durable, process, backend)) {
|
|
386
422
|
throw new Error(
|
|
387
423
|
"API retained-process settlement lost its exact durable backend identity",
|
|
388
424
|
);
|
|
@@ -398,6 +434,15 @@ export function wrapChannelABoxWithRouting(
|
|
|
398
434
|
reason: proof.reason,
|
|
399
435
|
idleGraceMs: settings.sandboxIdleGraceMs,
|
|
400
436
|
});
|
|
437
|
+
await settleSessionBackgroundCommandForRetainedProcess(db, {
|
|
438
|
+
accountId: ids.accountId,
|
|
439
|
+
workspaceId: ids.workspaceId,
|
|
440
|
+
sessionId: ids.sessionId,
|
|
441
|
+
retainedProcessId: process.id,
|
|
442
|
+
outcome: proof.outcome,
|
|
443
|
+
exitCode: proof.exitCode,
|
|
444
|
+
reason: proof.reason,
|
|
445
|
+
});
|
|
401
446
|
}
|
|
402
447
|
: undefined;
|
|
403
448
|
const resolver = makeActiveBackendResolver({
|
|
@@ -405,10 +450,19 @@ export function wrapChannelABoxWithRouting(
|
|
|
405
450
|
defaultBackend: established.session as RoutableBackendSession,
|
|
406
451
|
defaultKind: established.backendId,
|
|
407
452
|
getSandbox: async (sandboxId): Promise<RoutableSandbox | null> => {
|
|
408
|
-
const sandbox = await getSandbox(
|
|
453
|
+
const sandbox = await getSandbox(
|
|
454
|
+
db,
|
|
455
|
+
{
|
|
456
|
+
accountId: ids.accountId,
|
|
457
|
+
workspaceId: ids.workspaceId,
|
|
458
|
+
subjectId: ids.resourceSubjectId,
|
|
459
|
+
},
|
|
460
|
+
sandboxId,
|
|
461
|
+
);
|
|
409
462
|
return sandbox
|
|
410
463
|
? {
|
|
411
464
|
id: sandbox.id,
|
|
465
|
+
workspaceId: sandbox.workspaceId,
|
|
412
466
|
kind: sandbox.kind,
|
|
413
467
|
name: sandbox.name,
|
|
414
468
|
enrollmentId: sandbox.enrollmentId,
|
|
@@ -416,8 +470,16 @@ export function wrapChannelABoxWithRouting(
|
|
|
416
470
|
: null;
|
|
417
471
|
},
|
|
418
472
|
controlRpcFactory: controlRpcFactory(bus),
|
|
419
|
-
|
|
420
|
-
|
|
473
|
+
resolveSelfhostedConnection: (sandbox) =>
|
|
474
|
+
resolveSelfhostedConnection(
|
|
475
|
+
services,
|
|
476
|
+
{
|
|
477
|
+
accountId: ids.accountId,
|
|
478
|
+
workspaceId: ids.workspaceId,
|
|
479
|
+
subjectId: ids.resourceSubjectId,
|
|
480
|
+
},
|
|
481
|
+
sandbox,
|
|
482
|
+
),
|
|
421
483
|
relay: relayConfigFromSettings(settings),
|
|
422
484
|
selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
|
|
423
485
|
selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import {
|
|
3
|
+
CapabilityPack,
|
|
4
|
+
type RigVersion,
|
|
5
|
+
type Session,
|
|
6
|
+
type SandboxBackend,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
getRigVersion,
|
|
10
|
+
getWorkspacePack,
|
|
11
|
+
listPackInstallations,
|
|
12
|
+
type Database,
|
|
13
|
+
} from "@opengeni/db";
|
|
14
|
+
import { resolveModalCheckpointProviderBinding } from "@opengeni/runtime/sandbox";
|
|
15
|
+
import {
|
|
16
|
+
rigProviderImageContentHash,
|
|
17
|
+
rigProviderImageMatchesDefinition,
|
|
18
|
+
rigProviderImageProviderBindingKeyHash,
|
|
19
|
+
} from "../rigs/provider-images";
|
|
20
|
+
|
|
21
|
+
/** Pre-V2 Packs are the sole compatibility path where a Pack can still own a
|
|
22
|
+
* sandbox image. V2 installations select a Rig instead. Keep this predicate at
|
|
23
|
+
* the shared runtime boundary so turns, API-direct tools, viewers, Browser and
|
|
24
|
+
* Computer cannot resolve different physical machines for the same session. */
|
|
25
|
+
export function packInstallationUsesLegacyRuntime(input: {
|
|
26
|
+
manifestSnapshot: unknown | null;
|
|
27
|
+
manifestDigest: string | null;
|
|
28
|
+
}): boolean {
|
|
29
|
+
return input.manifestSnapshot === null && input.manifestDigest === null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function resolveWorkspaceLegacyRuntimePacks(
|
|
33
|
+
db: Database,
|
|
34
|
+
workspaceId: string,
|
|
35
|
+
): Promise<CapabilityPack[]> {
|
|
36
|
+
const installations = await listPackInstallations(db, workspaceId);
|
|
37
|
+
const packs: CapabilityPack[] = [];
|
|
38
|
+
for (const installation of installations) {
|
|
39
|
+
if (installation.status !== "active" || !packInstallationUsesLegacyRuntime(installation)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const registration = await getWorkspacePack(db, workspaceId, installation.packId);
|
|
43
|
+
const parsed = CapabilityPack.safeParse(registration?.pack);
|
|
44
|
+
if (parsed.success) packs.push(parsed.data);
|
|
45
|
+
}
|
|
46
|
+
return packs;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function legacySandboxRuntimeFromPacks(packs: readonly CapabilityPack[]): {
|
|
50
|
+
sandboxImage: string | null;
|
|
51
|
+
sandboxProviderImages: CapabilityPack["sandboxProviderImages"] | null;
|
|
52
|
+
} {
|
|
53
|
+
const imagePacks = packs.filter(
|
|
54
|
+
(pack) => typeof pack.sandboxImage === "string" && pack.sandboxImage.trim().length > 0,
|
|
55
|
+
);
|
|
56
|
+
if (imagePacks.length > 1) {
|
|
57
|
+
const ids = imagePacks
|
|
58
|
+
.map((pack) => pack.id)
|
|
59
|
+
.sort()
|
|
60
|
+
.join(", ");
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Multiple enabled packs declare a sandbox image (${ids}). Only one enabled pack per workspace may declare sandboxImage; disable the others and retry.`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
sandboxImage: imagePacks[0]?.sandboxImage?.trim() ?? null,
|
|
67
|
+
sandboxProviderImages: imagePacks[0]?.sandboxProviderImages ?? null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function settingsWithPackSandboxImage(
|
|
72
|
+
settings: Settings,
|
|
73
|
+
sandboxImage: string | null,
|
|
74
|
+
sandboxProviderImages: CapabilityPack["sandboxProviderImages"] | null = null,
|
|
75
|
+
): Settings {
|
|
76
|
+
if (!sandboxImage) return settings;
|
|
77
|
+
return {
|
|
78
|
+
...settings,
|
|
79
|
+
dockerImage: sandboxImage,
|
|
80
|
+
modalImageRef: sandboxImage,
|
|
81
|
+
modalImageId: sandboxProviderImages?.modal?.imageId,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function settingsWithRigImage(settings: Settings, rigImage: string | null): Settings {
|
|
86
|
+
if (!rigImage) return settings;
|
|
87
|
+
return {
|
|
88
|
+
...settings,
|
|
89
|
+
dockerImage: rigImage,
|
|
90
|
+
modalImageRef: rigImage,
|
|
91
|
+
modalImageId: undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function rigProviderImageSourceImage(
|
|
96
|
+
settings: Settings,
|
|
97
|
+
backend: SandboxBackend,
|
|
98
|
+
): string | null {
|
|
99
|
+
if (backend === "modal") return settings.modalImageId ?? settings.modalImageRef ?? null;
|
|
100
|
+
if (backend === "docker") return settings.dockerImage ?? null;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type RigProviderImageSelectionReason =
|
|
105
|
+
| "selected"
|
|
106
|
+
| "missing"
|
|
107
|
+
| "provider_unsupported"
|
|
108
|
+
| "not_ready"
|
|
109
|
+
| "not_cold_boot_validated"
|
|
110
|
+
| "content_mismatch"
|
|
111
|
+
| "provider_binding_unavailable"
|
|
112
|
+
| "provider_binding_mismatch";
|
|
113
|
+
|
|
114
|
+
export function resolveRigProviderImageSelection(
|
|
115
|
+
settings: Settings,
|
|
116
|
+
version: RigVersion | null,
|
|
117
|
+
backend: SandboxBackend,
|
|
118
|
+
currentProviderBindingKeyHash: string | null,
|
|
119
|
+
): {
|
|
120
|
+
settings: Settings;
|
|
121
|
+
reason: RigProviderImageSelectionReason;
|
|
122
|
+
contentHash: string | null;
|
|
123
|
+
imageId: string | null;
|
|
124
|
+
} {
|
|
125
|
+
if (!version) return { settings, reason: "missing", contentHash: null, imageId: null };
|
|
126
|
+
if (backend !== "modal") {
|
|
127
|
+
return { settings, reason: "provider_unsupported", contentHash: null, imageId: null };
|
|
128
|
+
}
|
|
129
|
+
const image = version.providerImages[backend];
|
|
130
|
+
if (!image) return { settings, reason: "missing", contentHash: null, imageId: null };
|
|
131
|
+
if (image.status !== "ready" || !image.imageId) {
|
|
132
|
+
return {
|
|
133
|
+
settings,
|
|
134
|
+
reason: "not_ready",
|
|
135
|
+
contentHash: image.contentHash,
|
|
136
|
+
imageId: image.imageId,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (image.coldBootValidation?.version !== 1) {
|
|
140
|
+
return {
|
|
141
|
+
settings,
|
|
142
|
+
reason: "not_cold_boot_validated",
|
|
143
|
+
contentHash: image.contentHash,
|
|
144
|
+
imageId: null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const sourceImage = rigProviderImageSourceImage(settings, backend);
|
|
148
|
+
const expectedContentHash = rigProviderImageContentHash({
|
|
149
|
+
backend,
|
|
150
|
+
sourceImage,
|
|
151
|
+
definition: version,
|
|
152
|
+
});
|
|
153
|
+
if (
|
|
154
|
+
image.sourceImage !== sourceImage ||
|
|
155
|
+
image.contentHash !== expectedContentHash ||
|
|
156
|
+
!rigProviderImageMatchesDefinition(image, version)
|
|
157
|
+
) {
|
|
158
|
+
return {
|
|
159
|
+
settings,
|
|
160
|
+
reason: "content_mismatch",
|
|
161
|
+
contentHash: expectedContentHash,
|
|
162
|
+
imageId: null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (!image.providerBindingKeyHash || !currentProviderBindingKeyHash) {
|
|
166
|
+
return {
|
|
167
|
+
settings,
|
|
168
|
+
reason: "provider_binding_unavailable",
|
|
169
|
+
contentHash: expectedContentHash,
|
|
170
|
+
imageId: null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (image.providerBindingKeyHash !== currentProviderBindingKeyHash) {
|
|
174
|
+
return {
|
|
175
|
+
settings,
|
|
176
|
+
reason: "provider_binding_mismatch",
|
|
177
|
+
contentHash: expectedContentHash,
|
|
178
|
+
imageId: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
settings: { ...settings, modalImageId: image.imageId },
|
|
183
|
+
reason: "selected",
|
|
184
|
+
contentHash: expectedContentHash,
|
|
185
|
+
imageId: image.imageId,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function resolveRigProviderImageForRun(
|
|
190
|
+
settings: Settings,
|
|
191
|
+
version: RigVersion | null,
|
|
192
|
+
backend: SandboxBackend,
|
|
193
|
+
resolveBinding: typeof resolveModalCheckpointProviderBinding = resolveModalCheckpointProviderBinding,
|
|
194
|
+
): Promise<ReturnType<typeof resolveRigProviderImageSelection>> {
|
|
195
|
+
const image = backend === "modal" ? version?.providerImages.modal : null;
|
|
196
|
+
if (image?.status !== "ready" || !image.providerBindingKeyHash) {
|
|
197
|
+
return resolveRigProviderImageSelection(settings, version, backend, null);
|
|
198
|
+
}
|
|
199
|
+
const structural = resolveRigProviderImageSelection(
|
|
200
|
+
settings,
|
|
201
|
+
version,
|
|
202
|
+
backend,
|
|
203
|
+
image.providerBindingKeyHash,
|
|
204
|
+
);
|
|
205
|
+
if (structural.reason !== "selected") return structural;
|
|
206
|
+
let currentProviderBindingKeyHash: string | null = null;
|
|
207
|
+
try {
|
|
208
|
+
const binding = await resolveBinding(settings);
|
|
209
|
+
currentProviderBindingKeyHash = rigProviderImageProviderBindingKeyHash(binding.key);
|
|
210
|
+
} catch {
|
|
211
|
+
// A provider-native image is an optimization. The exact logical image is
|
|
212
|
+
// still the correct cold-create fallback when provider identity is absent.
|
|
213
|
+
}
|
|
214
|
+
return resolveRigProviderImageSelection(
|
|
215
|
+
settings,
|
|
216
|
+
version,
|
|
217
|
+
backend,
|
|
218
|
+
currentProviderBindingKeyHash,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function settingsWithRigProviderImage(
|
|
223
|
+
settings: Settings,
|
|
224
|
+
version: RigVersion | null,
|
|
225
|
+
backend: SandboxBackend,
|
|
226
|
+
resolveBinding: typeof resolveModalCheckpointProviderBinding = resolveModalCheckpointProviderBinding,
|
|
227
|
+
): Promise<Settings> {
|
|
228
|
+
return (await resolveRigProviderImageForRun(settings, version, backend, resolveBinding)).settings;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export type SessionSandboxRuntime = {
|
|
232
|
+
/** Exact logical image/rig settings used to fence the durable group lease. */
|
|
233
|
+
settings: Settings;
|
|
234
|
+
image: string | null;
|
|
235
|
+
rigVersion: RigVersion | null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export async function resolveSessionSandboxRuntime(
|
|
239
|
+
db: Database,
|
|
240
|
+
settings: Settings,
|
|
241
|
+
session: Pick<Session, "workspaceId" | "sandboxBackend" | "rigId" | "rigVersionId">,
|
|
242
|
+
): Promise<SessionSandboxRuntime> {
|
|
243
|
+
const [packs, rigVersion] = await Promise.all([
|
|
244
|
+
resolveWorkspaceLegacyRuntimePacks(db, session.workspaceId),
|
|
245
|
+
session.rigId && session.rigVersionId
|
|
246
|
+
? getRigVersion(db, session.workspaceId, session.rigId, session.rigVersionId)
|
|
247
|
+
: Promise.resolve(null),
|
|
248
|
+
]);
|
|
249
|
+
if (session.rigVersionId && !rigVersion) {
|
|
250
|
+
throw new Error(`Frozen rig version ${session.rigVersionId} is unavailable`);
|
|
251
|
+
}
|
|
252
|
+
const legacy = legacySandboxRuntimeFromPacks(packs);
|
|
253
|
+
const logicalSettings = settingsWithRigImage(
|
|
254
|
+
settingsWithPackSandboxImage(settings, legacy.sandboxImage, legacy.sandboxProviderImages),
|
|
255
|
+
rigVersion?.image ?? null,
|
|
256
|
+
);
|
|
257
|
+
return {
|
|
258
|
+
settings: {
|
|
259
|
+
...logicalSettings,
|
|
260
|
+
sandboxBackend: session.sandboxBackend,
|
|
261
|
+
},
|
|
262
|
+
image: rigProviderImageSourceImage(logicalSettings, session.sandboxBackend),
|
|
263
|
+
rigVersion,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Resolve provider-native cold-create optimization only for the single CAS
|
|
268
|
+
* winner. Ordinary API calls never perform provider identity I/O. */
|
|
269
|
+
export async function providerSettingsForSessionSandboxRuntime(
|
|
270
|
+
runtime: SessionSandboxRuntime,
|
|
271
|
+
backend: SandboxBackend,
|
|
272
|
+
): Promise<Settings> {
|
|
273
|
+
return await settingsWithRigProviderImage(runtime.settings, runtime.rigVersion, backend);
|
|
274
|
+
}
|
|
@@ -67,52 +67,16 @@ function grantHasAgentAttemptAuthority(grant: AccessGrant): boolean {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
70
|
+
* Live agent attempts may address any workspace session that later private,
|
|
71
|
+
* Slack-owner, and optional host checks still allow. Parent/child lineage is
|
|
72
|
+
* not an access deny. Cross-session projections stay exact-target so a peer
|
|
73
|
+
* read does not receive another session's derived metadata.
|
|
74
74
|
*/
|
|
75
|
-
|
|
76
|
-
"session.read",
|
|
77
|
-
"session.events.read",
|
|
78
|
-
"session.stream.read",
|
|
79
|
-
"session.turns.read",
|
|
80
|
-
"session.queue.read",
|
|
81
|
-
"session.composer.read",
|
|
82
|
-
"session.lineage.read",
|
|
83
|
-
"session.capture.read",
|
|
84
|
-
"session.files.read",
|
|
85
|
-
"session.git.read",
|
|
86
|
-
"session.terminal.read",
|
|
87
|
-
"session.viewer.read",
|
|
88
|
-
"session.goal.read",
|
|
89
|
-
"session.human_input.read",
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
function enforceAgentSessionHierarchy(
|
|
75
|
+
function relatedSessionAccessForAgentAttempt(
|
|
93
76
|
actor: Extract<SessionAuthorizationActor, { kind: "agent_attempt" }>,
|
|
94
|
-
|
|
95
|
-
target: ResolvedSessionAuthorizationTarget,
|
|
96
|
-
operation: SessionAuthorizationOperation,
|
|
77
|
+
targetSessionId: string,
|
|
97
78
|
): "target" | "root" {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// A manager may inspect and operate an immediate child. Existing permission
|
|
101
|
-
// and host-policy checks still apply; lineage never grants a capability.
|
|
102
|
-
if (target.parentSessionId === actor.callerSessionId) return "target";
|
|
103
|
-
|
|
104
|
-
// A child may report to and inspect its immediate parent, but cannot mutate
|
|
105
|
-
// the parent except through the canonical machine-input message boundary.
|
|
106
|
-
if (callerParentSessionId === target.target.sessionId) {
|
|
107
|
-
if (operation === "session.append" || AGENT_PARENT_READ_OPERATIONS.has(operation)) {
|
|
108
|
-
return "target";
|
|
109
|
-
}
|
|
110
|
-
throw new SessionAuthorizationDeniedError("forbidden");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Siblings, skipped generations, other branches, and unrelated roots are
|
|
114
|
-
// never cross-session authority for a live agent attempt.
|
|
115
|
-
throw new SessionAuthorizationDeniedError("forbidden");
|
|
79
|
+
return targetSessionId === actor.callerSessionId ? "root" : "target";
|
|
116
80
|
}
|
|
117
81
|
|
|
118
82
|
export function sessionRlsActorForAuthorization(
|
|
@@ -201,14 +165,18 @@ export async function requireSessionAuthorization(
|
|
|
201
165
|
const target = resolvedTarget.target;
|
|
202
166
|
const agentRelatedSessionAccess =
|
|
203
167
|
actor.kind === "agent_attempt"
|
|
204
|
-
?
|
|
205
|
-
actor,
|
|
206
|
-
resolvedActor.callerParentSessionId,
|
|
207
|
-
resolvedTarget,
|
|
208
|
-
input.operation,
|
|
209
|
-
)
|
|
168
|
+
? relatedSessionAccessForAgentAttempt(actor, target.sessionId)
|
|
210
169
|
: null;
|
|
211
170
|
|
|
171
|
+
// Tool approvals are a human decision. An agent attempt may answer another
|
|
172
|
+
// session's structured human-input request (`session.human_input.write`,
|
|
173
|
+
// through `session_human_input_respond`) but may never approve or reject a
|
|
174
|
+
// pending tool approval on any session, including a child it spawned; an
|
|
175
|
+
// embedding-host port cannot widen this.
|
|
176
|
+
if (actor.kind === "agent_attempt" && input.operation === "session.approval.write") {
|
|
177
|
+
throw new SessionAuthorizationDeniedError("forbidden");
|
|
178
|
+
}
|
|
179
|
+
|
|
212
180
|
if (authority.visibility === "user_private") {
|
|
213
181
|
const allowed =
|
|
214
182
|
authority.ownerSubjectId !== null &&
|