From b181ca6bd6eb35c930f2020c4a0402cfb3741612 Mon Sep 17 00:00:00 2001 From: kronecker <2026692687@qq.com> Date: Sat, 17 Oct 2026 17:19:33 +0800 Subject: [PATCH] fix(delivery): preserve established language resource budgets --- backend/tests/test_delivery.py | 34 ++++++++++++++ backend/tests/test_judging_v2_contracts.py | 11 ++++- deploy/judge.admission.example.json | 52 ++++++++++----------- deploy/sandbox.managed.runtime.example.json | 16 +++---- scripts/release/README.md | 16 +++++++ 5 files changed, 94 insertions(+), 35 deletions(-) diff --git a/backend/tests/test_delivery.py b/backend/tests/test_delivery.py index b585e1a..cca20b1 100644 --- a/backend/tests/test_delivery.py +++ b/backend/tests/test_delivery.py @@ -832,6 +832,27 @@ def test_generated_configuration_preserves_wire_policies_and_backend_settings( } +def test_generated_configuration_retains_accepted_language_budgets(delivery_configuration): + """The installed policy must preserve the established runtime startup allowance.""" + _, raw, _, _, _, _ = delivery_configuration + admission = json.loads(raw["admission.json"]) + root = json.loads(raw["runtime.json"]) + policies = [admission["legacy"], admission["multi_runtime"]] + assert all(p["max_concurrent_jobs"] == p["prefetch"] == 1 for p in policies) + assert all(p["max_job_wall_time_us"] == 180_000_000 for p in policies) + for cap in admission["multi_runtime"]["capabilities"]: + java = cap["language"] == "java21" + assert cap["compile_ceiling"]["cpu_time_us"] >= (60_000_000 if java else 30_000_000) + assert cap["compile_ceiling"]["wall_time_us"] >= (120_000_000 if java else 60_000_000) + assert cap["run_ceiling"]["cpu_time_us"] >= 5_000_000 + assert cap["run_ceiling"]["wall_time_us"] >= 10_000_000 + assert cap["run_ceiling"]["memory_bytes"] >= (384 if java else 128) * 1024**2 + image = next(i for i in root["runtime_images"] if i["runtime_identity"] == cap["toolchain"]) + for stage in (cap["compile_ceiling"], cap["run_ceiling"]): + assert all(value <= image["per_task_ceiling"][key] for key, value in stage.items()) + assert all(value <= root["per_task_ceiling"][key] for key, value in stage.items()) + + def test_epoch_rebinds_when_kernel_changes(delivery_configuration): paths, raw, data, node, endpoints, observed = delivery_configuration before = json.loads(raw["measurement.json"])["epoch"] @@ -842,6 +863,19 @@ def test_epoch_rebinds_when_kernel_changes(delivery_configuration): assert json.loads(after["measurement.json"])["epoch"] != before +def test_epoch_rebinds_when_language_budget_changes(delivery_configuration, monkeypatch): + paths, raw, data, node, endpoints, _ = delivery_configuration + before = json.loads(raw["measurement.json"])["epoch"] + admission, root = configuration.admission_runtime(ROOT, data) + java = next(c for c in admission["multi_runtime"]["capabilities"] if c["language"] == "java21") + java["compile_ceiling"]["cpu_time_us"] -= 1 + monkeypatch.setattr(configuration, "admission_runtime", lambda *_: (admission, root)) + with pytest.raises(policy.DeliveryError, match="measurement policy mismatch"): + configuration.validate_contents(paths, ROOT, data, attest=True) + after = configuration.compose(ROOT, data, node, endpoints, attest=True) + assert json.loads(after["measurement.json"])["epoch"] != before + + def test_unattested_configuration_has_no_comparable_epoch(delivery_configuration): _, _, data, node, endpoints, _ = delivery_configuration raw = configuration.compose(ROOT, data, node, endpoints, attest=False) diff --git a/backend/tests/test_judging_v2_contracts.py b/backend/tests/test_judging_v2_contracts.py index 541d9c0..51a8cf4 100644 --- a/backend/tests/test_judging_v2_contracts.py +++ b/backend/tests/test_judging_v2_contracts.py @@ -256,9 +256,18 @@ def test_operator_registry_example_preserves_old_protocol_and_binds_managed_runt ceiling = v1.RunLimits.model_validate(selected["per_task_ceiling"]) assert cap.compile_ceiling.fits(ceiling) and cap.run_ceiling.fits(ceiling) manifest = fixture(f"judge-manifest.valid-{cap.language}", v2.JobManifest).model_copy( - update={"compile_limits": cap.compile_ceiling, "run_limits": cap.run_ceiling} + update={ + "compile_limits": cap.compile_ceiling, + "run_limits": cap.run_ceiling, + "job_wall_time_us": policy.multi_runtime.max_job_wall_time_us, + } ) policy.admit(manifest, 1) + with pytest.raises(ValueError, match="job deadline cannot cover execution budgets"): + policy.admit( + manifest.model_copy(update={"job_wall_time_us": cap.compile_ceiling.wall_time_us}), + 1, + ) changed = manifest.model_copy( update={"toolchain": manifest.toolchain.model_copy(update={"image_digest": "f" * 64})} ) diff --git a/deploy/judge.admission.example.json b/deploy/judge.admission.example.json index e791eda..a6dcad1 100644 --- a/deploy/judge.admission.example.json +++ b/deploy/judge.admission.example.json @@ -4,11 +4,11 @@ "max_concurrent_jobs": 1, "prefetch": 1, "max_retries": 2, - "max_job_wall_time_us": 60000000, + "max_job_wall_time_us": 180000000, "max_package_bytes": 67108864, "compile_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 30000000, + "wall_time_us": 60000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -19,9 +19,9 @@ "workdir_bytes": 268435456 }, "run_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, - "memory_bytes": 67108864, + "cpu_time_us": 5000000, + "wall_time_us": 10000000, + "memory_bytes": 134217728, "stack_bytes": 8388608, "processes": 8, "open_files": 64, @@ -40,7 +40,7 @@ "max_concurrent_jobs": 1, "prefetch": 1, "max_retries": 2, - "max_job_wall_time_us": 60000000, + "max_job_wall_time_us": 180000000, "max_package_bytes": 67108864, "schema_version": 2, "capabilities": [ @@ -54,8 +54,8 @@ "runtime_version": "gnu-cxx-v1" }, "compile_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 30000000, + "wall_time_us": 60000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -66,9 +66,9 @@ "workdir_bytes": 268435456 }, "run_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, - "memory_bytes": 67108864, + "cpu_time_us": 5000000, + "wall_time_us": 10000000, + "memory_bytes": 134217728, "stack_bytes": 8388608, "processes": 8, "open_files": 64, @@ -88,8 +88,8 @@ "runtime_version": "gnu-cxx-v1" }, "compile_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 30000000, + "wall_time_us": 60000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -100,9 +100,9 @@ "workdir_bytes": 268435456 }, "run_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, - "memory_bytes": 67108864, + "cpu_time_us": 5000000, + "wall_time_us": 10000000, + "memory_bytes": 134217728, "stack_bytes": 8388608, "processes": 8, "open_files": 64, @@ -122,8 +122,8 @@ "runtime_version": "3.13.16" }, "compile_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, + "cpu_time_us": 30000000, + "wall_time_us": 60000000, "memory_bytes": 67108864, "stack_bytes": 8388608, "processes": 8, @@ -134,9 +134,9 @@ "workdir_bytes": 67108864 }, "run_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, - "memory_bytes": 67108864, + "cpu_time_us": 5000000, + "wall_time_us": 10000000, + "memory_bytes": 134217728, "stack_bytes": 8388608, "processes": 8, "open_files": 64, @@ -156,8 +156,8 @@ "runtime_version": "21.0.12.1+1-LTS" }, "compile_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 60000000, + "wall_time_us": 120000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -168,8 +168,8 @@ "workdir_bytes": 268435456 }, "run_ceiling": { - "cpu_time_us": 1000000, - "wall_time_us": 3000000, + "cpu_time_us": 5000000, + "wall_time_us": 10000000, "memory_bytes": 402653184, "stack_bytes": 8388608, "processes": 32, diff --git a/deploy/sandbox.managed.runtime.example.json b/deploy/sandbox.managed.runtime.example.json index 400a7af..41befea 100644 --- a/deploy/sandbox.managed.runtime.example.json +++ b/deploy/sandbox.managed.runtime.example.json @@ -14,8 +14,8 @@ }, "max_concurrent_runs": 1, "per_task_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 60000000, + "wall_time_us": 120000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -49,8 +49,8 @@ "runtime_version": "gnu-cxx-v1" }, "per_task_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 60000000, + "wall_time_us": 120000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -72,8 +72,8 @@ "runtime_version": "3.13.16" }, "per_task_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 60000000, + "wall_time_us": 120000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, @@ -95,8 +95,8 @@ "runtime_version": "21.0.12.1+1-LTS" }, "per_task_ceiling": { - "cpu_time_us": 10000000, - "wall_time_us": 30000000, + "cpu_time_us": 60000000, + "wall_time_us": 120000000, "memory_bytes": 536870912, "stack_bytes": 16777216, "processes": 32, diff --git a/scripts/release/README.md b/scripts/release/README.md index 7117623..d1d9769 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -77,6 +77,22 @@ a new private batch. API, publisher, import, migration and account operations us their required DB authority; Worker receives its own MQ/control identity. Backend URLs use asyncpg. The tool preserves supplied passwords and earlier identities. +The generated single-node policy retains the established language budgets: + +| Stage | CPU limit | Wall limit | Memory limit | +| --- | --- | --- | --- | +| C17/C++20 compilation | 30 s | 60 s | 512 MiB | +| Python preparation | 30 s | 60 s | 64 MiB | +| Java compilation | 60 s | 120 s | 512 MiB | +| C17/C++20/Python execution, per case | 5 s | 10 s | 128 MiB | +| Java execution, per case | 5 s | 10 s | 384 MiB | + +The job wall limit is 180 s and admission remains one job at a time. The Root +image ceilings cover these limits without changing the task aggregate or its +single slot. Budgets are bound into the configuration receipt and measurement +epoch. Installing a changed policy requires a newly generated, explicitly +attested batch; historical reports keep their original measurement identity. + `install-configuration` validates the entire batch before writing. A batch has a content-bound `configuration_id`; installing another for the same source retains the first byte-for-byte. `check-configuration` validates installed bytes,