Files
cmux/web/services/vms/workflows.ts
T
Lawrence Chen 0aa8a724cb Fix Codex Security scan findings (#7437)
* Add security regression coverage

* Fix security scan findings

* Address security review follow-ups

* Fix review regressions in lease cleanup

* Fix final security review findings

* Fix remaining autoreview findings

* Address final autoreview regressions

* Keep active identity cleanup best effort

* Bound active identity cleanup

* Bound SSH cleanup before endpoint minting

* Make cleanup retries bounded and releasable

* Bound active identity cleanup preflight

* Preserve vault grants on retry presign failure

* Guard vault grant rollback state

* Back off failed expired lease cleanup

* Separate vault quota lock namespace

* Tighten VM identity cleanup ordering

* Fail closed without VM team membership

* Bound VM identity cleanup fanout

* Rollback endpoint resume on cleanup failure

* Remove nondeterministic vault upload test wait

* Fail closed on destroy identity cleanup

* Recreate Base when active provider VM is gone

* Keep Freestyle attach independent of exec probe

* Scope provider identity not-found handling

* Use reservation tokens for vault upload rollback

* Validate vault upload grants at commit

* Bound identity cleanup and duplicate vault reservations

* Stage vault uploads before commit

* Keep vault staging cleanup retryable

* Reuse active vault upload staging keys

* Preserve legacy vault upload commits

* Make vault staging cleanup recoverable

* Avoid endpoint resume rollback races

* Serialize vault upload grant cleanup

* Finalize vault staging outside quota locks

* Track superseded vault upload keys
2026-07-08 01:08:52 -07:00

2007 lines
66 KiB
TypeScript

import { createHash, randomUUID } from "node:crypto";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import type {
AttachEndpoint,
AttachOptions,
ExecResult,
ProviderId,
SSHEndpoint,
VMStatus,
} from "./drivers";
import {
VmBillingGateway,
VmBillingGatewayLive,
type BillingCustomerType,
type VmCreateCreditGrant,
type VmCreateCreditReservation,
type VmBillingGatewayShape,
} from "./billingGateway";
import {
VmBillingError,
VmCreateFailedError,
VmCreateInProgressError,
VmNotFoundError,
VmProviderOperationError,
VmSnapshotNotFoundError,
isVmCreateCreditsInsufficientError,
isVmLimitExceededError,
vmWorkflowErrorCause,
type VmDatabaseError,
type VmWorkflowError,
} from "./errors";
import { maxActiveVmsForPlan } from "./entitlements";
import { isProviderIdentityNotFoundError, isProviderNotFoundError } from "./providerErrors";
import { VmProviderGateway, VmProviderGatewayLive, type VmProviderGatewayShape } from "./providerGateway";
import {
VmRepository,
VmRepositoryLive,
type BeginCreateResult,
type BeginBaseCreateResult,
type CloudVmBaseGenerationRow,
type CloudVmBaseRow,
type CloudVmSessionRow,
type CloudVmStatus,
type CloudVmLeaseKind,
type CloudVmRow,
type VmRepositoryShape,
} from "./repository";
import { measureVmEffect, type VmTimingSink } from "./timings";
export type VmEntry = {
readonly providerVmId: string;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion: string | null;
readonly status: CloudVmStatus;
readonly createdAt: number;
};
export type BaseVmEntry = VmEntry & {
readonly baseId: string;
readonly baseName: string;
readonly generation: number;
readonly retainedProviderVmId: string | null;
};
export type CloudVmSessionEntry = CloudVmSessionRow;
export const VmWorkflowLive = Layer.mergeAll(VmRepositoryLive, VmProviderGatewayLive, VmBillingGatewayLive);
const EXPIRED_IDENTITY_REVOKE_BATCH = 5;
const EXPIRED_IDENTITY_REVOKE_RETRY_BACKOFF_MS = 10 * 60 * 1000;
const IDENTITY_REVOKE_PROVIDER_TIMEOUT = "5 seconds";
const ACTIVE_IDENTITY_REVOKE_HOT_PATH_LIMIT = 8;
const VM_STATUS_RECONCILE_BATCH_LIMIT = 200;
type ExistingVmAccessInput = {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
};
export type VmProviderStatusReconcileResult = {
readonly checked: number;
readonly updated: number;
readonly destroyed: number;
readonly skipped: number;
readonly skippedNoGetStatus: boolean;
};
export async function runVmWorkflow<A>(
program: Effect.Effect<A, VmWorkflowError, VmRepository | VmProviderGateway | VmBillingGateway>,
): Promise<A> {
try {
return await Effect.runPromise(program.pipe(Effect.provide(VmWorkflowLive)));
} catch (err) {
throw vmWorkflowErrorCause(err) ?? err;
}
}
export function listUserVms(userId: string, billingTeamId?: string | null) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const rows = yield* repo.listUserVms(userId, billingTeamId);
return rows.filter((row) => row.providerVmId).map(vmEntryFromRow);
});
}
export function getVm(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
const providerVmId = vm.providerVmId ?? input.providerVmId;
const getStatus = providers.getStatus;
if (!getStatus) return vmEntryFromRow(vm);
const providerStatus = yield* getStatus(vm.provider, providerVmId).pipe(
Effect.catchAll((err) =>
isProviderNotFoundError(err)
? Effect.succeed("destroyed" as const)
: Effect.fail(err),
),
);
if (providerStatus !== "creating" && providerStatus !== vm.status) {
const dbStatus = dbStatusFromProviderStatus(providerStatus);
const didUpdate = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: dbStatus,
});
if (didUpdate) return vmEntryFromRow({ ...vm, status: dbStatus, updatedAt: new Date() });
}
return vmEntryFromRow(vm);
});
}
export function reconcileVmProviderStatuses(input: {
readonly limit?: number;
} = {}): Effect.Effect<VmProviderStatusReconcileResult, VmWorkflowError, VmRepository | VmProviderGateway> {
return Effect.gen(function* () {
const providers = yield* VmProviderGateway;
const getStatus = providers.getStatus;
if (!getStatus) {
return {
checked: 0,
updated: 0,
destroyed: 0,
skipped: 0,
skippedNoGetStatus: true,
};
}
const repo = yield* VmRepository;
const candidates = yield* repo.reconciliationCandidates({
limit: boundedVmStatusReconcileLimit(input.limit),
});
const outcomes = yield* Effect.forEach(
candidates,
(vm) => reconcileObservedProviderStatus(repo, getStatus, vm, "provider_status_cron"),
{ concurrency: 10 },
);
let updated = 0;
let destroyed = 0;
let skipped = 0;
for (const outcome of outcomes) {
if (outcome === "updated") updated += 1;
else if (outcome === "destroyed") destroyed += 1;
else if (outcome === "skipped") skipped += 1;
}
return {
checked: candidates.length,
updated,
destroyed,
skipped,
skippedNoGetStatus: false,
};
});
}
export function createVm(input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly idempotencyKey?: string;
readonly bakedFreestyleSignedAdmin?: boolean;
readonly timing?: VmTimingSink;
}): Effect.Effect<VmEntry, VmWorkflowError, VmRepository | VmProviderGateway | VmBillingGateway> {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const billing = yield* VmBillingGateway;
const create = yield* beginCreateWithLazyProviderRefresh(repo, providers, input);
if (!create.inserted) {
const existing = create.vm;
if (existing.status === "failed") {
return yield* Effect.fail(
new VmCreateFailedError({
idempotencyKey: input.idempotencyKey ?? "",
code: existing.failureCode,
message: existing.failureMessage ?? "previous VM create failed",
}),
);
}
if (!existing.providerVmId) {
return yield* Effect.fail(
new VmCreateInProgressError({ idempotencyKey: input.idempotencyKey ?? "" }),
);
}
return vmEntryFromRow(existing);
}
const creditReservation = yield* reserveCreateCredit(billing, repo, input, create.vm);
yield* recordCreateRequestedEvents(repo, input, create.vm, creditReservation);
const handle = yield* measureVmEffect(
input.timing,
"provider_create",
providers.create(input.provider, {
image: input.image,
providerMetadata: create.vm.providerMetadata,
bakedFreestyleSignedAdmin: input.bakedFreestyleSignedAdmin,
}),
).pipe(
Effect.tapError((err) =>
Effect.all([
refundCredit(billing, repo, create.vm, creditReservation),
repo.markCreateFailed({
id: create.vm.id,
code: err.operation,
message: errorMessage(err.cause),
}),
repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: create.vm.id,
eventType: "vm.create.failed",
provider: input.provider,
imageId: input.image,
metadata: { operation: err.operation, message: errorMessage(err.cause) },
}),
], { discard: true }).pipe(Effect.catchAll(() => Effect.void))
),
);
const running = yield* measureVmEffect(
input.timing,
"mark_running",
repo.markCreateRunning({
id: create.vm.id,
providerVmId: handle.providerVmId,
image: handle.image,
imageVersion: input.imageVersion ?? null,
providerMetadata: handle.providerMetadata ?? create.vm.providerMetadata,
}),
).pipe(
Effect.catchAll((err) =>
Effect.gen(function* () {
yield* providers.destroy(input.provider, handle.providerVmId).pipe(Effect.catchAll(() => Effect.void));
yield* refundCredit(billing, repo, create.vm, creditReservation);
yield* repo.markCreateFailed({
id: create.vm.id,
code: "database_finalize_failed",
message: "Cloud VM state update failed.",
}).pipe(Effect.catchAll(() => Effect.void));
yield* recordCreateFailureEvent(
repo,
input,
create.vm,
"database_finalize_failed",
errorMessage(err.cause),
).pipe(Effect.catchAll(() => Effect.void));
return yield* Effect.fail(err);
}),
),
);
yield* recordCreateSuccessEvents(repo, input, running);
return vmEntryFromRow(running);
});
}
export function openBaseVm(input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly baseName?: string;
readonly bakedFreestyleSignedAdmin?: boolean;
readonly timing?: VmTimingSink;
}): Effect.Effect<BaseVmEntry, VmWorkflowError, VmRepository | VmProviderGateway | VmBillingGateway> {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const billing = yield* VmBillingGateway;
const create = yield* measureVmEffect(
input.timing,
"begin_base_open",
repo.beginBaseOpen(input),
);
return yield* finishBaseCreate(repo, providers, billing, input, create);
});
}
export function resetBaseVm(input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly baseName?: string;
readonly reason?: string | null;
readonly bakedFreestyleSignedAdmin?: boolean;
readonly timing?: VmTimingSink;
}): Effect.Effect<BaseVmEntry, VmWorkflowError, VmRepository | VmProviderGateway | VmBillingGateway> {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const billing = yield* VmBillingGateway;
const create = yield* measureVmEffect(
input.timing,
"begin_base_reset",
repo.beginBaseReset(input),
);
return yield* finishBaseCreate(repo, providers, billing, input, create);
});
}
function finishBaseCreate(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
billing: VmBillingGatewayShape,
input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly baseName?: string;
readonly bakedFreestyleSignedAdmin?: boolean;
readonly timing?: VmTimingSink;
},
create: BeginBaseCreateResult,
): Effect.Effect<BaseVmEntry, VmWorkflowError, never> {
return Effect.gen(function* () {
if (create.kind === "existing") {
const existing = create.vm;
if (existing.status === "failed") {
return yield* Effect.fail(
new VmCreateFailedError({
idempotencyKey: existing.idempotencyKey ?? "",
code: existing.failureCode ?? null,
message: existing.failureMessage ?? "previous Base create failed",
}),
);
}
if (!existing.providerVmId) {
return yield* Effect.fail(
new VmCreateInProgressError({ idempotencyKey: existing.idempotencyKey ?? "" }),
);
}
const replacement = yield* reopenBaseIfProviderDeleted(
repo,
providers,
input,
create,
existing,
existing.providerVmId,
);
if (replacement) {
return yield* finishBaseCreate(repo, providers, billing, input, replacement);
}
return baseVmEntryFromRows(create.base, create.generation, existing, null);
}
const idempotencyKey = create.vm.idempotencyKey ?? undefined;
const creditReservation = yield* reserveCreateCredit(billing, repo, {
...input,
idempotencyKey,
}, create.vm);
yield* recordCreateRequestedEvents(repo, {
...input,
idempotencyKey,
}, create.vm, creditReservation);
const handle = yield* measureVmEffect(
input.timing,
"provider_create",
providers.create(input.provider, {
image: input.image,
providerMetadata: create.vm.providerMetadata,
bakedFreestyleSignedAdmin: input.bakedFreestyleSignedAdmin,
}),
).pipe(
Effect.tapError((err) =>
Effect.all([
refundCredit(billing, repo, create.vm, creditReservation),
repo.markBaseCreateFailed({
baseId: create.base.id,
generation: create.generation.generation,
vmId: create.vm.id,
userId: input.userId,
code: err.operation,
message: errorMessage(err.cause),
}),
repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: create.vm.id,
eventType: "vm.base.create.failed",
provider: input.provider,
imageId: input.image,
metadata: { operation: err.operation, message: errorMessage(err.cause), baseName: input.baseName ?? "base" },
}),
], { discard: true }).pipe(Effect.catchAll(() => Effect.void))
),
);
const running = yield* measureVmEffect(
input.timing,
"mark_base_running",
repo.markBaseCreateRunning({
baseId: create.base.id,
generation: create.generation.generation,
vmId: create.vm.id,
providerVmId: handle.providerVmId,
image: handle.image,
imageVersion: input.imageVersion ?? null,
providerMetadata: handle.providerMetadata ?? create.vm.providerMetadata,
userId: input.userId,
}),
).pipe(
Effect.catchAll((err) =>
Effect.gen(function* () {
yield* providers.destroy(input.provider, handle.providerVmId).pipe(Effect.catchAll(() => Effect.void));
yield* refundCredit(billing, repo, create.vm, creditReservation);
yield* repo.markBaseCreateFailed({
baseId: create.base.id,
generation: create.generation.generation,
vmId: create.vm.id,
userId: input.userId,
code: "database_finalize_failed",
message: "Cloud VM Base state update failed.",
}).pipe(Effect.catchAll(() => Effect.void));
yield* recordCreateFailureEvent(
repo,
{
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: input.provider,
image: input.image,
},
create.vm,
"database_finalize_failed",
errorMessage(err.cause),
).pipe(Effect.catchAll(() => Effect.void));
return yield* Effect.fail(err);
}),
),
);
yield* recordCreateSuccessEvents(repo, { ...input, idempotencyKey }, running);
yield* repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: running.id,
eventType: create.previousVm ? "vm.base.reset" : "vm.base.opened",
provider: input.provider,
imageId: input.image,
metadata: {
baseName: input.baseName ?? "base",
generation: create.generation.generation,
retainedProviderVmId: create.previousVm?.providerVmId ?? null,
},
}).pipe(Effect.catchAll(() => Effect.void));
return baseVmEntryFromRows(
create.base,
create.generation,
running,
create.previousVm?.providerVmId ?? null,
);
});
}
function reopenBaseIfProviderDeleted(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
input: Parameters<VmRepositoryShape["beginBaseOpen"]>[0] & { readonly timing?: VmTimingSink },
create: Extract<BeginBaseCreateResult, { readonly kind: "existing" }>,
existing: CloudVmRow,
providerVmId: string,
): Effect.Effect<BeginBaseCreateResult | null, VmWorkflowError, never> {
const getStatus = providers.getStatus;
if (!getStatus) return Effect.succeed(null);
return getStatus(existing.provider, providerVmId).pipe(
Effect.as(null),
Effect.catchAll((err) =>
isProviderNotFoundError(err)
? Effect.gen(function* () {
const markedDestroyed = yield* repo.markProviderObservedStatus({
id: existing.id,
providerVmId,
status: "destroyed",
});
if (!markedDestroyed) {
return yield* Effect.fail(new VmNotFoundError({ vmId: providerVmId }));
}
yield* repo.recordUsageEvent({
userId: existing.userId,
billingTeamId: existing.billingTeamId,
billingPlanId: existing.billingPlanId,
vmId: existing.id,
eventType: "vm.destroyed",
provider: existing.provider,
imageId: existing.imageId,
metadata: {
source: "base_open_provider_missing",
baseName: input.baseName ?? "base",
generation: create.generation.generation,
},
}).pipe(Effect.catchAll(() => Effect.void));
return yield* measureVmEffect(
input.timing,
"begin_base_open",
repo.beginBaseOpen(input),
);
})
: Effect.succeed(null)
),
);
}
export function snapshotVm(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
readonly name?: string;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
const snapshot = yield* (providers.snapshot
? providers.snapshot(vm.provider, vm.providerVmId ?? input.providerVmId, input.name)
: Effect.fail(new VmProviderOperationError({
provider: vm.provider,
operation: "snapshot",
cause: new Error("Cloud VM snapshots are not supported by this provider gateway"),
})));
yield* repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.snapshot.created",
provider: vm.provider,
imageId: vm.imageId,
metadata: { snapshotId: snapshot.id, named: !!input.name, name: input.name ?? null },
});
return snapshot;
});
}
export function restoreVm(input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly provider: ProviderId;
readonly snapshotId: string;
readonly idempotencyKey?: string;
readonly timing?: VmTimingSink;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const hasSnapshot = yield* repo.hasOwnedSnapshot({
userId: input.userId,
billingTeamId: input.billingTeamId,
provider: input.provider,
snapshotId: input.snapshotId,
});
if (!hasSnapshot) {
return yield* Effect.fail(new VmSnapshotNotFoundError({ snapshotId: input.snapshotId }));
}
return yield* createVm({
userId: input.userId,
billingCustomerType: input.billingCustomerType,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
maxActiveVms: input.maxActiveVms,
provider: input.provider,
image: input.snapshotId,
imageVersion: null,
idempotencyKey: input.idempotencyKey,
bakedFreestyleSignedAdmin: false,
timing: input.timing,
});
});
}
export function forkVm(input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly teamIds?: readonly string[];
readonly billingPlanId: string;
readonly maxActiveVms: number;
readonly providerVmId: string;
readonly name?: string;
readonly idempotencyKey?: string;
readonly timing?: VmTimingSink;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const billing = yield* VmBillingGateway;
const source = yield* requireUserVm(input);
yield* preflightResumeIfSuspended(repo, providers, source, input.providerVmId, "fork");
if (source.provider === "freestyle" && providers.fork) {
const create = yield* beginCreateWithLazyProviderRefresh(repo, providers, {
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: source.provider,
image: source.imageId,
imageVersion: source.imageVersion,
maxActiveVms: input.maxActiveVms,
idempotencyKey: input.idempotencyKey,
timing: input.timing,
});
if (!create.inserted) {
const existing = create.vm;
if (existing.status === "failed") {
return yield* Effect.fail(
new VmCreateFailedError({
idempotencyKey: input.idempotencyKey ?? "",
code: existing.failureCode ?? null,
message: existing.failureMessage ?? "previous VM fork failed",
}),
);
}
if (!existing.providerVmId) {
return yield* Effect.fail(
new VmCreateInProgressError({ idempotencyKey: input.idempotencyKey ?? "" }),
);
}
return { snapshot: null, fork: vmEntryFromRow(existing) };
}
const creditReservation = yield* reserveCreateCredit(billing, repo, {
userId: input.userId,
billingCustomerType: input.billingCustomerType,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: source.provider,
image: source.imageId,
imageVersion: source.imageVersion,
idempotencyKey: input.idempotencyKey,
timing: input.timing,
}, create.vm);
yield* recordCreateRequestedEvents(repo, {
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: source.provider,
image: source.imageId,
imageVersion: source.imageVersion,
idempotencyKey: input.idempotencyKey,
timing: input.timing,
}, create.vm, creditReservation);
const handle = yield* measureVmEffect(
input.timing,
"provider_create",
providers.fork(source.provider, source.providerVmId ?? input.providerVmId),
).pipe(
Effect.tapError((err) =>
Effect.all([
refundCredit(billing, repo, create.vm, creditReservation),
repo.markCreateFailed({
id: create.vm.id,
code: err.operation,
message: errorMessage(err.cause),
}),
repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: create.vm.id,
eventType: "vm.create.failed",
provider: source.provider,
imageId: source.imageId,
metadata: { operation: err.operation, message: errorMessage(err.cause), sourceProviderVmId: source.providerVmId },
}),
], { discard: true }).pipe(Effect.catchAll(() => Effect.void))
),
);
const running = yield* measureVmEffect(
input.timing,
"mark_running",
repo.markCreateRunning({
id: create.vm.id,
providerVmId: handle.providerVmId,
image: source.imageId,
imageVersion: source.imageVersion,
providerMetadata: handle.providerMetadata ?? source.providerMetadata,
}),
).pipe(
Effect.catchAll((err) =>
Effect.gen(function* () {
yield* providers.destroy(source.provider, handle.providerVmId).pipe(Effect.catchAll(() => Effect.void));
yield* refundCredit(billing, repo, create.vm, creditReservation);
yield* repo.markCreateFailed({
id: create.vm.id,
code: "database_finalize_failed",
message: "Cloud VM fork state update failed.",
}).pipe(Effect.catchAll(() => Effect.void));
yield* recordCreateFailureEvent(
repo,
{
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: source.provider,
image: source.imageId,
},
create.vm,
"database_finalize_failed",
errorMessage(err.cause),
).pipe(Effect.catchAll(() => Effect.void));
return yield* Effect.fail(err);
}),
),
);
yield* recordCreateSuccessEvents(repo, input, running);
const fork = vmEntryFromRow(running);
yield* repo.recordUsageEvent({
userId: source.userId,
billingTeamId: source.billingTeamId,
billingPlanId: source.billingPlanId,
vmId: source.id,
eventType: "vm.forked",
provider: source.provider,
imageId: source.imageId,
metadata: {
native: true,
sourceProviderVmId: source.providerVmId,
forkProviderVmId: fork.providerVmId,
idempotencyKeySet: !!input.idempotencyKey,
},
}).pipe(Effect.catchAll(() => Effect.void));
return { snapshot: null, fork };
}
const snapshot = yield* snapshotVm({
userId: input.userId,
teamIds: input.teamIds,
billingTeamId: source.billingTeamId,
providerVmId: input.providerVmId,
name: input.name,
});
const fork = yield* createVm({
userId: input.userId,
billingCustomerType: input.billingCustomerType,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
maxActiveVms: input.maxActiveVms,
provider: source.provider,
image: snapshot.id,
imageVersion: null,
idempotencyKey: input.idempotencyKey,
timing: input.timing,
});
yield* repo.recordUsageEvent({
userId: source.userId,
billingTeamId: source.billingTeamId,
billingPlanId: source.billingPlanId,
vmId: source.id,
eventType: "vm.forked",
provider: source.provider,
imageId: source.imageId,
metadata: {
snapshotId: snapshot.id,
forkProviderVmId: fork.providerVmId,
idempotencyKeySet: !!input.idempotencyKey,
},
}).pipe(Effect.catchAll(() => Effect.void));
return { snapshot, fork };
});
}
function beginCreateWithLazyProviderRefresh(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
input: {
readonly userId: string;
readonly billingTeamId: string;
readonly timing?: VmTimingSink;
} & Parameters<VmRepositoryShape["beginCreate"]>[0],
): Effect.Effect<BeginCreateResult, VmWorkflowError, never> {
return measureVmEffect(input.timing, "begin_create", repo.beginCreate(input)).pipe(
Effect.catchAll((err) => {
if (!isVmLimitExceededError(err)) return Effect.fail(err);
return Effect.gen(function* () {
yield* measureVmEffect(
input.timing,
"limit_reconcile",
refreshActiveLimitProviderStatuses(repo, providers, input),
).pipe(Effect.catchAll(() => Effect.void));
return yield* measureVmEffect(input.timing, "begin_create", repo.beginCreate(input));
});
}),
);
}
function refreshActiveLimitProviderStatuses(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
input: {
readonly userId: string;
readonly billingTeamId: string;
},
): Effect.Effect<void, VmDatabaseError, never> {
return Effect.gen(function* () {
const getStatus = providers.getStatus;
if (!getStatus) return;
const candidates = yield* repo.activeLimitCandidates({
userId: input.userId,
billingTeamId: input.billingTeamId,
});
yield* Effect.forEach(candidates, (vm) => {
const providerVmId = vm.providerVmId;
if (vm.provider !== "freestyle" || !providerVmId) return Effect.void;
return reconcileObservedProviderStatus(repo, getStatus, vm, "provider_status_refresh").pipe(
Effect.asVoid,
);
}, { concurrency: "unbounded", discard: true });
});
}
function dbStatusFromProviderStatus(status: "running" | "paused" | "destroyed"): CloudVmStatus {
return status;
}
type ProviderStatusReconcileOutcome = "updated" | "destroyed" | "unchanged" | "skipped";
function reconcileObservedProviderStatus(
repo: VmRepositoryShape,
getStatus: NonNullable<VmProviderGatewayShape["getStatus"]>,
vm: CloudVmRow,
usageEventSource: string,
): Effect.Effect<ProviderStatusReconcileOutcome, never> {
return Effect.gen(function* () {
const providerVmId = vm.providerVmId;
if (!providerVmId) return "skipped" as const;
const providerStatus = yield* getStatus(vm.provider, providerVmId).pipe(
Effect.catchAll((err) =>
isProviderNotFoundError(err)
? Effect.succeed("destroyed" as const)
: Effect.succeed(null),
),
);
if (!providerStatus || providerStatus === "creating") return "skipped" as const;
const dbStatus = dbStatusFromProviderStatus(providerStatus);
if (dbStatus === vm.status) return "unchanged" as const;
const didUpdate = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: dbStatus,
}).pipe(Effect.catchAll(() => Effect.succeed(false)));
if (!didUpdate) return "skipped" as const;
if (dbStatus === "destroyed") {
yield* repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.destroyed",
provider: vm.provider,
imageId: vm.imageId,
metadata: { source: usageEventSource },
}).pipe(Effect.catchAll(() => Effect.void));
return "destroyed" as const;
}
return "updated" as const;
});
}
function boundedVmStatusReconcileLimit(limit: number | undefined): number {
if (limit === undefined || !Number.isFinite(limit)) return VM_STATUS_RECONCILE_BATCH_LIMIT;
return Math.max(1, Math.min(VM_STATUS_RECONCILE_BATCH_LIMIT, Math.trunc(limit)));
}
const RESUME_STATUS_PROBE_TIMEOUT = "5 seconds";
const RESUME_SETTLE_ATTEMPTS = 10;
const RESUME_SETTLE_INTERVAL = "1 second";
type VmResumeSource = "exec" | "attach" | "ssh" | "fork";
// resume() can legitimately return a not-yet-running handle (Freestyle maps a
// post-start "starting" state to "creating"), so poll briefly until the VM is
// observably running; never record a running transition for a VM that has not
// settled, and fail without a durable write if it does not.
function waitForRunningStatus(
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
): Effect.Effect<boolean, never> {
return Effect.gen(function* () {
const getStatus = providers.getStatus;
if (!getStatus) return true;
for (let attempt = 0; attempt < RESUME_SETTLE_ATTEMPTS; attempt += 1) {
const status = yield* getStatus(vm.provider, providerVmId).pipe(
Effect.timeoutFail({
duration: RESUME_STATUS_PROBE_TIMEOUT,
onTimeout: () =>
new VmProviderOperationError({
provider: vm.provider,
operation: `getStatus(${providerVmId})`,
cause: new Error("status probe timed out"),
}),
}),
Effect.catchAll(() => Effect.succeed(null as VMStatus | null)),
);
if (status === "running") return true;
yield* Effect.sleep(RESUME_SETTLE_INTERVAL);
}
return false;
});
}
function bestEffortPause(
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
): Effect.Effect<void, never> {
const pause = providers.pause;
if (!pause) return Effect.void;
return pause(vm.provider, providerVmId).pipe(Effect.catchAll(() => Effect.void));
}
function resumeUntilRunning(
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
): Effect.Effect<void, VmWorkflowError> {
return Effect.gen(function* () {
const resume = providers.resume;
if (!resume) return;
const handle = yield* resume(vm.provider, providerVmId);
if (handle.status === "running") return;
const settled = yield* waitForRunningStatus(providers, vm, providerVmId);
if (settled) return;
// The provider start already happened; roll back so a started-but-
// unrecorded VM is never left running outside Postgres accounting.
yield* bestEffortPause(providers, vm, providerVmId);
return yield* Effect.fail(
new VmProviderOperationError({
provider: vm.provider,
operation: `resume(${providerVmId})`,
cause: new Error("VM did not reach running after resume"),
}),
);
});
}
function reservePausedResumeIfTeam(
repo: VmRepositoryShape,
vm: CloudVmRow,
providerVmId: string,
): Effect.Effect<boolean, VmWorkflowError> {
if (!vm.billingTeamId) return Effect.succeed(false);
return Effect.gen(function* () {
const reserved = yield* repo.reservePausedResume({
id: vm.id,
userId: vm.userId,
billingTeamId: vm.billingTeamId,
providerVmId,
maxActiveVms: maxActiveVmsForPlan(vm.billingPlanId),
});
if (!reserved) {
return yield* Effect.fail(new VmNotFoundError({ vmId: providerVmId }));
}
if (reserved.status !== "running") {
return yield* Effect.fail(
new VmProviderOperationError({
provider: vm.provider,
operation: `reservePausedResume(${providerVmId})`,
cause: new Error(`VM resume reservation returned ${reserved.status}`),
}),
);
}
return vm.status === "paused";
});
}
function rollbackPausedResumeReservation(
repo: VmRepositoryShape,
vm: CloudVmRow,
providerVmId: string,
reserved: boolean,
): Effect.Effect<void, never> {
if (!reserved) return Effect.void;
return repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: "paused",
}).pipe(Effect.catchAll(() => Effect.void));
}
function recordResumeUsageEvent(
repo: VmRepositoryShape,
vm: CloudVmRow,
resumeSource: VmResumeSource,
): Effect.Effect<void, never> {
return repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.resumed",
provider: vm.provider,
imageId: vm.imageId,
metadata: { source: resumeSource },
}).pipe(Effect.catchAll(() => Effect.void));
}
// Active-limit note: the control-plane-owned paused-row resume path is
// limit-gated for billing teams by reservePausedResume before the provider
// resume starts. Freestyle can still resume a VM outside the control plane
// (for example through its SSH gateway); those already-running observations
// are reconciled durably here, and beginCreate re-counts provider-running VMs
// before allocating another active slot.
function preflightResumeIfSuspended(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
resumeSource: VmResumeSource,
): Effect.Effect<boolean, VmWorkflowError> {
return Effect.gen(function* () {
const getStatus = providers.getStatus;
const resume = providers.resume;
if (!getStatus || !resume) return false;
const status = yield* getStatus(vm.provider, providerVmId).pipe(
Effect.timeoutFail({
duration: RESUME_STATUS_PROBE_TIMEOUT,
onTimeout: () =>
new VmProviderOperationError({
provider: vm.provider,
operation: `getStatus(${providerVmId})`,
cause: new Error("status probe timed out"),
}),
}),
Effect.catchAll((err) =>
// Fail closed when the row durably says paused and the probe cannot
// prove otherwise: minting endpoints against a suspended VM would
// hand out unusable credentials and record leases/usage for it.
vm.status === "paused"
? Effect.fail(err)
: Effect.succeed(null as VMStatus | null),
),
);
if (status === "creating") {
// Another caller's resume is in flight; wait for it rather than
// minting endpoints or running commands against a not-yet-ready VM.
const settled = yield* waitForRunningStatus(providers, vm, providerVmId);
if (!settled) {
return yield* Effect.fail(
new VmProviderOperationError({
provider: vm.provider,
operation: `getStatus(${providerVmId})`,
cause: new Error("VM stayed in a resuming state"),
}),
);
}
// Persist the observed running state ourselves in case the resuming
// caller dies before its own durable write. An already-running row
// still matches the update (returns true); false means the row was
// destroyed or replaced concurrently, so fail closed. No pause
// rollback here: the caller that started the VM owns compensation.
const recorded = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: "running",
});
if (!recorded) {
return yield* Effect.fail(new VmNotFoundError({ vmId: providerVmId }));
}
return false;
}
if (status === "running") {
// Freestyle's SSH gateway can resume a VM entirely outside the control
// plane; if the durable row still says paused, record the observed
// running state so active-limit reconciliation can see the VM.
if (vm.status === "paused") {
const recorded = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: "running",
});
if (!recorded) {
return yield* Effect.fail(new VmNotFoundError({ vmId: providerVmId }));
}
}
return false;
}
if (status !== "paused") return false;
const reserved = yield* reservePausedResumeIfTeam(repo, vm, providerVmId);
yield* resumeUntilRunning(providers, vm, providerVmId).pipe(
Effect.tapError(() => rollbackPausedResumeReservation(repo, vm, providerVmId, reserved)),
);
yield* recordRunningTransition(
repo,
providers,
vm,
providerVmId,
new VmNotFoundError({ vmId: providerVmId }),
).pipe(
Effect.tapError(() => rollbackPausedResumeReservation(repo, vm, providerVmId, reserved)),
);
if (reserved) yield* recordResumeUsageEvent(repo, vm, resumeSource);
return true;
});
}
function withResumeOnSuspendedAfterFailure<A>(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
resumeSource: VmResumeSource,
op: Effect.Effect<A, VmWorkflowError>,
): Effect.Effect<A, VmWorkflowError> {
return op.pipe(
Effect.catchAll((originalError) => {
const getStatus = providers.getStatus;
const resume = providers.resume;
if (!getStatus || !resume) return Effect.fail(originalError);
return Effect.gen(function* () {
const status = yield* getStatus(vm.provider, providerVmId).pipe(
Effect.catchAll(() => Effect.succeed(null as VMStatus | null)),
);
if (status === "creating") {
const settled = yield* waitForRunningStatus(providers, vm, providerVmId);
if (!settled) return yield* Effect.fail(originalError);
const recorded = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: "running",
}).pipe(Effect.catchAll(() => Effect.succeed(false)));
if (!recorded) return yield* Effect.fail(originalError);
return yield* op;
}
if (status !== "paused") {
return yield* Effect.fail(originalError);
}
const reserved = yield* reservePausedResumeIfTeam(repo, vm, providerVmId);
yield* resumeUntilRunning(providers, vm, providerVmId).pipe(
Effect.tapError(() => rollbackPausedResumeReservation(repo, vm, providerVmId, reserved)),
Effect.catchAll(() => Effect.fail(originalError)),
);
yield* recordRunningTransition(repo, providers, vm, providerVmId, originalError).pipe(
Effect.tapError(() => rollbackPausedResumeReservation(repo, vm, providerVmId, reserved)),
);
if (reserved) yield* recordResumeUsageEvent(repo, vm, resumeSource);
return yield* op;
});
}),
);
}
// After a successful provider resume, Postgres must record the running
// transition before the workflow proceeds. When the write fails (or the row
// was destroyed concurrently), roll the provider back to the durable state
// with a best-effort pause so a running VM is never left invisible to
// active-limit accounting; Freestyle's idle auto-suspend (~10s) is the
// backstop if the pause itself fails.
function recordRunningTransition<E extends VmWorkflowError>(
repo: VmRepositoryShape,
providers: VmProviderGatewayShape,
vm: CloudVmRow,
providerVmId: string,
staleRowError: E,
): Effect.Effect<void, VmDatabaseError | E> {
const rollbackPause = (): Effect.Effect<void, never> => {
const pause = providers.pause;
if (!pause) return Effect.void;
return pause(vm.provider, providerVmId).pipe(Effect.catchAll(() => Effect.void));
};
return Effect.gen(function* () {
const didUpdate = yield* repo.markProviderObservedStatus({
id: vm.id,
providerVmId,
status: "running",
}).pipe(
Effect.tapError(() => rollbackPause()),
);
if (!didUpdate) {
yield* rollbackPause();
return yield* Effect.fail(staleRowError);
}
});
}
export function destroyVm(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
yield* revokeActiveIdentities(vm, { failOnCleanupError: true });
yield* providers.destroy(vm.provider, vm.providerVmId ?? input.providerVmId).pipe(
Effect.catchAll((err) => {
if (isProviderNotFoundError(err.cause)) return Effect.void;
return Effect.fail(err);
}),
);
yield* repo.markDestroyed(vm.id);
yield* repo.recordUsageEvent({
userId: input.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.destroyed",
provider: vm.provider,
imageId: vm.imageId,
}).pipe(Effect.catchAll(() => Effect.void));
});
}
export function revokeExpiredIdentityLeases(input: {
readonly now?: Date;
readonly limit?: number;
} = {}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const expiredIdentityLeases = repo.expiredIdentityLeases;
if (!expiredIdentityLeases) return 0;
const now = input.now ?? new Date();
const leases = yield* expiredIdentityLeases({
now,
limit: input.limit ?? EXPIRED_IDENTITY_REVOKE_BATCH,
});
const revokedIds: string[] = [];
for (const lease of leases) {
const identityHandle = lease.providerIdentityHandle;
if (!identityHandle) continue;
const retryAfter = new Date(now.getTime() + EXPIRED_IDENTITY_REVOKE_RETRY_BACKOFF_MS);
yield* (repo.markLeaseRevocationRetry?.({
id: lease.id,
retryAfter,
error: "revoke pending",
}) ?? Effect.void).pipe(Effect.catchAll(() => Effect.void));
const revoked = yield* revokeSSHIdentityForCleanup(providers, lease.provider, identityHandle).pipe(
Effect.as(true),
Effect.catchAll((err) => {
if (isProviderIdentityNotFoundError(err.cause)) return Effect.succeed(true);
return Effect.succeed(false);
}),
);
if (revoked) revokedIds.push(lease.id);
}
yield* repo.markLeasesRevoked(revokedIds);
return revokedIds.length;
});
}
export function execVm(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
readonly command: string;
readonly timeoutMs: number;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
yield* preflightResumeIfSuspended(
repo,
providers,
vm,
input.providerVmId,
"exec",
);
const result = yield* providers.exec(vm.provider, input.providerVmId, input.command, {
timeoutMs: input.timeoutMs,
});
yield* repo.recordUsageEvent({
userId: input.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.exec",
provider: vm.provider,
imageId: vm.imageId,
metadata: { commandLength: input.command.length, exitCode: result.exitCode },
}).pipe(Effect.catchAll(() => Effect.void));
return result satisfies ExecResult;
});
}
type OpenAttachEndpointInput = {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
readonly options?: AttachOptions;
readonly sessionTitle?: string | null;
};
export function openAttachEndpoint(input: OpenAttachEndpointInput) {
return Effect.gen(function* () {
const result = yield* openAttachEndpointResult(input);
return result.endpoint;
});
}
export function openVmSession(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
readonly sessionId?: string;
readonly attachmentId?: string;
readonly title?: string | null;
}) {
const sessionId = input.sessionId?.trim() || `session-${randomUUID()}`;
const attachmentId = input.attachmentId?.trim() || `attach-${randomUUID()}`;
return openAttachEndpointResult({
userId: input.userId,
billingTeamId: input.billingTeamId,
teamIds: input.teamIds,
providerVmId: input.providerVmId,
sessionTitle: input.title,
options: {
requireDaemon: true,
sessionId,
attachmentId,
},
});
}
export function listVmSessions(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const vm = yield* requireUserVm(input);
return yield* repo.listVmSessions({ userId: input.userId, vmId: vm.id });
});
}
function openAttachEndpointResult(input: OpenAttachEndpointInput) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
yield* preflightResumeIfSuspended(repo, providers, vm, input.providerVmId, "attach");
// Once preflight records the VM as running, that state is externally
// visible to concurrent attach/SSH requests. Later cleanup failures must
// fail closed without pausing a VM another request may have attached to.
yield* revokeActiveIdentities(vm, { failOnCleanupError: true });
const endpoint = yield* withResumeOnSuspendedAfterFailure(
repo,
providers,
vm,
input.providerVmId,
"attach",
providers.openAttach(vm.provider, input.providerVmId, {
...(input.options ?? {}),
providerMetadata: vm.providerMetadata,
}),
);
yield* storeEndpointLeases(vm, endpoint).pipe(
Effect.catchAll((err) =>
revokeEndpointIdentity(vm.provider, endpoint).pipe(
Effect.andThen(Effect.fail(err)),
),
),
);
yield* repo.recordUsageEvent({
userId: input.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.attach",
provider: vm.provider,
imageId: vm.imageId,
metadata: {
transport: endpoint.transport,
requireDaemon: input.options?.requireDaemon === true,
requestedSessionId: input.options?.sessionId ?? null,
daemonAvailable: endpoint.transport === "websocket" && !!endpoint.daemon,
},
}).pipe(Effect.catchAll(() => Effect.void));
const session = endpoint.transport === "websocket"
? yield* repo.upsertVmSession({
vmId: vm.id,
userId: input.userId,
providerSessionId: endpoint.sessionId,
title: input.sessionTitle ?? null,
status: "running",
attachmentCount: 1,
metadata: {
transport: endpoint.transport,
daemonAvailable: !!endpoint.daemon,
attachmentId: endpoint.attachmentId,
},
})
: undefined;
return { endpoint, session };
});
}
export function openSshEndpoint(input: {
readonly userId: string;
readonly billingTeamId?: string | null;
readonly teamIds?: readonly string[];
readonly providerVmId: string;
}) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const vm = yield* requireUserVm(input);
yield* preflightResumeIfSuspended(repo, providers, vm, input.providerVmId, "ssh");
yield* revokeActiveIdentities(vm, { failOnCleanupError: true });
const endpoint = yield* withResumeOnSuspendedAfterFailure(
repo,
providers,
vm,
input.providerVmId,
"ssh",
providers.openSSH(vm.provider, input.providerVmId),
);
yield* storeEndpointLeases(vm, endpoint).pipe(
Effect.catchAll((err) =>
revokeEndpointIdentity(vm.provider, endpoint).pipe(
Effect.andThen(Effect.fail(err)),
),
),
);
yield* repo.recordUsageEvent({
userId: input.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.ssh_endpoint",
provider: vm.provider,
imageId: vm.imageId,
metadata: { credentialKind: endpoint.credential.kind },
}).pipe(Effect.catchAll(() => Effect.void));
return endpoint;
});
}
function requireUserVm(input: ExistingVmAccessInput) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const vm = yield* repo.findUserVm({
userId: input.userId,
billingTeamId: input.billingTeamId,
providerVmId: input.providerVmId,
});
if (!vm || !vm.providerVmId) {
return yield* Effect.fail(new VmNotFoundError({ vmId: input.providerVmId }));
}
if (!callerStillOwnsBillingScope(input, vm)) {
return yield* Effect.fail(new VmNotFoundError({ vmId: input.providerVmId }));
}
return vm;
});
}
function callerStillOwnsBillingScope(input: ExistingVmAccessInput, vm: CloudVmRow): boolean {
const billingTeamId = vm.billingTeamId?.trim();
if (!billingTeamId) return true;
if (billingTeamId === input.userId) return true;
if (!input.teamIds) return false;
return new Set(input.teamIds).has(billingTeamId);
}
function revokeActiveIdentities(
vm: CloudVmRow,
options: { readonly failOnCleanupError?: boolean; readonly limit?: number } = {},
) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
const providers = yield* VmProviderGateway;
const leases = yield* repo.activeIdentityLeases(
vm.id,
options.failOnCleanupError ? ACTIVE_IDENTITY_REVOKE_HOT_PATH_LIMIT + 1 : options.limit,
);
if (options.failOnCleanupError && leases.length > ACTIVE_IDENTITY_REVOKE_HOT_PATH_LIMIT) {
return yield* Effect.fail(new VmProviderOperationError({
provider: vm.provider,
operation: "revokeSSHIdentity",
cause: new Error(`too many active identity leases pending cleanup: ${leases.length}`),
}));
}
const revokedIds: string[] = [];
for (const lease of leases) {
const identityHandle = lease.providerIdentityHandle;
if (!identityHandle) continue;
const revoked = yield* revokeSSHIdentityForCleanup(providers, vm.provider, identityHandle).pipe(
Effect.as(true),
Effect.catchAll((err) => {
if (isProviderIdentityNotFoundError(err.cause)) return Effect.succeed(true);
if (!options.failOnCleanupError) return Effect.succeed(false);
return repo.markLeasesRevoked(revokedIds).pipe(
Effect.andThen(Effect.fail(err)),
);
}),
);
if (revoked) revokedIds.push(lease.id);
}
yield* repo.markLeasesRevoked(revokedIds);
});
}
function revokeSSHIdentityForCleanup(
providers: VmProviderGatewayShape,
provider: ProviderId,
identityHandle: string,
): Effect.Effect<void, VmProviderOperationError> {
return providers.revokeSSHIdentity(provider, identityHandle).pipe(
Effect.timeoutFail({
duration: IDENTITY_REVOKE_PROVIDER_TIMEOUT,
onTimeout: () =>
new VmProviderOperationError({
provider,
operation: "revokeSSHIdentity",
cause: new Error("identity revoke timed out"),
}),
}),
);
}
function storeEndpointLeases(vm: CloudVmRow, endpoint: AttachEndpoint | SSHEndpoint) {
return Effect.gen(function* () {
if (endpoint.transport === "ssh") {
yield* recordEndpointLease(vm, {
kind: "ssh",
token: sshCredentialToken(endpoint),
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
providerIdentityHandle: endpoint.identityHandle || undefined,
transport: "ssh",
metadata: { credentialKind: endpoint.credential.kind },
});
if (endpoint.daemon) {
yield* recordEndpointLease(vm, {
kind: "rpc",
token: endpoint.daemon.token,
expiresAt: new Date(endpoint.daemon.expiresAtUnix * 1000),
sessionId: endpoint.daemon.sessionId,
transport: "websocket",
});
}
return;
}
yield* recordEndpointLease(vm, {
kind: "pty",
token: endpoint.token,
expiresAt: new Date(endpoint.expiresAtUnix * 1000),
sessionId: endpoint.sessionId,
transport: "websocket",
});
if (endpoint.daemon) {
yield* recordEndpointLease(vm, {
kind: "rpc",
token: endpoint.daemon.token,
expiresAt: new Date(endpoint.daemon.expiresAtUnix * 1000),
sessionId: endpoint.daemon.sessionId,
transport: "websocket",
});
}
});
}
function recordCreditEvent(
repo: VmRepositoryShape,
vm: CloudVmRow,
eventType: string,
reservation: VmCreateCreditReservation,
) {
if (reservation.kind === "none") return Effect.void;
return repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType,
provider: vm.provider,
imageId: vm.imageId,
metadata: {
itemId: reservation.itemId,
amount: reservation.amount,
customerType: reservation.customerType,
customerIdSet: !!reservation.customerId,
},
});
}
function reserveCreateCredit(
billing: VmBillingGatewayShape,
repo: VmRepositoryShape,
input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly idempotencyKey?: string;
readonly timing?: VmTimingSink;
},
vm: CloudVmRow,
) {
return measureVmEffect(
input.timing,
"billing",
Effect.gen(function* () {
yield* seedInitialCreateCredits(billing, repo, input, vm).pipe(
Effect.catchAll((err) =>
repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: vm.id,
eventType: "vm.create.credit.grant_failed",
provider: input.provider,
imageId: input.image,
metadata: {
idempotencyKeySet: !!input.idempotencyKey,
imageVersion: input.imageVersion ?? null,
message: errorMessage(err),
},
}).pipe(Effect.catchAll(() => Effect.void))
),
);
const creditReservation = yield* billing.reserveCreate({
userId: input.userId,
billingCustomerType: input.billingCustomerType,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
provider: input.provider,
image: input.image,
imageVersion: input.imageVersion ?? null,
vmId: vm.id,
idempotencyKey: input.idempotencyKey,
}).pipe(
Effect.tapError((err) =>
Effect.all([
repo.markCreateFailed({
id: vm.id,
code: isVmCreateCreditsInsufficientError(err)
? "billing_credits_insufficient"
: "billing_reserve_failed",
message: errorMessage(err),
}),
repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: vm.id,
eventType: "vm.create.billing_failed",
provider: input.provider,
imageId: input.image,
metadata: {
idempotencyKeySet: !!input.idempotencyKey,
imageVersion: input.imageVersion ?? null,
errorTag: typeof err === "object" && err !== null && "_tag" in err
? String((err as { _tag?: unknown })._tag)
: null,
},
}),
], { discard: true }).pipe(Effect.catchAll(() => Effect.void))
),
);
return creditReservation;
}),
);
}
function recordCreateRequestedEvents(
repo: VmRepositoryShape,
input: {
readonly userId: string;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly provider: ProviderId;
readonly image: string;
readonly imageVersion?: string | null;
readonly idempotencyKey?: string;
readonly timing?: VmTimingSink;
},
requestedVm: CloudVmRow,
creditReservation: VmCreateCreditReservation,
) {
return measureVmEffect(
input.timing,
"usage_events",
repo.recordUsageEvents([
...(creditReservation.kind === "none"
? []
: [creditUsageEvent(requestedVm, "vm.create.credit.reserved", creditReservation)]),
{
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: requestedVm.id,
eventType: "vm.create.requested",
provider: input.provider,
imageId: input.image,
metadata: {
idempotencyKeySet: !!input.idempotencyKey,
imageVersion: input.imageVersion ?? null,
},
},
]).pipe(Effect.catchAll(() => Effect.void)),
);
}
function recordCreateSuccessEvents(
repo: VmRepositoryShape,
input: {
readonly idempotencyKey?: string;
readonly timing?: VmTimingSink;
},
running: CloudVmRow,
) {
return measureVmEffect(
input.timing,
"usage_events",
repo.recordUsageEvents([
{
userId: running.userId,
billingTeamId: running.billingTeamId,
billingPlanId: running.billingPlanId,
vmId: running.id,
eventType: "vm.created",
provider: running.provider,
imageId: running.imageId,
metadata: {
idempotencyKeySet: !!input.idempotencyKey,
imageVersion: running.imageVersion,
},
},
]).pipe(Effect.catchAll(() => Effect.void)),
);
}
function recordCreateFailureEvent(
repo: VmRepositoryShape,
input: {
readonly userId: string;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly provider: ProviderId;
readonly image: string;
},
requestedVm: CloudVmRow,
operation: string,
message: string,
) {
return repo.recordUsageEvent({
userId: input.userId,
billingTeamId: input.billingTeamId,
billingPlanId: input.billingPlanId,
vmId: requestedVm.id,
eventType: "vm.create.failed",
provider: input.provider,
imageId: input.image,
metadata: { operation, message },
});
}
function creditUsageEvent(
vm: CloudVmRow,
eventType: string,
reservation: Exclude<VmCreateCreditReservation, { readonly kind: "none" }>,
) {
return {
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType,
provider: vm.provider,
imageId: vm.imageId,
metadata: {
itemId: reservation.itemId,
amount: reservation.amount,
customerType: reservation.customerType,
customerIdSet: !!reservation.customerId,
},
};
}
function seedInitialCreateCredits(
billing: VmBillingGatewayShape,
repo: VmRepositoryShape,
input: {
readonly userId: string;
readonly billingCustomerType: BillingCustomerType;
readonly billingTeamId: string;
readonly billingPlanId: string;
readonly provider: ProviderId;
},
vm: CloudVmRow,
) {
return Effect.gen(function* () {
const grant = yield* Effect.try({
try: () => billing.resolveInitialCreateCreditGrant(input),
catch: (cause) => new VmBillingError({ operation: "resolveInitialCreateCreditGrant", cause }),
});
if (grant.kind === "none") return;
const claim = yield* repo.claimBillingGrant({
billingCustomerType: grant.customerType,
billingCustomerId: grant.customerId,
billingPlanId: input.billingPlanId,
itemId: grant.itemId,
amount: grant.amount,
reason: grant.reason,
});
if (claim.kind !== "inserted") return;
yield* billing.applyCreateCreditGrant(grant).pipe(
Effect.tapError(() =>
repo.deleteBillingGrant(claim.grantId).pipe(Effect.catchAll(() => Effect.void))
),
);
yield* repo.markBillingGrantApplied(claim.grantId).pipe(Effect.catchAll(() => Effect.void));
yield* recordGrantEvent(repo, vm, "vm.create.credit.granted", grant)
.pipe(Effect.catchAll(() => Effect.void));
});
}
function recordGrantEvent(
repo: VmRepositoryShape,
vm: CloudVmRow,
eventType: string,
grant: VmCreateCreditGrant,
) {
if (grant.kind === "none") return Effect.void;
return repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType,
provider: vm.provider,
imageId: vm.imageId,
metadata: {
itemId: grant.itemId,
amount: grant.amount,
reason: grant.reason,
customerType: grant.customerType,
customerIdSet: !!grant.customerId,
},
});
}
function refundCredit(
billing: VmBillingGatewayShape,
repo: VmRepositoryShape,
vm: CloudVmRow,
reservation: VmCreateCreditReservation,
) {
return billing.refundCreate(reservation).pipe(
Effect.andThen(recordCreditEvent(repo, vm, "vm.create.credit.refunded", reservation)),
Effect.catchAll(() => Effect.void),
);
}
function recordEndpointLease(
vm: CloudVmRow,
input: {
readonly kind: CloudVmLeaseKind;
readonly token: string;
readonly expiresAt: Date;
readonly providerIdentityHandle?: string;
readonly sessionId?: string;
readonly transport?: string;
readonly metadata?: Record<string, unknown>;
},
) {
return Effect.gen(function* () {
const repo = yield* VmRepository;
yield* repo.recordLease({
vmId: vm.id,
userId: vm.userId,
kind: input.kind,
tokenHash: hashToken(input.token),
expiresAt: input.expiresAt,
providerIdentityHandle: input.providerIdentityHandle,
sessionId: input.sessionId,
transport: input.transport,
metadata: input.metadata,
});
});
}
function revokeEndpointIdentity(provider: ProviderId, endpoint: AttachEndpoint | SSHEndpoint) {
return Effect.gen(function* () {
if (endpoint.transport !== "ssh" || !endpoint.identityHandle) return;
const providers = yield* VmProviderGateway;
yield* providers.revokeSSHIdentity(provider, endpoint.identityHandle).pipe(Effect.catchAll(() => Effect.void));
});
}
function vmEntryFromRow(row: CloudVmRow): VmEntry {
if (!row.providerVmId) {
throw new Error(`VM row has no provider VM id: ${row.id}`);
}
return {
providerVmId: row.providerVmId,
provider: row.provider,
image: row.imageId,
imageVersion: row.imageVersion,
status: row.status,
createdAt: row.createdAt.getTime(),
};
}
function baseVmEntryFromRows(
base: CloudVmBaseRow,
generation: CloudVmBaseGenerationRow,
row: CloudVmRow,
retainedProviderVmId: string | null,
): BaseVmEntry {
return {
...vmEntryFromRow(row),
baseId: base.id,
baseName: base.name,
generation: generation.generation,
retainedProviderVmId,
};
}
function sshCredentialToken(endpoint: SSHEndpoint): string {
return endpoint.credential.kind === "password"
? endpoint.credential.value
: endpoint.credential.privateKeyPem;
}
function hashToken(token: string): string {
return createHash("sha256").update(token).digest("hex");
}
function errorMessage(cause: unknown): string {
return cause instanceof Error ? cause.message : String(cause);
}