@claxedo/sandbox-manager 0.10.1 → 0.10.3
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 +100 -30
- package/dist/checkpoint-manager.mjs +19 -0
- package/dist/contract.d.ts +40 -17
- package/dist/driver-billing.d.ts +23 -0
- package/dist/driver-catalog.mjs +57 -3
- package/dist/driver-ensure-input.d.ts +22 -0
- package/dist/drivers/agent37-client.d.ts +87 -0
- package/dist/drivers/agent37.d.ts +25 -0
- package/dist/drivers/agent37.mjs +888 -0
- package/dist/drivers/boat-client.d.ts +4 -4
- package/dist/drivers/boat-machine.d.ts +80 -0
- package/dist/drivers/boat-preparation.test-support.d.ts +2 -0
- package/dist/drivers/boat-template.mjs +196 -92
- package/dist/drivers/boat.d.ts +0 -17
- package/dist/drivers/boat.mjs +200 -108
- package/dist/drivers/cloudflare.d.ts +5 -9
- package/dist/drivers/cloudflare.mjs +71 -7
- package/dist/drivers/docker.mjs +57 -2
- package/dist/drivers/local-brokering.d.ts +12 -0
- package/dist/drivers/local-brokering.mjs +99 -24
- package/dist/drivers/modal.mjs +57 -2
- package/dist/drivers/transient-error.d.ts +0 -1
- package/dist/drivers/vercel.mjs +61 -3
- package/dist/egress-policy.d.ts +33 -32
- package/dist/egress-warnings.d.ts +4 -6
- package/dist/hosted-network-policy.d.ts +10 -64
- package/dist/hosted-network-policy.mjs +1 -1
- package/dist/image-name.d.ts +0 -1
- package/dist/image-name.mjs +5 -0
- package/dist/image.d.ts +1 -0
- package/dist/image.mjs +6 -0
- package/dist/index.mjs +180 -112
- package/dist/json.d.ts +1 -10
- package/dist/lease-failure.d.ts +20 -1
- package/dist/lease-types.d.ts +1 -10
- package/dist/runtime-env.d.ts +6 -0
- package/dist/runtime-env.mjs +5 -0
- package/dist/runtime-version.d.ts +25 -7
- package/dist/runtime-version.mjs +13 -2
- package/dist/start-admission.d.ts +32 -0
- package/dist/start-telemetry.d.ts +5 -3
- package/package.json +12 -4
package/dist/index.mjs
CHANGED
|
@@ -83,9 +83,9 @@ function sandboxNetworkPolicyWithBrokeredHosts(policy, secrets) {
|
|
|
83
83
|
...hosts.length ? { hosts } : {}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
function sandboxEgressDisposition(control, net) {
|
|
86
|
+
function sandboxEgressDisposition(control, net, uncontained = "refuse") {
|
|
87
87
|
if (!net || net.mode === "allow-all") return { action: "enforce" };
|
|
88
|
-
if (control === "none") return { action: "withhold", reason: "sandbox_egress_uncontained" };
|
|
88
|
+
if (control === "none") return { action: uncontained === "allow" ? "withhold" : "refuse", reason: "sandbox_egress_uncontained" };
|
|
89
89
|
const hosts = net.hosts?.length ?? 0;
|
|
90
90
|
const cidrs = net.cidrs?.length ?? 0;
|
|
91
91
|
if (hosts === 0 && cidrs === 0) return { action: "enforce" };
|
|
@@ -110,6 +110,27 @@ var boatBilling = {
|
|
|
110
110
|
large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
|
+
var AGENT37_USD_PER_MONTH = { vcpu: 0.8, memoryGb: 0.7, diskGb: 0.09 };
|
|
114
|
+
var AGENT37_MONTH_HOURS = 730;
|
|
115
|
+
var agent37MachineShapes = {
|
|
116
|
+
small: { cpu: 2, memory: 4, disk: 10 },
|
|
117
|
+
default: { cpu: 4, memory: 8, disk: 20 },
|
|
118
|
+
large: { cpu: 4, memory: 16, disk: 40 }
|
|
119
|
+
};
|
|
120
|
+
function agent37Rate(shape) {
|
|
121
|
+
const usdPerMonth = shape.cpu * AGENT37_USD_PER_MONTH.vcpu + shape.memory * AGENT37_USD_PER_MONTH.memoryGb + shape.disk * AGENT37_USD_PER_MONTH.diskGb;
|
|
122
|
+
return { size: `${shape.cpu} vCPU / ${shape.memory} GB`, vcpu: shape.cpu, memoryGb: shape.memory, usdPerHour: usdPerMonth / AGENT37_MONTH_HOURS };
|
|
123
|
+
}
|
|
124
|
+
var agent37Billing = {
|
|
125
|
+
source: { url: "https://www.agent37.com/docs/agents-api/billing", readOn: "2026-11-11" },
|
|
126
|
+
meter: "Agent37 meters an instance per minute at a monthly rate over 730 hours: $0.80 per vCPU, $0.70 per GB of memory and $0.09 per GB of disk while it runs, and the disk alone while it is stopped.",
|
|
127
|
+
rates: {
|
|
128
|
+
small: agent37Rate(agent37MachineShapes.small),
|
|
129
|
+
default: agent37Rate(agent37MachineShapes.default),
|
|
130
|
+
large: agent37Rate(agent37MachineShapes.large)
|
|
131
|
+
},
|
|
132
|
+
excludes: `the disk of a stopped instance, billed at $${AGENT37_USD_PER_MONTH.diskGb} per GB a month until the instance is deleted`
|
|
133
|
+
};
|
|
113
134
|
var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
|
|
114
135
|
var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
|
|
115
136
|
var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
|
|
@@ -314,16 +335,17 @@ function isPrivateIpAddress(ip) {
|
|
|
314
335
|
return isPrivateIpv4(embedded);
|
|
315
336
|
return false;
|
|
316
337
|
}
|
|
317
|
-
var sandboxCheckpointUnusableCauses = ["expired", "
|
|
338
|
+
var sandboxCheckpointUnusableCauses = ["expired", "runtime_modified", "not_restorable"];
|
|
318
339
|
function isSandboxCheckpointUnusableCause(value) {
|
|
319
340
|
return sandboxCheckpointUnusableCauses.some((cause) => cause === value);
|
|
320
341
|
}
|
|
321
|
-
var sandboxDriverIds = ["modal", "vercel", "cloudflare", "boat", "docker"];
|
|
342
|
+
var sandboxDriverIds = ["modal", "vercel", "cloudflare", "boat", "agent37", "docker"];
|
|
322
343
|
var sandboxDriverLabels = {
|
|
323
344
|
modal: "Modal",
|
|
324
345
|
vercel: "Vercel",
|
|
325
346
|
cloudflare: "Cloudflare",
|
|
326
347
|
boat: "Boat",
|
|
348
|
+
agent37: "Agent37",
|
|
327
349
|
docker: "Docker"
|
|
328
350
|
};
|
|
329
351
|
function isSandboxDriverID(input) {
|
|
@@ -378,6 +400,17 @@ var SandboxRuntimeBootError = class extends Error {
|
|
|
378
400
|
this.name = "SandboxRuntimeBootError";
|
|
379
401
|
}
|
|
380
402
|
};
|
|
403
|
+
function isSandboxImageDownloadFailure(err) {
|
|
404
|
+
return err instanceof Error && "imageDownloadFailed" in err && err.imageDownloadFailed === true;
|
|
405
|
+
}
|
|
406
|
+
var SandboxImageDownloadError = class extends Error {
|
|
407
|
+
imageDownloadFailed = true;
|
|
408
|
+
constructor(reason) {
|
|
409
|
+
super(reason);
|
|
410
|
+
this.name = "SandboxImageDownloadError";
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var SANDBOX_UPDATE_DOWNLOAD_INTERRUPTED = "update_download_interrupted";
|
|
381
414
|
function isSandboxCheckpointUnusable(err) {
|
|
382
415
|
return !!err && typeof err === "object" && isSandboxCheckpointUnusableCause(err.checkpointUnusable);
|
|
383
416
|
}
|
|
@@ -423,6 +456,13 @@ function sandboxDriverLabel(driver) {
|
|
|
423
456
|
}
|
|
424
457
|
var PROVIDER_KEY_REFUSED = "provider_key_refused:";
|
|
425
458
|
var PROVIDER_FAILED = "provider_failed:";
|
|
459
|
+
var EGRESS_UNCONTAINED = "sandbox_egress_uncontained:";
|
|
460
|
+
function sandboxEgressUncontainedError(driver) {
|
|
461
|
+
return `${EGRESS_UNCONTAINED}${driver}`;
|
|
462
|
+
}
|
|
463
|
+
function uncontainedEgressReason(provider) {
|
|
464
|
+
return `${provider} cannot contain a sandbox's network; an org admin can allow unrestricted internet for ${provider} machines in Settings \u2192 Sandbox providers.`;
|
|
465
|
+
}
|
|
426
466
|
function sandboxProviderKeyRefusedError(driver, message) {
|
|
427
467
|
return `${PROVIDER_KEY_REFUSED}${driver}: ${message}`;
|
|
428
468
|
}
|
|
@@ -516,7 +556,8 @@ var SANDBOX_RUNTIME_UNHEALTHY = "runtime_unhealthy";
|
|
|
516
556
|
var SANDBOX_RETRY_CAP_EXCEEDED = "runtime_retry_cap_exceeded";
|
|
517
557
|
var CODED_REASONS = /* @__PURE__ */ new Map([
|
|
518
558
|
[SANDBOX_RUNTIME_UNHEALTHY, "The workspace machine stopped answering."],
|
|
519
|
-
[SANDBOX_RETRY_CAP_EXCEEDED, "Several tries in a row failed; try again later."]
|
|
559
|
+
[SANDBOX_RETRY_CAP_EXCEEDED, "Several tries in a row failed; try again later."],
|
|
560
|
+
[SANDBOX_UPDATE_DOWNLOAD_INTERRUPTED, "The update couldn't finish downloading; your workspace is still running. Try again."]
|
|
520
561
|
]);
|
|
521
562
|
function firstLine(text) {
|
|
522
563
|
return text.split("\n", 1)[0]?.trim() ?? text;
|
|
@@ -530,6 +571,8 @@ function sandboxStartFailureReason(error) {
|
|
|
530
571
|
if (keyRefused !== void 0) return `${sandboxDriverLabel(keyRefused)} refused the sandbox provider key; fix it in Settings \u2192 Sandbox providers.`;
|
|
531
572
|
const failed = codedDriver(error, PROVIDER_FAILED);
|
|
532
573
|
if (failed !== void 0) return `${sandboxDriverLabel(failed)} returned an error.`;
|
|
574
|
+
const uncontained = codedDriver(error, EGRESS_UNCONTAINED);
|
|
575
|
+
if (uncontained !== void 0) return uncontainedEgressReason(sandboxDriverLabel(uncontained));
|
|
533
576
|
return CODED_REASONS.get(error) ?? firstLine(error);
|
|
534
577
|
}
|
|
535
578
|
var LEASE_ENDED_ERRORS = /* @__PURE__ */ new Set(["runtime_lease_changed", "runtime_lease_stopped", "runtime_lease_destroyed"]);
|
|
@@ -575,6 +618,11 @@ var PUBLIC_API_ERRORS = {
|
|
|
575
618
|
org_invitation_invalid: { family: "access", status: 409, message: "Invitation is invalid or unavailable" },
|
|
576
619
|
org_invitation_pending: { family: "access", status: 409, message: "An invitation to this address is already pending" },
|
|
577
620
|
org_invitation_rate_limited: { family: "access", status: 429, message: "Organization invitation limit exceeded" },
|
|
621
|
+
org_limit_reached: { family: "access", status: 403, message: "An account owns at most 3 organizations" },
|
|
622
|
+
org_creation_rate_limited: { family: "access", status: 429, message: "Organization creation limit exceeded" },
|
|
623
|
+
project_limit_reached: { family: "access", status: 402, message: "Free accounts have 3 projects. Get Cloud or Self-host for unlimited projects." },
|
|
624
|
+
seats_full: { family: "access", status: 402, message: "Add teammate seats to invite more people." },
|
|
625
|
+
email_verification_required: { family: "access", status: 403, message: "A verified email address is required" },
|
|
578
626
|
org_invitation_email_mismatch: { family: "access", status: 403, message: "Sign in with the invited verified email address" },
|
|
579
627
|
org_invitation_delivery_unavailable: { family: "access", status: 503, message: "Invitation delivery is unavailable" },
|
|
580
628
|
team_not_found: { family: "access", status: 404, message: "Team not found" },
|
|
@@ -584,6 +632,12 @@ var PUBLIC_API_ERRORS = {
|
|
|
584
632
|
project_member_not_found: { family: "access", status: 404, message: "Project member not found" },
|
|
585
633
|
project_member_owner_immutable: { family: "access", status: 409, message: "The project owner's access cannot be changed" },
|
|
586
634
|
resource_conflict: { family: "access", status: 409, message: "Organization or team authority changed concurrently" },
|
|
635
|
+
org_not_deletable: { family: "access", status: 409, message: "A personal organization goes with its account, and a deployment's with the deployment" },
|
|
636
|
+
org_has_workspaces: { family: "access", status: 409, message: "Delete the organization's workspaces first" },
|
|
637
|
+
account_owns_workspaces: { family: "access", status: 409, message: "Delete your workspaces first" },
|
|
638
|
+
account_founds_organization: { family: "access", status: 409, message: "Delete the organizations you founded first" },
|
|
639
|
+
account_has_cloud_subscription: { family: "access", status: 409, message: "Cancel Cloud before deleting this account" },
|
|
640
|
+
recent_sign_in_required: { family: "access", status: 403, message: "Sign in again to confirm this deletion" },
|
|
587
641
|
session_not_found: { family: "session_share", status: 404, message: "This session is not on the control plane, so it cannot be shared from People yet." },
|
|
588
642
|
session_share_admin_required: { family: "session_share", status: 403, message: "Only the person who started this session can manage its People." },
|
|
589
643
|
session_share_target_required: { family: "session_share", status: 400, message: "A session is shared with exactly one person" },
|
|
@@ -607,11 +661,19 @@ var PUBLIC_API_ERRORS = {
|
|
|
607
661
|
enrollment_paused: { status: 403, message: "enrollment_paused" },
|
|
608
662
|
enrollment_owner_ineligible: { status: 403, message: "enrollment_owner_ineligible" },
|
|
609
663
|
enrollment_key_version_mismatch: { status: 403, message: "enrollment_key_version_mismatch" },
|
|
664
|
+
enrollment_denied: { status: 403, message: "enrollment_denied" },
|
|
665
|
+
enrollment_approval_expired: { status: 403, message: "enrollment_approval_expired" },
|
|
666
|
+
enrollment_awaiting_approval: { status: 403, message: "enrollment_awaiting_approval" },
|
|
610
667
|
invitation_invalid: { status: 403, message: "invitation_invalid" },
|
|
611
668
|
invitation_expired: { status: 410, message: "invitation_expired" },
|
|
612
669
|
invitation_revoked: { status: 410, message: "invitation_revoked" },
|
|
613
670
|
invitation_redeemed: { status: 409, message: "invitation_redeemed" },
|
|
614
671
|
invitation_host_conflict: { status: 409, message: "invitation_host_conflict" },
|
|
672
|
+
pairing_invalid: { status: 403, message: "pairing_invalid" },
|
|
673
|
+
pairing_expired: { status: 410, message: "pairing_expired" },
|
|
674
|
+
pairing_used: { status: 409, message: "pairing_used" },
|
|
675
|
+
pairing_owner_signed_out: { status: 410, message: "pairing_owner_signed_out" },
|
|
676
|
+
pairing_decided: { status: 409, message: "pairing_decided" },
|
|
615
677
|
enrollment_generation_superseded: { status: 409, message: "enrollment_generation_superseded" },
|
|
616
678
|
host_assignment_outside_scope: { status: 400, message: "host_assignment_outside_scope" },
|
|
617
679
|
host_sealing_key_undeclared: { status: 409, message: "host_sealing_key_undeclared" },
|
|
@@ -779,7 +841,7 @@ var SANDBOX_MODEL_PROVIDER_HOSTS = [
|
|
|
779
841
|
"api.openai.com",
|
|
780
842
|
"generativelanguage.googleapis.com",
|
|
781
843
|
"openrouter.ai",
|
|
782
|
-
//
|
|
844
|
+
// The model catalog and engine config a runner fetches at startup.
|
|
783
845
|
"models.dev",
|
|
784
846
|
"opencode.ai"
|
|
785
847
|
];
|
|
@@ -865,13 +927,55 @@ async function refusedReason(leaseStore, workspaceId, epoch) {
|
|
|
865
927
|
return "runtime_lease_not_serving";
|
|
866
928
|
}
|
|
867
929
|
|
|
930
|
+
// src/driver-ensure-input.ts
|
|
931
|
+
function sandboxId(driver, workspaceId) {
|
|
932
|
+
return `${driver}-${workspaceId}`;
|
|
933
|
+
}
|
|
934
|
+
function leaseHostId(driver, workspaceId, lease, managerInput) {
|
|
935
|
+
return managerInput?.hostId ?? lease.hostId ?? sandboxId(driver.id, workspaceId);
|
|
936
|
+
}
|
|
937
|
+
function ensureHostInput(input) {
|
|
938
|
+
const egress = sandboxEgressDisposition(input.driver.metadata.egressControl, input.managerInput?.net, input.managerInput?.uncontainedEgress);
|
|
939
|
+
const labels = {
|
|
940
|
+
app: input.appLabel,
|
|
941
|
+
workspaceId: input.workspaceId,
|
|
942
|
+
epoch: String(input.lease.epoch),
|
|
943
|
+
homeRegion: input.homeRegion,
|
|
944
|
+
...input.managerInput?.labels
|
|
945
|
+
};
|
|
946
|
+
return {
|
|
947
|
+
workspaceId: input.workspaceId,
|
|
948
|
+
hostId: leaseHostId(input.driver, input.workspaceId, input.lease, input.managerInput),
|
|
949
|
+
homeRegion: input.homeRegion,
|
|
950
|
+
epoch: input.lease.epoch,
|
|
951
|
+
labels,
|
|
952
|
+
bootSource: input.managerInput?.bootSource ?? (input.lease.checkpoint && input.lease.status !== "ready" && input.driver.metadata.persistence.resume === "replacement-restore" ? { kind: "driver-snapshot", snapshotId: input.lease.checkpoint.providerReference } : { kind: "default" }),
|
|
953
|
+
workspaceRoot: input.managerInput?.workspaceRoot ?? "/workspace",
|
|
954
|
+
runtimeCwd: input.managerInput?.runtimeCwd,
|
|
955
|
+
workspaceRuntimePort: input.managerInput?.workspaceRuntimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT,
|
|
956
|
+
env: { ...input.managerInput?.env, ...input.leaseEnv },
|
|
957
|
+
// Brokered secrets ride their own channel — NEVER merged into labels or env.
|
|
958
|
+
// Presence of the key, not its length, is the signal: `[]` is the caller
|
|
959
|
+
// withdrawing every brokered secret, and dropping it would reach the driver
|
|
960
|
+
// as "preserve what you have".
|
|
961
|
+
...input.managerInput?.secrets ? { secrets: input.managerInput.secrets } : {},
|
|
962
|
+
source: input.managerInput?.source,
|
|
963
|
+
...input.managerInput?.updateImage ? { updateImage: true } : {},
|
|
964
|
+
...input.lease.machineClass ?? input.managerInput?.machineClass ? { machineClass: input.lease.machineClass ?? input.managerInput?.machineClass } : {},
|
|
965
|
+
exposure: input.managerInput?.exposure ?? (input.driver.metadata.targetAccess === "loopback" ? { kind: "loopback" } : { kind: "relay" }),
|
|
966
|
+
// Withheld where the start allows it, never downgraded: `undefined`
|
|
967
|
+
// here is the same shape a caller that asked for no containment produces,
|
|
968
|
+
// so the throwing drivers take their allow-all path and their throws stay
|
|
969
|
+
// intact for anyone who hands them a policy directly.
|
|
970
|
+
...egress.action === "enforce" && input.managerInput?.net ? { net: sandboxNetworkPolicyWithBrokeredHosts(input.managerInput.net, input.managerInput.secrets) } : {},
|
|
971
|
+
snapshot: input.managerInput?.snapshot
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
868
975
|
// src/egress-warnings.ts
|
|
869
976
|
function egressUnenforcedMessage(input) {
|
|
870
|
-
|
|
871
|
-
const withheld = input.requested ? ` The requested allowlist (${(input.requested.hosts ?? []).length} host(s), ${(input.requested.cidrs ?? []).length} cidr(s)) was withheld, not applied.` : "";
|
|
872
|
-
return `[sandbox-manager] SANDBOX EGRESS IS UNRESTRICTED: driver "${input.driver}" declares egressControl: "none", so ${scope} can reach ANY host on the internet, including attacker-controlled buckets \u2014 agent-authored code inside the sandbox has an unmonitored exfiltration path.${withheld} Select a driver that can enforce egress (vercel) to close it. See public-docs/sandbox-egress.md.`;
|
|
977
|
+
return `[sandbox-manager] SANDBOX EGRESS IS UNRESTRICTED: driver "${input.driver}" declares egressControl: "none" and this start allows uncontained egress, so workspace ${input.workspaceId} can reach ANY host on the internet, including attacker-controlled buckets \u2014 agent-authored code inside the sandbox has an unmonitored exfiltration path. The requested allowlist (${(input.requested.hosts ?? []).length} host(s), ${(input.requested.cidrs ?? []).length} cidr(s)) was withheld, not applied. Select a driver that can enforce egress (cloudflare, vercel), or stop allowing uncontained egress so such starts are refused. See public-docs/sandbox-egress.md.`;
|
|
873
978
|
}
|
|
874
|
-
var compositionEgressWarnings = /* @__PURE__ */ new Set();
|
|
875
979
|
var ensureEgressWarnings = /* @__PURE__ */ new Set();
|
|
876
980
|
function defaultEgressUnenforcedSink(event) {
|
|
877
981
|
console.warn(event.message);
|
|
@@ -880,26 +984,15 @@ function createEgressWarnings(input) {
|
|
|
880
984
|
const { id, egressControl } = input.driver;
|
|
881
985
|
const sink = input.sink ?? defaultEgressUnenforcedSink;
|
|
882
986
|
const key = `${id}|${egressControl}`;
|
|
883
|
-
if (egressControl === "none" && !compositionEgressWarnings.has(key)) {
|
|
884
|
-
compositionEgressWarnings.add(key);
|
|
885
|
-
sink({
|
|
886
|
-
phase: "composition",
|
|
887
|
-
reason: "sandbox_egress_uncontained",
|
|
888
|
-
driver: id,
|
|
889
|
-
egressControl,
|
|
890
|
-
message: egressUnenforcedMessage({ phase: "composition", driver: id })
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
987
|
return {
|
|
894
988
|
withheld(start) {
|
|
895
989
|
if (ensureEgressWarnings.has(key)) return;
|
|
896
990
|
ensureEgressWarnings.add(key);
|
|
897
991
|
sink({
|
|
898
|
-
phase: "ensure",
|
|
899
992
|
reason: "sandbox_egress_uncontained",
|
|
900
993
|
driver: id,
|
|
901
994
|
egressControl,
|
|
902
|
-
message: egressUnenforcedMessage({
|
|
995
|
+
message: egressUnenforcedMessage({ driver: id, workspaceId: start.workspaceId, requested: start.requested }),
|
|
903
996
|
workspaceId: start.workspaceId,
|
|
904
997
|
...start.labels ? { labels: start.labels } : {},
|
|
905
998
|
requested: start.requested
|
|
@@ -908,6 +1001,51 @@ function createEgressWarnings(input) {
|
|
|
908
1001
|
};
|
|
909
1002
|
}
|
|
910
1003
|
|
|
1004
|
+
// src/runtime-env.ts
|
|
1005
|
+
var WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS = [
|
|
1006
|
+
"WORKSPACE_RUNTIME_WORKSPACE_ID",
|
|
1007
|
+
"WORKSPACE_RUNTIME_HOST_ID",
|
|
1008
|
+
"WORKSPACE_RUNTIME_RELAY_WORKSPACE_IDS"
|
|
1009
|
+
];
|
|
1010
|
+
function workspaceRuntimeIdentityEnvConflicts(env) {
|
|
1011
|
+
if (!env) return [];
|
|
1012
|
+
return WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS.filter((key) => env[key] !== void 0);
|
|
1013
|
+
}
|
|
1014
|
+
var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1015
|
+
...WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS,
|
|
1016
|
+
"WORKSPACE_RUNTIME_DIRECTORY",
|
|
1017
|
+
"WORKSPACE_RUNTIME_PORT",
|
|
1018
|
+
"WORKSPACE_RUNTIME_HOST",
|
|
1019
|
+
"WORKSPACE_RUNTIME_SOURCE_KIND",
|
|
1020
|
+
"WORKSPACE_RUNTIME_GIT_REPO_URL",
|
|
1021
|
+
"WORKSPACE_RUNTIME_GIT_BRANCH",
|
|
1022
|
+
"WORKSPACE_RUNTIME_NATIVE_HARNESS",
|
|
1023
|
+
"WORKSPACE_RUNTIME_RELAY_JWKS_URL",
|
|
1024
|
+
"WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL",
|
|
1025
|
+
"WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL"
|
|
1026
|
+
]);
|
|
1027
|
+
|
|
1028
|
+
// src/start-admission.ts
|
|
1029
|
+
function refusedStart(input) {
|
|
1030
|
+
const { driver, workspaceId, start } = input;
|
|
1031
|
+
const egress = sandboxEgressDisposition(driver.metadata.egressControl, start.net, start.uncontainedEgress);
|
|
1032
|
+
if (egress.action === "withhold" && start.net) {
|
|
1033
|
+
input.egressWarnings.withheld({ workspaceId, ...start.labels ? { labels: start.labels } : {}, requested: start.net });
|
|
1034
|
+
}
|
|
1035
|
+
if (egress.action === "refuse") {
|
|
1036
|
+
const error = egress.reason === "sandbox_egress_uncontained" ? sandboxEgressUncontainedError(driver.id) : egress.reason;
|
|
1037
|
+
return { status: "unavailable", error, homeRegion: start.homeRegion };
|
|
1038
|
+
}
|
|
1039
|
+
const identityConflicts = workspaceRuntimeIdentityEnvConflicts(start.env);
|
|
1040
|
+
if (identityConflicts.length) {
|
|
1041
|
+
return { status: "unavailable", error: `sandbox env cannot set runtime identity: ${identityConflicts.join(", ")}`, homeRegion: start.homeRegion };
|
|
1042
|
+
}
|
|
1043
|
+
if (start.machineClass && !driver.metadata.machineClasses?.includes(start.machineClass)) {
|
|
1044
|
+
return { status: "unavailable", error: `sandbox_machine_class_unsupported: ${driver.id} has no ${start.machineClass} machines`, homeRegion: start.homeRegion };
|
|
1045
|
+
}
|
|
1046
|
+
return void 0;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
911
1049
|
// src/garbage-collect.ts
|
|
912
1050
|
async function sweepSandboxes(input) {
|
|
913
1051
|
const { driver, leaseStore, appLabel } = input;
|
|
@@ -997,13 +1135,15 @@ function createSandboxStartRecorder(options) {
|
|
|
997
1135
|
* lease as it ends, so a poller reading the lease mid-boot sees how far the
|
|
998
1136
|
* start has come. `opened` writes the epoch's `lease_decision` before the
|
|
999
1137
|
* driver runs, so a stop that lands mid-boot finds the boot it ends on the
|
|
1000
|
-
* row. A serving lease is not starting
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1138
|
+
* row. A serving lease is not starting, unless the step is a person's
|
|
1139
|
+
* update, which is a start of its own on the same epoch. An epoch acquired
|
|
1140
|
+
* with a start it continues has that start and no phase yet; one acquired
|
|
1141
|
+
* without begins its own here.
|
|
1003
1142
|
*/
|
|
1004
1143
|
observe(lease, input) {
|
|
1005
|
-
if (lease.status === "ready") return void 0;
|
|
1006
|
-
|
|
1144
|
+
if (lease.status === "ready" && !input.update) return void 0;
|
|
1145
|
+
const begun = { requestedAt: input.enteredAt, startedAt: input.enteredAt, bootMode: input.bootMode, markedAt: input.enteredAt, phases: [] };
|
|
1146
|
+
let progress = lease.status === "ready" ? begun : lease.start ?? begun;
|
|
1007
1147
|
const ended = [];
|
|
1008
1148
|
let markedAt = progress.markedAt;
|
|
1009
1149
|
const mark = (phase) => {
|
|
@@ -1109,74 +1249,8 @@ async function startSandboxFresh(input) {
|
|
|
1109
1249
|
return { ok: true, status };
|
|
1110
1250
|
}
|
|
1111
1251
|
|
|
1112
|
-
// src/runtime-env.ts
|
|
1113
|
-
var WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS = [
|
|
1114
|
-
"WORKSPACE_RUNTIME_WORKSPACE_ID",
|
|
1115
|
-
"WORKSPACE_RUNTIME_HOST_ID",
|
|
1116
|
-
"WORKSPACE_RUNTIME_RELAY_WORKSPACE_IDS"
|
|
1117
|
-
];
|
|
1118
|
-
function workspaceRuntimeIdentityEnvConflicts(env) {
|
|
1119
|
-
if (!env) return [];
|
|
1120
|
-
return WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS.filter((key) => env[key] !== void 0);
|
|
1121
|
-
}
|
|
1122
|
-
var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1123
|
-
...WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS,
|
|
1124
|
-
"WORKSPACE_RUNTIME_DIRECTORY",
|
|
1125
|
-
"WORKSPACE_RUNTIME_PORT",
|
|
1126
|
-
"WORKSPACE_RUNTIME_HOST",
|
|
1127
|
-
"WORKSPACE_RUNTIME_SOURCE_KIND",
|
|
1128
|
-
"WORKSPACE_RUNTIME_GIT_REPO_URL",
|
|
1129
|
-
"WORKSPACE_RUNTIME_GIT_BRANCH",
|
|
1130
|
-
"WORKSPACE_RUNTIME_NATIVE_HARNESS",
|
|
1131
|
-
"WORKSPACE_RUNTIME_RELAY_JWKS_URL",
|
|
1132
|
-
"WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL",
|
|
1133
|
-
"WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL"
|
|
1134
|
-
]);
|
|
1135
|
-
|
|
1136
1252
|
// src/manager.ts
|
|
1137
1253
|
var DEFAULT_APP_LABEL = "claxedo";
|
|
1138
|
-
function sandboxId(driver, workspaceId) {
|
|
1139
|
-
return `${driver}-${workspaceId}`;
|
|
1140
|
-
}
|
|
1141
|
-
function leaseHostId(driver, workspaceId, lease, managerInput) {
|
|
1142
|
-
return managerInput?.hostId ?? lease.hostId ?? sandboxId(driver.id, workspaceId);
|
|
1143
|
-
}
|
|
1144
|
-
function ensureHostInput(input) {
|
|
1145
|
-
const egress = sandboxEgressDisposition(input.driver.metadata.egressControl, input.managerInput?.net);
|
|
1146
|
-
const labels = {
|
|
1147
|
-
app: input.appLabel,
|
|
1148
|
-
workspaceId: input.workspaceId,
|
|
1149
|
-
epoch: String(input.lease.epoch),
|
|
1150
|
-
homeRegion: input.homeRegion,
|
|
1151
|
-
...input.managerInput?.labels
|
|
1152
|
-
};
|
|
1153
|
-
return {
|
|
1154
|
-
workspaceId: input.workspaceId,
|
|
1155
|
-
hostId: leaseHostId(input.driver, input.workspaceId, input.lease, input.managerInput),
|
|
1156
|
-
homeRegion: input.homeRegion,
|
|
1157
|
-
epoch: input.lease.epoch,
|
|
1158
|
-
labels,
|
|
1159
|
-
bootSource: input.managerInput?.bootSource ?? (input.lease.checkpoint && input.lease.status !== "ready" && input.driver.metadata.persistence.resume === "replacement-restore" ? { kind: "driver-snapshot", snapshotId: input.lease.checkpoint.providerReference } : { kind: "default" }),
|
|
1160
|
-
workspaceRoot: input.managerInput?.workspaceRoot ?? "/workspace",
|
|
1161
|
-
runtimeCwd: input.managerInput?.runtimeCwd,
|
|
1162
|
-
workspaceRuntimePort: input.managerInput?.workspaceRuntimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT,
|
|
1163
|
-
env: { ...input.managerInput?.env, ...input.leaseEnv },
|
|
1164
|
-
// Brokered secrets ride their own channel — NEVER merged into labels or env.
|
|
1165
|
-
// Presence of the key, not its length, is the signal: `[]` is the caller
|
|
1166
|
-
// withdrawing every brokered secret, and dropping it would reach the driver
|
|
1167
|
-
// as "preserve what you have".
|
|
1168
|
-
...input.managerInput?.secrets ? { secrets: input.managerInput.secrets } : {},
|
|
1169
|
-
source: input.managerInput?.source,
|
|
1170
|
-
...input.lease.machineClass ?? input.managerInput?.machineClass ? { machineClass: input.lease.machineClass ?? input.managerInput?.machineClass } : {},
|
|
1171
|
-
exposure: input.managerInput?.exposure ?? (input.driver.metadata.targetAccess === "loopback" ? { kind: "loopback" } : { kind: "relay" }),
|
|
1172
|
-
// Withheld, never downgraded: `undefined` here is the same shape a caller
|
|
1173
|
-
// that asked for no containment produces, so the throwing drivers take
|
|
1174
|
-
// their allow-all path and their throws stay intact for anyone who hands
|
|
1175
|
-
// them a policy directly.
|
|
1176
|
-
...egress.action === "enforce" && input.managerInput?.net ? { net: sandboxNetworkPolicyWithBrokeredHosts(input.managerInput.net, input.managerInput.secrets) } : {},
|
|
1177
|
-
snapshot: input.managerInput?.snapshot
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
1254
|
function createSandboxManager(options) {
|
|
1181
1255
|
const persistence = validateSandboxPersistenceCapabilities(options.driver.metadata.persistence);
|
|
1182
1256
|
if (!persistence.valid) throw new Error(`Invalid sandbox persistence capabilities: ${persistence.reason}`);
|
|
@@ -1286,7 +1360,7 @@ function createSandboxManager(options) {
|
|
|
1286
1360
|
}
|
|
1287
1361
|
const resuming = Boolean(lease.sandboxId && ensure.bootSource?.kind === "default" && options.driver.resumeHost);
|
|
1288
1362
|
const bootMode = resuming && lease.url ? "resume" : ensure.bootSource?.kind === "driver-snapshot" ? "restore" : "cold-start";
|
|
1289
|
-
const start = starts.observe(lease, { bootMode, enteredAt, labels: ensure.labels });
|
|
1363
|
+
const start = starts.observe(lease, { bootMode, enteredAt, labels: ensure.labels, ...managerInput.updateImage ? { update: true } : {} });
|
|
1290
1364
|
await start?.opened();
|
|
1291
1365
|
ensure.onResource = async (resource) => {
|
|
1292
1366
|
const recorded = await options.leaseStore.recordTarget(workspaceId, lease.epoch, {
|
|
@@ -1352,6 +1426,10 @@ function createSandboxManager(options) {
|
|
|
1352
1426
|
const error = checkpointUnusable ? sandboxCheckpointUnusableError(checkpointUnusable, message) : bootFailed ? sandboxRuntimeBootFailedError(message) : startLimit ? sandboxProviderRateLimitedError(options.driver.id, startLimit) : isSandboxProviderKeyRefused(err) ? sandboxProviderKeyRefusedError(options.driver.id, message) : isSandboxStartRefusal(err) || sandboxLeaseEndedUnderStart(message) ? message : sandboxProviderFailedError(options.driver.id, message);
|
|
1353
1427
|
if (lease.status === "ready" && !bootFailed) {
|
|
1354
1428
|
const updated = await options.leaseStore.update(workspaceId, lease.epoch, { lastError: error }, "ready");
|
|
1429
|
+
if (managerInput.updateImage) {
|
|
1430
|
+
console.warn("A workspace's update to the deployment's image failed; it keeps serving its image", { workspaceId, epoch: lease.epoch, error });
|
|
1431
|
+
return { status: "unavailable", error: isSandboxImageDownloadFailure(err) ? SANDBOX_UPDATE_DOWNLOAD_INTERRUPTED : error, epoch: lease.epoch, homeRegion };
|
|
1432
|
+
}
|
|
1355
1433
|
if (updated) {
|
|
1356
1434
|
const resolved = await leaseTarget(updated);
|
|
1357
1435
|
if (resolved.status === "ready") return { ...resolved, stale: true };
|
|
@@ -1482,24 +1560,8 @@ function createSandboxManager(options) {
|
|
|
1482
1560
|
return lease.sandboxId ? lease.nextRetryAt !== void 0 || stale : lease.nextRetryAt !== void 0 && !stale;
|
|
1483
1561
|
}
|
|
1484
1562
|
async function admit(workspaceId, input) {
|
|
1485
|
-
const
|
|
1486
|
-
if (
|
|
1487
|
-
egressWarnings.withheld({ workspaceId, ...input.labels ? { labels: input.labels } : {}, requested: input.net });
|
|
1488
|
-
}
|
|
1489
|
-
if (egress.action === "refuse") {
|
|
1490
|
-
return { status: "unavailable", error: egress.reason, homeRegion: input.homeRegion };
|
|
1491
|
-
}
|
|
1492
|
-
const identityConflicts = workspaceRuntimeIdentityEnvConflicts(input.env);
|
|
1493
|
-
if (identityConflicts.length) {
|
|
1494
|
-
return {
|
|
1495
|
-
status: "unavailable",
|
|
1496
|
-
error: `sandbox env cannot set runtime identity: ${identityConflicts.join(", ")}`,
|
|
1497
|
-
homeRegion: input.homeRegion
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
if (input.machineClass && !options.driver.metadata.machineClasses?.includes(input.machineClass)) {
|
|
1501
|
-
return { status: "unavailable", error: `sandbox_machine_class_unsupported: ${options.driver.id} has no ${input.machineClass} machines`, homeRegion: input.homeRegion };
|
|
1502
|
-
}
|
|
1563
|
+
const refused = refusedStart({ driver: options.driver, workspaceId, start: input, egressWarnings });
|
|
1564
|
+
if (refused) return refused;
|
|
1503
1565
|
const existing = await options.leaseStore.get(workspaceId);
|
|
1504
1566
|
const wait = waiting(existing);
|
|
1505
1567
|
if (wait) return wait;
|
|
@@ -1698,12 +1760,16 @@ export {
|
|
|
1698
1760
|
SANDBOX_RETRY_CAP_EXCEEDED,
|
|
1699
1761
|
SANDBOX_RUNTIME_UNHEALTHY,
|
|
1700
1762
|
SANDBOX_START_WINDOWS,
|
|
1763
|
+
SANDBOX_UPDATE_DOWNLOAD_INTERRUPTED,
|
|
1701
1764
|
SandboxCheckpointUnusableError,
|
|
1702
1765
|
SandboxHostGoneError,
|
|
1766
|
+
SandboxImageDownloadError,
|
|
1703
1767
|
SandboxProviderKeyRefusedError,
|
|
1704
1768
|
SandboxProviderRateLimitedError,
|
|
1705
1769
|
SandboxRuntimeBootError,
|
|
1706
1770
|
SandboxStartRefusedError,
|
|
1771
|
+
agent37Billing,
|
|
1772
|
+
agent37MachineShapes,
|
|
1707
1773
|
applySandboxLeaseFailure,
|
|
1708
1774
|
applySandboxLeasePatch,
|
|
1709
1775
|
applySandboxProvisionedTarget,
|
|
@@ -1719,6 +1785,7 @@ export {
|
|
|
1719
1785
|
isSandboxCheckpointUnusable,
|
|
1720
1786
|
isSandboxCheckpointUnusableCause,
|
|
1721
1787
|
isSandboxHostGone,
|
|
1788
|
+
isSandboxImageDownloadFailure,
|
|
1722
1789
|
isSandboxListingUnsupported,
|
|
1723
1790
|
isSandboxProviderKeyRefused,
|
|
1724
1791
|
isSandboxProviderRateLimited,
|
|
@@ -1731,6 +1798,7 @@ export {
|
|
|
1731
1798
|
sandboxCheckpointUnusableCauses,
|
|
1732
1799
|
sandboxCheckpointUnusableError,
|
|
1733
1800
|
sandboxEgressDisposition,
|
|
1801
|
+
sandboxEgressUncontainedError,
|
|
1734
1802
|
sandboxHostGoneError,
|
|
1735
1803
|
sandboxLeaseEndedUnderStart,
|
|
1736
1804
|
sandboxLeaseFailure,
|
package/dist/json.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading untrusted JSON: driver API responses, CLI output, stored records.
|
|
3
|
-
*
|
|
4
|
-
* Drivers all face the same problem — a provider hands back `any`, and the code
|
|
5
|
-
* needs a few named fields out of it. Narrowing through these keeps the check
|
|
6
|
-
* and the type in the same place, instead of each site asserting a shape it
|
|
7
|
-
* never verified. The object check is `@claxedo/helpers/guards`, re-exported
|
|
8
|
-
* under the names the drivers use.
|
|
9
|
-
*/
|
|
10
1
|
export { asRecord as record, isRecord } from "@claxedo/helpers/guards";
|
|
11
|
-
/**
|
|
2
|
+
/** An empty string is absent. */
|
|
12
3
|
export declare function text(input: unknown): string | undefined;
|
|
13
4
|
export declare function num(input: unknown): number | undefined;
|
package/dist/lease-failure.d.ts
CHANGED
|
@@ -15,6 +15,23 @@ export declare class SandboxRuntimeBootError extends Error implements SandboxRun
|
|
|
15
15
|
readonly runtimeBootFailed: true;
|
|
16
16
|
constructor(reason: string);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A driver throws this shape when the image a start pulls did not finish
|
|
20
|
+
* downloading because the registry link kept breaking. Nothing the machine
|
|
21
|
+
* served has changed, so an update that fails this way leaves the workspace
|
|
22
|
+
* serving its image.
|
|
23
|
+
*/
|
|
24
|
+
export type SandboxImageDownloadFailure = {
|
|
25
|
+
imageDownloadFailed: true;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function isSandboxImageDownloadFailure(err: unknown): err is SandboxImageDownloadFailure;
|
|
29
|
+
export declare class SandboxImageDownloadError extends Error implements SandboxImageDownloadFailure {
|
|
30
|
+
readonly imageDownloadFailed: true;
|
|
31
|
+
constructor(reason: string);
|
|
32
|
+
}
|
|
33
|
+
/** The answer to a person's update whose image download kept breaking; the lease keeps the provider's own text. */
|
|
34
|
+
export declare const SANDBOX_UPDATE_DOWNLOAD_INTERRUPTED = "update_download_interrupted";
|
|
18
35
|
export { isSandboxCheckpointUnusableCause, sandboxCheckpointUnusableCauses, type SandboxCheckpointUnusableCause };
|
|
19
36
|
/**
|
|
20
37
|
* A driver throws this shape when the checkpoint a start restores cannot be
|
|
@@ -74,6 +91,8 @@ export declare class SandboxStartRefusedError extends Error implements SandboxSt
|
|
|
74
91
|
readonly startRefused: true;
|
|
75
92
|
constructor(reason: string);
|
|
76
93
|
}
|
|
94
|
+
/** The error a start answers when `driver` declares no egress control and the start does not allow uncontained egress. */
|
|
95
|
+
export declare function sandboxEgressUncontainedError(driver: string): string;
|
|
77
96
|
/** The error a lease keeps when `driver`'s provider refused its key; the driver's message stays for the operator. */
|
|
78
97
|
export declare function sandboxProviderKeyRefusedError(driver: string, message: string): string;
|
|
79
98
|
/** The error a lease keeps when `driver` failed at its provider; the driver's message, which names hosts, requests and provider codes, stays for the operator. */
|
|
@@ -127,7 +146,7 @@ export declare function workspaceRuntimeBootReason(diagnostic: string): string |
|
|
|
127
146
|
/**
|
|
128
147
|
* What a driver keeps of a runtime's log within `maxChars`. A Node stack
|
|
129
148
|
* under the boot-failure line is longer than the bound a driver puts on the
|
|
130
|
-
* tail (600 chars on Boat
|
|
149
|
+
* tail (600 chars on Boat), so a tail alone keeps the
|
|
131
150
|
* frames and loses the one line that names the reason.
|
|
132
151
|
*/
|
|
133
152
|
export declare function runtimeBootDiagnostic(log: string, maxChars: number): string;
|
package/dist/lease-types.d.ts
CHANGED
|
@@ -6,16 +6,7 @@ export type SandboxLeaseRow = {
|
|
|
6
6
|
home_region?: string;
|
|
7
7
|
epoch: number;
|
|
8
8
|
status: SandboxLeaseRowStatus;
|
|
9
|
-
/**
|
|
10
|
-
* The id the driver was registered with, which is any string: the manager
|
|
11
|
-
* dispatches on whatever a composition registers (`stores/d1.test.ts`
|
|
12
|
-
* provisions one called `"test-provider"`), and every other type this value
|
|
13
|
-
* flows between — `SandboxLease["driver"]`, `SandboxLeaseAcquireInput["driver"]`,
|
|
14
|
-
* `SandboxTarget["driver"]["id"]` — is already `string`. Typing the row as the
|
|
15
|
-
* closed `SandboxDriverID` made it the odd one out and forced every store to
|
|
16
|
-
* assert into it. Narrow with `isSandboxDriverID` where a CATALOG entry is
|
|
17
|
-
* needed, which is the only place the closed set is the right question.
|
|
18
|
-
*/
|
|
9
|
+
/** Any string: the manager dispatches on whatever a composition registers. Narrow with `isSandboxDriverID` where a catalog entry is needed. */
|
|
19
10
|
driver: string;
|
|
20
11
|
driver_resource_id: string | null;
|
|
21
12
|
driver_snapshot_id: string | null;
|
package/dist/runtime-env.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export type WorkspaceRuntimeControlEnv = {
|
|
|
8
8
|
export declare function workspaceRuntimeIdentityEnvConflicts(env: Record<string, string> | undefined): string[];
|
|
9
9
|
export declare function assertWorkspaceRuntimeIdentityEnv(env: Record<string, string> | undefined): void;
|
|
10
10
|
export declare function workspaceRuntimeBootSecrets(env: Record<string, string>): string[];
|
|
11
|
+
/**
|
|
12
|
+
* `text` with every boot secret in it replaced. Redact before bounding the
|
|
13
|
+
* text, so a secret the bound would cut in half is already gone. Values
|
|
14
|
+
* shorter than the floor (ports, flags) are left readable.
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactWorkspaceRuntimeBootSecrets(text: string, secrets: readonly string[]): string;
|
|
11
17
|
export declare function workspaceRuntimeBootEnv(input: Parameters<typeof workspaceRuntimeTargetEnv>[0] & Parameters<typeof workspaceRuntimeSourceEnv>[0] & {
|
|
12
18
|
env?: Record<string, string>;
|
|
13
19
|
nativeHarness?: string;
|
package/dist/runtime-env.mjs
CHANGED
|
@@ -31,6 +31,10 @@ var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
31
31
|
function workspaceRuntimeBootSecrets(env) {
|
|
32
32
|
return Object.entries(env).flatMap(([key, value]) => WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS.has(key) ? [] : [value]);
|
|
33
33
|
}
|
|
34
|
+
var REDACTED_VALUE_MIN_LENGTH = 8;
|
|
35
|
+
function redactWorkspaceRuntimeBootSecrets(text, secrets) {
|
|
36
|
+
return secrets.filter((secret) => secret.length >= REDACTED_VALUE_MIN_LENGTH).reduce((output, secret) => output.split(secret).join("[redacted]"), text);
|
|
37
|
+
}
|
|
34
38
|
function workspaceRuntimeBootEnv(input) {
|
|
35
39
|
assertWorkspaceRuntimeIdentityEnv(input.env);
|
|
36
40
|
const env = {
|
|
@@ -73,6 +77,7 @@ function workspaceRuntimeServiceExposureEnv(input) {
|
|
|
73
77
|
}
|
|
74
78
|
export {
|
|
75
79
|
assertWorkspaceRuntimeIdentityEnv,
|
|
80
|
+
redactWorkspaceRuntimeBootSecrets,
|
|
76
81
|
workspaceRuntimeBootEnv,
|
|
77
82
|
workspaceRuntimeBootSecrets,
|
|
78
83
|
workspaceRuntimeIdentityEnvConflicts,
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
export declare function workspaceRuntimeVersion(): string;
|
|
2
2
|
/**
|
|
3
|
-
* The oldest workspace-runtime version
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* The oldest workspace-runtime version this control plane and relay serve, for
|
|
4
|
+
* every driver: a workspace's snapshot restored, or the older image a Boat wake
|
|
5
|
+
* keeps serving instead of waiting on the deployment's. A filesystem snapshot
|
|
6
|
+
* carries the runtime it was taken with, and a kept image the runtime it
|
|
7
|
+
* shipped, so a fix to the runtime reaches a workspace that keeps waking only
|
|
8
|
+
* when this rises. Below it a Cloudflare restore is refused and its person
|
|
9
|
+
* must start the workspace fresh, and a Boat wake boots the deployment's image.
|
|
10
|
+
*
|
|
11
|
+
* Raise it, and the workspace-runtime version with it in the same commit, for
|
|
12
|
+
* a runtime security fix or for a control-plane or relay contract an older
|
|
13
|
+
* runtime cannot speak, and name the first cloud image published at the new
|
|
14
|
+
* version: the version-skew suite (`bun run --cwd packages/harness flows:skew`)
|
|
15
|
+
* runs the current control plane and relay against exactly that image.
|
|
16
|
+
* Bumping the version with it keeps the comparison honest: every image below
|
|
17
|
+
* the new version was built before the change. What it does not cover: the
|
|
18
|
+
* images built after the floor's at the same version are trusted, not tested.
|
|
9
19
|
*/
|
|
10
|
-
export declare const
|
|
20
|
+
export declare const SERVED_WORKSPACE_RUNTIME_FLOOR: {
|
|
21
|
+
readonly version: "0.10.1";
|
|
22
|
+
readonly image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8";
|
|
23
|
+
};
|
|
24
|
+
export declare const MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION: "0.10.1";
|
|
25
|
+
/** Where the sandbox image keeps the version of the runtime it ships, from the image's root. */
|
|
26
|
+
export declare const SANDBOX_IMAGE_RUNTIME_VERSION_FILE = "opt/workspace-runtime/workspace-runtime-version";
|
|
27
|
+
/** A shell test that passes when the runtime version in `file`, a shell word, is one this control plane serves. */
|
|
28
|
+
export declare function servedRuntimeVersionTest(file: string): string;
|
package/dist/runtime-version.mjs
CHANGED
|
@@ -3,8 +3,19 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
3
3
|
function workspaceRuntimeVersion() {
|
|
4
4
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
5
5
|
}
|
|
6
|
-
var
|
|
6
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
7
|
+
version: "0.10.1",
|
|
8
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
9
|
+
};
|
|
10
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
11
|
+
var SANDBOX_IMAGE_RUNTIME_VERSION_FILE = "opt/workspace-runtime/workspace-runtime-version";
|
|
12
|
+
function servedRuntimeVersionTest(file) {
|
|
13
|
+
return `version=$(cat ${file} 2>/dev/null) && [ -n "$version" ] && [ "$(printf '%s\\n%s\\n' ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} "$version" | sort -V | head -n 1)" = ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} ]`;
|
|
14
|
+
}
|
|
7
15
|
export {
|
|
8
|
-
|
|
16
|
+
MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION,
|
|
17
|
+
SANDBOX_IMAGE_RUNTIME_VERSION_FILE,
|
|
18
|
+
SERVED_WORKSPACE_RUNTIME_FLOOR,
|
|
19
|
+
servedRuntimeVersionTest,
|
|
9
20
|
workspaceRuntimeVersion
|
|
10
21
|
};
|