feat: release Dever 0.1.5 with direct workers and long transactions
This commit is contained in:
1 parent
89629c2197
commit
a900d7e8db
153 files changed
+3676
-3708
No files matched your search
@@ -17,8 +17,8 @@ mod daemon;
|
||||
mod inputs;
|
||||
#[path = "../../dever-tests/tests/support/process.rs"]
|
||||
mod process;
|
||||
#[path = "../../dever-tests/tests/support/sandbox.rs"]
|
||||
mod sandbox;
|
||||
#[path = "../../dever-tests/tests/support/process_runtime.rs"]
|
||||
mod process_runtime;
|
||||
use super::temp::TemporaryDirectory;
|
||||
|
||||
#[path = "ecosystem_acceptance.rs"]
|
||||
@@ -147,11 +147,15 @@ fn author_inputs(author: &Path, workspace: &Path) -> Ed25519KeyPair {
|
||||
),
|
||||
);
|
||||
}
|
||||
let compiler = input(author, Path::new(env!("CARGO_BIN_EXE_dever")), "dever");
|
||||
let compiler = input(
|
||||
author,
|
||||
&workspace.join("target/native-release-inputs/bin/dever"),
|
||||
"dever",
|
||||
);
|
||||
let core_libraries = inputs::core_libraries()
|
||||
.into_iter()
|
||||
.map(|(name, source)| {
|
||||
let mut value = input(author, &source, name);
|
||||
let mut value = input(author, &source, &name);
|
||||
value["path"] = json!(name);
|
||||
value
|
||||
})
|
||||
@@ -171,11 +175,11 @@ fn author_inputs(author: &Path, workspace: &Path) -> Ed25519KeyPair {
|
||||
.map(|name| links[*name].clone())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let sandbox = sandbox::assets(workspace).into_iter().map(|asset| {
|
||||
let process_runtime = process_runtime::assets(workspace).into_iter().map(|asset| {
|
||||
let relative = format!("inputs/{}", asset.path);
|
||||
write(author, &relative, &asset.bytes);
|
||||
fs::set_permissions(author.join(&relative), fs::Permissions::from_mode(if asset.executable { 0o755 } else { 0o644 })).unwrap();
|
||||
json!({"source":relative, "path":asset.path.strip_prefix("sandbox/").unwrap(), "sha256":sha256_file(&author.join(&relative)).unwrap()})
|
||||
json!({"source":relative, "path":asset.path.strip_prefix("process-runtime/").unwrap(), "sha256":sha256_file(&author.join(&relative)).unwrap()})
|
||||
}).collect::<Vec<_>>();
|
||||
write(
|
||||
author,
|
||||
@@ -193,7 +197,7 @@ fn author_inputs(author: &Path, workspace: &Path) -> Ed25519KeyPair {
|
||||
"skill":[skill],
|
||||
"start":ordered(&["crt1.o","crti.o","crtbeginT.o"]),
|
||||
"libraries":ordered(&["libc.a","libm.a","libmvec.a","libgcc.a","libgcc_eh.a"]),
|
||||
"end":ordered(&["crtend.o","crtn.o"]), "sandbox":sandbox
|
||||
"end":ordered(&["crtend.o","crtn.o"]), "process_runtime":process_runtime
|
||||
}))
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -211,10 +215,11 @@ fn isolated_os_command(root: &Path) -> Command {
|
||||
}
|
||||
|
||||
fn isolated_os_namespace(root: &Path) -> Command {
|
||||
let assets = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../target/sandbox-inputs/assets")
|
||||
.canonicalize()
|
||||
.expect("prepare the explicit Linux isolation assets");
|
||||
let isolator = PathBuf::from(
|
||||
std::env::var_os("DEVER_TEST_ISOLATOR")
|
||||
.expect("set an absolute test-only namespace isolator path"),
|
||||
);
|
||||
assert!(isolator.is_absolute());
|
||||
let init = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../target/debug/examples/native-acceptance-init")
|
||||
.canonicalize()
|
||||
@@ -222,10 +227,7 @@ fn isolated_os_namespace(root: &Path) -> Command {
|
||||
let bytes = fs::read(&init).unwrap();
|
||||
let executable = goblin::elf::Elf::parse(&bytes).unwrap();
|
||||
assert!(executable.interpreter.is_none() && executable.libraries.is_empty());
|
||||
// A plain chroot cannot create a nested user namespace. Use a real mount
|
||||
// namespace root so the program can retain its normal Worker sandbox.
|
||||
dever_sandbox::validate_bwrap(&fs::read(assets.join("bin/bwrap")).unwrap()).unwrap();
|
||||
let mut command = Command::new(assets.join("bin/bwrap"));
|
||||
let mut command = Command::new(isolator);
|
||||
command
|
||||
.env_clear()
|
||||
.args([
|
||||
@@ -301,6 +303,33 @@ fn application(root: &Path, profile: &str) {
|
||||
"exercise() (okay: Bool) { dever.task.sleep(1)\n okay = true }\n"
|
||||
};
|
||||
write(root, "module/sample/value/app.dever", app);
|
||||
if matches!(profile, "sqlite" | "both") {
|
||||
write(
|
||||
root,
|
||||
"module/sample/value/api.dever",
|
||||
"cmd exercise = app.exercise\nlong cmd stages = app.stages\n",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/sample/value/app.dever",
|
||||
format!(
|
||||
"{app}\nstages() (okay: Bool) {{ id = store()\n dever.task.sleep(1)\n okay = finish(id) }}\ntransaction store() (id: model.id) {{ saved = model.create({{ name = \"stage\" }})\n id = saved.id }}\ntransaction finish(id: model.id) (okay: Bool) {{ count = model.count()\n removed = model.delete(id)\n okay = count == 1 and removed == 1 }}\n"
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_profile_projects_have_valid_source_contracts() {
|
||||
for profile in PROFILES {
|
||||
let project = TemporaryDirectory::new();
|
||||
application(project.path(), profile);
|
||||
command(
|
||||
Command::new(env!("CARGO_BIN_EXE_dever"))
|
||||
.arg("check")
|
||||
.arg(project.path()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_response(output: Output) {
|
||||
@@ -489,7 +518,7 @@ fn signed_core_builds_without_host_compiler_libraries_or_tools() {
|
||||
#[test]
|
||||
#[ignore = "requires four optimized archives and component-fixture; starts only an owned daemon, SQLite and exec Worker fixtures"]
|
||||
fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon() {
|
||||
let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let workspace = inputs::workspace();
|
||||
let profiles = archive_report(&workspace);
|
||||
let author = TemporaryDirectory::new();
|
||||
let key = author_inputs(author.path(), &workspace);
|
||||
@@ -505,7 +534,7 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||
let release = layout.downloads().join(version.as_str());
|
||||
command(
|
||||
Command::new(workspace.join("target/debug/examples/native-release"))
|
||||
Command::new(workspace.join("target/native-release-inputs/bin/native-release"))
|
||||
.arg(author.path())
|
||||
.arg("--output")
|
||||
.arg(&release),
|
||||
@@ -523,10 +552,13 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
fs::remove_dir_all(&release).unwrap();
|
||||
drop(author);
|
||||
let compiler = layout.versions().join(version.as_str()).join("dever-core");
|
||||
let mut daemon = daemon::TestDaemon::start(&layout);
|
||||
let mut daemon = daemon::TestDaemon::start_with_executable(
|
||||
&layout,
|
||||
&workspace.join("target/native-release-inputs/bin/deverd"),
|
||||
);
|
||||
let projects = TemporaryDirectory::new();
|
||||
let mut binaries = serde_json::Map::new();
|
||||
let mut programs: Vec<(PathBuf, PathBuf)> = Vec::new();
|
||||
let mut programs = Vec::new();
|
||||
for profile in PROFILES {
|
||||
let project = projects.path().join(profile);
|
||||
application(&project, profile);
|
||||
@@ -538,6 +570,15 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
"{}",
|
||||
]),
|
||||
));
|
||||
if matches!(profile, "sqlite" | "both") {
|
||||
assert_response(command(
|
||||
Command::new(&compiler).arg("run").arg(&project).args([
|
||||
"--",
|
||||
"sample.value.stages",
|
||||
"{}",
|
||||
]),
|
||||
));
|
||||
}
|
||||
let executable = project.join("program");
|
||||
command(
|
||||
Command::new(&compiler)
|
||||
@@ -547,7 +588,7 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
.arg(&executable),
|
||||
);
|
||||
binaries.insert(profile.into(), json!({"bytes":fs::metadata(&executable).unwrap().len(),"sha256":sha256_file(&executable).unwrap()}));
|
||||
programs.push((project, executable));
|
||||
programs.push((profile, project, executable));
|
||||
}
|
||||
assert_eq!(cache_status(&layout).unwrap().entries, 4);
|
||||
let exec_project = TemporaryDirectory::new();
|
||||
@@ -595,19 +636,25 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
"managed_run": true, "standalone_run": true,
|
||||
"source_project_removed": true, "response": 7
|
||||
});
|
||||
for (project, executable) in programs {
|
||||
for (profile, project, executable) in programs {
|
||||
fs::remove_dir_all(project.join("module")).unwrap();
|
||||
assert_response(command(
|
||||
Command::new(executable).args(["sample.value.exercise", "{}"]),
|
||||
Command::new(&executable).args(["sample.value.exercise", "{}"]),
|
||||
));
|
||||
if matches!(profile, "sqlite" | "both") {
|
||||
assert_response(command(
|
||||
Command::new(executable).args(["sample.value.stages", "{}"]),
|
||||
));
|
||||
}
|
||||
}
|
||||
let report = json!({
|
||||
"format":"dever-native-release-acceptance-v1", "platform":"linux-x86_64",
|
||||
"version":version.as_str(), "profiles":profiles,"programs":binaries,"exec":exec,
|
||||
"manifest_sha256":format!("{:x}", sha2::Sha256::digest(&manifest)),
|
||||
"checks":{"pack_determinism":true,"signed_install":true,"managed_profiles":4,"standalone_profiles":4,"sqlite_crud":true,"signed_exec_worker":true},
|
||||
"checks":{"pack_determinism":true,"signed_install":true,"managed_profiles":4,"standalone_profiles":4,"sqlite_crud":true,"long_staged_transactions":true,"signed_exec_worker":true},
|
||||
"postgres_server":"not_run", "cross_host_source_reproducibility":"not_run"
|
||||
});
|
||||
retain_candidate(&layout, &version, "native", &report);
|
||||
use sha2::Digest;
|
||||
write(
|
||||
&workspace,
|
||||
@@ -615,3 +662,45 @@ fn optimized_profiles_make_reproducible_signed_release_and_run_through_daemon()
|
||||
serde_json::to_vec_pretty(&report).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn retain_candidate(layout: &Layout, version: &Version, name: &str, report: &Value) {
|
||||
let Some(destination) = std::env::var_os("DEVER_NATIVE_ACCEPTANCE_OUTPUT") else {
|
||||
return;
|
||||
};
|
||||
let destination = PathBuf::from(destination);
|
||||
assert!(destination.is_absolute());
|
||||
fs::create_dir_all(&destination).unwrap();
|
||||
let destination = destination.join(name);
|
||||
fs::create_dir(&destination).unwrap();
|
||||
let payload = destination.join("release");
|
||||
let resources = dever_cli::toolchain::SignedResources::load(layout, version).unwrap();
|
||||
let manifest = resources.manifest();
|
||||
for artifact in manifest.artifacts.iter().chain(
|
||||
manifest
|
||||
.extensions
|
||||
.iter()
|
||||
.flat_map(|extension| &extension.artifacts),
|
||||
) {
|
||||
let output = payload.join(&artifact.path);
|
||||
fs::create_dir_all(output.parent().unwrap()).unwrap();
|
||||
fs::copy(resources.path(&artifact.path).unwrap(), &output).unwrap();
|
||||
assert_eq!(sha256_file(&output).unwrap(), artifact.sha256);
|
||||
}
|
||||
for file in ["manifest.json", "manifest.sig"] {
|
||||
fs::copy(
|
||||
layout.versions().join(version.as_str()).join(file),
|
||||
payload.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
fs::copy(
|
||||
layout.state().join("trusted-release-key"),
|
||||
destination.join("test-release.pub"),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
destination.join("verification.json"),
|
||||
serde_json::to_vec_pretty(report).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -64,7 +64,6 @@ fn bootstrap_payload_has_a_separate_signed_complete_loader_closure() {
|
||||
"bootstrap/deverd",
|
||||
"bootstrap/lib/libgcc_s.so.1",
|
||||
"bootstrap/deverd.service",
|
||||
"bootstrap/dever-sandbox",
|
||||
] {
|
||||
let artifact = manifest
|
||||
.artifacts
|
||||
@@ -78,9 +77,12 @@ fn bootstrap_payload_has_a_separate_signed_complete_loader_closure() {
|
||||
assert!(unit.contains("RuntimeDirectory=dever"));
|
||||
assert!(!unit.contains("Environment"));
|
||||
assert!(!unit.contains("PATH"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(release.join("bootstrap/dever-sandbox")).unwrap(),
|
||||
dever_sandbox::APPARMOR_PROFILE
|
||||
assert!(!release.join("bootstrap/dever-process").exists());
|
||||
assert!(
|
||||
!manifest
|
||||
.artifacts
|
||||
.iter()
|
||||
.any(|artifact| artifact.path.contains("bwrap") || artifact.path.contains("apparmor"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,6 +176,73 @@ fn signed_installed_versions_keep_their_original_bootstrap_service_template() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_historical_bootstrap_profiles_are_read_only_and_version_bound() {
|
||||
use dever_cli::toolchain::{Artifact, Layout, MachineManager, Version};
|
||||
for release_version in ["0.1.4", "0.1.5"] {
|
||||
let mut author = Author::new();
|
||||
author.settings["version"] = json!(release_version);
|
||||
add_bootstrap(&mut author);
|
||||
author.save();
|
||||
let machine = TemporaryDirectory::new();
|
||||
let layout = Layout::new(machine.path().join("machine"));
|
||||
layout.initialize().unwrap();
|
||||
let version = Version::parse(release_version).unwrap();
|
||||
let installed = layout.versions().join(release_version);
|
||||
let mut manifest = packaging::create(&author.root, &installed).unwrap();
|
||||
let profile = installed.join("bootstrap/dever-sandbox");
|
||||
fs::write(&profile, b"historical profile fixture\n").unwrap();
|
||||
manifest.artifacts.push(Artifact {
|
||||
path: "bootstrap/dever-sandbox".into(),
|
||||
bytes: fs::metadata(&profile).unwrap().len(),
|
||||
sha256: sha256_file(&profile).unwrap(),
|
||||
});
|
||||
let encoded = serde_json::to_vec(&manifest).unwrap();
|
||||
let key = Ed25519KeyPair::from_pkcs8(&fs::read(author.root.join("signing.pk8")).unwrap())
|
||||
.unwrap();
|
||||
fs::write(installed.join("manifest.json"), &encoded).unwrap();
|
||||
fs::write(
|
||||
installed.join("manifest.sig"),
|
||||
key.sign(&encoded)
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
layout.state().join("trusted-release-key"),
|
||||
author
|
||||
.public_key
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap();
|
||||
let manager = MachineManager::new(layout);
|
||||
if release_version == "0.1.4" {
|
||||
assert_eq!(
|
||||
manager.resolve_core(&version).unwrap(),
|
||||
installed.join("dever-core")
|
||||
);
|
||||
fs::write(profile, b"unsigned change").unwrap();
|
||||
assert!(
|
||||
manager
|
||||
.resolve_core(&version)
|
||||
.unwrap_err()
|
||||
.contains("integrity verification")
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
manager
|
||||
.resolve_core(&version)
|
||||
.unwrap_err()
|
||||
.contains("unsupported artifact")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "root-only installer trust rejection; never touches the actual machine installation"]
|
||||
fn bootstrap_installer_rejects_untrusted_inputs_before_publication() {
|
||||
@@ -313,7 +382,6 @@ fn bootstrap_recovery_is_idempotent_and_rejects_unrecorded_targets() {
|
||||
for directory in [
|
||||
"usr/local/bin",
|
||||
"etc/systemd/system/multi-user.target.wants",
|
||||
"etc/apparmor.d",
|
||||
] {
|
||||
fs::create_dir_all(root.join(directory)).unwrap();
|
||||
}
|
||||
@@ -331,20 +399,12 @@ fn bootstrap_recovery_is_idempotent_and_rejects_unrecorded_targets() {
|
||||
let link_hash =
|
||||
|target: &str| format!("{:x}", Sha256::digest(format!("link\0{target}").as_bytes()));
|
||||
let journal = json!({"format":"dever-bootstrap-transaction-v1","phase":"prepared","service_was_active":null,"service_start_requested":false,"changes":[
|
||||
{"target":"sandbox","before":null,"after":node_hash("directory",b"")},
|
||||
{"target":"apparmor","before":null,"after":node_hash("file",b"new profile")},
|
||||
{"target":"bin","before":node_hash("directory",b""),"after":node_hash("directory",b"")},
|
||||
{"target":"service","before":null,"after":node_hash("file",b"new service")},
|
||||
{"target":"entry","before":null,"after":link_hash("/opt/dever/bin/dever")},
|
||||
{"target":"enable","before":null,"after":link_hash("../deverd.service")},
|
||||
{"target":"active","before":null,"after":node_hash("file",b"new active")}
|
||||
]});
|
||||
fs::create_dir(root.join("opt/dever/.dever-bootstrap-sandbox.next")).unwrap();
|
||||
fs::write(
|
||||
root.join("etc/apparmor.d/.dever-bootstrap-dever-sandbox.next"),
|
||||
b"new profile",
|
||||
)
|
||||
.unwrap();
|
||||
let path = layout.state().join("bootstrap-install.json");
|
||||
fs::write(&path, serde_json::to_vec(&journal).unwrap()).unwrap();
|
||||
let mut invalid = journal.clone();
|
||||
@@ -374,8 +434,6 @@ fn bootstrap_recovery_is_idempotent_and_rejects_unrecorded_targets() {
|
||||
// undo the new active version, including after a failed cleanup retry.
|
||||
fs::write(&service, b"new service").unwrap();
|
||||
symlink("/opt/dever/bin/dever", &entry).unwrap();
|
||||
fs::create_dir(root.join("opt/dever/sandbox")).unwrap();
|
||||
fs::write(root.join("etc/apparmor.d/dever-sandbox"), b"new profile").unwrap();
|
||||
symlink(
|
||||
"../deverd.service",
|
||||
root.join("etc/systemd/system/multi-user.target.wants/deverd.service"),
|
||||
|
||||
@@ -212,24 +212,13 @@ fn signed_bootstrap_installs_and_serves_two_projects_without_host_libraries() {
|
||||
.arg(installer.path()),
|
||||
);
|
||||
let layout = Layout::new(image.path().join("opt/dever"));
|
||||
let sandbox_artifact = manifest
|
||||
.artifacts
|
||||
.iter()
|
||||
.find(|artifact| artifact.path == "sandbox/linux-x86_64/bin/bwrap")
|
||||
.unwrap();
|
||||
let sandbox_store = layout.root().join("sandbox");
|
||||
let sandbox_helper = sandbox_store.join(&sandbox_artifact.sha256).join("bwrap");
|
||||
assert_eq!(
|
||||
sha256_file(&sandbox_helper).unwrap(),
|
||||
sandbox_artifact.sha256
|
||||
);
|
||||
assert_eq!(
|
||||
fs::metadata(&sandbox_helper).unwrap().mode() & 0o7777,
|
||||
0o755
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(image.path().join("etc/apparmor.d/dever-sandbox")).unwrap(),
|
||||
dever_sandbox::APPARMOR_PROFILE
|
||||
assert!(!image.path().join("etc/apparmor.d/dever-process").exists());
|
||||
assert!(
|
||||
!manifest
|
||||
.artifacts
|
||||
.iter()
|
||||
.any(|artifact| artifact.path.ends_with("/bin/bwrap")
|
||||
|| artifact.path.ends_with("/bin/guard"))
|
||||
);
|
||||
let launcher = layout.bin().join("dever");
|
||||
let executable = layout.bin().join("deverd");
|
||||
@@ -305,38 +294,6 @@ fn signed_bootstrap_installs_and_serves_two_projects_without_host_libraries() {
|
||||
active
|
||||
);
|
||||
fs::write(&unit_path, old_unit).unwrap();
|
||||
// Retain a different, correctly signed static helper identity through an
|
||||
// upgrade. ELF trailing padding keeps this executable's actual behavior.
|
||||
let mut previous_helper = fs::read(&sandbox_helper).unwrap();
|
||||
previous_helper.push(0);
|
||||
let previous_hash = dever_sandbox::validate_bwrap(&previous_helper).unwrap();
|
||||
let previous_root = sandbox_store.join(&previous_hash);
|
||||
fs::create_dir(&previous_root).unwrap();
|
||||
fs::write(previous_root.join("bwrap"), &previous_helper).unwrap();
|
||||
fs::set_permissions(
|
||||
previous_root.join("bwrap"),
|
||||
fs::Permissions::from_mode(0o755),
|
||||
)
|
||||
.unwrap();
|
||||
let mut previous_manifest = manifest.clone();
|
||||
let previous_artifact = previous_manifest
|
||||
.artifacts
|
||||
.iter_mut()
|
||||
.find(|artifact| artifact.path == sandbox_artifact.path)
|
||||
.unwrap();
|
||||
previous_artifact.sha256 = previous_hash.clone();
|
||||
previous_artifact.bytes = previous_helper.len() as u64;
|
||||
let previous_encoded = serde_json::to_vec(&previous_manifest).unwrap();
|
||||
fs::write(
|
||||
previous_root.join("manifest.sig"),
|
||||
key.sign(&previous_encoded)
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(previous_root.join("manifest.json"), previous_encoded).unwrap();
|
||||
// Retry is supported, but a later damaged package cannot damage the
|
||||
// published launcher or compiler selection.
|
||||
command(
|
||||
@@ -345,15 +302,6 @@ fn signed_bootstrap_installs_and_serves_two_projects_without_host_libraries() {
|
||||
.arg(installer.path()),
|
||||
);
|
||||
assert_eq!(fs::read_to_string(&unit_path).unwrap(), current_unit);
|
||||
assert_eq!(
|
||||
sha256_file(&sandbox_helper).unwrap(),
|
||||
sandbox_artifact.sha256
|
||||
);
|
||||
assert_eq!(
|
||||
sha256_file(&previous_root.join("bwrap")).unwrap(),
|
||||
previous_hash
|
||||
);
|
||||
assert_eq!(fs::read_dir(&sandbox_store).unwrap().count(), 2);
|
||||
let before_bad_install = fs::read(layout.state().join("active-version")).unwrap();
|
||||
fs::write(
|
||||
release.join("bootstrap/lib/libgcc_s.so.1"),
|
||||
|
||||
@@ -98,23 +98,11 @@ fn packaged_project(root: &Path, source: &Path, layout: &Layout) {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit current runtime archive, author C/LLVM inputs and owned daemon/sandbox"]
|
||||
#[ignore = "requires explicit current runtime archive, author C/LLVM inputs and owned daemon/process_runtime"]
|
||||
fn signed_cli_runs_and_packages_ordinary_command_with_private_library() {
|
||||
let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let workspace = inputs::workspace();
|
||||
let author = TemporaryDirectory::new();
|
||||
let key = author_inputs(author.path(), &workspace);
|
||||
// This test validates the command boundary, not release-profile trimming.
|
||||
// Use the explicitly rebuilt archive containing the current wire ABI.
|
||||
let current = input(
|
||||
author.path(),
|
||||
&workspace.join("target/native-runtime-abi/debug/libdever_backend_bridge.a"),
|
||||
"command-base.a",
|
||||
);
|
||||
let settings_path = author.path().join("config/setting.json");
|
||||
let mut settings: Value = serde_json::from_slice(&fs::read(&settings_path).unwrap()).unwrap();
|
||||
settings["profiles"]["base"] = current;
|
||||
fs::write(settings_path, serde_json::to_vec(&settings).unwrap()).unwrap();
|
||||
|
||||
let machine = machine_temporary_directory(&workspace);
|
||||
let layout = Layout::new(machine.path());
|
||||
layout.initialize().unwrap();
|
||||
@@ -123,7 +111,10 @@ fn signed_cli_runs_and_packages_ordinary_command_with_private_library() {
|
||||
packaging::create(author.path(), &release).unwrap();
|
||||
install_release(&layout, &version, &key);
|
||||
let compiler = layout.versions().join(version.as_str()).join("dever-core");
|
||||
let mut daemon = daemon::TestDaemon::start(&layout);
|
||||
let mut daemon = daemon::TestDaemon::start_with_executable(
|
||||
&layout,
|
||||
&workspace.join("target/native-release-inputs/bin/deverd"),
|
||||
);
|
||||
let source = TemporaryDirectory::new();
|
||||
project(source.path(), &workspace);
|
||||
command(Command::new(&compiler).arg("check").arg(source.path()));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Explicit x86 compiler -> ARM application acceptance; QEMU user mode does
|
||||
//! not establish the namespace/seccomp contract of external Workers.
|
||||
//! not establish external Worker lifecycle behavior on a real target kernel.
|
||||
use super::*;
|
||||
use dever_cli::toolchain::BuildTarget;
|
||||
use dever_cli::toolchain::compilation::{CompileKind, CompileRequest};
|
||||
@@ -281,7 +281,7 @@ fn signed_host_compiler_builds_arm_profiles_and_runs_standalone_under_qemu() {
|
||||
"format":"dever-cross-application-acceptance-v1", "host":"linux-x86_64", "target":"linux-aarch64",
|
||||
"programs":evidence, "http":http, "qemu_sha256":sha256_file(&emulator).unwrap(),
|
||||
"checks":{"signed_install":true,"profiles":4,"private_arm_build":true,"standalone_after_source_and_machine_removal":true,"sqlite_crud":true,"cache_entries":6,"cache_target_isolation":true,"tampered_target_cache_hit_rejected":true},
|
||||
"postgres_server":"not_run", "worker_sandbox":"requires_arm_kernel_system_emulation"
|
||||
"postgres_server":"not_run", "worker_execution":"requires_arm_kernel_system_emulation"
|
||||
});
|
||||
write(
|
||||
&workspace,
|
||||
|
||||
@@ -15,8 +15,8 @@ fn digest(bytes: &[u8]) -> String {
|
||||
format!("{:x}", Sha256::digest(bytes))
|
||||
}
|
||||
|
||||
fn add_target_sandbox(author: &Path, workspace: &Path) {
|
||||
let root = workspace.join("target/arm64-cross/sandbox");
|
||||
fn add_target_process_runtime(author: &Path, workspace: &Path) {
|
||||
let root = workspace.join("target/arm64-cross/process_runtime");
|
||||
let mut files = Vec::new();
|
||||
let mut inputs = Vec::new();
|
||||
for directory in ["bin", "lib"] {
|
||||
@@ -25,16 +25,16 @@ fn add_target_sandbox(author: &Path, workspace: &Path) {
|
||||
assert!(entry.file_type().unwrap().is_file());
|
||||
let relative = format!("{directory}/{}", entry.file_name().to_str().unwrap());
|
||||
let bytes = fs::read(entry.path()).unwrap();
|
||||
let source = format!("inputs/arm-sandbox/{relative}");
|
||||
let source = format!("inputs/arm-process-runtime/{relative}");
|
||||
write(author, &source, &bytes);
|
||||
inputs.push(json!({"source":source,"path":relative,"sha256":digest(&bytes)}));
|
||||
files.push((relative, bytes));
|
||||
}
|
||||
}
|
||||
dever_sandbox::validate_assets_for_target(&files, TARGET.platform()).unwrap();
|
||||
dever_process::validate_assets_for_target(&files, TARGET.platform()).unwrap();
|
||||
let path = author.join("config/setting.json");
|
||||
let mut settings: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
|
||||
settings["sandbox_targets"] = json!({"linux-aarch64":inputs});
|
||||
settings["process_runtime_targets"] = json!({"linux-aarch64":inputs});
|
||||
fs::write(path, serde_json::to_vec(&settings).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ fn prepare_locked_application(
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit ARM runtimes/dependencies/sandbox and native archives; builds with an owned host daemon and exports apps for ARM system QEMU"]
|
||||
#[ignore = "requires explicit ARM runtimes/dependencies/process_runtime and native archives; builds with an owned host daemon and exports apps for ARM system QEMU"]
|
||||
fn signed_arm_ecosystems_build_offline_standalone_apps_for_system_guest() {
|
||||
build_guest_apps(&ECOSYSTEMS, "ecosystem-build-acceptance.json");
|
||||
}
|
||||
@@ -294,7 +294,7 @@ fn build_guest_apps(ecosystems: &[&str], report: &str) {
|
||||
let author = TemporaryDirectory::new();
|
||||
let key = author_inputs(author.path(), &workspace);
|
||||
add_arm_inputs(author.path(), &workspace);
|
||||
add_target_sandbox(author.path(), &workspace);
|
||||
add_target_process_runtime(author.path(), &workspace);
|
||||
let machine = machine_temporary_directory(&workspace);
|
||||
let layout = Layout::new(machine.path());
|
||||
layout.initialize().unwrap();
|
||||
@@ -349,6 +349,6 @@ fn build_guest_apps(ecosystems: &[&str], report: &str) {
|
||||
"checks":{"signed_runtime_install":true,"offline_lib_doctor":ecosystems.len(),"actual_cli_arm_build":ecosystems.len(),"source_and_build_machine_removed":true},
|
||||
"guest_probe":{"arguments":["sample.worker.probe","{}"],"expected_data":true},
|
||||
"guest_failure":{"arguments":["sample.worker.reject","{}"],"expected":"runtime fault; next fresh probe still succeeds; no remaining Worker"},
|
||||
"guest_execution":"not_run", "sandbox_execution":"requires full ARM kernel guest"
|
||||
"guest_execution":"not_run", "worker_execution":"requires ARM system guest verification"
|
||||
})).unwrap());
|
||||
}
|
||||
@@ -204,6 +204,115 @@ fn ecosystem_inputs(author: &Path, workspace: &Path) {
|
||||
fs::write(config, serde_json::to_vec(&settings).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit native and zero-Lib ecosystem packs; uses an owned root installation and daemon"]
|
||||
fn signed_zero_lib_ecosystems_run_and_build_in_the_current_container() {
|
||||
let workspace = inputs::workspace();
|
||||
let author = TemporaryDirectory::new();
|
||||
let key = author_inputs(author.path(), &workspace);
|
||||
ecosystem_inputs(author.path(), &workspace);
|
||||
let machine = machine_temporary_directory(&workspace);
|
||||
let layout = Layout::new(machine.path());
|
||||
layout.initialize().unwrap();
|
||||
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||
let release = layout.downloads().join(version.as_str());
|
||||
packaging::create(author.path(), &release).unwrap();
|
||||
install_release(&layout, &version, &key);
|
||||
fs::remove_dir_all(&release).unwrap();
|
||||
drop(author);
|
||||
let core = layout.versions().join(version.as_str()).join("dever-core");
|
||||
let mut daemon = daemon::TestDaemon::start_with_executable(
|
||||
&layout,
|
||||
&workspace.join("target/native-release-inputs/bin/deverd"),
|
||||
);
|
||||
let deployments = TemporaryDirectory::new();
|
||||
let mut programs = Vec::new();
|
||||
for ecosystem in ECOSYSTEMS {
|
||||
let project = TemporaryDirectory::new();
|
||||
let (extension, source) = match ecosystem {
|
||||
"pip" => (
|
||||
"py",
|
||||
"import gzip\nasync def probe(payload, setting):\n return {\"okay\": gzip.decompress(gzip.compress(b'dever')) == b'dever'}\n",
|
||||
),
|
||||
"npm" => (
|
||||
"mjs",
|
||||
"import {createHash} from 'node:crypto';\nexport async function probe() { return {okay:createHash('sha256').update('dever').digest().length === 32}; }\n",
|
||||
),
|
||||
"go" => ("go", include_str!("../../ecosystem-release/probe.go")),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
write(project.path(), "config/setting.json", "{}");
|
||||
write(
|
||||
project.path(),
|
||||
"module/sample/worker/port.dever",
|
||||
"probe() (okay: Bool) fails app.ProbeResult\n",
|
||||
);
|
||||
write(
|
||||
project.path(),
|
||||
"module/sample/worker/app.dever",
|
||||
"type ProbeResult { error Unavailable(message: Text) }\nprobe() (okay: Bool) { okay = port.probe() }\n",
|
||||
);
|
||||
write(
|
||||
project.path(),
|
||||
"module/sample/worker/api.dever",
|
||||
"cmd probe = app.probe\n",
|
||||
);
|
||||
write(
|
||||
project.path(),
|
||||
"module/sample/worker/adapter.dever",
|
||||
format!("external {ecosystem} \"worker/probe.{extension}\" {{}}\n"),
|
||||
);
|
||||
write(
|
||||
project.path(),
|
||||
&format!("module/sample/worker/worker/probe.{extension}"),
|
||||
source,
|
||||
);
|
||||
command(Command::new(&core).arg("check").arg(project.path()));
|
||||
command(Command::new(&core).args(["lib", "add"]).arg(project.path()));
|
||||
assert_response(command(
|
||||
Command::new(&core).arg("run").arg(project.path()).args([
|
||||
"--",
|
||||
"sample.worker.probe",
|
||||
"{}",
|
||||
]),
|
||||
));
|
||||
let destination = deployments.path().join(ecosystem);
|
||||
write(&destination, "config/setting.json", "{}");
|
||||
let program = destination.join("program");
|
||||
command(
|
||||
Command::new(&core)
|
||||
.arg("build")
|
||||
.arg(project.path())
|
||||
.arg("--output")
|
||||
.arg(&program),
|
||||
);
|
||||
programs.push((ecosystem, destination, program));
|
||||
}
|
||||
daemon.stop();
|
||||
for (ecosystem, destination, program) in programs {
|
||||
assert_response(command(
|
||||
Command::new(program)
|
||||
.current_dir(destination)
|
||||
.args(["sample.worker.probe", "{}"]),
|
||||
));
|
||||
eprintln!(
|
||||
"{ecosystem}: signed runtime, managed execution and source-free standalone execution passed"
|
||||
);
|
||||
}
|
||||
retain_candidate(
|
||||
&layout,
|
||||
&version,
|
||||
"ecosystems",
|
||||
&json!({
|
||||
"format":"dever-zero-lib-release-acceptance-v1",
|
||||
"platform":"linux-x86_64", "version":version.as_str(),
|
||||
"checks":{"signed_install":true,"managed_ecosystems":3,"standalone_ecosystems":3,"source_projects_removed":true},
|
||||
"third_party_packages":"not_run", "host_language_absence":"not_verified",
|
||||
"gpu":"not_run", "public_release":"not_published"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn application(root: &Path, ecosystem: &str) {
|
||||
let (extension, source) = match ecosystem {
|
||||
"pip" => ("py", include_str!("../../ecosystem-release/probe.py")),
|
||||
@@ -389,10 +498,10 @@ fn signed_ecosystems_build_and_run_without_system_language_installations() {
|
||||
nonroot_standalone(&isolated.path().join(ecosystem), &programs[ecosystem]),
|
||||
);
|
||||
}
|
||||
write(&workspace, "target/closure-sandbox-ecosystem-release-acceptance.json", serde_json::to_vec_pretty(&json!({
|
||||
write(&workspace, "target/closure-process_runtime-ecosystem-release-acceptance.json", serde_json::to_vec_pretty(&json!({
|
||||
"format":"dever-ecosystem-release-acceptance-v1", "platform":"linux-x86_64",
|
||||
"runtimes":runtimes,"programs":programs,"standalone_nonroot":standalone_nonroot,
|
||||
"checks":{"signed_install":true,"locked_runtimes":3,"managed_check_run_build":3,"standalone_isolated_root":3,"standalone_nonroot":3,"shared_compile_entries":3,"python_stdlib_paths_in_bundle":true,"os_sandbox":true,"offline_build_receipt_replay":true},
|
||||
"checks":{"signed_install":true,"locked_runtimes":3,"managed_check_run_build":3,"standalone_isolated_root":3,"standalone_nonroot":3,"shared_compile_entries":3,"python_stdlib_paths_in_bundle":true,"direct_workers":true,"offline_build_receipt_replay":true},
|
||||
"os_baseline":"explicit Linux GNU OS libraries and named device nodes inside an owned root",
|
||||
"runtime_pack_provenance":"test author signed the exact pip/npm packs bound to the completed source builds; maker determinism is verified separately",
|
||||
"third_party_packages":{"pip":"simplejson@3.20.1 _speedups C extension","npm":"bufferutil@4.0.9 build/Release addon"}, "other_platforms":"not_run", "public_release":"not_published"
|
||||
|
||||
@@ -298,10 +298,8 @@ fn go_cross_pack_binds_host_tools_and_arm_standard_library_separately() {
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn sandbox_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
|
||||
fn process_runtime_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
|
||||
let mut files = vec![
|
||||
("bin/bwrap".into(), native_elf::static_executable()),
|
||||
("bin/guard".into(), native_elf::static_executable()),
|
||||
(
|
||||
"lib/ld-linux-aarch64.so.1".into(),
|
||||
native_elf::library("ld-linux-aarch64.so.1", &[]),
|
||||
@@ -314,12 +312,12 @@ fn sandbox_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
|
||||
for (_, bytes) in &mut files {
|
||||
bytes[18..20].copy_from_slice(&183u16.to_le_bytes());
|
||||
}
|
||||
dever_sandbox::validate_assets_for_target(&files, "linux-aarch64").unwrap();
|
||||
dever_process::validate_assets_for_target(&files, "linux-aarch64").unwrap();
|
||||
let mut author = super::targets::cross_author();
|
||||
let inputs = files
|
||||
.iter()
|
||||
.map(|(path, bytes)| {
|
||||
let source = format!("inputs/arm-sandbox/{path}");
|
||||
let source = format!("inputs/arm-process-runtime/{path}");
|
||||
let file = author.root.join(&source);
|
||||
fs::create_dir_all(file.parent().unwrap()).unwrap();
|
||||
fs::write(&file, bytes).unwrap();
|
||||
@@ -327,18 +325,16 @@ fn sandbox_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
|
||||
json!({"source":source,"path":path,"sha256":sha256_file(&file).unwrap()})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
author.settings["sandbox_targets"] = json!({"linux-aarch64":inputs});
|
||||
author.settings["process_runtime_targets"] = json!({"linux-aarch64":inputs});
|
||||
author.save();
|
||||
let output = author.output("cross-sandbox");
|
||||
let output = author.output("cross-process_runtime");
|
||||
packaging::create(&author.root, &output).unwrap();
|
||||
for (path, mode) in [
|
||||
("bin/bwrap", 0o755),
|
||||
("bin/guard", 0o755),
|
||||
("lib/ld-linux-aarch64.so.1", 0o755),
|
||||
("lib/libc.so.6", 0o644),
|
||||
] {
|
||||
assert_eq!(
|
||||
fs::metadata(output.join("sandbox/linux-aarch64").join(path))
|
||||
fs::metadata(output.join("process-runtime/linux-aarch64").join(path))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
@@ -349,7 +345,7 @@ fn sandbox_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
|
||||
}
|
||||
files[1].1[18..20].copy_from_slice(&62u16.to_le_bytes());
|
||||
assert!(
|
||||
dever_sandbox::validate_assets_for_target(&files, "linux-aarch64")
|
||||
dever_process::validate_assets_for_target(&files, "linux-aarch64")
|
||||
.unwrap_err()
|
||||
.contains("incompatible target")
|
||||
);
|
||||
|
||||
@@ -2,6 +2,45 @@ use super::*;
|
||||
use dever_cli::toolchain::runtime_pack::RuntimePack;
|
||||
use dever_runtime::config::RuntimeProfile;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn target_resource_namespaces_are_bound_to_the_signed_release_version() {
|
||||
use dever_cli::toolchain::{
|
||||
Artifact, Extension, ExtensionKind, ReleaseManifest, Version, validate_catalog,
|
||||
};
|
||||
let mut manifest = ReleaseManifest::new(
|
||||
Version::parse("0.1.4").unwrap(),
|
||||
vec![Artifact {
|
||||
path: "dever-core".into(),
|
||||
bytes: 1,
|
||||
sha256: "0".repeat(64),
|
||||
}],
|
||||
);
|
||||
manifest.extensions.push(Extension {
|
||||
kind: ExtensionKind::Target,
|
||||
target: BuildTarget::LinuxAarch64,
|
||||
artifacts: vec![Artifact {
|
||||
path: "sandbox/linux-aarch64/lib/libc.so.6".into(),
|
||||
bytes: 1,
|
||||
sha256: "0".repeat(64),
|
||||
}],
|
||||
});
|
||||
validate_catalog(&manifest).unwrap();
|
||||
manifest.version = Version::parse("0.1.5").unwrap();
|
||||
assert!(
|
||||
validate_catalog(&manifest)
|
||||
.unwrap_err()
|
||||
.contains("resource namespace")
|
||||
);
|
||||
manifest.extensions[0].artifacts[0].path = "process-runtime/linux-aarch64/lib/libc.so.6".into();
|
||||
validate_catalog(&manifest).unwrap();
|
||||
manifest.version = Version::parse("0.1.4").unwrap();
|
||||
assert!(
|
||||
validate_catalog(&manifest)
|
||||
.unwrap_err()
|
||||
.contains("resource namespace")
|
||||
);
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(super) fn cross_author() -> Author {
|
||||
let mut author = Author::new();
|
||||
|
||||
Reference in new issue
Block a user