@opengeni/core 0.28.1 → 1.0.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/README.md +1 -1
- package/dist/access/external-actor-authority.d.ts +47 -0
- package/dist/access/index.d.ts +182 -3
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +24 -0
- package/dist/application/connect-authority.d.ts +13 -0
- package/dist/application/connect-operation.d.ts +31 -0
- package/dist/application/external-continuation.d.ts +15 -0
- package/dist/application/external-identity-lifecycle.d.ts +20 -0
- package/dist/application/external-link-work-admission.d.ts +12 -0
- package/dist/application/external-workspace-members.d.ts +34 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +39 -2
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +26 -13
- package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
- package/dist/application/session-tenancy.d.ts +19 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/billing/limits.d.ts +11 -0
- package/dist/canonical-human-identities.d.ts +7 -2
- package/dist/canonical-human-identities.js +17 -4
- package/dist/canonical-human-identities.js.map +1 -1
- package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
- package/dist/chunk-2MKMRGSY.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
- package/dist/chunk-GVO3N37G.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
- package/dist/chunk-LCQXNR62.js.map +1 -0
- package/dist/chunk-MM6N7CTO.js +387 -0
- package/dist/chunk-MM6N7CTO.js.map +1 -0
- package/dist/chunk-QO5GVFFO.js +235 -0
- package/dist/chunk-QO5GVFFO.js.map +1 -0
- package/dist/codex-model-availability.d.ts +14 -0
- package/dist/default-session-model.d.ts +139 -0
- package/dist/dependencies.d.ts +87 -4
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +58 -0
- package/dist/domain/capabilities.d.ts +22 -4
- package/dist/domain/company-profile-agent-admin.d.ts +25 -0
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +36 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +4 -3
- package/dist/domain/environments.d.ts +5 -9
- package/dist/domain/external-creation-attribution.d.ts +6 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/github-action-policies.d.ts +36 -0
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +28 -1
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/personal-connection-delegations.d.ts +87 -4
- package/dist/domain/pr-review.d.ts +249 -0
- package/dist/domain/resources.d.ts +24 -3
- package/dist/domain/scheduled-task-access.d.ts +247 -0
- package/dist/domain/scheduled-tasks.d.ts +103 -6
- package/dist/domain/session-connection-accounts.d.ts +16 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +93 -4
- package/dist/domain/sessions.d.ts +275 -28
- package/dist/domain/skill-imports.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +117 -0
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/editable-artifact-live/application.d.ts +12 -3
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +5 -2
- package/dist/editable-artifact-live/ticket.d.ts +2 -2
- package/dist/editable-artifact-live/types.d.ts +14 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -4
- package/dist/index.d.ts +49 -3
- package/dist/index.js +18812 -6833
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +131 -0
- package/dist/managed-auth-session-sets.js +51 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-session.d.ts +98 -11
- package/dist/model-catalog.d.ts +117 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +22 -5
- package/dist/rigs/provider-images.d.ts +1 -1
- package/dist/sandbox/fleet.d.ts +59 -10
- package/dist/sandbox/routing.d.ts +22 -2
- package/dist/sandbox/runtime-settings.d.ts +56 -0
- package/dist/session-authorization.d.ts +38 -1
- package/dist/site-session-origin.d.ts +9 -0
- package/dist/transcription.d.ts +12 -1
- package/dist/turn-surface.d.ts +18 -0
- package/dist/user-presence.d.ts +32 -0
- package/package.json +18 -11
- package/src/access/external-actor-authority.ts +94 -0
- package/src/access/index.ts +1386 -44
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +77 -0
- package/src/application/connect-authority.ts +77 -0
- package/src/application/connect-operation.ts +80 -0
- package/src/application/external-continuation.ts +129 -0
- package/src/application/external-identity-lifecycle.ts +48 -0
- package/src/application/external-link-work-admission.ts +90 -0
- package/src/application/external-workspace-members.ts +232 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +222 -29
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +430 -73
- package/src/application/session-mcp-credential-rotation.ts +217 -0
- package/src/application/session-tenancy.ts +445 -0
- package/src/application/user-resource-grants.ts +154 -0
- package/src/billing/limits.ts +103 -24
- package/src/canonical-human-identities.ts +22 -3
- package/src/codex-model-availability.ts +136 -0
- package/src/default-session-model.ts +656 -0
- package/src/dependencies.ts +97 -5
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +270 -0
- package/src/domain/capabilities.ts +244 -151
- package/src/domain/company-profile-agent-admin.ts +56 -0
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +371 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +61 -15
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +32 -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 +25 -45
- package/src/domain/external-creation-attribution.ts +22 -0
- package/src/domain/file-owner.ts +67 -0
- package/src/domain/github-action-policies.ts +169 -0
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/github-skill-source.ts +233 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +270 -169
- package/src/domain/knowledge-files.ts +46 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +106 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +561 -0
- package/src/domain/knowledge-search.ts +256 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/mcp-account-bindings.ts +171 -0
- package/src/domain/mcp-account-routes.ts +121 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/organization-membership-lifecycle.ts +10 -0
- package/src/domain/personal-connection-delegations.ts +758 -115
- package/src/domain/pr-review.ts +687 -0
- package/src/domain/resources.ts +182 -25
- package/src/domain/scheduled-task-access.ts +1052 -0
- package/src/domain/scheduled-tasks.ts +1952 -215
- package/src/domain/session-connection-accounts.ts +67 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +55 -0
- package/src/domain/session-tool-policy.ts +443 -19
- package/src/domain/sessions.ts +3417 -619
- package/src/domain/skill-imports.ts +137 -17
- package/src/domain/skill-search.ts +221 -0
- package/src/domain/skills.ts +104 -0
- package/src/domain/slack-bot.ts +128 -3
- package/src/domain/workspace-members.ts +34 -2
- package/src/editable-artifact-live/application.ts +29 -4
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +167 -13
- package/src/editable-artifact-live/ticket.ts +87 -6
- package/src/editable-artifact-live/types.ts +16 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +69 -4
- package/src/managed-auth-session-sets.ts +371 -0
- package/src/managed-session.ts +520 -2
- package/src/model-catalog.ts +873 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +179 -63
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/fleet.ts +471 -59
- package/src/sandbox/routing.ts +160 -33
- package/src/sandbox/runtime-settings.ts +281 -0
- package/src/session-authorization.ts +262 -78
- package/src/site-session-origin.ts +22 -0
- package/src/transcription.ts +13 -1
- package/src/turn-surface.ts +45 -0
- package/src/user-presence.ts +134 -0
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-IBOEYG6N.js +0 -34
- package/dist/chunk-IBOEYG6N.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/dist/domain/packs.d.ts +0 -55
- package/src/domain/durable-learning-slack-publication.ts +0 -202
- package/src/domain/packs.ts +0 -556
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SESSION_TURN_SURFACES, type SessionTurnSurface } from "@opengeni/contracts";
|
|
2
|
+
import type { Observability } from "@opengeni/observability";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Live, content-free product usage counters recorded where work is accepted.
|
|
6
|
+
* Every label is a closed set: the product surface (or `unknown` when no
|
|
7
|
+
* surface was captured), who created a session (`subject` or `service`), and
|
|
8
|
+
* whether the session is a root. No workspace, user, model or text value ever
|
|
9
|
+
* becomes a label.
|
|
10
|
+
*/
|
|
11
|
+
export type ProductUsageMetricsSink = Pick<Observability, "incrementCounter">;
|
|
12
|
+
|
|
13
|
+
export const PRODUCT_USAGE_SURFACE_LABELS = [...SESSION_TURN_SURFACES, "unknown"] as const;
|
|
14
|
+
export type ProductUsageSurfaceLabel = (typeof PRODUCT_USAGE_SURFACE_LABELS)[number];
|
|
15
|
+
export const SESSION_CREATED_BY_KINDS = ["subject", "service"] as const;
|
|
16
|
+
export type SessionCreatedByKind = (typeof SESSION_CREATED_BY_KINDS)[number];
|
|
17
|
+
|
|
18
|
+
const SESSIONS_CREATED_METRIC = {
|
|
19
|
+
name: "opengeni_sessions_created_total",
|
|
20
|
+
help: "Sessions created by product surface, creator kind, and whether they are root sessions.",
|
|
21
|
+
} as const;
|
|
22
|
+
const USER_MESSAGES_METRIC = {
|
|
23
|
+
name: "opengeni_user_messages_total",
|
|
24
|
+
help: "Accepted user messages and scheduled prompts by product surface.",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export function productUsageSurfaceLabel(
|
|
28
|
+
surface: SessionTurnSurface | null | undefined,
|
|
29
|
+
): ProductUsageSurfaceLabel {
|
|
30
|
+
return surface && (SESSION_TURN_SURFACES as readonly string[]).includes(surface)
|
|
31
|
+
? surface
|
|
32
|
+
: "unknown";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sessionCreatedByKind(kind: string | null | undefined): SessionCreatedByKind {
|
|
36
|
+
return kind === "subject" ? "subject" : "service";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Count one newly created session. Never throws; replays must not call it. */
|
|
40
|
+
export function recordSessionCreated(
|
|
41
|
+
sink: ProductUsageMetricsSink | null | undefined,
|
|
42
|
+
input: {
|
|
43
|
+
surface: SessionTurnSurface | null | undefined;
|
|
44
|
+
createdByKind: string | null | undefined;
|
|
45
|
+
parentSessionId: string | null | undefined;
|
|
46
|
+
},
|
|
47
|
+
): void {
|
|
48
|
+
try {
|
|
49
|
+
sink?.incrementCounter({
|
|
50
|
+
...SESSIONS_CREATED_METRIC,
|
|
51
|
+
labels: {
|
|
52
|
+
surface: productUsageSurfaceLabel(input.surface),
|
|
53
|
+
created_by_kind: sessionCreatedByKind(input.createdByKind),
|
|
54
|
+
root: input.parentSessionId ? "false" : "true",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
} catch {
|
|
58
|
+
// Telemetry must never change the create outcome.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Count one newly accepted user message. Never throws; replays must not call it. */
|
|
63
|
+
export function recordUserMessageAccepted(
|
|
64
|
+
sink: ProductUsageMetricsSink | null | undefined,
|
|
65
|
+
input: { surface: SessionTurnSurface | null | undefined },
|
|
66
|
+
): void {
|
|
67
|
+
try {
|
|
68
|
+
sink?.incrementCounter({
|
|
69
|
+
...USER_MESSAGES_METRIC,
|
|
70
|
+
labels: { surface: productUsageSurfaceLabel(input.surface) },
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
// Telemetry must never change the send outcome.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Publish every closed series at zero so dashboards can tell a quiet surface
|
|
79
|
+
* from missing instrumentation. Never throws.
|
|
80
|
+
*/
|
|
81
|
+
export function registerProductUsageMetricBaselines(
|
|
82
|
+
sink: ProductUsageMetricsSink | null | undefined,
|
|
83
|
+
): void {
|
|
84
|
+
if (!sink) return;
|
|
85
|
+
try {
|
|
86
|
+
for (const surface of PRODUCT_USAGE_SURFACE_LABELS) {
|
|
87
|
+
sink.incrementCounter({ ...USER_MESSAGES_METRIC, labels: { surface }, amount: 0 });
|
|
88
|
+
for (const createdByKind of SESSION_CREATED_BY_KINDS) {
|
|
89
|
+
for (const root of ["true", "false"]) {
|
|
90
|
+
sink.incrementCounter({
|
|
91
|
+
...SESSIONS_CREATED_METRIC,
|
|
92
|
+
labels: { surface, created_by_kind: createdByKind, root },
|
|
93
|
+
amount: 0,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Telemetry only; series appear on their first real increment.
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/rigs/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ProposeRigChangeRequest,
|
|
12
12
|
Rig,
|
|
13
13
|
RigChange,
|
|
14
|
+
ResourceAuthorityScope,
|
|
14
15
|
RigVersion,
|
|
15
16
|
UpdateRigRequest,
|
|
16
17
|
} from "@opengeni/contracts";
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
createRigChange,
|
|
22
23
|
createRigVersion,
|
|
23
24
|
createRigVersionForChangePromotion,
|
|
24
|
-
deleteRigIfNoActiveSessions,
|
|
25
25
|
getRig,
|
|
26
26
|
getRigByName,
|
|
27
27
|
getRigChange,
|
|
@@ -30,12 +30,15 @@ import {
|
|
|
30
30
|
listRigChanges,
|
|
31
31
|
listRigVersions,
|
|
32
32
|
recordAuditEvent,
|
|
33
|
+
revokeScopedRig,
|
|
33
34
|
RigActiveVersionChangedError,
|
|
34
35
|
RigChangeTransitionError,
|
|
35
|
-
|
|
36
|
+
updateScopedRig,
|
|
36
37
|
type Database,
|
|
37
38
|
} from "@opengeni/db";
|
|
38
39
|
import { HTTPException } from "hono/http-exception";
|
|
40
|
+
import { boundedParallelMap } from "@opengeni/runtime/mcp-network";
|
|
41
|
+
import { requirePermission } from "../access";
|
|
39
42
|
import { rigProviderImagesFromVerification } from "./provider-images";
|
|
40
43
|
|
|
41
44
|
export * from "./provider-images";
|
|
@@ -44,6 +47,34 @@ export const MAX_RIGS_PER_WORKSPACE = 50;
|
|
|
44
47
|
export const MAX_CHECKS_PER_RIG = 100;
|
|
45
48
|
export const MAX_CREDENTIAL_HOOKS_PER_RIG = 50;
|
|
46
49
|
export const MAX_DEFAULT_VARIABLE_SETS_PER_RIG = 25;
|
|
50
|
+
export const RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY = 4;
|
|
51
|
+
|
|
52
|
+
type VariableSetEnvironment = { values: Record<string, string> } | null;
|
|
53
|
+
|
|
54
|
+
/** Load complete rig defaults concurrently while preserving listed precedence. */
|
|
55
|
+
export async function loadRigDefaultVariableSetEnvironment(
|
|
56
|
+
variableSetIds: readonly string[],
|
|
57
|
+
load: (variableSetId: string) => Promise<VariableSetEnvironment>,
|
|
58
|
+
): Promise<Record<string, string>> {
|
|
59
|
+
const variableSets = await boundedParallelMap(
|
|
60
|
+
variableSetIds,
|
|
61
|
+
RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY,
|
|
62
|
+
load,
|
|
63
|
+
);
|
|
64
|
+
const values: Record<string, string> = {};
|
|
65
|
+
for (const variableSet of variableSets) {
|
|
66
|
+
Object.assign(values, variableSet?.values ?? {});
|
|
67
|
+
}
|
|
68
|
+
return values;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Rig defaults layer below the session's explicitly selected variable set. */
|
|
72
|
+
export function mergeRigDefaultVariableSetEnvironment(
|
|
73
|
+
rigDefaultValues: Record<string, string>,
|
|
74
|
+
sessionValues: Record<string, string>,
|
|
75
|
+
): Record<string, string> {
|
|
76
|
+
return { ...rigDefaultValues, ...sessionValues };
|
|
77
|
+
}
|
|
47
78
|
|
|
48
79
|
export type RigServices = {
|
|
49
80
|
db: Database;
|
|
@@ -92,12 +123,12 @@ export function rigActorForGrant(grant: AccessGrant): string {
|
|
|
92
123
|
|
|
93
124
|
export async function requireRigForApi(
|
|
94
125
|
db: Database,
|
|
95
|
-
|
|
126
|
+
grant: AccessGrant,
|
|
96
127
|
rigId: string,
|
|
97
128
|
): Promise<Rig> {
|
|
98
|
-
const rig = await getRig(db,
|
|
129
|
+
const rig = await getRig(db, grant, rigId);
|
|
99
130
|
if (!rig) {
|
|
100
|
-
throw new HTTPException(404, { message: "
|
|
131
|
+
throw new HTTPException(404, { message: "sandbox environment not found" });
|
|
101
132
|
}
|
|
102
133
|
return rig;
|
|
103
134
|
}
|
|
@@ -112,7 +143,7 @@ export async function requireRigChangeForApi(
|
|
|
112
143
|
// RLS + the workspace clause make a cross-workspace id indistinguishable from
|
|
113
144
|
// missing; the rigId clause keeps the change addressable only under its rig.
|
|
114
145
|
if (!change || change.rigId !== rigId) {
|
|
115
|
-
throw new HTTPException(404, { message: "
|
|
146
|
+
throw new HTTPException(404, { message: "sandbox environment change not found" });
|
|
116
147
|
}
|
|
117
148
|
return change;
|
|
118
149
|
}
|
|
@@ -120,7 +151,7 @@ export async function requireRigChangeForApi(
|
|
|
120
151
|
function trimmedRigName(name: string): string {
|
|
121
152
|
const trimmed = name.trim();
|
|
122
153
|
if (!trimmed) {
|
|
123
|
-
throw new HTTPException(422, { message: "
|
|
154
|
+
throw new HTTPException(422, { message: "sandbox environment name is required" });
|
|
124
155
|
}
|
|
125
156
|
return trimmed;
|
|
126
157
|
}
|
|
@@ -133,7 +164,9 @@ function assertUniqueCheckNames(checks: ReadonlyArray<{ name: string }> | undefi
|
|
|
133
164
|
const seen = new Set<string>();
|
|
134
165
|
for (const check of checks) {
|
|
135
166
|
if (seen.has(check.name)) {
|
|
136
|
-
throw new HTTPException(422, {
|
|
167
|
+
throw new HTTPException(422, {
|
|
168
|
+
message: `duplicate sandbox environment check name: ${check.name}`,
|
|
169
|
+
});
|
|
137
170
|
}
|
|
138
171
|
seen.add(check.name);
|
|
139
172
|
}
|
|
@@ -143,47 +176,83 @@ function assertUniqueCheckNames(checks: ReadonlyArray<{ name: string }> | undefi
|
|
|
143
176
|
// cross-workspace id indistinguishable from a missing one, so both map to 422.
|
|
144
177
|
async function assertVariableSetsExist(
|
|
145
178
|
db: Database,
|
|
146
|
-
|
|
179
|
+
access: AccessGrant,
|
|
180
|
+
rigScope: ResourceAuthorityScope,
|
|
147
181
|
ids: ReadonlyArray<string> | undefined,
|
|
148
182
|
): Promise<void> {
|
|
149
|
-
if (
|
|
183
|
+
if (ids === undefined) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
requirePermission(access, "variable-sets:attach");
|
|
187
|
+
if (ids.length === 0) {
|
|
150
188
|
return;
|
|
151
189
|
}
|
|
190
|
+
requirePermission(access, "variable-sets:use");
|
|
152
191
|
const unique = [...new Set(ids)];
|
|
153
192
|
for (const id of unique) {
|
|
154
|
-
const variableSet = await getVariableSet(db,
|
|
193
|
+
const variableSet = await getVariableSet(db, access, id);
|
|
155
194
|
if (!variableSet) {
|
|
156
|
-
throw new HTTPException(422, {
|
|
195
|
+
throw new HTTPException(422, {
|
|
196
|
+
message: `unknown defaultVariableSetId: ${id}`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (!variableSetScopeAllowedForRig(rigScope, variableSet.scope)) {
|
|
200
|
+
throw new HTTPException(422, {
|
|
201
|
+
message: `${rigScope}-scoped sandbox environments cannot use ${variableSet.scope}-scoped variable sets`,
|
|
202
|
+
});
|
|
157
203
|
}
|
|
158
204
|
}
|
|
159
205
|
}
|
|
160
206
|
|
|
207
|
+
/** A resource may depend only on Variable Sets whose audience is no narrower
|
|
208
|
+
* than its own. User-owned rigs may use anything visible to their owner. */
|
|
209
|
+
export function variableSetScopeAllowedForRig(
|
|
210
|
+
rigScope: ResourceAuthorityScope,
|
|
211
|
+
variableSetScope: ResourceAuthorityScope,
|
|
212
|
+
): boolean {
|
|
213
|
+
return (
|
|
214
|
+
rigScope === "user" ||
|
|
215
|
+
variableSetScope === "organization" ||
|
|
216
|
+
(rigScope === "workspace" && variableSetScope === "workspace")
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
161
220
|
export async function createRigForApi(
|
|
162
221
|
deps: RigServices,
|
|
163
222
|
grant: AccessGrant,
|
|
164
223
|
payload: CreateRigRequest,
|
|
224
|
+
options: { allowOrganization?: boolean } = {},
|
|
165
225
|
): Promise<Rig> {
|
|
166
|
-
const workspaceId = grant.workspaceId;
|
|
167
226
|
const name = trimmedRigName(payload.name);
|
|
168
227
|
assertUniqueCheckNames(payload.checks);
|
|
169
|
-
await assertVariableSetsExist(
|
|
170
|
-
|
|
228
|
+
await assertVariableSetsExist(
|
|
229
|
+
deps.db,
|
|
230
|
+
grant,
|
|
231
|
+
payload.scope,
|
|
232
|
+
payload.defaultVariableSetIds.length > 0 ? payload.defaultVariableSetIds : undefined,
|
|
233
|
+
);
|
|
234
|
+
if ((await countRigs(deps.db, grant, payload.scope)) >= MAX_RIGS_PER_WORKSPACE) {
|
|
171
235
|
throw new HTTPException(422, {
|
|
172
|
-
message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE}
|
|
236
|
+
message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE} sandbox environments`,
|
|
173
237
|
});
|
|
174
238
|
}
|
|
175
|
-
if (await getRigByName(deps.db,
|
|
176
|
-
throw new HTTPException(409, {
|
|
239
|
+
if (await getRigByName(deps.db, grant, name, payload.scope)) {
|
|
240
|
+
throw new HTTPException(409, {
|
|
241
|
+
message: `sandbox environment name is already in use: ${name}`,
|
|
242
|
+
});
|
|
177
243
|
}
|
|
178
244
|
const createdBy = rigActorForGrant(grant);
|
|
179
245
|
const rig = await createRig(deps.db, {
|
|
180
246
|
accountId: grant.accountId,
|
|
181
|
-
workspaceId,
|
|
247
|
+
workspaceId: grant.workspaceId,
|
|
248
|
+
scope: payload.scope,
|
|
249
|
+
subjectId: grant.subjectId,
|
|
250
|
+
allowOrganization: options.allowOrganization === true,
|
|
182
251
|
name,
|
|
183
252
|
description: payload.description ?? null,
|
|
184
253
|
createdBy,
|
|
185
254
|
initialVersion: {
|
|
186
|
-
image:
|
|
255
|
+
image: null,
|
|
187
256
|
setupScript: payload.setupScript ?? null,
|
|
188
257
|
checks: payload.checks,
|
|
189
258
|
credentialHooks: payload.credentialHooks,
|
|
@@ -192,7 +261,11 @@ export async function createRigForApi(
|
|
|
192
261
|
createdBy,
|
|
193
262
|
},
|
|
194
263
|
});
|
|
195
|
-
await recordRigAuditEvent(deps.db, {
|
|
264
|
+
await recordRigAuditEvent(deps.db, {
|
|
265
|
+
grant,
|
|
266
|
+
action: "rig.created",
|
|
267
|
+
rigId: rig.id,
|
|
268
|
+
});
|
|
196
269
|
return rig;
|
|
197
270
|
}
|
|
198
271
|
|
|
@@ -201,20 +274,27 @@ export async function updateRigForApi(
|
|
|
201
274
|
grant: AccessGrant,
|
|
202
275
|
rig: Rig,
|
|
203
276
|
payload: UpdateRigRequest,
|
|
277
|
+
options: { allowOrganization?: boolean } = {},
|
|
204
278
|
): Promise<Rig> {
|
|
205
|
-
const workspaceId = grant.workspaceId;
|
|
206
279
|
const name = payload.name !== undefined ? trimmedRigName(payload.name) : undefined;
|
|
207
280
|
if (name !== undefined && name !== rig.name) {
|
|
208
|
-
const existing = await getRigByName(deps.db,
|
|
281
|
+
const existing = await getRigByName(deps.db, grant, name, rig.scope);
|
|
209
282
|
if (existing && existing.id !== rig.id) {
|
|
210
|
-
throw new HTTPException(409, {
|
|
283
|
+
throw new HTTPException(409, {
|
|
284
|
+
message: `sandbox environment name is already in use: ${name}`,
|
|
285
|
+
});
|
|
211
286
|
}
|
|
212
287
|
}
|
|
213
|
-
const updated = await
|
|
288
|
+
const updated = await updateScopedRig(deps.db, grant, rig.id, {
|
|
214
289
|
...(name !== undefined ? { name } : {}),
|
|
215
290
|
...(payload.description !== undefined ? { description: payload.description } : {}),
|
|
291
|
+
allowOrganization: options.allowOrganization === true,
|
|
292
|
+
});
|
|
293
|
+
await recordRigAuditEvent(deps.db, {
|
|
294
|
+
grant,
|
|
295
|
+
action: "rig.updated",
|
|
296
|
+
rigId: rig.id,
|
|
216
297
|
});
|
|
217
|
-
await recordRigAuditEvent(deps.db, { grant, action: "rig.updated", rigId: rig.id });
|
|
218
298
|
return updated;
|
|
219
299
|
}
|
|
220
300
|
|
|
@@ -222,18 +302,27 @@ export async function deleteRigForApi(
|
|
|
222
302
|
deps: RigServices,
|
|
223
303
|
grant: AccessGrant,
|
|
224
304
|
rig: Rig,
|
|
305
|
+
options: { allowOrganization?: boolean } = {},
|
|
225
306
|
): Promise<void> {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
throw new HTTPException(409, {
|
|
230
|
-
message: `rig is referenced by ${deleted.activeSessionCount} active session(s); it cannot be deleted`,
|
|
307
|
+
try {
|
|
308
|
+
await revokeScopedRig(deps.db, grant, rig.id, {
|
|
309
|
+
allowOrganization: options.allowOrganization === true,
|
|
231
310
|
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
let current: unknown = error;
|
|
313
|
+
while (current instanceof Error) {
|
|
314
|
+
if (current.message.includes("active sessions")) {
|
|
315
|
+
throw new HTTPException(409, { message: current.message });
|
|
316
|
+
}
|
|
317
|
+
current = current.cause;
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
232
320
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
321
|
+
await recordRigAuditEvent(deps.db, {
|
|
322
|
+
grant,
|
|
323
|
+
action: "rig.deleted",
|
|
324
|
+
rigId: rig.id,
|
|
325
|
+
});
|
|
237
326
|
}
|
|
238
327
|
|
|
239
328
|
// Records a proposed change against the rig's CURRENT active version (the base
|
|
@@ -247,15 +336,18 @@ export async function proposeRigChangeForApi(
|
|
|
247
336
|
request: ProposeRigChangeRequest,
|
|
248
337
|
options: { proposedBy?: string } = {},
|
|
249
338
|
): Promise<RigChange> {
|
|
250
|
-
const workspaceId =
|
|
339
|
+
const workspaceId = rig.workspaceId;
|
|
251
340
|
if (!rig.activeVersion) {
|
|
252
|
-
throw new HTTPException(422, {
|
|
341
|
+
throw new HTTPException(422, {
|
|
342
|
+
message: "sandbox environment has no active version to base a change on",
|
|
343
|
+
});
|
|
253
344
|
}
|
|
254
345
|
if (request.kind === "definition_edit") {
|
|
255
346
|
assertUniqueCheckNames(request.payload.checks);
|
|
256
347
|
await assertVariableSetsExist(
|
|
257
348
|
deps.db,
|
|
258
|
-
|
|
349
|
+
grant,
|
|
350
|
+
rig.scope,
|
|
259
351
|
request.payload.defaultVariableSetIds ?? undefined,
|
|
260
352
|
);
|
|
261
353
|
}
|
|
@@ -320,7 +412,7 @@ async function promoteChangeWithActiveCas(
|
|
|
320
412
|
} catch (error) {
|
|
321
413
|
if (error instanceof RigActiveVersionChangedError) {
|
|
322
414
|
throw new HTTPException(409, {
|
|
323
|
-
message: `
|
|
415
|
+
message: `sandbox environment moved since this change was verified (base ${error.expectedVersionId}, now ${error.actualVersionId ?? "none"}); re-verify before promoting`,
|
|
324
416
|
});
|
|
325
417
|
}
|
|
326
418
|
if (error instanceof RigChangeTransitionError) {
|
|
@@ -342,21 +434,25 @@ export async function promoteSetupAppendChange(
|
|
|
342
434
|
});
|
|
343
435
|
}
|
|
344
436
|
if (change.status !== "proposed" && change.status !== "verifying") {
|
|
345
|
-
throw new HTTPException(409, {
|
|
437
|
+
throw new HTTPException(409, {
|
|
438
|
+
message: `sandbox environment change is ${change.status}; cannot promote`,
|
|
439
|
+
});
|
|
346
440
|
}
|
|
347
441
|
if (!change.baseVersionId) {
|
|
348
|
-
throw new HTTPException(422, { message: "
|
|
442
|
+
throw new HTTPException(422, { message: "sandbox environment change has no base version" });
|
|
349
443
|
}
|
|
350
|
-
const base = await getRigVersion(deps.db,
|
|
444
|
+
const base = await getRigVersion(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
351
445
|
if (!base) {
|
|
352
|
-
throw new HTTPException(404, { message: "base
|
|
446
|
+
throw new HTTPException(404, { message: "base sandbox environment version not found" });
|
|
353
447
|
}
|
|
354
448
|
const payload = change.payload as { command?: unknown; note?: unknown };
|
|
355
449
|
if (typeof payload.command !== "string" || !payload.command.trim()) {
|
|
356
|
-
throw new HTTPException(422, {
|
|
450
|
+
throw new HTTPException(422, {
|
|
451
|
+
message: "setup_append change is missing command",
|
|
452
|
+
});
|
|
357
453
|
}
|
|
358
454
|
const nextDefinition = {
|
|
359
|
-
image:
|
|
455
|
+
image: null,
|
|
360
456
|
setupScript: appendRigSetupCommand(base.setupScript, payload.command),
|
|
361
457
|
checks: base.checks,
|
|
362
458
|
credentialHooks: base.credentialHooks,
|
|
@@ -364,7 +460,7 @@ export async function promoteSetupAppendChange(
|
|
|
364
460
|
};
|
|
365
461
|
const { version, change: updated } = await promoteChangeWithActiveCas(
|
|
366
462
|
deps,
|
|
367
|
-
|
|
463
|
+
rig.workspaceId,
|
|
368
464
|
rig.id,
|
|
369
465
|
change.id,
|
|
370
466
|
{
|
|
@@ -382,13 +478,21 @@ export async function promoteSetupAppendChange(
|
|
|
382
478
|
grant,
|
|
383
479
|
action: "rig.change.merged",
|
|
384
480
|
rigId: rig.id,
|
|
385
|
-
metadata: {
|
|
481
|
+
metadata: {
|
|
482
|
+
changeId: change.id,
|
|
483
|
+
versionId: version.id,
|
|
484
|
+
version: version.version,
|
|
485
|
+
},
|
|
386
486
|
});
|
|
387
487
|
await recordRigAuditEvent(deps.db, {
|
|
388
488
|
grant,
|
|
389
489
|
action: "rig.version.promoted",
|
|
390
490
|
rigId: rig.id,
|
|
391
|
-
metadata: {
|
|
491
|
+
metadata: {
|
|
492
|
+
changeId: change.id,
|
|
493
|
+
versionId: version.id,
|
|
494
|
+
version: version.version,
|
|
495
|
+
},
|
|
392
496
|
});
|
|
393
497
|
return { change: updated, version };
|
|
394
498
|
}
|
|
@@ -400,10 +504,14 @@ export async function promoteVerifiedDefinitionEditChangeForApi(
|
|
|
400
504
|
change: RigChange,
|
|
401
505
|
): Promise<{ change: RigChange; version: RigVersion }> {
|
|
402
506
|
if (change.kind !== "definition_edit") {
|
|
403
|
-
throw new HTTPException(422, {
|
|
507
|
+
throw new HTTPException(422, {
|
|
508
|
+
message: "only definition_edit changes use explicit promote",
|
|
509
|
+
});
|
|
404
510
|
}
|
|
405
511
|
if (change.status !== "proposed") {
|
|
406
|
-
throw new HTTPException(409, {
|
|
512
|
+
throw new HTTPException(409, {
|
|
513
|
+
message: `sandbox environment change is ${change.status}; cannot promote`,
|
|
514
|
+
});
|
|
407
515
|
}
|
|
408
516
|
if (change.verification?.passed !== true) {
|
|
409
517
|
throw new HTTPException(422, {
|
|
@@ -411,14 +519,13 @@ export async function promoteVerifiedDefinitionEditChangeForApi(
|
|
|
411
519
|
});
|
|
412
520
|
}
|
|
413
521
|
if (!change.baseVersionId) {
|
|
414
|
-
throw new HTTPException(422, { message: "
|
|
522
|
+
throw new HTTPException(422, { message: "sandbox environment change has no base version" });
|
|
415
523
|
}
|
|
416
|
-
const base = await getRigVersion(deps.db,
|
|
524
|
+
const base = await getRigVersion(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
417
525
|
if (!base) {
|
|
418
|
-
throw new HTTPException(404, { message: "base
|
|
526
|
+
throw new HTTPException(404, { message: "base sandbox environment version not found" });
|
|
419
527
|
}
|
|
420
528
|
const payload = change.payload as {
|
|
421
|
-
image?: unknown;
|
|
422
529
|
setupScript?: unknown;
|
|
423
530
|
checks?: unknown;
|
|
424
531
|
credentialHooks?: unknown;
|
|
@@ -426,7 +533,7 @@ export async function promoteVerifiedDefinitionEditChangeForApi(
|
|
|
426
533
|
changelog?: unknown;
|
|
427
534
|
};
|
|
428
535
|
const nextDefinition = {
|
|
429
|
-
image:
|
|
536
|
+
image: null,
|
|
430
537
|
setupScript:
|
|
431
538
|
payload.setupScript === undefined ? base.setupScript : (payload.setupScript as string | null),
|
|
432
539
|
checks: Array.isArray(payload.checks) ? (payload.checks as RigVersion["checks"]) : base.checks,
|
|
@@ -439,7 +546,7 @@ export async function promoteVerifiedDefinitionEditChangeForApi(
|
|
|
439
546
|
};
|
|
440
547
|
const { version, change: updated } = await promoteChangeWithActiveCas(
|
|
441
548
|
deps,
|
|
442
|
-
|
|
549
|
+
rig.workspaceId,
|
|
443
550
|
rig.id,
|
|
444
551
|
change.id,
|
|
445
552
|
{
|
|
@@ -457,13 +564,21 @@ export async function promoteVerifiedDefinitionEditChangeForApi(
|
|
|
457
564
|
grant,
|
|
458
565
|
action: "rig.change.merged",
|
|
459
566
|
rigId: rig.id,
|
|
460
|
-
metadata: {
|
|
567
|
+
metadata: {
|
|
568
|
+
changeId: change.id,
|
|
569
|
+
versionId: version.id,
|
|
570
|
+
version: version.version,
|
|
571
|
+
},
|
|
461
572
|
});
|
|
462
573
|
await recordRigAuditEvent(deps.db, {
|
|
463
574
|
grant,
|
|
464
575
|
action: "rig.version.promoted",
|
|
465
576
|
rigId: rig.id,
|
|
466
|
-
metadata: {
|
|
577
|
+
metadata: {
|
|
578
|
+
changeId: change.id,
|
|
579
|
+
versionId: version.id,
|
|
580
|
+
version: version.version,
|
|
581
|
+
},
|
|
467
582
|
});
|
|
468
583
|
return { change: updated, version };
|
|
469
584
|
}
|
|
@@ -475,21 +590,22 @@ export async function createRigVersionForApi(
|
|
|
475
590
|
payload: RigDefinitionEditPayload,
|
|
476
591
|
): Promise<RigVersion> {
|
|
477
592
|
if (!rig.activeVersion) {
|
|
478
|
-
throw new HTTPException(422, { message: "
|
|
593
|
+
throw new HTTPException(422, { message: "sandbox environment has no active version" });
|
|
479
594
|
}
|
|
480
595
|
assertUniqueCheckNames(payload.checks);
|
|
481
596
|
await assertVariableSetsExist(
|
|
482
597
|
deps.db,
|
|
483
|
-
grant
|
|
598
|
+
grant,
|
|
599
|
+
rig.scope,
|
|
484
600
|
payload.defaultVariableSetIds ?? undefined,
|
|
485
601
|
);
|
|
486
602
|
const base = rig.activeVersion;
|
|
487
603
|
const version = await createRigVersion(
|
|
488
604
|
deps.db,
|
|
489
|
-
|
|
605
|
+
rig.workspaceId,
|
|
490
606
|
rig.id,
|
|
491
607
|
{
|
|
492
|
-
image:
|
|
608
|
+
image: null,
|
|
493
609
|
setupScript: payload.setupScript === undefined ? base.setupScript : payload.setupScript,
|
|
494
610
|
checks: payload.checks ?? base.checks,
|
|
495
611
|
credentialHooks: payload.credentialHooks ?? base.credentialHooks,
|
|
@@ -516,7 +632,7 @@ export async function activateRigVersionForApi(
|
|
|
516
632
|
rig: Rig,
|
|
517
633
|
versionId: string,
|
|
518
634
|
): Promise<RigVersion> {
|
|
519
|
-
const workspaceId =
|
|
635
|
+
const workspaceId = rig.workspaceId;
|
|
520
636
|
const version = await activateRigVersion(deps.db, workspaceId, rig.id, versionId);
|
|
521
637
|
await recordRigAuditEvent(deps.db, {
|
|
522
638
|
grant,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export type RigProviderImageDefinition = Pick<
|
|
13
13
|
RigVersion,
|
|
14
|
-
"
|
|
14
|
+
"setupScript" | "checks" | "credentialHooks" | "defaultVariableSetIds"
|
|
15
15
|
>;
|
|
16
16
|
|
|
17
17
|
function sha256(value: string): string {
|
|
@@ -42,10 +42,9 @@ export function rigProviderImageContentHash(input: {
|
|
|
42
42
|
}): string {
|
|
43
43
|
return sha256(
|
|
44
44
|
stableJson({
|
|
45
|
-
version:
|
|
45
|
+
version: 2,
|
|
46
46
|
backend: input.backend,
|
|
47
47
|
sourceImage: input.sourceImage,
|
|
48
|
-
image: input.definition.image,
|
|
49
48
|
setupScript: input.definition.setupScript,
|
|
50
49
|
checks: input.definition.checks,
|
|
51
50
|
credentialHooks: input.definition.credentialHooks,
|