fix(delivery): derive installation reserve from verified inventory

This commit is contained in:
2026-10-12 20:50:11 +08:00
parent 3cde198358
commit 7145fb3fa4
4 changed files with 26 additions and 7 deletions
+4
View File
@@ -10,6 +10,10 @@ The target has at least 4 GiB RAM and 12 GiB installation/data capacity. Judging
and problem-import concurrency remain one each. Configured service and execution
budgets, effective kernel limits and free capacity must also pass preflight; total
RAM alone does not prove enough capacity or verified execution isolation.
Preflight requires at least 12 GiB of filesystem capacity and derives required
free bytes from the verified application, protected runtime-image copies and
separate GNU legacy copy, plus 4 GiB of mutable-data/operation reserve. It does
not assume that a nominal 12-GiB filesystem exposes 12 GiB free after metadata.
Release payloads are directory bundles with a bounded manifest: at most 50,000
single-link regular files, 256 MiB per file, 4 GiB total and 8 MiB metadata. The
+2 -2
View File
@@ -61,8 +61,8 @@ def main() -> None:
elif args.command == "build":
result = build.build(args.repo, args.inputs, args.output, args.work)
elif args.command == "preflight":
manifest.verify(args.bundle, args.manifest_sha256)
result = preflight.observe(args.bundle / "payload")
data = manifest.verify(args.bundle, args.manifest_sha256)
result = preflight.observe(args.bundle / "payload", bundle_data=data)
print(json.dumps(result, sort_keys=True))
if not result["eligible"]:
parser.exit(1)
+1 -1
View File
@@ -182,7 +182,7 @@ def files(bundle: Path, expected: str) -> dict:
raise DeliveryError("installer requires root")
preflight.protected(bundle, directory=True)
data = manifest.verify(bundle, expected)
observation = preflight.observe(bundle / "payload")
observation = preflight.observe(bundle / "payload", bundle_data=data)
if not observation["eligible"]:
raise DeliveryError("installation preflight refused: " + "; ".join(observation["errors"]))
# Per-entry ownership matters even when the inventory is identical.
+19 -4
View File
@@ -120,7 +120,7 @@ def identity_conflicts() -> list[str]:
return errors
def observe(payload: Path, *, require_root: bool = True) -> dict:
def observe(payload: Path, *, bundle_data: dict, require_root: bool = True) -> dict:
errors = []
observations = {}
if platform.system() != "Linux" or platform.machine() != "x86_64":
@@ -183,9 +183,24 @@ def observe(payload: Path, *, require_root: bool = True) -> dict:
# A 4-GiB physical guest exposes slightly less MemTotal after kernel reserve.
if memory["MemTotal"] < 15 * GIB // 4 or memory["MemAvailable"] < 5 * GIB // 2:
errors.append("physical/available memory below installation capacity")
observations["disk_free_bytes"] = shutil.disk_usage("/var/lib").free
if observations["disk_free_bytes"] < 12 * GIB:
errors.append("installation/data disk reserve below 12 GiB")
disk = shutil.disk_usage("/var/lib")
protected_images = sum(
entry["bytes"] for entry in bundle_data["files"] if entry["path"].startswith("images/")
)
legacy_image = sum(
entry["bytes"] for entry in bundle_data["files"] if entry["path"].startswith("images/gnu/")
)
# Count a full new application + protected image copies + legacy image and
# four GiB of mutable data/operation reserve. Filesystem metadata makes a
# 12-GiB device expose less than 12 GiB free even when it is empty.
required = bundle_data["payload_bytes"] + protected_images + legacy_image + 4 * GIB
observations["disk"] = {
"total_bytes": disk.total,
"free_bytes": disk.free,
"required_free_bytes": required,
}
if disk.total < 12 * GIB or disk.free < required:
errors.append("installation/data capacity or inventory-derived free reserve insufficient")
for name in ("libc.so.6", "ld-linux-x86-64.so.2"):
system = Path("/usr/lib") / name
try: