From 5231d83fded6bfc7f56486de6f25b721785dbb10 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 6 Oct 2026 16:58:45 +0200 Subject: [PATCH] Fix Campbell provenance lookup in installed wheel --- CHANGELOG.md | 3 +++ src/solveur/core/analyses/campbell.py | 12 +++++++++++- tests/unit/test_campbell.py | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a4f89f..6f9926af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ the candidate scope; it does not announce a release or assign a version DOI. - The candidate adds `rotating_modal` and `campbell` routes with provisional result contracts. They are not represented as stable API guarantees before the 0.2.11 publication decision. +- Fixed Campbell execution-provenance hashing to resolve implementation files + from the installed wheel layout as well as a source checkout; no solver or + tracking calculation changed. - Package metadata identifies 0.2.11; `CITATION.cff` continues to identify the actually published 0.2.10 release. The package DOI URL uses the published project concept DOI until a version DOI exists. diff --git a/src/solveur/core/analyses/campbell.py b/src/solveur/core/analyses/campbell.py index a191e48e..1572c60d 100644 --- a/src/solveur/core/analyses/campbell.py +++ b/src/solveur/core/analyses/campbell.py @@ -250,6 +250,15 @@ def _result_hash(result: RotatingModalResult) -> str: return content_digest(payload.encode("utf-8")) +def _implementation_source_path(relative: str) -> Path: + """Resolve a selected solveur source path in either checkout or wheel layout.""" + prefix = "src/solveur/" + if not relative.startswith(prefix) or "\\" in relative or ".." in Path(relative).parts: + raise ValueError(f"Unexpected Campbell implementation path: {relative}") + package_root = Path(__file__).resolve().parents[2] + return package_root / relative[len(prefix) :] + + def _execution_identity( *, model_digest: str, validated: ValidatedCampbellInput, matrix_hash: str ) -> tuple[dict[str, Any], dict[str, Any]]: @@ -275,8 +284,9 @@ def _execution_identity( "src/solveur/core/model.py", "src/solveur/post/campbell.py", ) + code_hashes = { - relative: hashlib.sha256((repository / relative).read_bytes()).hexdigest() + relative: hashlib.sha256(_implementation_source_path(relative).read_bytes()).hexdigest() for relative in implementation_files } environment = { diff --git a/tests/unit/test_campbell.py b/tests/unit/test_campbell.py index 2d8ac9b3..337be54c 100644 --- a/tests/unit/test_campbell.py +++ b/tests/unit/test_campbell.py @@ -3,10 +3,12 @@ from __future__ import annotations import json +from pathlib import Path import numpy as np import pytest +import solveur.core.analyses.campbell as campbell_analysis from solveur.core.analyses.campbell import CampbellInputValidator, CampbellSolver from solveur.core.analyses.settings import AnalysisSettings from solveur.core.dofs import DOF_ORDER @@ -18,6 +20,20 @@ from solveur.io.schema import JsonSchemaValidator +def test_implementation_hash_paths_support_installed_wheel_layout(tmp_path: Path, monkeypatch) -> None: + package_root = tmp_path / "venv" / "Lib" / "site-packages" / "solveur" + module_file = package_root / "core" / "analyses" / "campbell.py" + module_file.parent.mkdir(parents=True) + module_file.write_text("# installed wheel layout\n", encoding="utf-8") + monkeypatch.setattr(campbell_analysis, "__file__", str(module_file)) + + implementation = campbell_analysis._implementation_source_path( + "src/solveur/core/analyses/rotating_modal.py" + ) + + assert implementation == package_root / "core" / "analyses" / "rotating_modal.py" + + def _beam_material() -> dict[str, float | str]: radius = 0.05 area = np.pi * radius**2