feat(shared-root): S3 — configurable shared-root resolver with load-bearing override test
One call-time resolver (env PORTFOLIO_SHARED_ROOT, default the in-repo shared/) consumed by both MAF-side readers of the shared core: persona._example_path() (the _EXAMPLE_PATH monkeypatch seam is kept) and simulation._default_bundle_dir() (replaces the _BUNDLE_DIR module global). De-risks the S4 extraction: re-pointing the commons becomes an env var, not a code change. Override test proves the marker follows a tmp copy of the whole shared tree; both detach points proven RED. Suite 155->157. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AaQCFnfsh3tfq1VfzdJpoi
This commit is contained in:
parent
5de1c93b69
commit
cebba7d954
4 changed files with 110 additions and 15 deletions
|
|
@ -17,16 +17,20 @@ import json
|
|||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# Module-global so tests can monkeypatch it; read at CALL time inside the loader (never frozen into
|
||||
# a default argument), which is what makes the simulation's persona genuinely artifact-driven.
|
||||
_EXAMPLE_PATH = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "shared"
|
||||
/ "skills"
|
||||
/ "expert-reviewer"
|
||||
/ "references"
|
||||
/ "example-verdict.json"
|
||||
)
|
||||
from portfolio_optimiser.shared_root import shared_root
|
||||
|
||||
# Test seam: when set (monkeypatched), wins over the resolver. Read at CALL time inside the loader
|
||||
# (never frozen into a default argument), which is what makes the simulation's persona genuinely
|
||||
# artifact-driven.
|
||||
_EXAMPLE_PATH: Path | None = None
|
||||
|
||||
_EXAMPLE_SUBPATH = Path("skills") / "expert-reviewer" / "references" / "example-verdict.json"
|
||||
|
||||
|
||||
def _example_path() -> Path:
|
||||
"""The persona example's location: the test seam if set, else resolved under ``shared_root()``
|
||||
(env ``PORTFOLIO_SHARED_ROOT`` re-points it — the S4 extraction seam)."""
|
||||
return _EXAMPLE_PATH if _EXAMPLE_PATH is not None else shared_root() / _EXAMPLE_SUBPATH
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -42,9 +46,9 @@ class PersonaExample:
|
|||
|
||||
def load_persona_example() -> PersonaExample:
|
||||
"""Read the persona's canonical example verdict. Fail-fast: a missing file raises
|
||||
``FileNotFoundError`` and a missing key raises ``KeyError`` (required input). Reads
|
||||
``_EXAMPLE_PATH`` at call time so the path is patchable."""
|
||||
data = json.loads(_EXAMPLE_PATH.read_text(encoding="utf-8"))
|
||||
``FileNotFoundError`` and a missing key raises ``KeyError`` (required input). Resolves the
|
||||
path at call time (``_example_path``) so both the test seam and the env override are live."""
|
||||
data = json.loads(_example_path().read_text(encoding="utf-8"))
|
||||
return PersonaExample(
|
||||
decision=data["decision"],
|
||||
rationale=data["rationale"],
|
||||
|
|
|
|||
23
src/portfolio_optimiser/shared_root.py
Normal file
23
src/portfolio_optimiser/shared_root.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""Resolver for the location of the shared framework-neutral core (S3, R1-forberedelse).
|
||||
|
||||
``shared/`` is today an in-repo directory, but is slated for extraction into its own commons repo
|
||||
(R1/S4). Every MAF-side consumer resolves its location through this ONE seam so the extraction is
|
||||
a re-point (env var), not a code change. Pure stdlib — the shared core itself stays framework-free.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ENV_VAR = "PORTFOLIO_SHARED_ROOT"
|
||||
|
||||
_DEFAULT = Path(__file__).resolve().parents[2] / "shared"
|
||||
|
||||
|
||||
def shared_root() -> Path:
|
||||
"""Resolve the shared-core root at CALL time: ``PORTFOLIO_SHARED_ROOT`` if set (non-empty),
|
||||
else the in-repo default. The call-time read is what keeps the override testable and the S4
|
||||
extraction re-pointable without touching consumers."""
|
||||
override = os.environ.get(ENV_VAR)
|
||||
return Path(override) if override else _DEFAULT
|
||||
|
|
@ -38,12 +38,19 @@ from agent_framework_openai import OpenAIChatCompletionClient
|
|||
|
||||
from portfolio_optimiser.persona import load_persona_example
|
||||
from portfolio_optimiser.run import RunResult, run_project
|
||||
from portfolio_optimiser.shared_root import shared_root
|
||||
from portfolio_optimiser.validator import ValidatedProposal
|
||||
from portfolio_optimiser.verdicts import VerdictStore, promote_verdict, seed_store_from_bundle
|
||||
|
||||
_BUNDLE_DIR = Path(__file__).resolve().parents[2] / "shared" / "examples" / "bygg-energi-mikro"
|
||||
_PROJECT_ID = "BYGG-KONTOR-NORD"
|
||||
|
||||
|
||||
def _default_bundle_dir() -> Path:
|
||||
"""The demo bundle under the shared core, resolved at CALL time via ``shared_root()`` (env
|
||||
``PORTFOLIO_SHARED_ROOT`` re-points it — the S4 extraction seam)."""
|
||||
return shared_root() / "examples" / "bygg-energi-mikro"
|
||||
|
||||
|
||||
# A VALID SavingsProposal for BYGG-KONTOR-NORD: total = 300000 x 1.0, P90 = 0.30 x 300000 = 90000,
|
||||
# claimed 30000 <= 90000 -> validates on the first attempt (no `assumptions` -> degenerate MC).
|
||||
_VALID_PROPOSAL = (
|
||||
|
|
@ -246,7 +253,7 @@ def main(argv: list[str] | None = None) -> int: # pragma: no cover - console tr
|
|||
import tempfile
|
||||
|
||||
work = tempfile.mkdtemp(prefix="po-sim-")
|
||||
result = asyncio.run(simulate_learning_loop(str(_BUNDLE_DIR), work))
|
||||
result = asyncio.run(simulate_learning_loop(str(_default_bundle_dir()), work))
|
||||
|
||||
print("=" * 78)
|
||||
print("OFFLINE SIMULATION — scripted agent replies, NO real model.")
|
||||
|
|
|
|||
61
tests/test_shared_root_loadbearing.py
Normal file
61
tests/test_shared_root_loadbearing.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Load-bearing tests for the configurable shared-root resolver (S3, R1-forberedelse).
|
||||
|
||||
Both MAF-side consumers of the shared framework-neutral core resolve its location through ONE
|
||||
seam — ``shared_root()`` (env ``PORTFOLIO_SHARED_ROOT``, default: the in-repo ``shared/``) — read
|
||||
at CALL time. That seam is what makes the S4 extraction (``shared/`` -> commons repo) a re-point,
|
||||
not a code change. The override test goes RED the moment a consumer stops resolving through the
|
||||
seam: a hardcoded path keeps reading the repo tree, so the tmp copy's swapped marker never
|
||||
surfaces.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from portfolio_optimiser import persona, simulation
|
||||
from portfolio_optimiser.shared_root import shared_root
|
||||
from portfolio_optimiser.simulation import simulate_learning_loop
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_shared_root_defaults_to_in_repo_tree(monkeypatch) -> None:
|
||||
"""Without the env override, the resolver yields today's in-repo ``shared/`` — the S3 change is
|
||||
behaviour-preserving for every existing caller."""
|
||||
monkeypatch.delenv("PORTFOLIO_SHARED_ROOT", raising=False)
|
||||
assert shared_root() == REPO_ROOT / "shared"
|
||||
|
||||
|
||||
async def test_shared_root_override_redirects_both_consumers(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Point ``PORTFOLIO_SHARED_ROOT`` at a tmp COPY of ``shared/`` carrying a swapped persona
|
||||
marker and confirm (a) the persona loader reads the copy, (b) the simulation's default bundle
|
||||
resolves under the copy, and (c) the end-to-end sim traces the COPY's marker across the
|
||||
promotion loop. RED without the resolver: the env var is ignored and the repo artifact's
|
||||
marker is read instead."""
|
||||
copy_root = tmp_path / "shared-copy"
|
||||
shutil.copytree(REPO_ROOT / "shared", copy_root)
|
||||
|
||||
swapped_marker = "realiseringsgrad=0.57"
|
||||
example_path = copy_root / "skills" / "expert-reviewer" / "references" / "example-verdict.json"
|
||||
data = json.loads(example_path.read_text(encoding="utf-8"))
|
||||
assert data["marker"] != swapped_marker, "control: the swap must differ from the repo artifact"
|
||||
data["marker"] = swapped_marker
|
||||
data["rationale"] = f"Godkjent. I drift realiseres erfaringsvis ~57% ({swapped_marker})."
|
||||
example_path.write_text(json.dumps(data), encoding="utf-8")
|
||||
|
||||
monkeypatch.setenv("PORTFOLIO_SHARED_ROOT", str(copy_root))
|
||||
|
||||
example = persona.load_persona_example()
|
||||
assert example.marker == swapped_marker, "persona loader did not resolve via shared_root()"
|
||||
|
||||
bundle_dir = simulation._default_bundle_dir()
|
||||
assert bundle_dir == copy_root / "examples" / "bygg-energi-mikro", (
|
||||
"simulation default bundle did not resolve via shared_root()"
|
||||
)
|
||||
|
||||
result = await simulate_learning_loop(str(bundle_dir), str(tmp_path / "work"))
|
||||
assert result.marker == swapped_marker
|
||||
assert not result.marker_in_run_a_prompt, "swapped marker leaked into Run A (causality broken)"
|
||||
assert result.marker_in_run_b_prompt, "the copy's marker did not cross into Run B's prompt"
|
||||
Loading…
Add table
Add a link
Reference in a new issue