feat: ship slim releases with shared on-demand extensions and locked restore

This commit is contained in:
Antigravity committed 2026-10-05 00:19:31 +00:00
1 parent 83b78a0db6
commit 4fda7c8c1a
58 files changed
+3774 -425

No files matched your search

+91
View File
@@ -0,0 +1,91 @@
//! Opt-in actual-asset acceptance: only the download transport is local.
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use dever_cli::toolchain::{
BuildTarget, ExtensionId, ExtensionSource, Layout, MachineManager, ReleaseManifest,
SignedResources, Version, prepare_extension_with, validate_catalog,
};
use ring::signature::Ed25519KeyPair;
struct LocalAssets(PathBuf);
impl ExtensionSource for LocalAssets {
fn open(&self, _: &Version, extension: &ExtensionId) -> Result<Box<dyn Read>, String> {
let name = format!(
"dever-{}.{}",
BuildTarget::host()?.platform(),
extension.asset_suffix()
);
File::open(self.0.join(name))
.map(|file| Box::new(file) as Box<dyn Read>)
.map_err(|error| format!("cannot open actual extension asset: {error}"))
}
}
fn run() -> Result<(), String> {
let arguments: Vec<_> = std::env::args().skip(1).collect();
if let [operation, manifest, key] = arguments.as_slice()
&& operation == "--sign-catalog"
{
let bytes = fs::read(manifest).map_err(|error| error.to_string())?;
let catalog: ReleaseManifest =
serde_json::from_slice(&bytes).map_err(|error| error.to_string())?;
validate_catalog(&catalog)?;
let key = fs::read(key).map_err(|error| error.to_string())?;
let pair = Ed25519KeyPair::from_pkcs8(&key).map_err(|error| error.to_string())?;
let signature: String = pair
.sign(&bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect();
return fs::write(
Path::new(manifest).with_file_name("manifest.sig"),
signature,
)
.map_err(|error| error.to_string());
}
let [machine, assets, version, names @ ..] = arguments.as_slice() else {
return Err(
"usage: prepare-extensions <machine> <assets> <version> <extension-name>...".into(),
);
};
if names.is_empty() {
return Err("at least one signed extension must be selected".into());
}
let layout = Layout::new(machine);
let version = Version::parse(version)?;
let source = LocalAssets(PathBuf::from(assets));
if names == ["--update"] {
let installed = MachineManager::new(layout).update_with(&source)?;
if installed != version {
return Err(format!(
"expected update to {version}, received {installed}"
));
}
println!("updated {installed}");
return Ok(());
}
let resources = SignedResources::load(&layout, &version)?;
for name in names {
let extension = resources
.manifest()
.extensions
.iter()
.find(|extension| extension.id().name() == *name)
.ok_or_else(|| format!("signed catalog does not declare extension '{name}'"))?;
prepare_extension_with(&layout, &version, &extension.id(), &source)?;
println!("prepared {}", extension.id().name());
}
Ok(())
}
fn main() {
if let Err(error) = run() {
eprintln!("prepare-extensions: {error}");
std::process::exit(1);
}
}
+91 -47
View File
@@ -2,7 +2,6 @@
import argparse
import contextlib
import gzip
import hashlib
import importlib.util
import io
@@ -40,6 +39,7 @@ class InstallerTests(unittest.TestCase):
self.payloads = {
"dever-core": b"fixture core, never executed\n",
"bootstrap/dever": b"fixture launcher, never executed\n",
"bootstrap/lib/libfixture.so": b"fixture private closure\n",
"skills/dever-language/SKILL.md": b"fixture skill\n",
}
self.catalog = {}
@@ -52,8 +52,9 @@ class InstallerTests(unittest.TestCase):
"sha256": hashlib.sha256(content).hexdigest(),
}
manifest = {
"format": "dever-release-v1", "platform": self.platform,
"format": "dever-release-v2", "platform": self.platform,
"version": "1.2.3", "artifacts": list(self.catalog.values()),
"extensions": [],
}
(self.release / "manifest.json").write_text(json.dumps(manifest))
self.private = self.root / "test-key.pem"
@@ -82,24 +83,14 @@ class InstallerTests(unittest.TestCase):
return installer.authenticate(self.release, key or self.public, self.scratch,
self.platform, "1.2.3")
def archive(self, entries, trailer=None):
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w", format=tarfile.USTAR_FORMAT) as archive:
for name, content, kind in entries:
member = tarfile.TarInfo(name)
member.type = kind
member.size = len(content) if kind == tarfile.REGTYPE else 0
if kind == tarfile.SYMTYPE:
member.linkname = "../../outside"
archive.addfile(member, io.BytesIO(content) if member.size else None)
contents = stream.getvalue()
if trailer is not None:
contents += trailer
return gzip.compress(contents)
def create_assets(self, output):
return assets.create(self.release, output, Path("/usr/bin/zstd"))
def extract(self, compressed, destination):
with mock.patch.object(installer, "download", return_value=io.BytesIO(compressed)):
installer.payload(destination, None, "1.2.3", self.platform, self.catalog)
def archive_payloads(self, path):
contents = subprocess.run(["/usr/bin/zstd", "-q", "-d", "-c", str(path)],
env={}, capture_output=True, check=True, timeout=15).stdout
with tarfile.open(fileobj=io.BytesIO(contents)) as archive:
return {member.name: archive.extractfile(member).read() for member in archive}
def test_signature_accepts_independent_key_and_rejects_wrong_key_or_changed_manifest(self):
version, catalog, key = self.authenticate()
@@ -119,19 +110,47 @@ class InstallerTests(unittest.TestCase):
first = self.root / "first"
second = self.root / "second"
(self.release / "unlisted").write_text("not published")
names = assets.create(self.release, first)
self.assertEqual(names, assets.create(self.release, second))
self.assertEqual(len(names), 3)
names = self.create_assets(first)
self.assertEqual(names, self.create_assets(second))
self.assertEqual(len(names), 5)
for name in names:
self.assertEqual((first / name).read_bytes(), (second / name).read_bytes())
extracted = self.directory("extracted")
self.extract((first / f"dever-{self.platform}.tar.gz").read_bytes(), extracted)
actual = {str(path.relative_to(extracted)): path.read_bytes()
for path in extracted.rglob("*") if path.is_file()}
actual = self.archive_payloads(first / f"dever-{self.platform}.tar.zst")
self.assertEqual(actual, self.payloads)
self.assertEqual((extracted / "bootstrap/dever").stat().st_mode & 0o777, 0o755)
for name in ("bootstrap/dever", "bootstrap/lib/libfixture.so"):
blob = first / f"dever-{self.platform}.blob-{self.catalog[name]['sha256']}"
self.assertEqual(blob.read_bytes(), self.payloads[name])
with self.assertRaisesRegex(ValueError, "already exists"):
assets.create(self.release, first)
self.create_assets(first)
def test_extension_assets_are_separate_and_cannot_overlap_base(self):
manifest_path = self.release / "manifest.json"
manifest = json.loads(manifest_path.read_text())
name = f"runtime/pip/{self.platform}/runtime.pack"
path = self.release / name
path.parent.mkdir(parents=True)
contents = b"optional Python bytes"
path.write_bytes(contents)
entry = {"path": name, "bytes": len(contents), "sha256": hashlib.sha256(contents).hexdigest()}
manifest["extensions"] = [{"kind": {"type": "runtime", "ecosystem": "pip"},
"target": self.platform, "artifacts": [entry]}]
manifest_path.write_text(json.dumps(manifest))
self.sign_manifest()
self.authenticate()
output = self.root / "split"
self.create_assets(output)
self.assertEqual(self.archive_payloads(output / f"dever-{self.platform}.tar.zst"), self.payloads)
extension = output / f"dever-{self.platform}.ext-runtime-pip-{self.platform}.tar.zst"
self.assertEqual(self.archive_payloads(extension), {name: contents})
manifest["extensions"][0]["artifacts"].append(self.catalog["dever-core"])
manifest_path.write_text(json.dumps(manifest))
with self.assertRaisesRegex(ValueError, "resource namespace"):
self.create_assets(self.root / "overlap")
manifest["extensions"] = [{"kind": {"type": "target"}, "target": self.platform,
"artifacts": [self.catalog["dever-core"]]}]
manifest_path.write_text(json.dumps(manifest))
with self.assertRaisesRegex(ValueError, "base package"):
self.create_assets(self.root / "host-target")
def test_signed_release_without_matching_skill_is_rejected(self):
path = self.release / "manifest.json"
@@ -179,7 +198,7 @@ class InstallerTests(unittest.TestCase):
with mock.patch.object(assets, "publish", side_effect=publish), \
self.assertRaises(OSError):
assets.create(self.release, output)
self.create_assets(output)
self.assertTrue(output.is_dir())
if populated:
self.assertEqual((output / "user-file").read_bytes(), b"concurrent user bytes")
@@ -187,23 +206,48 @@ class InstallerTests(unittest.TestCase):
self.assertEqual(list(output.iterdir()), [])
self.assertEqual(list(self.root.glob(".dever-release-assets-*")), [])
def test_archive_rejects_unsigned_duplicate_symlink_wrong_size_missing_and_bad_tail(self):
normal = [(name, content, tarfile.REGTYPE) for name, content in self.payloads.items()]
cases = {
"unsigned": normal + [("outside", b"extra", tarfile.REGTYPE)],
"duplicate": normal + [normal[0]],
"symlink": [(normal[0][0], b"", tarfile.SYMTYPE)] + normal[1:],
"size": [(normal[0][0], b"short", tarfile.REGTYPE)] + normal[1:],
"missing": normal[:-1],
}
for name, entries in cases.items():
with self.subTest(name=name), self.assertRaises(ValueError):
self.extract(self.archive(entries), self.directory(name))
with self.assertRaisesRegex(ValueError, "尾部"):
self.extract(self.archive(normal, b"hidden"), self.directory("tail"))
corrupted = [(normal[0][0], b"x" * len(normal[0][1]), tarfile.REGTYPE)] + normal[1:]
with self.assertRaisesRegex(ValueError, "摘要"):
self.extract(self.archive(corrupted), self.directory("digest"))
def test_network_payload_executes_only_verified_helper_with_private_libraries(self):
assets_path = self.root / "assets"
self.create_assets(assets_path)
calls = []
def download(address):
return (assets_path / address.rsplit("/", 1)[-1]).open("rb")
def extract(command, **kwargs):
self.assertEqual(command[1], "--dever-extract")
launcher = Path(command[0])
self.assertEqual(launcher.read_bytes(), self.payloads["bootstrap/dever"])
self.assertEqual((launcher.parent / "lib/libfixture.so").read_bytes(),
self.payloads["bootstrap/lib/libfixture.so"])
self.assertEqual(launcher.stat().st_mode & 0o777, 0o755)
configuration = json.loads(Path(command[2]).read_text())
self.assertEqual(configuration["trusted_key"], str(self.public))
self.assertEqual(kwargs["env"], {})
self.assertTrue(Path(configuration["archive"]).is_file())
calls.append(command)
with mock.patch.object(installer, "download", side_effect=download), \
mock.patch.object(installer.subprocess, "run", side_effect=extract):
installer.payload(self.directory("network"), None, "1.2.3", self.platform,
self.catalog, self.public)
self.assertEqual(len(calls), 1)
self.assertEqual(list(self.root.glob(".extract-*")), [])
blob = assets_path / f"dever-{self.platform}.blob-{self.catalog['bootstrap/dever']['sha256']}"
for corrupt in (b"x" * len(self.payloads["bootstrap/dever"]), self.payloads["bootstrap/dever"] + b"extra"):
blob.write_bytes(corrupt)
with mock.patch.object(installer, "download", side_effect=download), \
mock.patch.object(installer.subprocess, "run") as execute, \
self.assertRaises(ValueError):
installer.payload(self.release, None, "1.2.3", self.platform, self.catalog, self.public)
execute.assert_not_called()
def test_download_archive_is_bounded(self):
with mock.patch.object(installer, "MAX_RELEASE", 4), \
mock.patch.object(installer, "download", return_value=io.BytesIO(b"12345")), \
self.assertRaisesRegex(ValueError, "超过"):
installer.download_archive("unused", self.root / "oversized.zst")
def test_local_payload_rejects_symlink_and_source_directory(self):
copied = self.directory("copied")
@@ -215,7 +259,7 @@ class InstallerTests(unittest.TestCase):
with self.assertRaisesRegex(ValueError, "符号链接"):
installer.payload(self.directory("symlink"), self.release, "1.2.3", self.platform, self.catalog)
with self.assertRaisesRegex(ValueError, "symlinks"):
assets.create(self.release, self.root / "invalid-assets")
self.create_assets(self.root / "invalid-assets")
core.unlink()
core.mkdir()
with self.assertRaisesRegex(ValueError, "类型"):
+128 -22
View File
@@ -33,8 +33,8 @@ def execute(arguments, timeout=180):
return completed.stdout.strip()
def response(output):
expected = {"code": 0, "message": "ok", "data": "Hello, Dever"}
def response(output, data="Hello, Dever"):
expected = {"code": 0, "message": "ok", "data": data}
if json.loads(output) != expected:
raise RuntimeError(f"unexpected application response: {output}")
@@ -50,30 +50,56 @@ def minimal_os(image):
(image / name).mkdir()
def isolated(image):
def worker_project(image, ecosystem):
"""Small no-Lib Adapter: preparation must still acquire its managed runtime."""
entries = {
"pip": ("py", "import gzip\n\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", (WORKSPACE / "test/ecosystem-release/probe.go").read_text()),
}
extension, source = entries[ecosystem]
root = image / f"worker-{ecosystem}"
files = {
"config/setting.json": "{}\n",
"module/sample/worker/port.dever": "probe() (okay: Bool) fails app.ProbeResult\n",
"module/sample/worker/app.dever": "type ProbeResult { error Unavailable(message: Text) }\nprobe() (okay: Bool) { okay = port.probe() }\n",
"module/sample/worker/api.dever": "cmd probe = app.probe\n",
"module/sample/worker/adapter.dever": f'external {ecosystem} "worker/probe.{extension}" {{}}\n',
f"module/sample/worker/worker/probe.{extension}": source,
}
for relative, contents in files.items():
path = root / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(contents)
return root
def isolated(image, init):
return [
WORKSPACE / "target/sandbox-inputs/assets/bin/bwrap",
"--unshare-all", "--die-with-parent", "--new-session", "--bind", image,
"/", "--proc", "/proc", "--dev", "/dev", "--chdir", "/", "--ro-bind",
WORKSPACE / "target/debug/examples/native-acceptance-init", "/fixture-proc-init",
init, "/fixture-proc-init",
]
class InstalledDaemon:
"""All clients enter the same PID namespace so SO_PEERCRED stays valid."""
def __init__(self, image, records):
def __init__(self, image, records, init):
self.image = image
self.records = records
self.init = init
self.child = None
self.pidfd = None
self.pid = None
def __enter__(self):
self.records.mkdir(parents=True, exist_ok=True)
self.information = (self.records / "namespace.json").open("w+")
self.errors = (self.records / "daemon.log").open("w+")
self.child = subprocess.Popen(
[str(value) for value in isolated(self.image) + [
[str(value) for value in isolated(self.image, self.init) + [
"--info-fd", "1", "--", "/fixture-proc-init",
"/opt/dever/bin/deverd", "--root", "/opt/dever",
]], env={}, stdout=self.information, stderr=self.errors,
@@ -134,6 +160,38 @@ class InstalledDaemon:
self.errors.close()
def ordinary_uid_target_reuse(image):
# bwrap's single-UID user namespace cannot represent a second identity.
# Use the installed private machine with real kernel UIDs for this check;
# no host language tools or global service participate in target reuse.
machine = image / "opt/dever"
child = subprocess.Popen([str(machine / "bin/deverd"), "--root", str(machine)],
env={}, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
try:
deadline = time.monotonic() + 10
while not (machine / "state/deverd.sock").exists():
if child.poll() is not None or time.monotonic() >= deadline:
raise RuntimeError("private UID acceptance daemon did not become ready")
time.sleep(0.02)
for uid in (65533, 65534):
completed = subprocess.run(
[str(machine / "bin/dever"), "target", "add", "linux-aarch64"],
env={}, user=uid, group=uid, extra_groups=[], capture_output=True,
text=True, timeout=180, check=False,
)
if completed.returncode:
raise RuntimeError(f"UID {uid} target reuse failed: {completed.stderr}")
finally:
if child.poll() is None:
child.terminate()
try:
child.wait(timeout=10)
except subprocess.TimeoutExpired:
child.kill()
child.wait(timeout=10)
child.stderr.close()
def install_assets(assets, image, manifest):
# Only transport is local: signature verification, full tar extraction and
# the final bootstrap executable are the real first-install implementation.
@@ -146,8 +204,12 @@ def install_assets(assets, image, manifest):
routes = {
f"{base}/latest/download/{stem}.manifest.json": assets / f"{stem}.manifest.json",
f"{base}/latest/download/{stem}.manifest.sig": assets / f"{stem}.manifest.sig",
f"{base}/download/v{manifest['version']}/{stem}.tar.gz": assets / f"{stem}.tar.gz",
f"{base}/download/v{manifest['version']}/{stem}.tar.zst": assets / f"{stem}.tar.zst",
}
for artifact in manifest["artifacts"]:
if artifact["path"] == "bootstrap/dever" or artifact["path"].startswith("bootstrap/lib/"):
name = f"{stem}.blob-{artifact['sha256']}"
routes[f"{base}/download/v{manifest['version']}/{name}"] = assets / name
installer.download = lambda address: routes[address].open("rb")
installer.install(argparse.Namespace(
version="latest", release=None, system_root=image, no_start=True,
@@ -155,7 +217,7 @@ def install_assets(assets, image, manifest):
))
def accept(release, assets, image, report):
def accept(release, assets, image, report, init, extension_helper=None):
image.mkdir(mode=0o755)
report.parent.mkdir(parents=True, exist_ok=True)
manifest = json.loads((release / "manifest.json").read_bytes())
@@ -185,13 +247,19 @@ def accept(release, assets, image, report):
if (installed / "manifest.json").read_bytes() != (release / "manifest.json").read_bytes():
raise RuntimeError("installed package differs from the release under acceptance")
passed("actual_signed_release_first_install")
for extension in manifest["extensions"]:
if any((installed / artifact["path"]).exists() for artifact in extension["artifacts"]):
raise RuntimeError("base install unexpectedly includes optional extension bytes")
passed("base_install_excludes_optional_extensions")
launcher = image / "opt/dever/bin/dever"
if execute([launcher, "version"]) != manifest["version"]:
raise RuntimeError("installed version mismatch")
if (image / "usr/local/bin/dever").readlink() != Path("/opt/dever/bin/dever"):
raise RuntimeError("public launcher link mismatch")
passed("installed_version_and_public_entry")
with InstalledDaemon(image, report.parent) as daemon:
standalone = [("first", "hello.greeting.greet", '{"name":"Dever"}', "Hello, Dever"),
("second", "hello.greeting.greet", '{"name":"Dever"}', "Hello, Dever")]
with InstalledDaemon(image, report.parent / f"{report.stem}-process", init) as daemon:
for project, markdown in (("first", False), ("second", True)):
root = "/" + project
args = ["new", root] + (["--markdown"] if markdown else [])
@@ -211,27 +279,62 @@ def accept(release, assets, image, report):
if not (image / "ai-skill/SKILL.md").is_file():
raise RuntimeError("missing installed skill entry")
passed("versioned_skill_resolution_and_loader_install")
daemon.command("build", "/first", "--target", "linux-aarch64", "--output", "/first/program.arm64")
with (image / "first/program.arm64").open("rb") as executable:
header = executable.read(20)
if header[:4] != b"\x7fELF" or header[18:20] != b"\xb7\x00":
raise RuntimeError("cross build did not produce an ARM64 ELF")
passed("arm64_application_cross_build")
try:
daemon.command("build", "/first", "--target", "linux-aarch64", "--output", "/first/program.arm64")
except RuntimeError as error:
if "dever target add linux-aarch64" not in str(error):
raise
else:
raise RuntimeError("unprepared cross build should fail without fetching resources")
passed("missing_target_fails_offline_with_preparation_command")
if assets and extension_helper:
execute([extension_helper, image / "opt/dever", assets, manifest["version"], "target-linux-aarch64"])
daemon.command("target", "add", "linux-aarch64")
daemon.command("build", "/first", "--target", "linux-aarch64", "--output", "/first/program.arm64")
with (image / "first/program.arm64").open("rb") as executable:
header = executable.read(20)
if header[:4] != b"\x7fELF" or header[18:20] != b"\xb7\x00":
raise RuntimeError("cross build did not produce an ARM64 ELF")
passed("actual_target_extension_install_reuse_and_arm64_application_cross_build")
for ecosystem in ("pip", "npm", "go"):
execute([extension_helper, image / "opt/dever", assets, manifest["version"],
f"runtime-{ecosystem}-{manifest['platform']}"])
project = worker_project(image, ecosystem)
root = "/" + project.name
daemon.command("lib", "update", root)
before = [(project / name).read_bytes() for name in ("config/setting.json", "dever.lock")]
daemon.command("lib", "install", root)
after = [(project / name).read_bytes() for name in ("config/setting.json", "dever.lock")]
if before != after:
raise RuntimeError("exact Lib installation modified project configuration or lock")
daemon.command("check", root)
response(daemon.command("run", root, "--", "sample.worker.probe", "{}"), True)
daemon.command("build", root, "--output", root + "/program")
standalone.append((project.name, "sample.worker.probe", "{}", True))
passed(f"actual_{ecosystem}_extension_no_lib_worker_exact_install_run_build")
result["cache"] = json.loads(daemon.command("cache", "status"))
if assets and extension_helper:
ordinary_uid_target_reuse(image)
passed("two_ordinary_uids_reuse_shared_signed_target_extension")
# Remove sources and the entire machine from the visible OS root. Keep
# them outside that root for a subsequent online-update acceptance.
moved = []
try:
for relative in ("opt/dever", "first/module", "second/module"):
for relative in ["opt/dever"] + [f"{project}/module" for project, *_ in standalone]:
source = image / relative
hidden = image.parent / ("hidden-" + relative.replace("/", "-"))
source.rename(hidden)
moved.append((source, hidden))
for project in ("first", "second"):
response(execute(isolated(image) + [
for project, entry, parameters, data in standalone:
# These projects were created above; remove only their generated
# Worker cache so standalone execution must extract its own bundle.
cache = image / project / "data/cache/lib"
if cache.exists():
shutil.rmtree(cache)
response(execute(isolated(image, init) + [
"--", "/fixture-proc-init", f"/{project}/program",
"hello.greeting.greet", '{"name":"Dever"}',
]))
entry, parameters,
]), data)
passed("standalone_programs_without_sources_or_machine")
finally:
for source, hidden in reversed(moved):
@@ -248,14 +351,17 @@ def accept(release, assets, image, report):
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--release", required=True, type=Path)
parser.add_argument("--assets", type=Path, help="also exercise the three actual download assets")
parser.add_argument("--assets", type=Path, help="exercise actual split download assets")
parser.add_argument("--extension-helper", type=Path, help="explicit prepare-extensions author fixture")
parser.add_argument("--init", type=Path, required=True, help="explicit static native-acceptance-init fixture")
parser.add_argument("--image", required=True, type=Path)
parser.add_argument("--report", required=True, type=Path)
args = parser.parse_args()
if os.geteuid() != 0:
parser.error("requires root for an owned image/namespace; never installs host services")
accept(args.release.resolve(), args.assets.resolve() if args.assets else None,
args.image.absolute(), args.report.absolute())
args.image.absolute(), args.report.absolute(), args.init.absolute(),
args.extension_helper.absolute() if args.extension_helper else None)
if __name__ == "__main__":
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Actual-asset update transaction in an owned image; never publishes the fixture version."""
import argparse
import hashlib
import json
import os
from pathlib import Path
from verify_prepared_release import InstalledDaemon, execute, install_assets, minimal_os, response
def prepare_update(image, manifest, key, helper):
# Reuse real payloads under a newly signed, acceptance-only catalog identity.
# This tests the update owner without claiming a second public release exists.
next_manifest = dict(manifest)
major, minor, patch = map(int, manifest["version"].split("."))
version = f"{major}.{minor}.{patch + 1}"
next_manifest["version"] = version
machine = image / "opt/dever"
package = machine / "cache/downloads" / version
package.mkdir()
installed = machine / "versions" / manifest["version"]
for artifact in manifest["artifacts"]:
destination = package / artifact["path"]
destination.parent.mkdir(parents=True, exist_ok=True)
os.link(installed / artifact["path"], destination)
catalog = package / "manifest.json"
catalog.write_text(json.dumps(next_manifest, separators=(",", ":")))
execute([helper, "--sign-catalog", catalog, key])
(machine / "cache/downloads/latest").write_text(version)
return version, hashlib.sha256(catalog.read_bytes()).hexdigest()
def accept(args):
manifest = json.loads((args.release / "manifest.json").read_bytes())
result = {"version": manifest["version"], "checks": [],
"transport": "local actual assets; acceptance-only next-version catalog",
"online_update": "not_run"}
def passed(name):
result["checks"].append(name)
args.report.write_text(json.dumps(result, indent=2) + "\n")
print(f"PASS {name}", flush=True)
try:
args.image.mkdir()
minimal_os(args.image)
install_assets(args.assets, args.image, manifest)
machine = args.image / "opt/dever"
extension = f"runtime-pip-{manifest['platform']}"
execute([args.extension_helper, machine, args.assets, manifest["version"], extension])
records = args.report.parent / f"{args.report.stem}-process"
with InstalledDaemon(args.image, records, args.init) as daemon:
daemon.command("new", "/project")
response(daemon.command("run", "/project", "--", "hello.greeting.greet", '{"name":"Dever"}'))
passed("actual_release_and_selected_extension_before_update")
version, catalog = prepare_update(args.image, manifest, args.key, args.extension_helper)
result["fixture_version"] = version
previous_active = (machine / "state/active-version").read_bytes()
empty_assets = args.image / "missing-extension-assets"
empty_assets.mkdir()
try:
execute([args.extension_helper, machine, empty_assets, version, "--update"])
except RuntimeError as error:
if "cannot open actual extension asset" not in str(error):
raise
else:
raise RuntimeError("update with a missing extension should fail")
if (machine / "state/active-version").read_bytes() != previous_active:
raise RuntimeError("failed update changed active version")
if daemon.command("version") != manifest["version"]:
raise RuntimeError("failed update changed the public launcher selection")
response(daemon.command("run", "/project", "--", "hello.greeting.greet", '{"name":"Dever"}'))
passed("failed_extension_update_keeps_old_active_and_application_working")
execute([args.extension_helper, machine, args.assets, version, "--update"])
if daemon.command("version") != version:
raise RuntimeError("successful update did not activate the new version")
if daemon.command("skill", "path") != f"/opt/dever/versions/{version}/skills/dever-language":
raise RuntimeError("skill did not follow the updated core")
extensions = machine / "cache/extensions" / version / catalog
if {path.name for path in extensions.iterdir()} != {extension}:
raise RuntimeError("update did not preserve exactly the installed extension selection")
passed("successful_update_activates_core_skill_and_only_installed_extensions")
# Only the catalog version changes in this transaction fixture.
# The managed compiler correctly requires its real build version;
# execute it again only after selecting the actual release.
daemon.command("use", manifest["version"])
response(daemon.command("run", "/project", "--", "hello.greeting.greet", '{"name":"Dever"}'))
passed("actual_release_remains_usable_after_update_and_explicit_switch_back")
result["status"] = "passed"
except BaseException as error:
result["status"] = "failed"
result["error"] = str(error)
raise
finally:
args.report.write_text(json.dumps(result, indent=2) + "\n")
def main():
parser = argparse.ArgumentParser(description=__doc__)
for name in ("release", "assets", "image", "report", "init", "extension-helper", "key"):
parser.add_argument(f"--{name}", required=True, type=Path)
args = parser.parse_args()
if os.geteuid() != 0:
parser.error("requires root for an owned image; never installs host services")
for name, value in vars(args).items():
setattr(args, name, value.absolute())
args.report.parent.mkdir(parents=True, exist_ok=True)
accept(args)
if __name__ == "__main__":
main()