Skip to content

feat(snapshot): finalize artifacts and direct archives - #1487

Open
appcypher wants to merge 2 commits into
releases/v0.7.0from
appcypher/direct-snapshot-archives
Open

feat(snapshot): finalize artifacts and direct archives#1487
appcypher wants to merge 2 commits into
releases/v0.7.0from
appcypher/direct-snapshot-archives

Conversation

@appcypher

Copy link
Copy Markdown
Member

TL;DR

Finalize schema-1 snapshot artifacts and add direct stopped-sandbox archive capture and archive-to-child creation without installing an intermediate snapshot directory.

Description

  • represent file snapshots as an ordered layer closure with stable snapshot and layer identities, a separate descriptor digest, and mutable metadata sidecars
  • read and translate released v0.6.6 through v0.6.16 snapshot layouts while retaining representable downgrade paths
  • stream stopped or crashed sandbox state directly to plain tar or zstd archives, with optional pinned image closure
  • create a child sandbox directly from an archive through the Rust, Python, TypeScript, and Go SDK surfaces and the CLI
  • validate archive paths, limits, sparse extents, ownership bindings, transport integrity, and failure cleanup before publication
  • document the finalized snapshot shape and direct archive workflows

Test Plan

  • cargo fmt --all -- --check
  • cargo test --locked --offline -p microsandbox-image --lib (211 passed, 4 ignored)
  • cargo test --locked --offline -p microsandbox --test snapshot_artifact (43 passed)
  • Live macOS ARM64/HVF matrix: tar and zstd, image included and omitted, direct capture, direct child creation, corruption rejection, and v0.6.6 compatibility
  • Live Linux x86_64/KVM matrix: tar and zstd, image included and omitted, direct capture, direct child creation, and restart persistence

Separate stable snapshot identity from descriptor integrity and represent file state as an ordered layer closure. Preserve adjacent-release compatibility with automatic forward migration and explicit representable downgrade paths.\n\nAdd direct stopped-sandbox archive capture and archive-backed sandbox restore across the CLI and all SDKs without materializing an installed snapshot directory. Keep mutable labels in an owner-bound metadata sidecar and document the new artifact and API contracts.
Clean up incomplete archive-to-child staging and defer pinned image resolution to the single-pass local restore path while rejecting unsupported cloud conversion.

Use portable archive member names across hosts and expose the root upper.ext4 binding before publishing v0.6.6 downgrade descriptors. Document archive inputs consistently across SDKs.
@appcypher
appcypher requested a review from toksdotdev as a code owner August 31, 2026 15:08
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Sequence Diagram

sequenceDiagram
    participant Client as CLI / SDK
    participant Builder as Snapshot or Sandbox Builder
    participant Runtime as Local Runtime
    participant Archive as Tar / Zstd Archive
    participant Child as Child Sandbox
    Client->>Builder: Capture stopped sandbox directly
    Builder->>Runtime: Validate state and assemble closure
    Runtime->>Archive: Stream descriptor, layers, and optional image
    Runtime-->>Client: Stable snapshot ID and descriptor digest
    Client->>Builder: Create child from archive
    Builder->>Runtime: Validate and stage archive
    Runtime->>Child: Materialize upper and pinned image closure
    Runtime-->>Client: Return child sandbox handle
Loading

Fix all with Greploop Fix All in Codex Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
crates/cli/lib/commands/snapshot.rs:231-239
**Archive errors leave spinner active**

When direct archive creation fails, `?` returns without clearing the active spinner, leaving stale or malformed terminal output instead of following the adjacent snapshot-creation error path.

```suggestion
    if let Some(archive_path) = args.archive.as_ref() {
        return match builder.create_archive(archive_path, args.plain_tar).await {
            Ok(archive) => {
                spinner.finish_success("Snapshotted");
                if !args.quiet {
                    println!("{}", archive.id());
                    println!("{}", archive.path().display());
                }
                Ok(())
            }
            Err(e) => {
                spinner.finish_clear();
                Err(e.into())
            }
        };
    }
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(snapshot): harden direct archive com..." | Re-trigger Greptile

Comment on lines +231 to +239
if let Some(archive_path) = args.archive.as_ref() {
let archive = builder.create_archive(archive_path, args.plain_tar).await?;
spinner.finish_success("Snapshotted");
if !args.quiet {
println!("{}", archive.id());
println!("{}", archive.path().display());
}
return Ok(());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Archive errors leave spinner active

When direct archive creation fails, ? returns without clearing the active spinner, leaving stale or malformed terminal output instead of following the adjacent snapshot-creation error path.

Suggested change
if let Some(archive_path) = args.archive.as_ref() {
let archive = builder.create_archive(archive_path, args.plain_tar).await?;
spinner.finish_success("Snapshotted");
if !args.quiet {
println!("{}", archive.id());
println!("{}", archive.path().display());
}
return Ok(());
}
if let Some(archive_path) = args.archive.as_ref() {
return match builder.create_archive(archive_path, args.plain_tar).await {
Ok(archive) => {
spinner.finish_success("Snapshotted");
if !args.quiet {
println!("{}", archive.id());
println!("{}", archive.path().display());
}
Ok(())
}
Err(e) => {
spinner.finish_clear();
Err(e.into())
}
};
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/cli/lib/commands/snapshot.rs
Line: 231-239

Comment:
**Archive errors leave spinner active**

When direct archive creation fails, `?` returns without clearing the active spinner, leaving stale or malformed terminal output instead of following the adjacent snapshot-creation error path.

```suggestion
    if let Some(archive_path) = args.archive.as_ref() {
        return match builder.create_archive(archive_path, args.plain_tar).await {
            Ok(archive) => {
                spinner.finish_success("Snapshotted");
                if !args.quiet {
                    println!("{}", archive.id());
                    println!("{}", archive.path().display());
                }
                Ok(())
            }
            Err(e) => {
                spinner.finish_clear();
                Err(e.into())
            }
        };
    }
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code


/// Result of direct sandbox-to-archive capture.
#[napi(js_name = "SnapshotArchive")]
pub struct JsSnapshotArchive {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants