mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-01 02:04:24 +00:00
Show the sandbox's ready duration and retention outcome on the run
`RunSandboxInstance` carries `ready_duration_ms` from the root scope's `scope.acquired` and `retained` from its `scope.released`, so the view says how long the sandbox took and whether it still exists after the run. The OpenAPI schema, the TypeScript client and the web sandbox tab's overview show both; the host sandbox scenario asserts them on a real run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0fe066d420
commit
a4af6fac92
16 changed files with 158 additions and 60 deletions
|
|
@ -217,12 +217,14 @@ describe("RunSandbox route", () => {
|
|||
test("renders panels for a fully populated sandbox", () => {
|
||||
currentDetails = sandboxDetails({
|
||||
sandbox: {
|
||||
provider: "docker",
|
||||
image: "ghcr.io/fabro/sandbox:latest",
|
||||
runtime: {
|
||||
provider: "docker",
|
||||
image: "ghcr.io/fabro/sandbox:latest",
|
||||
runtime: {
|
||||
id: "abcdef123456",
|
||||
working_directory: "/workspace",
|
||||
},
|
||||
ready_duration_ms: 1500,
|
||||
retained: true,
|
||||
},
|
||||
status: {
|
||||
state: "running",
|
||||
|
|
@ -248,6 +250,10 @@ describe("RunSandbox route", () => {
|
|||
const copy = textContent(renderer);
|
||||
expect(copy).toContain("Allow all");
|
||||
expect(copy).toContain("4 GiB");
|
||||
expect(copy).toContain("Ready in");
|
||||
expect(copy).toContain("1.5s");
|
||||
expect(copy).toContain("Retained");
|
||||
expect(copy).toContain("Yes");
|
||||
});
|
||||
|
||||
test("links to the provider dashboard when a sandbox web URL is present", () => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
formatAbsoluteTs,
|
||||
formatBytesAsMemory,
|
||||
formatCpuCores,
|
||||
formatDurationMs,
|
||||
} from "../lib/format";
|
||||
import { useRun, useRunSandboxDetails, useRunState } from "../lib/queries";
|
||||
import {
|
||||
|
|
@ -57,6 +58,17 @@ function nullableTimestamp(value: string | null | undefined): string {
|
|||
return value ? formatAbsoluteTs(value) : EMPTY_VALUE;
|
||||
}
|
||||
|
||||
function nullableDuration(ms: number | null | undefined): string {
|
||||
return ms == null ? EMPTY_VALUE : formatDurationMs(ms);
|
||||
}
|
||||
|
||||
/// The release outcome: "Yes" when the sandbox still exists after the run
|
||||
/// released it, "No" when it was removed, and empty before the release.
|
||||
function nullableRetained(retained: boolean | null | undefined): string {
|
||||
if (retained == null) return EMPTY_VALUE;
|
||||
return retained ? "Yes" : "No";
|
||||
}
|
||||
|
||||
function nullableMegabytes(megabytes: number | null | undefined): string {
|
||||
return megabytes != null ? formatBytesAsMemory(megabytes * 1024 * 1024) : EMPTY_VALUE;
|
||||
}
|
||||
|
|
@ -203,6 +215,8 @@ function OverviewPanel({ details }: { details: SandboxDetails }) {
|
|||
value={nullable(status.image ?? status.snapshot ?? sandbox.image ?? sandbox.snapshot)}
|
||||
/>
|
||||
{status.sandbox_kind && <Row label="Kind" value={status.sandbox_kind} />}
|
||||
<Row label="Ready in" value={nullableDuration(sandbox.ready_duration_ms)} />
|
||||
<Row label="Retained" value={nullableRetained(sandbox.retained)} />
|
||||
{status.web_url && (
|
||||
<LinkRow
|
||||
label="Provider"
|
||||
|
|
|
|||
|
|
@ -13664,6 +13664,17 @@ components:
|
|||
type: ["string", "null"]
|
||||
runtime:
|
||||
$ref: "#/components/schemas/RunSandboxRuntime"
|
||||
ready_duration_ms:
|
||||
type: ["integer", "null"]
|
||||
format: uint64
|
||||
minimum: 0
|
||||
description: How long the sandbox took to become ready, when recorded.
|
||||
retained:
|
||||
type: ["boolean", "null"]
|
||||
description: |
|
||||
Whether the sandbox still exists after the run released it
|
||||
(kept, stopped or running) or was removed. Absent until the
|
||||
release.
|
||||
|
||||
RunSandboxFailure:
|
||||
description: Sandbox initialization failure details.
|
||||
|
|
|
|||
|
|
@ -742,6 +742,14 @@ async fn a_runs_projection_carries_its_host_sandbox_instance() {
|
|||
Path::new(working_directory).is_dir(),
|
||||
"the workspace is retained after the run: {working_directory}"
|
||||
);
|
||||
assert!(
|
||||
instance["ready_duration_ms"].is_u64(),
|
||||
"the acquisition's duration is on the instance: {instance}"
|
||||
);
|
||||
assert_eq!(
|
||||
instance["retained"], true,
|
||||
"the release said the sandbox still exists: {instance}"
|
||||
);
|
||||
assert!(sandbox.get("failure").is_none(), "{sandbox}");
|
||||
|
||||
let run = run_json(&app, &run_id).await;
|
||||
|
|
|
|||
|
|
@ -574,10 +574,10 @@ mod tests {
|
|||
snapshot: None,
|
||||
},
|
||||
RunSandboxInstance {
|
||||
provider: SandboxProviderKind::LOCAL,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: fabro_types::RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::LOCAL,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: fabro_types::RunSandboxRuntime {
|
||||
id: "sandbox-1".to_string(),
|
||||
working_directory: "/tmp/project".to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -588,6 +588,8 @@ mod tests {
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
},
|
||||
));
|
||||
let stage =
|
||||
|
|
|
|||
|
|
@ -125,9 +125,8 @@ engine-derived maps (`completed_nodes`, `node_retries`, `context_values`,
|
|||
`script_invocation` and `script_timing`, a stage's `notes`,
|
||||
`StageCompletion` details for a `parsed.note`, the sandbox instance's
|
||||
clone fields and workspace roots (Petri's checkout is a copy of the bound
|
||||
repository, not a clone; the roots are the provider's, read live) and the
|
||||
retention outcome (kept as `FoldState.sandbox_retained`; the view has no
|
||||
field for it), `Run.ask_fabro`, an interview option's `description` and
|
||||
repository, not a clone; the roots are the provider's, read live),
|
||||
`Run.ask_fabro`, an interview option's `description` and
|
||||
`preview`, the pull request `creation` state, and the run's notices,
|
||||
notifications and pairings (recorded, not shown).
|
||||
|
||||
|
|
|
|||
|
|
@ -214,9 +214,9 @@ which is Fabro's `local`; every other kind is spelled the same.
|
|||
| --- | --- | --- | --- |
|
||||
| plan | `RunSandbox.plan {provider, image, snapshot}` | `graph.registered`'s `fabro.environment` and `fabro.launch {sandbox_backend}` params; platform record `run.created` | run |
|
||||
| binding: isolated or inherited | none today | `invocation.declared {sandbox}` | invocation |
|
||||
| which: planned, initializing, ready, failed | `RunSandbox.kind`, `sandbox.initializing`, `sandbox.ready {duration_ms, name, url}`, `sandbox.failed {error, causes, duration_ms}` | `planned` from the platform record `run.created`; `initializing` from `run.started`; `ready` from the root invocation's `scope.acquired` (`provider`, `image`, `snapshot`); `failed` from its `scope.failed` (`provider`, `error`, `causes`, `duration_ms`). The ready duration (`scope.acquired` `duration_ms`) has no field on `RunSandbox` and is not projected | scope |
|
||||
| which: planned, initializing, ready, failed | `RunSandbox.kind`, `sandbox.initializing`, `sandbox.ready {duration_ms, name, url}`, `sandbox.failed {error, causes, duration_ms}` | `planned` from the platform record `run.created`; `initializing` from `run.started`; `ready` from the root invocation's `scope.acquired` (`provider`, `image`, `snapshot`); `failed` from its `scope.failed` (`provider`, `error`, `causes`, `duration_ms`). The ready duration (`scope.acquired` `duration_ms`) is `RunSandboxInstance.ready_duration_ms` | scope |
|
||||
| where: instance id, working directory, clone, workspace roots | `RunSandboxInstance.runtime {id, working_directory, repo_cloned, clone_origin_url, clone_branch, workspace_root, repos_root, primary_repo_path, primary_repo_link}`, `sandbox.initialized` | `scope.acquired` (`instance` is the id a reconnect attaches by, `working_directory`). The clone fields stay unset: `custom attractor.checkout` records Petri's copy of the bound repository into the workspace (`repository`, `commit`, `depth`, `files`), which is not a clone Fabro made, and the workspace roots are the provider's layout, read live | scope |
|
||||
| retention | none today; the run-end `sandbox_cleanup` hook | `scope.released {outcome, retained, problems}` of the root invocation's lease, kept as `FoldState.sandbox_retained`; the view has no field for it and `Run.sandbox` keeps naming the instance that ran. `run.note.recorded {kind: hook, point: scope_released}` when a hook ran | scope |
|
||||
| retention | `RunSandboxInstance.retained`; the run-end `sandbox_cleanup` hook | `scope.released {outcome, retained, problems}` of the root invocation's lease: `retained` is set on the instance (and kept as `FoldState.sandbox_retained`), and `Run.sandbox` keeps naming the instance that ran, whether or not it still exists. `run.note.recorded {kind: hook, point: scope_released}` when a hook ran | scope |
|
||||
| live status, resources, files, services, VNC, preview, SSH | `SandboxStatus`, `SandboxFileEntry`, `SandboxService`, `VncPreviewResponse`, `PreviewUrlResponse`, `SshAccessResponse`, `ssh.ready` | live: the sandbox-driver provider queried by the run label | run |
|
||||
| setup commands | `setup.started`, `setup.command.completed`, `setup.completed`, `setup.failed`, `cli.ensure.*` | the `run_prepare_N` stages (Stages section); `cli.ensure.*` has no Petri equivalent and is dropped (the image carries the CLI) | stage |
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ pub struct FoldState {
|
|||
pub finished_firings: BTreeSet<String>,
|
||||
/// Whether the run's sandbox still exists after its release
|
||||
/// (`scope.released` `retained`): kept stopped, or deleted. Absent until
|
||||
/// the root invocation's lease was released.
|
||||
/// the root invocation's lease was released. The view carries the same
|
||||
/// fact as `RunSandboxInstance.retained`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sandbox_retained: Option<bool>,
|
||||
}
|
||||
|
|
@ -462,9 +463,8 @@ impl RunView {
|
|||
self.conclude(status.to_string().as_str(), at);
|
||||
}
|
||||
// ── Sandbox: the retention outcome (VIEWS.md "Sandbox") ─────────
|
||||
// The view has no retention field; the fact is kept in the fold
|
||||
// state for the read side. The instance stays on `Run.sandbox`:
|
||||
// it names what ran, whether or not it still exists.
|
||||
// The instance stays on `Run.sandbox`: it names what ran, and
|
||||
// `retained` says whether it still exists.
|
||||
CoordinatorEvent::ScopeReleased {
|
||||
invocation,
|
||||
retained,
|
||||
|
|
@ -472,6 +472,13 @@ impl RunView {
|
|||
} => {
|
||||
if Some(invocation.raw()) == self.state.root {
|
||||
self.state.sandbox_retained = Some(*retained);
|
||||
if let Some(sandbox) = self
|
||||
.projection
|
||||
.as_mut()
|
||||
.and_then(|projection| projection.sandbox.as_mut())
|
||||
{
|
||||
sandbox.set_retained(*retained);
|
||||
}
|
||||
}
|
||||
}
|
||||
CoordinatorEvent::GraphRegistered { .. }
|
||||
|
|
@ -707,12 +714,16 @@ impl RunView {
|
|||
// invocation's scope (a parallel branch) shares or owns another
|
||||
// one and is not the run's; a re-acquisition (a resume, a
|
||||
// replaced sandbox) names the current instance.
|
||||
Event::ScopeAcquired { sandbox, .. } => {
|
||||
Event::ScopeAcquired {
|
||||
sandbox,
|
||||
duration_ms,
|
||||
..
|
||||
} => {
|
||||
if let Some(projection) = self.root_scope_projection(event) {
|
||||
let plan = sandbox_plan_of(projection);
|
||||
projection.sandbox = Some(RunSandbox::ready(
|
||||
plan.clone(),
|
||||
sandbox_instance(&plan, sandbox),
|
||||
sandbox_instance(&plan, sandbox, *duration_ms),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1515,20 +1526,26 @@ fn provider_kind(provider: &str) -> Option<SandboxProviderKind> {
|
|||
/// The run's sandbox instance from Petri's record of the scope's
|
||||
/// acquisition: the provider, the provider's id for the sandbox (what a
|
||||
/// reconnect attaches by), its image and snapshot when the provider knows
|
||||
/// them, and the working directory. The clone fields stay unset: Petri's
|
||||
/// checkout copies the bound repository into the workspace and is not a
|
||||
/// clone Fabro made, and the workspace roots are the provider's own layout,
|
||||
/// read live.
|
||||
fn sandbox_instance(plan: &RunSandboxPlan, sandbox: &SandboxInstance) -> RunSandboxInstance {
|
||||
/// them, the working directory, and how long the acquisition took. The
|
||||
/// clone fields stay unset: Petri's checkout copies the bound repository
|
||||
/// into the workspace and is not a clone Fabro made, and the workspace
|
||||
/// roots are the provider's own layout, read live. `retained` waits for
|
||||
/// the scope's release.
|
||||
fn sandbox_instance(
|
||||
plan: &RunSandboxPlan,
|
||||
sandbox: &SandboxInstance,
|
||||
ready_duration_ms: u64,
|
||||
) -> RunSandboxInstance {
|
||||
RunSandboxInstance {
|
||||
provider: provider_kind(&sandbox.provider).unwrap_or_else(|| plan.provider.clone()),
|
||||
image: sandbox
|
||||
provider: provider_kind(&sandbox.provider)
|
||||
.unwrap_or_else(|| plan.provider.clone()),
|
||||
image: sandbox
|
||||
.image
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| plan.image.clone()),
|
||||
snapshot: sandbox.snapshot.as_ref().map(ToString::to_string),
|
||||
runtime: RunSandboxRuntime {
|
||||
snapshot: sandbox.snapshot.as_ref().map(ToString::to_string),
|
||||
runtime: RunSandboxRuntime {
|
||||
id: sandbox.instance.to_string(),
|
||||
working_directory: sandbox.working_directory.to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -1539,6 +1556,8 @@ fn sandbox_instance(plan: &RunSandboxPlan, sandbox: &SandboxInstance) -> RunSand
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: Some(ready_duration_ms),
|
||||
retained: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,10 +93,10 @@ impl SandboxSpec {
|
|||
LayoutSource::ProviderWorkingDirectory => sandbox.workspace_layout(),
|
||||
};
|
||||
RunSandboxInstance {
|
||||
provider: self.kind.clone(),
|
||||
image: self.image(),
|
||||
snapshot: sandbox.snapshot_info(),
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: self.kind.clone(),
|
||||
image: self.image(),
|
||||
snapshot: sandbox.snapshot_info(),
|
||||
runtime: RunSandboxRuntime {
|
||||
id,
|
||||
working_directory,
|
||||
repo_cloned,
|
||||
|
|
@ -113,6 +113,8 @@ impl SandboxSpec {
|
|||
.as_ref()
|
||||
.and_then(|layout| layout.primary_repo_link.clone()),
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,10 +83,10 @@ fn serializable_projection_round_trips_and_trims_bulky_node_fields() {
|
|||
snapshot: None,
|
||||
};
|
||||
projection.sandbox = Some(RunSandbox::ready(sandbox_plan, RunSandboxInstance {
|
||||
provider: SandboxProviderKind::LOCAL,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::LOCAL,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "sandbox-1".to_string(),
|
||||
working_directory: "/tmp/project".to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -97,6 +97,8 @@ fn serializable_projection_round_trips_and_trims_bulky_node_fields() {
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
}));
|
||||
projection.pending_interviews = BTreeMap::new();
|
||||
let stage = projection.stage_entry(stage_id.node_id(), stage_id.visit(), first_event_seq(2));
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ fn run_sandbox_json_matches_openapi_shape() {
|
|||
snapshot: None,
|
||||
},
|
||||
RunSandboxInstance {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "container-abc123".to_string(),
|
||||
working_directory: "/workspace".to_string(),
|
||||
repo_cloned: Some(false),
|
||||
|
|
@ -40,6 +40,8 @@ fn run_sandbox_json_matches_openapi_shape() {
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: Some(1_250),
|
||||
retained: Some(true),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -63,7 +65,9 @@ fn run_sandbox_json_matches_openapi_shape() {
|
|||
"clone_branch": "main",
|
||||
"workspace_root": "/workspace",
|
||||
"repos_root": "/repos"
|
||||
}
|
||||
},
|
||||
"ready_duration_ms": 1250,
|
||||
"retained": true
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ fn sandbox_details_json_matches_openapi_shape() {
|
|||
));
|
||||
let details = SandboxDetails {
|
||||
sandbox: RunSandboxInstance {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: Some("ghcr.io/fabro/sandbox:latest".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: Some("ghcr.io/fabro/sandbox:latest".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "container-abc123".to_string(),
|
||||
working_directory: "/workspace".to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -69,6 +69,8 @@ fn sandbox_details_json_matches_openapi_shape() {
|
|||
primary_repo_path: Some("/repos/fabro-sh/fabro".to_string()),
|
||||
primary_repo_link: Some("/workspace/fabro".to_string()),
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
},
|
||||
status,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,12 +24,19 @@ pub struct RunSandboxPlan {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RunSandboxInstance {
|
||||
pub provider: SandboxProviderKind,
|
||||
pub provider: SandboxProviderKind,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub image: Option<String>,
|
||||
pub image: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub snapshot: Option<String>,
|
||||
pub runtime: RunSandboxRuntime,
|
||||
pub snapshot: Option<String>,
|
||||
pub runtime: RunSandboxRuntime,
|
||||
/// How long the sandbox took to become ready, when its record says.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ready_duration_ms: Option<u64>,
|
||||
/// Whether the sandbox still exists after the run released it: kept
|
||||
/// (stopped or running), or removed. Absent until the release.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub retained: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
@ -90,6 +97,14 @@ impl RunSandbox {
|
|||
self.instance.as_ref()
|
||||
}
|
||||
|
||||
/// Record the release outcome on the instance: whether the sandbox
|
||||
/// still exists. Nothing to record without an instance.
|
||||
pub fn set_retained(&mut self, retained: bool) {
|
||||
if let Some(instance) = self.instance.as_mut() {
|
||||
instance.retained = Some(retained);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_instance(self) -> Option<RunSandboxInstance> {
|
||||
self.instance
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ mod tests {
|
|||
));
|
||||
let details = SandboxDetails {
|
||||
sandbox: RunSandboxInstance {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: Some("ghcr.io/fabro/sandbox:latest".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: Some("ghcr.io/fabro/sandbox:latest".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "container-abc123".to_string(),
|
||||
working_directory: "/workspace".to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -49,6 +49,8 @@ mod tests {
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
},
|
||||
status,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ fn run_sandbox_serializes_canonical_identity_without_identifier() {
|
|||
snapshot: None,
|
||||
},
|
||||
RunSandboxInstance {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::DOCKER,
|
||||
image: None,
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "container-abc123".to_string(),
|
||||
working_directory: "/workspace".to_string(),
|
||||
repo_cloned: Some(true),
|
||||
|
|
@ -28,6 +28,8 @@ fn run_sandbox_serializes_canonical_identity_without_identifier() {
|
|||
primary_repo_path: Some("/repos/fabro-sh/fabro".to_string()),
|
||||
primary_repo_link: Some("/workspace/fabro".to_string()),
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -83,10 +85,10 @@ fn sandbox_details_keep_the_record_beside_the_status() {
|
|||
);
|
||||
let details = SandboxDetails {
|
||||
sandbox: RunSandboxInstance {
|
||||
provider: SandboxProviderKind::DAYTONA,
|
||||
image: Some("ubuntu:24.04".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
provider: SandboxProviderKind::DAYTONA,
|
||||
image: Some("ubuntu:24.04".to_string()),
|
||||
snapshot: None,
|
||||
runtime: RunSandboxRuntime {
|
||||
id: "daytona-sandbox-name".to_string(),
|
||||
working_directory: "/workspace".to_string(),
|
||||
repo_cloned: None,
|
||||
|
|
@ -97,6 +99,8 @@ fn sandbox_details_keep_the_record_beside_the_status() {
|
|||
primary_repo_path: None,
|
||||
primary_repo_link: None,
|
||||
},
|
||||
ready_duration_ms: None,
|
||||
retained: None,
|
||||
},
|
||||
status,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,4 +28,12 @@ export interface RunSandboxInstance {
|
|||
'image'?: string | null;
|
||||
'snapshot'?: string | null;
|
||||
'runtime': RunSandboxRuntime;
|
||||
/**
|
||||
* How long the sandbox took to become ready, when recorded.
|
||||
*/
|
||||
'ready_duration_ms'?: number | null;
|
||||
/**
|
||||
* Whether the sandbox still exists after the run released it (kept, stopped or running) or was removed. Absent until the release.
|
||||
*/
|
||||
'retained'?: boolean | null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue