From 5adac31c21f651aa90f7fff136a4c7aded1f68e0 Mon Sep 17 00:00:00 2001 From: Elie Bursztein Date: Sun, 27 Sep 2026 14:33:25 +0000 Subject: [PATCH 1/2] fix(cache): hold the Cargo maximum around every compiling gate command The shared Cargo target reached 237 GB against its 180 GiB maximum and a focus-test died with ENOSPC. Enforcement existed only as a prerequisite step of the host-build fragment and in front of bounded direct cargo, and only before compiling. Clippy in test-fast, the static coverage build, test-rust-affected, smoke, pack-initrd, bench-report and check-assets compiled into the shared target with no enforcement at all, and nothing enforced after any compile, so each worktree's salted units stood until some later enforcing path happened to run. The bound is now a resource of the command: every exclusive gate command whose plan claims workspace_binaries holds CargoCacheBound, which enforces before the first step and after the last, failed runs included. The fragment step and its cache_already_enforced opt-out are removed. The bounded wrapper also enforces after the command, reporting rather than refusing on that side so the command's own exit status stands. --- CHANGELOG.md | 9 ++ build_system/builder/gate/boundedlease.py | 24 ++-- build_system/builder/gate/cachecontrol.py | 24 ++++ build_system/builder/gate/candidateprepare.py | 8 +- build_system/builder/gate/command.py | 1 + build_system/builder/gate/hostbuild.py | 42 ++----- build_system/builder/gate/preflight.py | 24 +++- build_system/builder/gate/runtimeprepare.py | 9 +- .../tests/gate/test_bounded_machine_lease.py | 36 +++++- .../tests/gate/test_build_assets_profile.py | 3 +- .../tests/gate/test_cargo_cache_bound.py | 108 ++++++++++++++++++ .../tests/gate/test_gate_assetlanes.py | 3 +- build_system/tests/gate/test_gate_bench.py | 5 +- .../gate/test_gate_candidate_composition.py | 30 +---- .../tests/gate/test_gate_testmodules.py | 3 +- .../tests/gate/test_stale_process_reaper.py | 9 +- skills/dev-cache/SKILL.md | 9 ++ 17 files changed, 252 insertions(+), 95 deletions(-) create mode 100644 build_system/tests/gate/test_cargo_cache_bound.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ef96baca3..b324512b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,6 +191,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Every gate command that compiles now enforces the shared Cargo target's + maximum before its first step and again after its last, including after a + failed run, and bounded direct Cargo commands enforce it after the command + as well as before. Only the host-build fragment enforced it, and only before + building, so clippy in `fast-test`, the static coverage build, `smoke`, + `pack-initrd` and other compiling commands grew the target unchecked; it + reached 237 GB against its 180 GiB maximum and a focused test run died with + ENOSPC. + - On Linux x86_64 hosts, anything a process in the VM wrote to the console now reaches `capsem logs`. The emulated serial port never raised its transmit interrupt, so only kernel messages got out: a container started diff --git a/build_system/builder/gate/boundedlease.py b/build_system/builder/gate/boundedlease.py index 29c115202..66384bb13 100644 --- a/build_system/builder/gate/boundedlease.py +++ b/build_system/builder/gate/boundedlease.py @@ -69,15 +69,23 @@ def leased( ) with held(lock): _enforce_cargo_cache(root, command) - yield lock.environment() + try: + yield lock.environment() + finally: + _enforce_cargo_cache(root, command, refuse=False) -def _enforce_cargo_cache(root: Path, command: Sequence[str]) -> None: - """Apply the shared Cargo target contract before direct machine work. +def _enforce_cargo_cache(root: Path, command: Sequence[str], *, refuse: bool = True) -> None: + """Apply the shared Cargo target contract around direct machine work. - Gate plans expose the same operation as a timed prerequisite. Direct Cargo - has no plan, so the mandatory bounded-command choke point owns this half of - the invariant while it holds the same machine lock as a gate. + Gate commands hold the same bound as `CargoCacheBound`. Direct Cargo has no + plan, so the mandatory bounded-command choke point owns this half of the + invariant while it holds the same machine lock as a gate: before, and again + after, because the compile itself is what grows the target. + + Only the check before refuses. Afterwards the command has run and its exit + status is the answer the caller asked for; a bound that cannot be restored + is reported, and the next compile's check refuses it. """ policy = load_policy(root) paths = load_paths(root) @@ -87,8 +95,10 @@ def _enforce_cargo_cache(root: Path, command: Sequence[str]) -> None: "cargo", reason=f"bounded direct command: {shlex.join(command)}", ) - if result.violations: + if result.violations and refuse: raise GateError("; ".join(result.violations)) + if result.violations: + _to_stderr("Cargo cache above its contract after the command: " + "; ".join(result.violations)) if result.pruned: _to_stderr(_maintenance_notice(result)) diff --git a/build_system/builder/gate/cachecontrol.py b/build_system/builder/gate/cachecontrol.py index b6d3d61f5..740119809 100644 --- a/build_system/builder/gate/cachecontrol.py +++ b/build_system/builder/gate/cachecontrol.py @@ -7,6 +7,7 @@ from . import cachelayout from .config import for_root from .errors import GateError +from .lifecycle import Resource from .proc import Runner from .sourcecommit import SourceCommit @@ -99,3 +100,26 @@ def enforce(self, cache_id: str, label: str | None = None) -> None: """Enforce one cache owner's maximum before expensive work.""" reason = f"gate cache enforcement for {label or cache_id}" self._run("enforce", cache_id, "--reason", reason) + + +class CargoCacheBound(Resource, name="cargo-cache"): + """Hold the shared Cargo target to its contract around a compiling command. + + Acquired before the first step and released after the last, on every path. + Enforcing only in front of one fragment's build left every other compiling + step unbounded and every compile's growth standing until some later run + happened to look, so worktree-salted units reached 237 GB against 180 GiB. + """ + + def __init__(self, runner: Runner) -> None: + self._runner = runner + + def _enforce(self, moment: str) -> None: + if not self._runner.observing: + CacheControl(self._runner).enforce("cargo", f"{moment} compilation") + + def acquire(self) -> None: + self._enforce("before") + + def release(self) -> None: + self._enforce("after") diff --git a/build_system/builder/gate/candidateprepare.py b/build_system/builder/gate/candidateprepare.py index 8e86c3b13..dce137a78 100644 --- a/build_system/builder/gate/candidateprepare.py +++ b/build_system/builder/gate/candidateprepare.py @@ -102,13 +102,7 @@ def prepare( built_harness = phase.add(harness, after=(checked,)) fit = phase.add(fitness, after=(built_harness,)) dependencies = packagepreflight.fragment(plan, config, after=(fit,)) - return runtimeprepare.prepare( - plan, - config, - after=(dependencies,), - permission=permission, - cache_already_enforced=True, - ) + return runtimeprepare.prepare(plan, config, after=(dependencies,), permission=permission) def _enforce_cache(config: GateConfig) -> Call: diff --git a/build_system/builder/gate/command.py b/build_system/builder/gate/command.py index aa5c78b3e..316f8a280 100644 --- a/build_system/builder/gate/command.py +++ b/build_system/builder/gate/command.py @@ -260,6 +260,7 @@ def execute(self) -> None: self._config, runner, self.name, exclusive=self.exclusive, declared=egress.for_command(self, runner), + plan=plan, ) with held(*acquiring) as acquired: from .egress import guarded_runner_of diff --git a/build_system/builder/gate/hostbuild.py b/build_system/builder/gate/hostbuild.py index 513a75c1c..f66667fb7 100644 --- a/build_system/builder/gate/hostbuild.py +++ b/build_system/builder/gate/hostbuild.py @@ -1,12 +1,10 @@ -"""The bounded host-binary build fragment and its Cargo cache prerequisite.""" +"""The host-binary build fragment.""" from __future__ import annotations -from .actions import Call, Run -from .cachecontrol import CacheControl +from .actions import Run from .config import GateConfig from .execution import Kind, Needs, Speed, Step, step -from .opacity import CallJustification, Effect, OpaqueKind, machine_effects from .phase import Phase from .plan import Plan @@ -35,42 +33,18 @@ def _build_step(config: GateConfig, *, label: str = "build-binaries") -> Step: ) -def _cargo_cache_step(config: GateConfig) -> Step: - return step( - "cargo-cache-enforcement", - Call( - "enforce the Cargo cache maximum before host compilation", - lambda ctx: CacheControl(ctx.runner).enforce("cargo", "host compilation"), - justification=CallJustification( - kind=OpaqueKind.RUNTIME_DERIVED, - reason="the typed cache owner inventories shared compilation units at runtime", - effects=machine_effects(Effect.PROCESS, Effect.FILESYSTEM, Effect.HOST_STATE), - ), - ), - contends=(config.exclusive("workspace_binaries"),), - kind=Kind.PACKAGE, - needs=frozenset({Needs.DISK}), - speed=Speed.FAST, - ) - - def add( owner: Plan | Phase, config: GateConfig, *, after: tuple[Step, ...] = (), label: str = "build-binaries", - cache_already_enforced: bool = False, ) -> Step: - """Add the one host build path, with visible cache enforcement first. + """Add the one host build path. - Complete qualification already enforces every configured cache before it - reaches this fragment. Every focused composer takes the default and gets a - separate timed prerequisite, so the shared Cargo target cannot silently - grow past its contract again. + The shared Cargo target's maximum is held by the command, not here: + `CargoCacheBound` enforces it before the first and after the last step of + every plan that compiles, which a prerequisite of this one fragment could + not do for clippy, coverage, or any other compile beside it. """ - dependencies = after - if not cache_already_enforced: - bounded = owner.add(_cargo_cache_step(config), after=after) - dependencies = (bounded,) - return owner.add(_build_step(config, label=label), after=dependencies) + return owner.add(_build_step(config, label=label), after=after) diff --git a/build_system/builder/gate/preflight.py b/build_system/builder/gate/preflight.py index edde03857..abc711058 100644 --- a/build_system/builder/gate/preflight.py +++ b/build_system/builder/gate/preflight.py @@ -15,8 +15,10 @@ import os from collections.abc import Iterator from contextlib import contextmanager +from typing import TYPE_CHECKING from . import snapshot +from .cachecontrol import CargoCacheBound from .cachetooling import CompilerCache from .cargotarget import CheckoutBuildRoot from .config import GateConfig @@ -26,6 +28,9 @@ from .proc import Runner from .reaper import StaleProcesses +if TYPE_CHECKING: # pragma: no cover - imported for typing only + from .plan import Plan + def refuse_inside_a_run(config: GateConfig, name: str, *, exclusive: bool) -> None: """Refuse to take a lock this process tree is already holding. @@ -78,18 +83,35 @@ def holdings( *, exclusive: bool, declared: tuple[Resource, ...], + plan: Plan, ) -> tuple[Resource, ...]: - """Tooling and declared resources, inside the machine lock's outer scope.""" + """Tooling and declared resources, inside the machine lock's outer scope. + + The Cargo bound sits outside every declared resource, so its release + enforces after the last step and after whatever those resources tear down. + """ if not exclusive: return declared + bound = (CargoCacheBound(runner),) if compiles(plan, config) else () return ( StaleProcesses(config, runner), CheckoutBuildRoot(config, runner), CompilerCache(config, runner), + *bound, *declared, ) +def compiles(plan: Plan, config: GateConfig) -> bool: + """Whether any step drives Cargo, by the claim every such step must take. + + `tests/citadel/test_step_actions_are_atomic.py` holds that claim: a step + that reaches Cargo without `workspace_binaries` fails there. + """ + claim = config.exclusive("workspace_binaries").name + return any(held.name == claim for step in plan.steps for held in step.contends) + + def purpose(name: str) -> str: """What contention should call this, for whoever arrives next.""" return f"capsem-gate {name}" diff --git a/build_system/builder/gate/runtimeprepare.py b/build_system/builder/gate/runtimeprepare.py index bf0fe2472..0702e7faf 100644 --- a/build_system/builder/gate/runtimeprepare.py +++ b/build_system/builder/gate/runtimeprepare.py @@ -29,7 +29,6 @@ def prepare( permission: RebuildPermission = DEFAULT_PERMISSION, build_label: str = "build-binaries", sign_label: str = "sign", - cache_already_enforced: bool = False, ) -> Preparation: """Build one self-contained runtime, optionally including VM inputs.""" phase = plan.phase("prepare") @@ -46,13 +45,7 @@ def prepare( previous = (packed,) materialized = phase.add(materialize_config_step(config), after=previous) - built = hostbuild.add( - phase, - config, - after=(materialized,), - label=build_label, - cache_already_enforced=cache_already_enforced, - ) + built = hostbuild.add(phase, config, after=(materialized,), label=build_label) ready = phase.add(hostpackage.sign_step(config, label=sign_label), after=(built,)) return Preparation(ready=ready, profile_content=materialized) diff --git a/build_system/tests/gate/test_bounded_machine_lease.py b/build_system/tests/gate/test_bounded_machine_lease.py index 533e332e3..523ff550e 100644 --- a/build_system/tests/gate/test_bounded_machine_lease.py +++ b/build_system/tests/gate/test_bounded_machine_lease.py @@ -178,12 +178,42 @@ def test_direct_cargo_enforces_its_cache_contract_inside_the_machine_lease( monkeypatch.setattr( boundedlease, "_enforce_cargo_cache", - lambda root, command: enforced.append((root, tuple(command))), + lambda root, command, **_: enforced.append((root, tuple(command))), ) monkeypatch.setenv("HOME", str(tmp_path)) - with boundedlease.leased(("cargo", "test", "-p", "capsem-core"), ROOT, {}): - assert enforced == [(ROOT, ("cargo", "test", "-p", "capsem-core"))] + command = ("cargo", "test", "-p", "capsem-core") + with boundedlease.leased(command, ROOT, {}): + assert enforced == [(ROOT, command)] + # After as well as before: the compile itself is what grows the target, + # and a bound checked only on the way in leaves it above its maximum for + # every worktree that arrives next. + assert enforced == [(ROOT, command), (ROOT, command)] + + +def test_only_the_check_before_the_command_refuses( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """After the command, its own exit status is the answer; a bound that + cannot be restored is reported and refused by the next compile instead.""" + from capsem_builder.cache.enforcement import EnforcementResult + from capsem_builder.gate import boundedlease + from capsem_builder.gate.errors import GateError + + stuck = EnforcementResult( + cache_id="cargo", + before_size_bytes=2, + after_size_bytes=2, + pruned=False, + reclaim_bytes=0, + action_count=0, + violations=("cargo remains 2 bytes above max size 1",), + ) + monkeypatch.setattr(boundedlease, "enforce_repository", lambda *_, **__: stuck) + with pytest.raises(GateError, match="above max size"): + boundedlease._enforce_cargo_cache(ROOT, ("cargo", "build")) + boundedlease._enforce_cargo_cache(ROOT, ("cargo", "build"), refuse=False) + assert "above its contract after the command" in capsys.readouterr().err def test_zero_byte_expiry_does_not_claim_cargo_exceeded_its_limit() -> None: diff --git a/build_system/tests/gate/test_build_assets_profile.py b/build_system/tests/gate/test_build_assets_profile.py index 6ae5e66f2..30c89e82b 100644 --- a/build_system/tests/gate/test_build_assets_profile.py +++ b/build_system/tests/gate/test_build_assets_profile.py @@ -431,8 +431,7 @@ def test_runtime_recipes_materialize_generated_config_before_service() -> None: # that exact signed runtime exists. for command in ("ensure-service", "shell", "exec"): plan = _command(command, guest_command="true")._describe() - assert plan.after_of("prepare.cargo-cache-enforcement") == {"prepare.materialize-config"} - assert plan.after_of("prepare.build-binaries") == {"prepare.cargo-cache-enforcement"} + assert plan.after_of("prepare.build-binaries") == {"prepare.materialize-config"} assert plan.after_of("prepare.sign") == {"prepare.build-binaries"} assert plan.after_of("prepare") == {"prepare.sign"} diff --git a/build_system/tests/gate/test_cargo_cache_bound.py b/build_system/tests/gate/test_cargo_cache_bound.py new file mode 100644 index 000000000..08e5b1d33 --- /dev/null +++ b/build_system/tests/gate/test_cargo_cache_bound.py @@ -0,0 +1,108 @@ +"""Every gate command that compiles holds the shared Cargo target to its contract. + +The contract is `[stages.cargo]` in `config/cache.toml`, and it was enforced as +a plan step that only the host-build fragment added, in front of its build. +Everything else compiled into the same shared target with nothing in front of +it -- clippy in `test-fast`, the coverage build in `test-static`, +`test-rust-affected`, `smoke`, `pack-initrd` -- and nothing enforced after any +compile at all, so the stage was left above its maximum by whatever the last +run added. Every worktree's checkout path salts a fresh set of workspace units, +so those unbounded paths accumulated until the target reached 237 GB against a +180 GiB maximum and a `focus-test` died with ENOSPC. + +The bound is therefore a resource of the command, not a step one fragment +remembers: acquired before the first step and released after the last, on +every path including a failed one. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +from capsem_builder.gate import config as gate_config +from capsem_builder.gate import preflight +from capsem_builder.gate.cachecontrol import CargoCacheBound +from capsem_builder.gate.lifecycle import held +from helpers.gate import RecordingRunner, gate_plan + +ROOT = Path(__file__).resolve().parents[3] +CONFIG = gate_config.load(ROOT) +ENFORCE = r"capsem-cache .* enforce cargo --reason" + +#: Exclusive commands whose plans compile into the shared target. The first +#: seven had no Cargo enforcement anywhere in their plans. +COMPILING = ( + "test-fast", + "test-static", + "test-rust-affected", + "bench-report", + "smoke", + "pack-initrd", + "check-assets", + "test-functional", + "build-host", + "candidate", +) + + +def _holdings(command: str) -> tuple: + plan = gate_plan(command) + return preflight.holdings( + CONFIG, RecordingRunner(ROOT), command, exclusive=True, declared=(), plan=plan + ) + + +@pytest.mark.parametrize("command", COMPILING) +def test_every_compiling_command_holds_the_cargo_bound(command: str) -> None: + kinds = [type(resource) for resource in _holdings(command)] + assert CargoCacheBound in kinds, ( + f"{command} compiles into the shared Cargo target without enforcing its maximum" + ) + + +def test_a_plan_that_never_compiles_is_not_charged_for_the_scan() -> None: + assert not preflight.compiles(gate_plan("host-image"), CONFIG) + assert CargoCacheBound not in [type(resource) for resource in _holdings("host-image")] + + +def test_a_command_without_the_machine_never_mutates_the_cache() -> None: + held_resources = preflight.holdings( + CONFIG, + RecordingRunner(ROOT), + "test-release-contracts", + exclusive=False, + declared=(), + plan=gate_plan("test-release-contracts"), + ) + assert CargoCacheBound not in [type(resource) for resource in held_resources] + + +def _acting() -> RecordingRunner: + runner = RecordingRunner(ROOT) + runner.observing = False + return runner + + +def test_the_bound_enforces_before_the_first_step_and_after_the_last() -> None: + runner = _acting() + with held(CargoCacheBound(runner)): + assert len(runner.matching(ENFORCE)) == 1, "nothing enforced before compilation" + enforced = runner.matching(ENFORCE) + assert len(enforced) == 2, "nothing enforced after compilation" + assert "before" in enforced[0] and "after" in enforced[1] + + +def test_a_failed_plan_still_leaves_the_target_inside_its_contract() -> None: + runner = _acting() + with pytest.raises(RuntimeError), held(CargoCacheBound(runner)): + raise RuntimeError("the compile ran out of disk") + assert len(runner.matching(ENFORCE)) == 2 + + +def test_asking_what_a_plan_would_do_prunes_nothing() -> None: + runner = RecordingRunner(ROOT) + assert runner.observing + with held(CargoCacheBound(runner)): + pass + assert not runner.commands diff --git a/build_system/tests/gate/test_gate_assetlanes.py b/build_system/tests/gate/test_gate_assetlanes.py index 56bb88a3d..b70734bb5 100644 --- a/build_system/tests/gate/test_gate_assetlanes.py +++ b/build_system/tests/gate/test_gate_assetlanes.py @@ -185,8 +185,7 @@ def test_asset_plan_builds_and_signs_host_binaries_before_assembly() -> None: fragment(plan, CONFIG) assert plan.after_of("assets.pack-initrds") == {"assets.sweep"} - assert plan.after_of("assets.cargo-cache-enforcement") == {"assets.pack-initrds"} - assert plan.after_of("assets.build-host-binaries") == {"assets.cargo-cache-enforcement"} + assert plan.after_of("assets.build-host-binaries") == {"assets.pack-initrds"} assert plan.after_of("assets.sign-host-binaries") == {"assets.build-host-binaries"} assert plan.after_of("assets.assemble") == {"assets.sign-host-binaries"} assert CONFIG.path(CONFIG.service.binary) in plan.step_named( diff --git a/build_system/tests/gate/test_gate_bench.py b/build_system/tests/gate/test_gate_bench.py index 38c735eeb..c905d2c0b 100644 --- a/build_system/tests/gate/test_gate_bench.py +++ b/build_system/tests/gate/test_gate_bench.py @@ -233,12 +233,15 @@ def test_a_linked_worktree_reads_binaries_where_cargo_writes_them(tmp_path: Path resolve to the build root the compiler was given, as a prefix's do. """ from capsem_builder.gate import cargotarget, preflight + from capsem_builder.gate.plan import Plan config = _linked_worktree(tmp_path) shared = cargotarget.path(config) (shared / "debug").mkdir(parents=True) (shared / "debug" / "capsem").write_bytes(b"built") - resources = preflight.holdings(config, _ActingRunner(config.root), "measure", exclusive=True, declared=()) + resources = preflight.holdings( + config, _ActingRunner(config.root), "measure", exclusive=True, declared=(), plan=Plan("measure") + ) link = next(resource for resource in resources if isinstance(resource, cargotarget.CheckoutBuildRoot)) link.acquire() debug = config.root / "cache" / "target" / "cargo" / "debug" diff --git a/build_system/tests/gate/test_gate_candidate_composition.py b/build_system/tests/gate/test_gate_candidate_composition.py index 1d98c97cb..a8904419e 100644 --- a/build_system/tests/gate/test_gate_candidate_composition.py +++ b/build_system/tests/gate/test_gate_candidate_composition.py @@ -123,11 +123,8 @@ def test_standalone_signing_owns_its_build_dependency_and_timeouts( argparse.Namespace(dry_run=False, graph=False, timing=False), )._describe() - assert plan.after_of("build-binaries") == {"cargo-cache-enforcement"} + assert plan.after_of("build-binaries") == set() assert plan.after_of("sign") == {"build-binaries"} - assert "enforce the Cargo cache maximum before host compilation" in "\n".join( - plan.step_named("cargo-cache-enforcement").render() - ) assert f"[timeout {CONFIG.signing.build_timeout_seconds}s]" in "\n".join( plan.step_named("build-binaries").render() ) @@ -143,8 +140,7 @@ def test_standalone_host_build_is_the_same_bounded_gate_step() -> None: argparse.Namespace(dry_run=False, graph=False, timing=False), )._describe() - assert plan.labels == ("cargo-cache-enforcement", "build-binaries") - assert plan.after_of("build-binaries") == {"cargo-cache-enforcement"} + assert plan.labels == ("build-binaries",) assert f"[timeout {CONFIG.signing.build_timeout_seconds}s]" in "\n".join( plan.step_named("build-binaries").render() ) @@ -161,28 +157,13 @@ def test_ensure_service_owns_the_bounded_host_build_and_signing( argparse.Namespace(dry_run=False, graph=False, timing=False), )._describe() - assert plan.after_of("prepare.build-binaries") == {"prepare.cargo-cache-enforcement"} + assert plan.after_of("prepare.build-binaries") == {"prepare.materialize-config"} assert plan.after_of("prepare.sign") == {"prepare.build-binaries"} assert plan.after_of("prepare") == {"prepare.sign"} assert plan.after_of("materialize") == {"prepare"} assert plan.after_of("start") == {"materialize"} -def test_only_complete_qualification_may_reuse_prior_cache_enforcement() -> None: - """A focused composer cannot opt out of the host-build cache prerequisite.""" - gate = PROJECT_ROOT / "build_system/builder/gate" - claims = [ - path.relative_to(gate) - for path in gate.rglob("*.py") - if "cache_already_enforced=True" in path.read_text(encoding="utf-8") - ] - - assert claims == [Path("candidateprepare.py")] - candidate = _plan() - assert "prepare.cargo-cache-enforcement" not in candidate.labels - assert "prepare.cache-enforcement" in ancestors(candidate, "prepare.build-binaries") - - def test_runtime_commands_own_preparation_service_and_guest_edges() -> None: """No runtime prerequisite may live in a separate Just process.""" for command, final in (("shell", "shell"), ("exec", "exec")): @@ -195,10 +176,7 @@ def test_runtime_commands_own_preparation_service_and_guest_edges() -> None: plan = GateCommand.registry[command](RecordingRunner(PROJECT_ROOT), args)._describe() assert plan.after_of("prepare.materialize-config") - assert plan.after_of("prepare.cargo-cache-enforcement") == { - "prepare.materialize-config" - } - assert plan.after_of("prepare.build-binaries") == {"prepare.cargo-cache-enforcement"} + assert plan.after_of("prepare.build-binaries") == {"prepare.materialize-config"} assert plan.after_of("prepare.sign") == {"prepare.build-binaries"} assert plan.after_of("prepare") == {"prepare.sign"} assert plan.after_of("materialize") == {"prepare"} diff --git a/build_system/tests/gate/test_gate_testmodules.py b/build_system/tests/gate/test_gate_testmodules.py index e506130dc..8de82ac5f 100644 --- a/build_system/tests/gate/test_gate_testmodules.py +++ b/build_system/tests/gate/test_gate_testmodules.py @@ -145,8 +145,7 @@ def test_focused_static_builds_every_runtime_binary_before_macos_signing(monkeyp plan = _plan(StaticModule) build = plan.step_named("static.build-binaries") assert plan.after_of("static.sign") >= {build.label, "static.rust-coverage"} - assert plan.after_of(build.label) == {"static.cargo-cache-enforcement"} - assert plan.after_of("static.cargo-cache-enforcement") >= {"static.web.frontend-bundle"} + assert plan.after_of(build.label) >= {"static.web.frontend-bundle"} output = "\n".join(build.render()) assert "cargo build" in output assert {CONFIG.path(binary) for binary in CONFIG.signing.binaries} <= set(build.produces) diff --git a/build_system/tests/gate/test_stale_process_reaper.py b/build_system/tests/gate/test_stale_process_reaper.py index 11b6e93bd..a1e5fa7cc 100644 --- a/build_system/tests/gate/test_stale_process_reaper.py +++ b/build_system/tests/gate/test_stale_process_reaper.py @@ -28,6 +28,7 @@ from capsem_builder.gate import config as gate_config from capsem_builder.gate import preflight from capsem_builder.gate.cachetooling import CompilerCache +from capsem_builder.gate.plan import Plan from capsem_builder.gate.reaper import StaleProcesses from helpers.gate import RecordingRunner @@ -162,7 +163,9 @@ def test_a_different_program_is_never_reaped_whatever_its_environment(short: Pat def test_every_exclusive_command_reaps_before_it_starts_its_own_server() -> None: - held = preflight.holdings(CONFIG, RecordingRunner(ROOT), "test-fast", exclusive=True, declared=()) + held = preflight.holdings( + CONFIG, RecordingRunner(ROOT), "test-fast", exclusive=True, declared=(), plan=Plan("test-fast") + ) kinds = [type(resource) for resource in held] assert StaleProcesses in kinds, "an exclusive command started without reaping" assert kinds.index(StaleProcesses) < kinds.index(CompilerCache), ( @@ -172,7 +175,9 @@ def test_every_exclusive_command_reaps_before_it_starts_its_own_server() -> None def test_a_command_without_the_machine_reaps_nothing() -> None: """Without the lock, a live server may belong to a run that is using it.""" - held = preflight.holdings(CONFIG, RecordingRunner(ROOT), "lint", exclusive=False, declared=()) + held = preflight.holdings( + CONFIG, RecordingRunner(ROOT), "lint", exclusive=False, declared=(), plan=Plan("lint") + ) assert StaleProcesses not in [type(resource) for resource in held] diff --git a/skills/dev-cache/SKILL.md b/skills/dev-cache/SKILL.md index 352166740..c3668e21c 100644 --- a/skills/dev-cache/SKILL.md +++ b/skills/dev-cache/SKILL.md @@ -110,6 +110,15 @@ run (issue #205). Paths Cargo does not name by unit, such as uplifted binaries, count toward capacity but are never selected. Native Cargo output locks protect the whole stage and are acquired again through deletion; even an explicit cold clean preserves their lock inodes. +The Cargo maximum is held around compilation, not by any one step. Every +exclusive gate command whose plan claims `workspace_binaries` holds +`CargoCacheBound` (`gate/cachecontrol.py`), which enforces before the first +step and after the last, failed runs included; `run-bounded-command.py` +enforces before and after direct Cargo. Do not add an enforcement step to a +fragment: a prerequisite of the host build left clippy, coverage and every +other compile unbounded, and nothing enforced after any compile, until the +target reached 237 GB against 180 GiB. + The `objects` store (`object_store = true`) is retained the same way, one generation at a time: a component receipt together with the objects no other receipt names, receipt first. An object two receipts share belongs to neither From 65d97582828848e884161439ad1bea3c1ea6b863 Mon Sep 17 00:00:00 2001 From: Elie Bursztein Date: Sun, 27 Sep 2026 16:14:38 +0000 Subject: [PATCH 2/2] test(gate): register the new test file in the ownership ledger --- build_system/tests/test_ownership.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build_system/tests/test_ownership.toml b/build_system/tests/test_ownership.toml index 941f47f95..c3be9edf0 100644 --- a/build_system/tests/test_ownership.toml +++ b/build_system/tests/test_ownership.toml @@ -893,3 +893,8 @@ target = "build_system/tests/packaging/test_router_signing.py" owner = "gate" source = "tests/test_pytest_excerpt.py" target = "build_system/tests/gate/test_pytest_excerpt.py" + +[[owned]] +owner = "gate" +source = "tests/test_cargo_cache_bound.py" +target = "build_system/tests/gate/test_cargo_cache_bound.py"