a3s-box-runtime 3.2.3

MicroVM runtime engine — VM lifecycle, OCI images, attestation, networking
Documentation
use std::collections::BTreeSet;
use std::sync::Arc;

use a3s_box_core::OperationId;
use serde_json::Value;

use super::*;
use crate::oci::build::{
    build, BoxBuildOptions, BoxBuildPlan, BuildCachePolicy, BuildOperationIdentity,
    BuildOutputReceipt, OCI_IMAGE_INDEX_MEDIA_TYPE,
};
use crate::oci::ImageStore;

const STORE_CAPACITY: u64 = 64 * 1024 * 1024;

fn source_digest() -> String {
    format!("sha256:{}", "a".repeat(64))
}

fn plan(platform: &str) -> BoxBuildPlan {
    plan_with_cache(platform, "disabled")
}

fn plan_with_cache(platform: &str, cache: &str) -> BoxBuildPlan {
    BoxBuildPlan::parse_acl(&format!(
        r#"
build "oci" {{
  cache = "{cache}"
  context = "."
  file = "Dockerfile"
  network = "none"
  platform = "{platform}"
  schema = "a3s.box.build-plan.v1"
}}
"#
    ))
    .expect("valid assembly fixture plan")
}

fn write_source(root: &std::path::Path) {
    std::fs::create_dir_all(root).expect("source directory");
    std::fs::write(
        root.join("Dockerfile"),
        "FROM scratch\nCOPY payload /payload\n",
    )
    .expect("Dockerfile");
    std::fs::write(root.join("payload"), "shared-platform-payload\n").expect("payload");
}

async fn recorded_input(
    operation: &str,
    platform: &str,
    source: &std::path::Path,
    store: Arc<ImageStore>,
) -> BuildOutputAssemblyInput {
    let plan = plan(platform);
    let identity = BuildOperationIdentity::new(
        OperationId::new(operation).expect("operation ID"),
        source_digest(),
    )
    .expect("build identity");
    let output = build(
        plan.compile(
            source,
            BoxBuildOptions {
                tag: Some(identity.output_reference().to_string()),
                quiet: true,
            },
        )
        .expect("compiled plan"),
        store,
    )
    .await
    .expect("single-platform fixture build");
    let receipt = BuildOutputReceipt::from_result(
        &identity,
        plan.canonical_digest().expect("plan digest"),
        &output,
        BuildCachePolicy::Disabled,
        None,
    )
    .expect("recorded output receipt");
    BuildOutputAssemblyInput::new(plan, receipt)
}

async fn fixture() -> (
    tempfile::TempDir,
    Arc<ImageStore>,
    BuildOutputAssemblyInput,
    BuildOutputAssemblyInput,
) {
    let temporary = tempfile::tempdir().expect("assembly fixture");
    let source = temporary.path().join("source");
    write_source(&source);
    let store = Arc::new(
        ImageStore::new(&temporary.path().join("images"), STORE_CAPACITY).expect("image store"),
    );
    let amd64 = recorded_input("assembly-amd64", "linux/amd64", &source, Arc::clone(&store)).await;
    let arm64 = recorded_input("assembly-arm64", "linux/arm64", &source, Arc::clone(&store)).await;
    (temporary, store, amd64, arm64)
}

fn manifest_platforms(result: &MultiPlatformBuildResult) -> Vec<String> {
    let digest = result
        .descriptor
        .digest
        .strip_prefix("sha256:")
        .expect("root SHA-256 digest");
    let bytes = std::fs::read(
        result
            .layout_directory
            .join("blobs")
            .join("sha256")
            .join(digest),
    )
    .expect("root image index");
    let index: Value = serde_json::from_slice(&bytes).expect("root image index JSON");
    index["manifests"]
        .as_array()
        .expect("manifest descriptors")
        .iter()
        .map(|descriptor| {
            let platform = &descriptor["platform"];
            format!(
                "{}/{}",
                platform["os"].as_str().expect("platform OS"),
                platform["architecture"]
                    .as_str()
                    .expect("platform architecture")
            )
        })
        .collect()
}

fn blob_names(layout: &std::path::Path) -> BTreeSet<String> {
    std::fs::read_dir(layout.join("blobs").join("sha256"))
        .expect("blob directory")
        .map(|entry| {
            entry
                .expect("blob entry")
                .file_name()
                .into_string()
                .expect("UTF-8 blob name")
        })
        .collect()
}

#[tokio::test]
async fn two_recorded_outputs_produce_one_deterministic_sorted_index() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let reversed = BuildOutputAssembly::new(
        "example.test/a3s/app:reversed",
        source_digest(),
        vec![arm64.clone(), amd64.clone()],
    )
    .expect("valid reversed assembly");
    let ordered = BuildOutputAssembly::new(
        "example.test/a3s/app:ordered",
        source_digest(),
        vec![amd64, arm64],
    )
    .expect("valid ordered assembly");

    let reversed_result = assemble_recorded_build_outputs(&reversed, Arc::clone(&store))
        .await
        .expect("reversed assembly");
    let ordered_result = assemble_recorded_build_outputs(&ordered, Arc::clone(&store))
        .await
        .expect("ordered assembly");

    assert_eq!(
        reversed_result.descriptor.media_type,
        OCI_IMAGE_INDEX_MEDIA_TYPE
    );
    assert_eq!(reversed_result.descriptor, ordered_result.descriptor);
    assert_eq!(reversed_result.platforms, ordered_result.platforms);
    assert_eq!(
        manifest_platforms(&reversed_result),
        vec!["linux/amd64", "linux/arm64"]
    );
}

#[tokio::test]
async fn duplicate_platforms_are_rejected_before_publication() {
    let (_temporary, store, amd64, _arm64) = fixture().await;
    let target = "example.test/a3s/app:duplicate";
    let error = BuildOutputAssembly::new(target, source_digest(), vec![amd64.clone(), amd64])
        .expect_err("duplicate platform must fail");

    assert!(error.to_string().contains("unique"));
    assert!(store.get(target).await.is_none());
}

#[tokio::test]
async fn plan_and_receipt_platform_mismatch_is_rejected() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let mismatched = BuildOutputAssemblyInput::new(arm64.plan().clone(), amd64.receipt().clone());
    let target = "example.test/a3s/app:mismatch";
    let error = BuildOutputAssembly::new(target, source_digest(), vec![amd64, mismatched])
        .expect_err("plan and receipt mismatch must fail");

    assert!(error.to_string().contains("plan"));
    assert!(store.get(target).await.is_none());
}

#[tokio::test]
async fn non_platform_build_intent_drift_is_rejected() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let drifted = BuildOutputAssemblyInput::new(
        plan_with_cache("linux/arm64", "content-addressed"),
        arm64.receipt().clone(),
    );
    let target = "example.test/a3s/app:intent-drift";
    let error = BuildOutputAssembly::new(target, source_digest(), vec![amd64, drifted])
        .expect_err("non-platform intent drift must fail");

    assert!(error.to_string().contains("non-platform build intent"));
    assert!(store.get(target).await.is_none());
}

#[tokio::test]
async fn receipt_tampering_is_rejected_before_publication() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let mut receipt = amd64.receipt().clone();
    receipt.output.descriptor.digest = format!("sha256:{}", "b".repeat(64));
    let tampered = BuildOutputAssemblyInput::new(amd64.plan().clone(), receipt);
    let target = "example.test/a3s/app:receipt-tamper";
    let assembly = BuildOutputAssembly::new(target, source_digest(), vec![tampered, arm64])
        .expect("path-independent receipt shape remains valid");
    let error = assemble_recorded_build_outputs(&assembly, Arc::clone(&store))
        .await
        .expect_err("tampered receipt must fail revalidation");

    assert!(error.to_string().contains("receipt") || error.to_string().contains("output"));
    assert!(store.get(target).await.is_none());
}

#[tokio::test]
async fn input_layout_tampering_leaves_no_target_publication() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let assembly = BuildOutputAssembly::new(
        "example.test/a3s/app:layout-tamper",
        source_digest(),
        vec![amd64.clone(), arm64],
    )
    .expect("valid assembly");
    let stored = store
        .get(&amd64.receipt().output.reference)
        .await
        .expect("recorded input");
    let blob = std::fs::read_dir(stored.path.join("blobs").join("sha256"))
        .expect("input blobs")
        .next()
        .expect("one input blob")
        .expect("blob entry")
        .path();
    let mut bytes = std::fs::read(&blob).expect("input blob bytes");
    bytes[0] ^= 1;
    std::fs::write(blob, bytes).expect("tampered input blob");

    assemble_recorded_build_outputs(&assembly, Arc::clone(&store))
        .await
        .expect_err("tampered input layout must fail");
    assert!(store.get(assembly.reference()).await.is_none());
}

#[tokio::test]
async fn shared_blobs_are_copied_once_into_the_assembled_layout() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let mut source_blobs = BTreeSet::new();
    for input in [&amd64, &arm64] {
        let stored = store
            .get(&input.receipt().output.reference)
            .await
            .expect("recorded input");
        source_blobs.extend(blob_names(&stored.path));
    }
    let assembly = BuildOutputAssembly::new(
        "example.test/a3s/app:deduplicated",
        source_digest(),
        vec![amd64, arm64],
    )
    .expect("valid assembly");

    let result = assemble_recorded_build_outputs(&assembly, Arc::clone(&store))
        .await
        .expect("assembled output");
    let assembled_blobs = blob_names(&result.layout_directory);

    assert_eq!(assembled_blobs.len(), source_blobs.len() + 1);
    assert!(source_blobs.is_subset(&assembled_blobs));
    assert_eq!(result.blob_count, assembled_blobs.len());
}

#[tokio::test]
async fn repeated_publication_is_idempotent_in_the_one_image_store() {
    let (_temporary, store, amd64, arm64) = fixture().await;
    let assembly = BuildOutputAssembly::new(
        "example.test/a3s/app:idempotent",
        source_digest(),
        vec![amd64, arm64],
    )
    .expect("valid assembly");

    let first = assemble_recorded_build_outputs(&assembly, Arc::clone(&store))
        .await
        .expect("first assembly");
    let second = assemble_recorded_build_outputs(&assembly, Arc::clone(&store))
        .await
        .expect("idempotent assembly");

    assert_eq!(first.descriptor, second.descriptor);
    assert_eq!(first.layout_directory, second.layout_directory);
    assert_eq!(first.blob_inventory_digest, second.blob_inventory_digest);
    assert_eq!(store.list().await.len(), 3);
}