From afea1e756ab6deb531fb137df2fe98fc69123796 Mon Sep 17 00:00:00 2001 From: "sec-check[bot]" Date: Mon, 14 Sep 2026 21:18:04 -0400 Subject: [PATCH 1/2] =?UTF-8?q?[architect]=20test:=20files/=20payload=20re?= =?UTF-8?q?achability=20gate=20=E2=80=94=20tests/unit/test=5Ffiles=5Fpaylo?= =?UTF-8?q?ad=5Freachability.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every path under files/ reaches an image only if some element stages it with a kind: local source. Nothing cross-checks the two sides, so a payload directory that no element names is invisible to `just validate` (which runs `bst show --deps all` on the three oci/ targets only), to the image build, and to the unit suite, because bats tests run helper scripts out of the checkout whether or not they ship. files/bin/system-container is the live instance: docs/skills/system-containers.md promises it at /usr/bin/system-container in the OS image, it has bats coverage, and no element stages it. Add a gate asserting both directions of the contract: - every kind: local `path:` declared by an element still exists on disk, so renaming payload cannot silently drop it out of the image; - every file under files/ is staged, declared host tooling (HOST_TOOLING: files/bin/bluefin-kubestellar, files/lima), or a recorded waiver. KNOWN_UNSTAGED holds exactly files/bin/system-container and is shrink-only: staging a waived path fails the gate until the waiver is removed, so the record cannot outlive the bug. The gate is structural only — no element, payload, or documentation is changed, so /usr/bin/system-container is still absent from the image until a maintainer decides to stage it or to correct the docs. Refs #154 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: sec-check[bot] --- tests/unit/test_files_payload_reachability.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/unit/test_files_payload_reachability.py diff --git a/tests/unit/test_files_payload_reachability.py b/tests/unit/test_files_payload_reachability.py new file mode 100644 index 0000000..012536f --- /dev/null +++ b/tests/unit/test_files_payload_reachability.py @@ -0,0 +1,189 @@ +"""Drift gate: every path under ``files/`` is image payload or declared host tooling. + +``files/`` is the repository's image-payload tree. A file only reaches an image +if some element stages it with a ``kind: local`` source, e.g.:: + + sources: + - kind: local + path: files/os/sysupdate.d + +Nothing else pulls ``files/`` into the build graph, so a directory that no +element names is invisible to ``just validate`` (which only runs +``bst show --deps all`` on the three OCI targets) and to the image build. It is +also invisible to the unit suite, because bats tests execute helper scripts +straight out of the checkout whether or not they ship. + +That gap is not hypothetical: ``files/bin/system-container`` is documented in +``docs/skills/system-containers.md`` as "shipped in the OS image" at +``/usr/bin/system-container`` and has bats coverage, yet no element stages it — +see issue #154. + +This module enforces both directions of the contract: + +* every ``path:`` an element declares still exists on disk, so renaming a + payload directory cannot silently drop it out of the image; and +* every file under ``files/`` is staged, or is declared host-side tooling, or is + an explicitly recorded waiver. + +``KNOWN_UNSTAGED`` is shrink-only: staging a waived path fails the gate until +the waiver is deleted, so the record cannot outlive the bug it describes. +""" + +from pathlib import Path + +import pytest +import yaml + +ROOT = Path(__file__).resolve().parents[2] +ELEMENTS_DIR = ROOT / "elements" +FILES_DIR = ROOT / "files" + +# Paths under files/ that are host-side tooling, executed from a repository +# checkout and deliberately absent from every image. +HOST_TOOLING = { + # Host developer CLI: Justfile `install-vm` and + # scripts/lima-e2e-kubestellar-test.sh run it from the checkout. + "files/bin/bluefin-kubestellar", + # limactl VM template consumed by Justfile `test-e2e-lima`. + "files/lima", +} + +# Payload that claims to ship but is staged by no element. Each entry must name +# the issue tracking its resolution. Shrink this set; never grow it. +KNOWN_UNSTAGED = { + # docs/skills/system-containers.md promises /usr/bin/system-container in the + # OS image. No element stages it. Tracked by issue #154 — fixing that issue + # (staging it, or correcting the docs) must remove this entry. + "files/bin/system-container", +} + + +def _element_files(): + return sorted(ELEMENTS_DIR.rglob("*.bst")) + + +def _local_source_paths(document): + """Yield every ``path:`` of a ``kind: local`` source in a parsed element.""" + if not isinstance(document, dict): + return + for source in document.get("sources") or []: + if isinstance(source, dict) and source.get("kind") == "local": + path = source.get("path") + if isinstance(path, str): + yield path + + +def _declared_paths(): + """Map each declared ``kind: local`` path to the elements declaring it.""" + declared = {} + for element in _element_files(): + document = yaml.safe_load(element.read_text()) + for path in _local_source_paths(document): + rel = element.relative_to(ROOT).as_posix() + declared.setdefault(path.rstrip("/"), []).append(rel) + return declared + + +def _staged_paths(): + """Declared paths that live under ``files/``, as repo-relative strings.""" + return {path for path in _declared_paths() if path == "files" or path.startswith("files/")} + + +def _is_staged(rel_path, staged): + """True when ``rel_path`` is a declared path or sits under one.""" + candidate = Path(rel_path) + return any( + candidate == Path(path) or Path(path) in candidate.parents for path in staged + ) + + +def _is_host_tooling(rel_path): + candidate = Path(rel_path) + return any( + candidate == Path(path) or Path(path) in candidate.parents + for path in HOST_TOOLING + ) + + +def _payload_files(): + return sorted( + p.relative_to(ROOT).as_posix() for p in FILES_DIR.rglob("*") if p.is_file() + ) + + +def test_files_tree_is_present(): + assert FILES_DIR.is_dir(), "files/ payload tree missing" + assert _payload_files(), "files/ contains no files — payload tree emptied?" + + +def test_at_least_one_element_stages_payload(): + """Guards the gate itself: a parser regression must not silently pass.""" + staged = _staged_paths() + assert staged, ( + "no element declares a kind: local source under files/ — either the " + "payload wiring was removed or this gate stopped parsing elements" + ) + + +@pytest.mark.parametrize("declared", sorted(_declared_paths())) +def test_declared_source_paths_exist(declared): + """A stale ``path:`` silently drops payload out of the image.""" + owners = ", ".join(_declared_paths()[declared]) + assert (ROOT / declared).exists(), ( + f"{owners} declares a kind: local source path that does not exist: " + f"{declared}. Renaming payload requires updating the element that " + f"stages it." + ) + + +def test_every_payload_file_is_staged_or_declared(): + """No file under files/ may be unreachable without an explicit declaration.""" + staged = _staged_paths() + orphans = [ + rel + for rel in _payload_files() + if not _is_staged(rel, staged) + and not _is_host_tooling(rel) + and rel not in KNOWN_UNSTAGED + ] + assert not orphans, ( + "files/ paths reach no image and are not declared host tooling:\n " + + "\n ".join(orphans) + + "\n\nStage them with a kind: local source on an element that " + "os-stack.bst, installer-stack.bst, or an oci/ target depends on; or " + "add them to HOST_TOOLING if they are host-side only." + ) + + +def test_host_tooling_declarations_are_not_stale(): + staged = _staged_paths() + for declared in sorted(HOST_TOOLING): + assert (ROOT / declared).exists(), ( + f"HOST_TOOLING names {declared}, which no longer exists — drop the " + f"declaration." + ) + assert not _is_staged(declared, staged), ( + f"{declared} is declared host tooling but an element now stages it " + f"into an image. Remove it from HOST_TOOLING." + ) + + +def test_known_unstaged_waivers_are_still_unstaged(): + """Shrink-only: a waiver must be deleted once the path actually ships.""" + staged = _staged_paths() + for waived in sorted(KNOWN_UNSTAGED): + assert (ROOT / waived).exists(), ( + f"KNOWN_UNSTAGED names {waived}, which no longer exists — drop the " + f"waiver." + ) + assert not _is_staged(waived, staged), ( + f"{waived} is now staged by an element. Remove it from " + f"KNOWN_UNSTAGED so the gate protects it." + ) + + +def test_waivers_and_host_tooling_are_disjoint(): + overlap = HOST_TOOLING & KNOWN_UNSTAGED + assert not overlap, ( + f"paths declared both host tooling and unstaged payload: {sorted(overlap)}" + ) From adca82894cb95849b58979bc734696a723169b6e Mon Sep 17 00:00:00 2001 From: Jorge Castro Date: Fri, 18 Sep 2026 13:34:52 -0400 Subject: [PATCH 2/2] test: prune vacuous assertions from files payload gate Drop test_waivers_and_host_tooling_are_disjoint (a tautology over two module-level literal sets) and test_files_tree_is_present (subsumed by the anti-vacuity guard and the build itself). Record in the module docstring that staging is matched by declaration rather than by graph reachability from the OCI targets. Assisted-by: Claude Opus 4.6 via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unit/test_files_payload_reachability.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/unit/test_files_payload_reachability.py b/tests/unit/test_files_payload_reachability.py index 012536f..2423587 100644 --- a/tests/unit/test_files_payload_reachability.py +++ b/tests/unit/test_files_payload_reachability.py @@ -25,6 +25,9 @@ * every file under ``files/`` is staged, or is declared host-side tooling, or is an explicitly recorded waiver. +Staging is matched by declaration, not by graph reachability: a path named by an +element that no OCI target depends on still counts as staged here. + ``KNOWN_UNSTAGED`` is shrink-only: staging a waived path fails the gate until the waiver is deleted, so the record cannot outlive the bug it describes. """ @@ -111,11 +114,6 @@ def _payload_files(): ) -def test_files_tree_is_present(): - assert FILES_DIR.is_dir(), "files/ payload tree missing" - assert _payload_files(), "files/ contains no files — payload tree emptied?" - - def test_at_least_one_element_stages_payload(): """Guards the gate itself: a parser regression must not silently pass.""" staged = _staged_paths() @@ -180,10 +178,3 @@ def test_known_unstaged_waivers_are_still_unstaged(): f"{waived} is now staged by an element. Remove it from " f"KNOWN_UNSTAGED so the gate protects it." ) - - -def test_waivers_and_host_tooling_are_disjoint(): - overlap = HOST_TOOLING & KNOWN_UNSTAGED - assert not overlap, ( - f"paths declared both host tooling and unstaged payload: {sorted(overlap)}" - )