ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
check_python_lock_policy.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Enforce the repository's single uv dependency and environment authority."""
5
6from __future__ import annotations
7
8import argparse
9import ast
10import json
11import os
12import re
13import shutil
14import subprocess
15import sys
16import tempfile
17import tomllib
18from collections.abc import Mapping
19from dataclasses import dataclass
20from pathlib import Path
21
22sys.path.insert(0, str(Path(__file__).resolve().parent))
23
24import yaml
25from python_lock_policy_scan import (
26 SECONDARY_AUTHORITY_NAMES,
27 VENDOR_BOUNDARIES,
28 adjacent_import_closure_selftest,
29 cli_consumer_findings,
30 first_party_import_closure,
31 hil_preflight_findings,
32 hil_preflight_selftest,
33 load_hil_tasks,
34 python_source_paths,
35 read_authored_text,
36 requirement_findings,
37 scanner_selection_selftest,
38 unsafe_install_findings,
39)
40from python_lock_policy_uv_cache import uv_cache_policy_findings, uv_cache_policy_selftest
41from python_lock_policy_uv_execution import (
42 uv_execution_policy_findings,
43 uv_execution_policy_selftest,
44)
45from python_lock_policy_uv_runner import (
46 AuthenticatedUv,
47 execution_attack_selftest,
48 export_findings,
49 find_uv,
50)
51
52ROOT = Path(__file__).resolve().parents[2]
53PYPROJECT = ROOT / "pyproject.toml"
54MANIFEST = ROOT / "scripts" / "dev" / "uv_release.json"
55BOOTSTRAP = ROOT / "scripts" / "dev" / "bootstrap_uv.py"
56MIN_DIRECT_DEPENDENCIES = 15
57MIN_FIRST_PARTY_PYTHON_FILES = 250
58EXPECTED_ASSETS = {
59 "Darwin|aarch64": "uv-aarch64-apple-darwin.tar.gz",
60 "Darwin|x86_64": "uv-x86_64-apple-darwin.tar.gz",
61 "Windows|aarch64": "uv-aarch64-pc-windows-msvc.zip",
62 "Windows|x86_64": "uv-x86_64-pc-windows-msvc.zip",
63 "Linux|aarch64|gnu": "uv-aarch64-unknown-linux-gnu.tar.gz",
64 "Linux|aarch64|musl": "uv-aarch64-unknown-linux-musl.tar.gz",
65 "Linux|x86_64|gnu": "uv-x86_64-unknown-linux-gnu.tar.gz",
66 "Linux|x86_64|musl": "uv-x86_64-unknown-linux-musl.tar.gz",
67}
68EXPORTS = {
69 "k3s": Path("infra/ansible/roles/k3s_node/files/requirements.lock"),
70 "hil": Path("infra/ansible/roles/hil_bench/files/requirements.lock"),
71}
72GALAXY_MANIFEST = Path("infra/ansible/requirements.yml")
73INTERPRETER_IMPORT_ROOTS: frozenset[str] = frozenset({"__main__"})
74
75
76@dataclass(frozen=True)
77class ConsumerProof:
78 """Describe one direct dependency's owning group and repository consumer."""
79
80 group: str
81 relative: str
82 needle: str
83
84
85@dataclass(frozen=True)
86class ConsumerCatalog:
87 """Bind consumer proofs, import mappings, and a non-vacuity scan floor."""
88
89 proofs: Mapping[str, ConsumerProof]
90 external_imports: Mapping[str, str]
91 minimum_python_files: int
92 cli_census: bool = True
93
94
95CONSUMER_PROOF_ROWS = {
96 "pillow": ("hil", "scripts/hil/camera_livestream.sh", "from PIL import Image"),
97 "pyserial": (
98 "hil",
99 "infra/network/fg_bringup.py",
100 'importlib.import_module("serial")',
101 ),
102 "pyusb": ("hil", "scripts/hil/usb/libusb_bench.py", "import usb.core"),
103 "python-dotenv": (
104 "hil",
105 "scripts/hil/hil_secrets.py",
106 "from dotenv import load_dotenv",
107 ),
108 "python-kasa": (
109 "hil",
110 "scripts/hil/tapo_control.py",
111 "from kasa import Credentials",
112 ),
113 "pyyaml": ("runtime", "scripts/ci/check_ci_parity.py", "import yaml"),
114 "cmakelang": ("dev", "scripts/ci/gates/lint.sh", "cmake-format"),
115 "gcovr": ("dev", "scripts/ci/gates/tests.sh", "require_cmd gcovr"),
116 "libclang": (
117 "dev",
118 "scripts/ci/gates/checks.sh",
119 "require_python_mod clang.cindex",
120 ),
121 "ruff": ("dev", "scripts/ci/gates/lint.sh", "require_tool_versions ruff"),
122 "yamllint": ("dev", "scripts/ci/gates/lint.sh", "require_cmd yamllint"),
123 "ansible-core": ("infra", "scripts/dev/infra.sh", "ansible-playbook"),
124 "hvac": (
125 "infra",
126 "infra/ansible/group_vars/all.example.yml",
127 "community.hashi_vault",
128 ),
129 "kubernetes": (
130 "k3s",
131 "infra/ansible/roles/k3s_node/tasks/main.yml",
132 "import kubernetes",
133 ),
134 "ethos-u-vela": ("vela", "tools/vela/src/vela_gen.py", 'shutil.which("vela")'),
135}
136CONSUMER_PROOFS = {
137 package: ConsumerProof(*values) for package, values in CONSUMER_PROOF_ROWS.items()
138}
139EXTERNAL_IMPORTS = {
140 "PIL": "pillow",
141 "clang": "libclang",
142 "dotenv": "python-dotenv",
143 "kasa": "python-kasa",
144 "serial": "pyserial",
145 "usb": "pyusb",
146 "yaml": "pyyaml",
147}
148CONSUMER_CATALOG = ConsumerCatalog(CONSUMER_PROOFS, EXTERNAL_IMPORTS, MIN_FIRST_PARTY_PYTHON_FILES)
149
150
151def canonical(name: str) -> str:
152 """Canonicalize one Python distribution name."""
153 return re.sub(r"[-_.]+", "-", name).lower()
154
155
156def parse_dependency_entry(entry: object) -> tuple[str | None, str]:
157 """Return a canonical pin/version or an included group marker."""
158 if isinstance(entry, dict):
159 if set(entry) != {"include-group"}:
160 message = f"unsupported dependency-group record: {entry}"
161 raise ValueError(message)
162 included = entry["include-group"]
163 if not isinstance(included, str):
164 message = f"included dependency group must be a string: {entry}"
165 raise TypeError(message)
166 return None, included
167 if not isinstance(entry, str):
168 message = f"dependency entry must be a string: {entry!r}"
169 raise TypeError(message)
170 match = re.fullmatch(r"([A-Za-z0-9][A-Za-z0-9._-]*)==([0-9][A-Za-z0-9.!+_-]*)", entry)
171 if match is None:
172 message = f"direct dependency is not exactly pinned: {entry!r}"
173 raise ValueError(message)
174 name, version = match.groups()
175 return canonical(name), version
176
177
178def direct_declarations(path: Path) -> tuple[dict[str, str], dict[str, str]]:
179 """Return exact pins and owning groups, rejecting duplicate or loose entries."""
180 document = tomllib.loads(path.read_text(encoding="utf-8"))
181 groups = document.get("dependency-groups")
182 if not isinstance(groups, dict):
183 message = "missing dependency-groups table"
184 raise TypeError(message)
185 pins: dict[str, str] = {}
186 owners: dict[str, str] = {}
187 includes: set[str] = set()
188 for group, entries in groups.items():
189 if not isinstance(group, str) or not isinstance(entries, list):
190 message = "dependency group must have a string name and list value"
191 raise TypeError(message)
192 for entry in entries:
193 package, value = parse_dependency_entry(entry)
194 if package is None:
195 includes.add(value)
196 elif package in pins:
197 message = f"duplicate direct dependency: {package}"
198 raise ValueError(message)
199 else:
200 pins[package] = value
201 owners[package] = group
202 missing_groups = includes - set(groups)
203 if missing_groups:
204 message = f"included dependency groups do not exist: {sorted(missing_groups)}"
205 raise ValueError(message)
206 if len(pins) < MIN_DIRECT_DEPENDENCIES:
207 message = f"only {len(pins)} direct dependencies; policy floor is stale"
208 raise ValueError(message)
209 return pins, owners
210
211
212def direct_pins(path: Path) -> dict[str, str]:
213 """Return direct exact pins for callers that do not need owning groups."""
214 return direct_declarations(path)[0]
215
216
217def imported_roots(path: Path) -> tuple[set[str], list[str]]:
218 """Return absolute import roots, including literal dynamic imports."""
219 source, error = read_authored_text(path, "Python import policy input")
220 if error is not None:
221 return set(), [error]
222 try:
223 tree = ast.parse(source or "", filename=str(path))
224 except SyntaxError as error:
225 return set(), [f"{path}: cannot inspect Python imports: {error}"]
226 roots: set[str] = set()
227 importlib_modules = {"importlib"}
228 import_functions = {"__import__"}
229 for node in ast.walk(tree):
230 if isinstance(node, ast.Import):
231 roots.update(alias.name.partition(".")[0] for alias in node.names)
232 importlib_modules.update(
233 alias.asname or alias.name for alias in node.names if alias.name == "importlib"
234 )
235 elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
236 roots.add(node.module.partition(".")[0])
237 if node.module == "importlib":
238 import_functions.update(
239 alias.asname or alias.name
240 for alias in node.names
241 if alias.name == "import_module"
242 )
243 for node in ast.walk(tree):
244 if isinstance(node, ast.Call) and node.args:
245 function = node.func
246 is_dynamic = (isinstance(function, ast.Name) and function.id in import_functions) or (
247 isinstance(function, ast.Attribute)
248 and isinstance(function.value, ast.Name)
249 and function.value.id in importlib_modules
250 and function.attr == "import_module"
251 )
252 argument = node.args[0]
253 if (
254 is_dynamic
255 and isinstance(argument, ast.Constant)
256 and isinstance(argument.value, str)
257 ):
258 roots.add(argument.value.partition(".")[0])
259 return roots, []
260
261
262def import_consumer_findings(
263 root: Path,
264 pins: Mapping[str, str],
265 catalog: ConsumerCatalog,
266) -> list[str]:
267 """Find unclassified imports and imports without direct dependency proofs."""
268 findings: list[str] = []
269 sources, closure_errors = first_party_import_closure(
270 root, python_source_paths(root), imported_roots
271 )
272 findings.extend(closure_errors)
273 if len(sources) < catalog.minimum_python_files:
274 findings.append(
275 f"only {len(sources)} first-party Python files; import-consumer scan floor is stale"
276 )
277 local_roots = {path.stem for path in sources}
278 local_roots.update(path.parent.name for path in sources if path.name == "__init__.py")
279 local_roots.update(path.relative_to(root).parts[0] for path in sources)
280 used_packages: set[str] = set()
281 for path in sources:
282 imported, errors = imported_roots(path)
283 findings.extend(errors)
284 for root_name in imported:
285 package = catalog.external_imports.get(root_name)
286 if package is not None:
287 used_packages.add(package)
288 elif (
289 root_name not in INTERPRETER_IMPORT_ROOTS
290 and root_name not in sys.stdlib_module_names
291 and root_name not in local_roots
292 ):
293 findings.append(
294 f"{path.relative_to(root)}: unclassified third-party import root {root_name!r}"
295 )
296 for package in sorted(used_packages):
297 if package not in pins:
298 findings.append(f"imported dependency {package} is not directly pinned")
299 if package not in catalog.proofs:
300 findings.append(f"imported dependency {package} has no consumer/group proof")
301 unused_mappings = set(catalog.external_imports.values()) - used_packages
302 if unused_mappings:
303 findings.append(f"external import map has no live imports: {sorted(unused_mappings)}")
304 orphan_mappings = set(catalog.external_imports.values()) - set(catalog.proofs)
305 if orphan_mappings:
306 findings.append(f"external import map has no consumer proofs: {sorted(orphan_mappings)}")
307 return findings
308
309
310def check_consumers(
311 root: Path,
312 pins: dict[str, str],
313 owners: dict[str, str],
314 catalog: ConsumerCatalog = CONSUMER_CATALOG,
315) -> list[str]:
316 """Prove dependency pins, groups, consumers, and Python imports in both directions."""
317 findings: list[str] = []
318 if set(pins) != set(catalog.proofs):
319 findings.append(
320 f"direct dependency/consumer map differs: pins={sorted(pins)}, "
321 f"consumers={sorted(catalog.proofs)}"
322 )
323 if set(pins) != set(owners):
324 findings.append("direct dependency owner map differs from direct pins")
325 for package, proof in catalog.proofs.items():
326 if owners.get(package) != proof.group:
327 findings.append(
328 f"{package}: expected direct group {proof.group}, found {owners.get(package)!r}"
329 )
330 path = root / proof.relative
331 source, error = read_authored_text(path, "dependency consumer proof")
332 if error is not None:
333 findings.append(error)
334 elif proof.needle not in (source or ""):
335 findings.append(f"{package}: consumer proof missing from {proof.relative}")
336 findings.extend(import_consumer_findings(root, pins, catalog))
337 if catalog.cli_census:
338 findings.extend(cli_consumer_findings(root, pins))
339 return findings
340
341
342def check_manifest(path: Path) -> list[str]:
343 """Validate the one official uv version/checksum platform manifest."""
344 findings: list[str] = []
345 document = json.loads(path.read_text(encoding="ascii"))
346 if document.get("schema") != 1 or document.get("repository") != "astral-sh/uv":
347 findings.append("uv release manifest identity/schema is invalid")
348 version = document.get("version")
349 if not isinstance(version, str) or re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version) is None:
350 findings.append("uv release version is not exact semver")
351 assets = document.get("assets")
352 if not isinstance(assets, dict) or set(assets) != set(EXPECTED_ASSETS):
353 return [*findings, "uv release platform matrix is not exact"]
354 for key, expected_name in EXPECTED_ASSETS.items():
355 record = assets.get(key)
356 if not isinstance(record, dict) or set(record) != {"name", "sha256"}:
357 findings.append(f"{key}: malformed uv asset record")
358 continue
359 if record["name"] != expected_name:
360 findings.append(f"{key}: expected asset name {expected_name}")
361 digest = record["sha256"]
362 if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
363 findings.append(f"{key}: invalid uv asset SHA-256")
364 return findings
365
366
367def uv_cache_roots(
368 root: Path,
369 environment: Mapping[str, str] | None = None,
370 executable: str | None = None,
371) -> tuple[Path, ...]:
372 """Return explicit authenticated-cache locations for source and snapshots."""
373 env = os.environ if environment is None else environment
374 candidates = [root / ".tools" / "uv"]
375 configured = env.get("RA8_UV_CACHE_ROOT")
376 if configured:
377 candidates.append(Path(configured))
378 history_root = env.get("RA8_CI_HISTORY_REPO")
379 if history_root:
380 candidates.append(Path(history_root) / ".tools" / "uv")
381 if env.get("RA8_STAGED_HOOK_SNAPSHOT") == "1":
382 source_paths = [
383 Path(executable or "/usr/bin/python3"),
384 *(Path(item) for item in env["PATH"].split(":")),
385 ]
386 for source_path in source_paths:
387 candidate = source_path.absolute()
388 bin_dir = candidate if candidate.name == "bin" else candidate.parent
389 if bin_dir.name == "bin" and bin_dir.parent.name == ".venv":
390 candidates.append(bin_dir.parents[1] / ".tools" / "uv")
391 return tuple(dict.fromkeys(path.absolute() for path in candidates))
392
393
394def cache_routing_selftest(root: Path) -> list[str]:
395 """Prove snapshots and managed environments retain an explicit cache route."""
396 failures: list[str] = []
397 local_cache = root / ".tools" / "uv"
398 source_root = root / "source"
399 snapshot_root = root / "snapshot"
400 staged = uv_cache_roots(
401 snapshot_root,
402 {
403 "RA8_STAGED_HOOK_SNAPSHOT": "1",
404 "PATH": f"{source_root / '.venv' / 'bin'}:/usr/bin",
405 },
406 "/usr/bin/python3",
407 )
408 if staged != (snapshot_root / ".tools" / "uv", source_root / ".tools" / "uv"):
409 failures.append(f"staged cache routing failed: {staged}")
410 configured = uv_cache_roots(
411 root,
412 {
413 "RA8_UV_CACHE_ROOT": str(root / "configured"),
414 "RA8_CI_HISTORY_REPO": str(root / "history"),
415 },
416 )
417 if configured != (
418 local_cache,
419 root / "configured",
420 root / "history" / ".tools" / "uv",
421 ):
422 failures.append(f"explicit cache routing failed: {configured}")
423 return failures
424
425
426def consumer_selftest(root: Path) -> list[str]:
427 """Prove consumer and import coverage detects omissions and wrong groups."""
428 failures: list[str] = []
429 root.mkdir(parents=True, exist_ok=True)
430 source = root / "consumer.py"
431 source.write_text(
432 "import __main__\n"
433 "import importlib as loader\n"
434 "import pathlib\n"
435 "loader.import_module('yaml')\n",
436 encoding="utf-8",
437 )
438 pins = {"pyyaml": "6.0.3"}
439 owners = {"pyyaml": "runtime"}
440 proofs = {"pyyaml": ConsumerProof("runtime", "consumer.py", "loader.import_module('yaml')")}
441 catalog = ConsumerCatalog(proofs, {"yaml": "pyyaml"}, 1, cli_census=False)
442 if check_consumers(root, pins, owners, catalog):
443 failures.append("valid consumer/group/import fixture failed")
444 if not check_consumers(root, pins, {"pyyaml": "hil"}, catalog):
445 failures.append("wrong direct dependency group passed")
446 omitted_catalog = ConsumerCatalog({}, {"yaml": "pyyaml"}, 1, cli_census=False)
447 if not check_consumers(root, {}, {}, omitted_catalog):
448 failures.append("dependency omitted from both pins and proofs passed")
449 if not check_consumers(root, {}, {}, ConsumerCatalog({}, {}, 1, cli_census=False)):
450 failures.append("unclassified imported dependency passed")
451 source.write_text("import pathlib\n", encoding="utf-8")
452 if not check_consumers(root, pins, owners, catalog):
453 failures.append("consumer proof with no matching source passed")
454 source.write_bytes(b"# invalid utf-8: \xa3\n")
455 if not any("not valid UTF-8" in item for item in check_consumers(root, pins, owners, catalog)):
456 failures.append("non-UTF-8 Python consumer did not fail clearly")
457 failures.extend(adjacent_import_closure_selftest(root, source, imported_roots))
458 return failures
459
460
461def cli_consumer_selftest(root: Path) -> list[str]:
462 """Prove every CLI-only package survives an omit-pin/owner/proof attack."""
463 failures: list[str] = []
464 root.mkdir(parents=True, exist_ok=True)
465 (root / "consumers.sh").write_text(
466 "ruff --version\n"
467 "cmake-format --version\n"
468 "gcovr --version\n"
469 "yamllint --version\n"
470 "ansible-playbook --version\n",
471 encoding="utf-8",
472 )
473 python = root / "consumer.py"
474 python.write_text(
475 'import shutil\nfilesystem = shutil\nlocator = filesystem.which\ncommand = "vela"\n'
476 "locator(command)\n",
477 encoding="utf-8",
478 )
479 groups = {
480 "ansible-core": "infra",
481 "cmakelang": "dev",
482 "ethos-u-vela": "vela",
483 "gcovr": "dev",
484 "ruff": "dev",
485 "yamllint": "dev",
486 }
487 needles = {
488 "ansible-core": "ansible-playbook --version",
489 "cmakelang": "cmake-format --version",
490 "ethos-u-vela": "locator(command)",
491 "gcovr": "gcovr --version",
492 "ruff": "ruff --version",
493 "yamllint": "yamllint --version",
494 }
495 proofs = {
496 package: ConsumerProof(
497 group,
498 "consumer.py" if package == "ethos-u-vela" else "consumers.sh",
499 needles[package],
500 )
501 for package, group in groups.items()
502 }
503 pins = dict.fromkeys(groups, "1.0")
504 catalog = ConsumerCatalog(proofs, {}, 1)
505 if check_consumers(root, pins, groups, catalog):
506 failures.append("valid independent CLI consumer census failed")
507 for package in groups:
508 reduced_pins = {name: pin for name, pin in pins.items() if name != package}
509 reduced_owners = {name: group for name, group in groups.items() if name != package}
510 reduced_proofs = {name: proof for name, proof in proofs.items() if name != package}
511 findings = check_consumers(
512 root,
513 reduced_pins,
514 reduced_owners,
515 ConsumerCatalog(reduced_proofs, {}, 1),
516 )
517 expected = f"CLI dependency {package} is invoked but not directly pinned"
518 if expected not in findings:
519 failures.append(f"CLI omit-pin/owner/proof attack passed: {package}")
520 return failures
521
522
523def authority_selftest(root: Path) -> list[str]:
524 """Prove allowed derived/vendor metadata is quiet and parallel authorities fail."""
525 failures: list[str] = []
526 root.mkdir(parents=True, exist_ok=True)
527 (root / "pyproject.toml").write_text("[dependency-groups]\n", encoding="utf-8")
528 (root / "uv.lock").write_text("version = 1\n", encoding="utf-8")
529 (root / GALAXY_MANIFEST).parent.mkdir(parents=True, exist_ok=True)
530 (root / GALAXY_MANIFEST).write_text("collections: []\n", encoding="utf-8")
531 for relative in EXPORTS.values():
532 path = root / relative
533 path.parent.mkdir(parents=True, exist_ok=True)
534 path.write_text("# derived\n", encoding="utf-8")
535 vendor = root / VENDOR_BOUNDARIES[0]
536 vendor.mkdir(parents=True, exist_ok=True)
537 vendor_names = [
538 "requirements.txt",
539 "pyproject.toml",
540 "uv.lock",
541 *sorted(SECONDARY_AUTHORITY_NAMES),
542 ]
543 for name in vendor_names:
544 (vendor / name).write_text("# upstream\n", encoding="utf-8")
545 prose = root / "docs" / "product-requirements" / "notes.txt"
546 prose.parent.mkdir(parents=True, exist_ok=True)
547 prose.write_text("Operational requirements are described in prose.\n", encoding="utf-8")
548 constraints_prose = root / "docs" / "product-constraints" / "notes.txt"
549 constraints_prose.parent.mkdir(parents=True, exist_ok=True)
550 constraints_prose.write_text("Product constraints are described in prose.\n", encoding="utf-8")
551 data = root / "data" / "requirements.json"
552 data.parent.mkdir(parents=True, exist_ok=True)
553 data.write_text('{"labels": ["alpha", "beta"]}\n', encoding="utf-8")
554 constraints_data = root / "data" / "constraints.json"
555 constraints_data.write_text('{"limits": ["alpha", "beta"]}\n', encoding="utf-8")
556 if requirement_findings(root):
557 failures.append("allowed derived, vendor, prose, or data metadata failed")
558
559 stale_paths = [
560 (root / "requirements.txt", "split==1\n"),
561 (root / "constraints-dev.txt", "split==1\n"),
562 *((root / name, "# split authority\n") for name in sorted(SECONDARY_AUTHORITY_NAMES)),
563 (root / "tools" / "nested" / "pyproject.toml", "# split authority\n"),
564 (root / "tools" / "nested" / "uv.lock", "# split authority\n"),
565 (root / "config" / "requirements" / "dev.txt", "nested-package\n"),
566 (root / "config" / "constraints" / "dev.txt", "nested-package\n"),
567 ]
568 for stale, content in stale_paths:
569 stale.parent.mkdir(parents=True, exist_ok=True)
570 stale.write_text(content, encoding="utf-8")
571 if not any(str(stale.relative_to(root)) in item for item in requirement_findings(root)):
572 failures.append(f"secondary dependency authority passed: {stale.relative_to(root)}")
573 stale.unlink()
574 return failures
575
576
577def unsafe_installer_fixtures() -> dict[str, str]:
578 """Return adversarial installer snippets that the policy must reject."""
579 return {
580 "argv-pip.py": (
581 "import subprocess, sys\n"
582 "subprocess.run([sys.executable, '-m', 'pip', 'install', 'rogue'], check=True)\n"
583 ),
584 "argv-uv.py": (
585 "import subprocess\n"
586 "subprocess.run(['/opt/bin/uv', 'pip', 'install', 'rogue'], check=True)\n"
587 ),
588 "alias-pip.py": (
589 "import sys\nfrom subprocess import run as launch\n"
590 "launch([sys.executable, '-m', 'pip', 'install', 'rogue'], check=True)\n"
591 ),
592 "alias-keyword.py": (
593 "import subprocess as runner, sys as runtime\n"
594 "runner.run("
595 "args=[runtime.executable, '-m', 'pip', 'install', 'rogue'], check=True)\n"
596 ),
597 "from-alias-keyword.py": (
598 "from subprocess import run as launch\n"
599 "from sys import executable as interpreter\n"
600 "launch(args=[interpreter, '-m', 'pip', 'install', 'rogue'], check=True)\n"
601 ),
602 "os-system.py": (
603 "import os as host_os\nhost_os.system('python3.11 -m pip3.11 install rogue')\n"
604 ),
605 "assigned-argv.py": (
606 "import subprocess, sys\n"
607 "command = [sys.executable, '-m', 'pip', 'install', 'rogue']\n"
608 "subprocess.run(command, check=True)\n"
609 ),
610 "assigned-launcher.py": (
611 "import subprocess, sys\nprocess = subprocess\nlauncher = process.run\n"
612 "interpreter = sys.executable\n"
613 "launcher([interpreter, '-m', 'pip', 'install', 'rogue'], check=True)\n"
614 ),
615 "assigned-tuple.py": (
616 "from subprocess import run\ncommand = ('uvx', 'rogue')\nrun(command, check=True)\n"
617 ),
618 "assigned-shell.py": (
619 "import os\ncommand = 'python3 -m pip install rogue'\nos.system(command)\n"
620 ),
621 "concatenated-shell.py": ("import os\nos.system('python3 -m pip ' + 'install rogue')\n"),
622 "formatted-shell.py": (
623 "import subprocess\npackage = 'rogue'\n"
624 "subprocess.run(f'python3 -m pip install {package}', shell=True)\n"
625 ),
626 "shell.sh": "python3 -m pip install rogue\n",
627 "uvx.sh": "uvx rogue\n",
628 "windows.cmd": "py.exe -m pip.exe install rogue\n",
629 }
630
631
632def unsafe_installer_selftest(root: Path) -> list[str]:
633 """Prove shell and Python process installers fail while non-installs remain valid."""
634 failures: list[str] = []
635 root.mkdir(parents=True, exist_ok=True)
636 safe = root / "safe.py"
637 safe.write_text(
638 "import subprocess as runner, sys as runtime\n"
639 "command = [runtime.executable, '-m', 'pip', 'check']\n"
640 "runner.run(args=command, check=True)\n",
641 encoding="utf-8",
642 )
643 if unsafe_install_findings(root):
644 failures.append("safe Python dependency graph check was rejected")
645 for name, content in unsafe_installer_fixtures().items():
646 fixture = root / name
647 fixture.write_text(content, encoding="utf-8")
648 if not any(name in item for item in unsafe_install_findings(root)):
649 failures.append(f"unsafe installer passed: {name}")
650 fixture.unlink()
651 vendor = root / "libs" / "third_party" / "upstream" / "install.sh"
652 vendor.parent.mkdir(parents=True, exist_ok=True)
653 vendor.write_text("pip install upstream-build-helper\n", encoding="utf-8")
654 if unsafe_install_findings(root):
655 failures.append("vendored installer was treated as first-party policy")
656 return failures
657
658
659def export_freshness_selftest(uv: AuthenticatedUv) -> list[str]:
660 """Prove current lock/exports pass and mutations fail with authenticated uv."""
661 failures: list[str] = []
662 with tempfile.TemporaryDirectory(prefix="ra8-uv-export-test-") as raw:
663 fixture = Path(raw)
664 shutil.copy2(PYPROJECT, fixture / "pyproject.toml")
665 shutil.copy2(ROOT / "uv.lock", fixture / "uv.lock")
666 for relative in EXPORTS.values():
667 target = fixture / relative
668 target.parent.mkdir(parents=True, exist_ok=True)
669 shutil.copy2(ROOT / relative, target)
670 if export_findings(fixture, EXPORTS, uv):
671 failures.append("current lock/export freshness fixture failed")
672
673 stale_relative = next(iter(EXPORTS.values()))
674 stale_export = fixture / stale_relative
675 stale_export.write_text(
676 stale_export.read_text(encoding="utf-8") + "# stale\n", encoding="utf-8"
677 )
678 if not any(
679 "is stale versus uv.lock" in item for item in export_findings(fixture, EXPORTS, uv)
680 ):
681 failures.append("mutated derived export passed freshness check")
682 shutil.copy2(ROOT / stale_relative, stale_export)
683
684 project = fixture / "pyproject.toml"
685 content = project.read_text(encoding="utf-8")
686 project.write_text(content.replace("PyYAML==6.0.3", "PyYAML==6.0.2"), encoding="utf-8")
687 if not any("uv.lock is stale" in item for item in export_findings(fixture, EXPORTS, uv)):
688 failures.append("mutated project passed lock freshness check")
689 return failures
690
691
692def selftest() -> int:
693 """Prove every lock-policy boundary detects valid and invalid fixtures."""
694 with tempfile.TemporaryDirectory(prefix="ra8-python-policy-test-") as raw:
695 root = Path(raw)
696 project = root / "pyproject.toml"
697 failures: list[str] = []
698 entries = [f"package-{index}==1.0.0" for index in range(MIN_DIRECT_DEPENDENCIES)]
699 project.write_text(
700 "[dependency-groups]\ndev = [" + ",".join(repr(item) for item in entries) + "]\n",
701 encoding="utf-8",
702 )
703 pins, owners = direct_declarations(project)
704 if len(pins) != MIN_DIRECT_DEPENDENCIES or set(owners.values()) != {"dev"}:
705 failures.append("valid direct pins failed")
706 for extra in (entries[0], "package-0==2.0.0", "loose>=1"):
707 project.write_text(
708 "[dependency-groups]\ndev = ["
709 + ",".join(repr(item) for item in [*entries, extra])
710 + "]\n",
711 encoding="utf-8",
712 )
713 try:
714 direct_pins(project)
715 except ValueError:
716 pass
717 else:
718 failures.append(f"invalid direct pin passed: {extra}")
719 manifest = json.loads(MANIFEST.read_text(encoding="ascii"))
720 fixture = root / "uv.json"
721 fixture.write_text(json.dumps(manifest), encoding="ascii")
722 if check_manifest(fixture):
723 failures.append("valid uv manifest failed")
724 manifest["assets"].pop(next(iter(manifest["assets"])))
725 fixture.write_text(json.dumps(manifest), encoding="ascii")
726 if not check_manifest(fixture):
727 failures.append("incomplete uv matrix passed")
728 failures.extend(consumer_selftest(root / "consumers"))
729 failures.extend(cli_consumer_selftest(root / "cli-consumers"))
730 failures.extend(authority_selftest(root / "authorities"))
731 failures.extend(unsafe_installer_selftest(root / "installers"))
732 failures.extend(scanner_selection_selftest(root / "selection"))
733 failures.extend(cache_routing_selftest(root))
734 failures.extend(uv_cache_policy_selftest(ROOT))
735 failures.extend(uv_execution_policy_selftest(ROOT))
736 failures.extend(hil_preflight_selftest(ROOT))
737 live_manifest = json.loads(MANIFEST.read_text(encoding="ascii"))
738 version = live_manifest.get("version")
739 if not isinstance(version, str):
740 failures.append("live uv manifest has no version")
741 else:
742 uv = find_uv(ROOT, version, MANIFEST, uv_cache_roots(ROOT))
743 failures.extend(export_freshness_selftest(uv))
744 failures.extend(execution_attack_selftest(BOOTSTRAP, EXPORTS))
745 if failures:
746 print("selftest: " + "; ".join(failures), file=sys.stderr)
747 return 1
748 print("check_python_lock_policy.py --selftest: PASS")
749 return 0
750
751
752def main() -> int:
753 """Run the offline policy or its synthetic selftest."""
754 parser = argparse.ArgumentParser(description=__doc__)
755 parser.add_argument("--selftest", action="store_true")
756 args = parser.parse_args()
757 if args.selftest:
758 return selftest()
759 try:
760 pins, owners = direct_declarations(PYPROJECT)
761 manifest = json.loads(MANIFEST.read_text(encoding="ascii"))
762 version = manifest.get("version")
763 if not isinstance(version, str):
764 return fatal("uv manifest version is malformed")
765 uv = find_uv(ROOT, version, MANIFEST, uv_cache_roots(ROOT))
766 findings = [
767 *check_consumers(ROOT, pins, owners),
768 *check_manifest(MANIFEST),
769 *requirement_findings(ROOT),
770 *unsafe_install_findings(ROOT),
771 *export_findings(ROOT, EXPORTS, uv),
772 *hil_preflight_findings(load_hil_tasks(ROOT)),
773 *uv_cache_policy_findings(ROOT),
774 *uv_execution_policy_findings(ROOT),
775 ]
776 except (
777 OSError,
778 TypeError,
779 ValueError,
780 json.JSONDecodeError,
781 yaml.YAMLError,
782 subprocess.SubprocessError,
783 ) as error:
784 print(f"check_python_lock_policy.py: FATAL: {error}", file=sys.stderr)
785 return 2
786 if findings:
787 print("\n".join(findings), file=sys.stderr)
788 return 1
789 print("Python dependencies, uv bootstrap, and managed exports match one lock")
790 return 0
791
792
793def fatal(message: str) -> int:
794 """Report a fatal policy-input error."""
795 print(f"check_python_lock_policy.py: FATAL: {message}", file=sys.stderr)
796 return 2
797
798
799if __name__ == "__main__":
800 raise SystemExit(main())
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298