Skip to content

Commit 2e0a677

Browse files
fix(release): require selected extras in SBOM closure; restore galaxy travel floor
Two outstanding Codex review findings on PR 148: 1. _declared_dependencies() now includes every marker-applicable requirement from the [all] and [test] extras (the groups the release workflow installs before capturing the SBOM), evaluating PEP 508 environment markers against the capture interpreter via packaging.markers. A truncated SBOM containing only the core dependency can no longer pass the closure check. 2. Restored the Complete Galaxy orbital-travel floors from .00005 back to .001: 96ff9b6 had restored them but the change was lost in the following commit's rewrite of the file. Added focused regression tests for both extras-closure behavior and marker-inapplicable extras being correctly not required.
1 parent 273a2f1 commit 2e0a677

3 files changed

Lines changed: 88 additions & 10 deletions

File tree

scripts/release_evidence.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
tomllib = None
2222

2323
try: # Prefer the installed packaging module when available.
24+
from packaging.markers import InvalidMarker, Marker
2425
from packaging.specifiers import InvalidSpecifier, SpecifierSet
2526
from packaging.version import InvalidVersion, Version
2627
except ImportError: # pragma: no cover - fallback for environments without top-level packaging
28+
from pip._vendor.packaging.markers import InvalidMarker, Marker
2729
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
2830
from pip._vendor.packaging.version import InvalidVersion, Version
2931

@@ -37,6 +39,10 @@
3739
_PACKAGE_LOCK_LINE = re.compile(r"([A-Za-z0-9][A-Za-z0-9_.-]*)==([^\s]+)\Z")
3840
_IMAGE_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
3941
_BUILDER_IMAGE = "github-hosted:ubuntu-latest/python-3.11"
42+
# Extras installed by the release workflow (`.github/workflows/release.yml` runs
43+
# `pip install ... ".[all,test]"` before capturing the SBOM), so the captured
44+
# closure must include every marker-applicable requirement they declare.
45+
_RELEASE_EXTRAS = ("all", "test")
4046
_BUILDER_TOOLCHAIN = {
4147
"build": "1.5.0",
4248
"pip": "26.2",
@@ -271,10 +277,13 @@ def _version_satisfies(version: str, specifier: str) -> bool:
271277

272278

273279
def _declared_dependencies(root: Path) -> dict[str, str | None]:
274-
"""Return {canonical_name: specifier} from pyproject.toml [project].dependencies.
280+
"""Return {canonical_name: specifier} required in the captured SBOM closure.
275281
276-
Only core runtime dependencies are validated against the SBOM closure.
277-
Optional extras are intentionally excluded.
282+
Covers [project].dependencies plus every requirement declared by the extras
283+
the release workflow installs (``_RELEASE_EXTRAS``). PEP 508 environment
284+
markers are evaluated against the running interpreter, which in the release
285+
workflow is the same environment that captures the SBOM; requirements whose
286+
markers do not apply are not required.
278287
"""
279288
pyproject = root / "pyproject.toml"
280289
try:
@@ -288,9 +297,18 @@ def _declared_dependencies(root: Path) -> dict[str, str | None]:
288297
except (KeyError, ValueError):
289298
parsed = {}
290299
project = parsed.get("project", {}) if isinstance(parsed, dict) else {}
291-
core = project.get("dependencies", []) if isinstance(project, dict) else []
292-
if isinstance(core, list):
293-
requirements.extend(item for item in core if isinstance(item, str))
300+
if isinstance(project, dict):
301+
core = project.get("dependencies", [])
302+
if isinstance(core, list):
303+
requirements.extend(item for item in core if isinstance(item, str))
304+
extras = project.get("optional-dependencies", {})
305+
if isinstance(extras, dict):
306+
for extra in _RELEASE_EXTRAS:
307+
group = extras.get(extra)
308+
if isinstance(group, list):
309+
requirements.extend(
310+
item for item in group if isinstance(item, str)
311+
)
294312
else:
295313
project = re.search(r"(?ms)^\[project\]\s*(.*?)(?=^\[|\Z)", raw)
296314
if project is not None:
@@ -299,13 +317,38 @@ def _declared_dependencies(root: Path) -> dict[str, str | None]:
299317
)
300318
if deps_block is not None:
301319
requirements.extend(re.findall(r'"([^"]+)"', deps_block.group(1)))
320+
extras_table = re.search(
321+
r"(?ms)^\[project\.optional-dependencies\]\s*(.*?)(?=^\[|\Z)", raw,
322+
)
323+
if extras_table is not None:
324+
for extra in _RELEASE_EXTRAS:
325+
group = re.search(
326+
r"(?m)^" + re.escape(extra) + r"\s*=\s*\[(.*?)\]",
327+
extras_table.group(1), re.DOTALL,
328+
)
329+
if group is not None:
330+
requirements.extend(re.findall(r'"([^"]+)"', group.group(1)))
302331
deps: dict[str, str | None] = {}
303332
for requirement in requirements:
304333
if not isinstance(requirement, str) or not requirement.strip():
305334
continue
306335
name, specifier = _parse_requirement(requirement)
307-
if name:
308-
deps[_canonical_package_name(name)] = specifier
336+
canonical = _canonical_package_name(name)
337+
if not canonical or canonical == PACKAGE:
338+
continue
339+
marker_text = requirement.split(";", 1)[1].strip() if ";" in requirement else ""
340+
if marker_text:
341+
try:
342+
applies = Marker(marker_text).evaluate()
343+
except InvalidMarker as exc:
344+
raise EvidenceError(
345+
"pyproject.toml declares an unparsable environment marker: "
346+
+ marker_text
347+
) from exc
348+
if not applies:
349+
continue
350+
if canonical not in deps or deps[canonical] is None:
351+
deps[canonical] = specifier
309352
return deps
310353

311354
def _python_sbom_packages(document: dict[str, Any]) -> set[tuple[str, str]]:

tests/e2e/graph-engine.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,10 +2270,10 @@ test('served Complete Galaxy uses the lightweight all-body orbit path instead of
22702270
expect(after.diagnostics.lastRelationCorrections).toBe(0);
22712271
expect(phases.every(phase => phase.global.count === 3335 && phase.global.missing === 0
22722272
&& phase.global.nonFinite === 0 && phase.global.frozen === 0 && phase.global.totalFrozen === 0
2273-
&& phase.global.minTravel > .00005), JSON.stringify(phases.map(phase => phase.global))).toBe(true);
2273+
&& phase.global.minTravel > .001), JSON.stringify(phases.map(phase => phase.global))).toBe(true);
22742274
expect(phases.every(phase => phase.local.count === 2960 && phase.local.missing === 0
22752275
&& phase.local.nonFinite === 0 && phase.local.frozen === 0 && phase.local.totalFrozen === 0
2276-
&& phase.local.minTravel > .00005), JSON.stringify(phases.map(phase => phase.local))).toBe(true);
2276+
&& phase.local.minTravel > .001), JSON.stringify(phases.map(phase => phase.local))).toBe(true);
22772277
expect(phases.every(phase => phase.carrierCount === 375 && phase.systemCount === 375
22782278
&& phase.carrierFailures.length === 0 && phase.carrierMaxError < 1e-8),
22792279
JSON.stringify(phases.map(phase => ({ count: phase.carrierCount, error: phase.carrierMaxError,

tests/test_release_evidence.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,41 @@ def test_release_evidence_rejects_prerelease_versions_against_pep440_floors(tmp_
617617
_build(root, dist, inputs=inputs)
618618

619619

620+
def test_release_evidence_requires_selected_extra_dependencies(tmp_path):
621+
"""The closure must include requirements from the extras the workflow installs."""
622+
root = _root(tmp_path)
623+
(root / "pyproject.toml").write_text(
624+
'[project]\nname = "engraphis"\nversion = "1.2.3"\n'
625+
'dependencies = ["alpha-package>=1.0"]\n'
626+
"[project.optional-dependencies]\n"
627+
"all = ['extra-dep>=1.0; python_version >= \"3.9\"']\n"
628+
"test = ['test-dep>=0.1']\n",
629+
encoding="utf-8",
630+
)
631+
dist = _dist(root)
632+
inputs = _release_inputs(root, dist)
633+
# SBOM and lock carry only the core dependency; extra-dep/test-dep are missing.
634+
with pytest.raises(EvidenceError, match="missing declared dependencies"):
635+
_build(root, dist, inputs=inputs)
636+
637+
638+
def test_release_evidence_ignores_extra_dependencies_with_inapplicable_markers(tmp_path):
639+
"""Extras requirements whose environment marker excludes this interpreter are
640+
not required, mirroring what pip installs in the capture environment."""
641+
root = _root(tmp_path)
642+
(root / "pyproject.toml").write_text(
643+
'[project]\nname = "engraphis"\nversion = "1.2.3"\n'
644+
'dependencies = ["alpha-package>=1.0"]\n'
645+
"[project.optional-dependencies]\n"
646+
"all = ['future-dep>=1.0; python_version < \"3.9\"']\n"
647+
"test = ['legacy-dep>=0.1; python_version < \"3.9\"']\n",
648+
encoding="utf-8",
649+
)
650+
dist = _dist(root)
651+
evidence = _build(root, dist)
652+
assert evidence["environment_lock"]["package_count"] == 2
653+
654+
620655
def test_release_evidence_rejects_sbom_with_mismatched_root_purl(tmp_path):
621656
"""An SBOM whose metadata.component PURL names a different package must fail."""
622657
root = _root(tmp_path)

0 commit comments

Comments
 (0)