feat(4a): wheelen bærer shared/ som pakkede data — arbeidstreet er overstyringen
README:24 sa det rett ut: shared/ ble lest fra arbeidstreet ved kjøretid, og
derfor kunne repoet verken publiseres som wheel eller kjøre i container. Målt
før endringen: 1.0.0-wheelen bar 58 filer, null under shared/.
Endringen er én søm + én pakkelinje:
- hatchling force-include speiler shared/ byte-identisk til
portfolio_optimiser/_shared/ (wheel 122 filer, 64 under _shared/; sdist
bærer treet, målt via uv build sdist→wheel)
- shared_root() løser ved kall-tid: PORTFOLIO_SHARED_ROOT → arbeidstreets
shared/ når det finnes (en checkout er autoritativ — det holder pull-only-
subtree-kontrakten og goldenene urørt) → pakket kopi
Iron Law fulgt: tests/test_shared_packaged_data_loadbearing.py skrevet FØRST,
alle tre røde mot dagens kode (ordnings-testen felt av sin egen kontroll på at
pakket kopi finnes). Deretter fiks, deretter MÅLT mutasjon mot hele suiten:
- detach fallbacken → 1 rød (resolusjons-testen)
- detach force-include → 3 røde
- snu rekkefølgen (pakket før arbeidstre) → 1 rød (ordnings-testen, som var
grønn før fiksen — flip-mutasjonen er beviset på at den diskriminerer)
Kontroll grønn: 813 passed / 4 skipped (baseline 810/4 målt på 142bfa9 samme
økt). Goldenene byte-uendret før og etter (shasum -c på demo-transkript +
begge nav-goldens). shared/ selv er urørt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hfm6sWTk17Cbh6ZHYhvCu
This commit is contained in:
parent
142bfa9666
commit
f5ec8c84f6
5 changed files with 238 additions and 13 deletions
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -14,7 +14,7 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
- Modell-valg som konfig (modell-map rolle→Foundry-deployment), ikke spredt i kode.
|
- Modell-valg som konfig (modell-map rolle→Foundry-deployment), ikke spredt i kode.
|
||||||
- Metode kodifiseres som **Agent Skill** (`agentskills.io`: `SKILL.md` + `scripts/` + `references/`).
|
- Metode kodifiseres som **Agent Skill** (`agentskills.io`: `SKILL.md` + `scripts/` + `references/`).
|
||||||
- Datatilgang: in-process `FunctionTool` er default-sømmen i kjørestien. **MCP er wiret som opt-in i kjørestien** (`mcp_tools.py` + `--mcp-config`, Trekk B 2026-08-05): konkrete eksterne servere blir verktøy agentene kan kalle UNDER debatten. Uten konfig gjøres null nettverkskall og verktøylista er uendret. Tre regler er load-bearing: **allowlist er påkrevd** (tom liste ville latt motparten bestemme hva agentene får kalle), **hver server og hvert tillatte verktøy navngis i kunngjøringen før første kall** (også uten `--mandate` — ingen udeklarert egress), og `--live-dry-run` åpner **ingenting**. Egen søm fra `ingest_mcp.py` (kildedokumenter FØR kjøring, null-argument-tools) — samme protokoll, ulik jobb. `build_mcp_server` (`datasource.py`) er fortsatt kun demo. Data-source-konfig JSON-Schema-validert, fail-fast.
|
- Datatilgang: in-process `FunctionTool` er default-sømmen i kjørestien. **MCP er wiret som opt-in i kjørestien** (`mcp_tools.py` + `--mcp-config`, Trekk B 2026-08-05): konkrete eksterne servere blir verktøy agentene kan kalle UNDER debatten. Uten konfig gjøres null nettverkskall og verktøylista er uendret. Tre regler er load-bearing: **allowlist er påkrevd** (tom liste ville latt motparten bestemme hva agentene får kalle), **hver server og hvert tillatte verktøy navngis i kunngjøringen før første kall** (også uten `--mandate` — ingen udeklarert egress), og `--live-dry-run` åpner **ingenting**. Egen søm fra `ingest_mcp.py` (kildedokumenter FØR kjøring, null-argument-tools) — samme protokoll, ulik jobb. `build_mcp_server` (`datasource.py`) er fortsatt kun demo. Data-source-konfig JSON-Schema-validert, fail-fast.
|
||||||
- `shared/` er en **git subtree** av [`portfolio-optimiser-commons`](https://git.fromaitochitta.com/open/portfolio-optimiser-commons) (source of truth, R1 realisert 2026-07-03; publisert i `open/` 2026-08-04 — `commons`-remoten peker fortsatt på `ktg/` og virker uendret). Synk er **pull-only**: endringer committes i commons og hentes med `git subtree pull --prefix=shared commons main --squash`. ALDRI `git subtree push` fra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd 2026-07-03). Se `shared/README.md`.
|
- `shared/` er en **git subtree** av [`portfolio-optimiser-commons`](https://git.fromaitochitta.com/open/portfolio-optimiser-commons) (source of truth, R1 realisert 2026-07-03; publisert i `open/` 2026-08-04 — `commons`-remoten peker fortsatt på `ktg/` og virker uendret). Synk er **pull-only**: endringer committes i commons og hentes med `git subtree pull --prefix=shared commons main --squash`. ALDRI `git subtree push` fra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd 2026-07-03). Se `shared/README.md`. Wheelen bærer treet som pakkede data siden Fase 4a — se invarianten under.
|
||||||
|
|
||||||
## Kommandoer
|
## Kommandoer
|
||||||
- Sync: `uv sync` — installerer to konsoll-kommandoer: `portfolio-optimiser` (CLI, `run:main`) og
|
- Sync: `uv sync` — installerer to konsoll-kommandoer: `portfolio-optimiser` (CLI, `run:main`) og
|
||||||
|
|
@ -125,6 +125,20 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
avvisningstestene stå grønne — beslutningen så dekket ut uten å være testet. Båndet der tieren
|
avvisningstestene stå grønne — beslutningen så dekket ut uten å være testet. Båndet der tieren
|
||||||
faktisk avgjør er høy-entropi-innhold (`quarantine_review` vs `warn`), og testen ble skrevet mot
|
faktisk avgjør er høy-entropi-innhold (`quarantine_review` vs `warn`), og testen ble skrevet mot
|
||||||
nøyaktig det.
|
nøyaktig det.
|
||||||
|
- **`shared/` leses som PAKKEDE DATA, med arbeidstreet som overstyring (Fase 4a):** wheelen bærer
|
||||||
|
en byte-identisk speiling av hele `shared/`-treet under `portfolio_optimiser/_shared/`
|
||||||
|
(hatchling force-include i `pyproject.toml`), og `shared_root()` løser ved KALL-tid i fast
|
||||||
|
rekkefølge: `PORTFOLIO_SHARED_ROOT` → arbeidstreets `shared/` når det finnes → pakket kopi.
|
||||||
|
**Arbeidstreet er autoritativt i en checkout** — det er dét som holder pull-only-subtree-
|
||||||
|
kontrakten og de byte-eksakte goldenene urørt (målt: goldens shasum-identiske før/etter, og
|
||||||
|
`shared/` selv urørt). Den pakkede kopien er dét som gjør wheel og container mulig uten klone
|
||||||
|
(målt før: 1.0.0-wheelen bar 58 filer, null under `shared/`; etter: 122, hvorav 64 under
|
||||||
|
`_shared/`, og sdist→wheel-kjeden bærer treet). Speilingen er ALDRI en redigert derivat —
|
||||||
|
byte-identitet er egenskapen som lar commons-goldenene fortsatt gate den pakkede kopien.
|
||||||
|
Load-bearing MÅLT (`tests/test_shared_packaged_data_loadbearing.py`, ekte `uv build` i
|
||||||
|
fixturen — pakkekonfigen er selv en søm), tre mutasjoner alle røde mot hele suiten: detach
|
||||||
|
fallbacken (1 rød) · detach force-include (3 røde) · snu rekkefølgen (1 rød — ordnings-testen
|
||||||
|
var grønn før fiksen; dens kontroll på at pakket kopi FINNES er det som gjør flippen målbar).
|
||||||
- **Stoppkriterier + budsjett-tak påkrevd ved oppstart** (fail-fast, aldri ubegrenset loop).
|
- **Stoppkriterier + budsjett-tak påkrevd ved oppstart** (fail-fast, aldri ubegrenset loop).
|
||||||
- **Group Chat maker-checker** som debatt-default (IKKE Magentic, som er eksperimentell).
|
- **Group Chat maker-checker** som debatt-default (IKKE Magentic, som er eksperimentell).
|
||||||
- **To falsifiserere, samme kandidat (Steg 3/4, målbilde §2/§6):** den deterministiske validatoren
|
- **To falsifiserere, samme kandidat (Steg 3/4, målbilde §2/§6):** den deterministiske validatoren
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,11 @@ cd portfolio-optimiser
|
||||||
uv sync
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
Clone rather than install into an existing environment: the shared spec, the persona skill and the
|
The clone is the documented path because the walkthrough below points at files in the tree. It is
|
||||||
example bundles under [`shared/`](shared/README.md) are read from the working tree at run time.
|
no longer a technical requirement: a built wheel carries the shared spec, the persona skill and the
|
||||||
|
example bundles under [`shared/`](shared/README.md) as packaged data, and the resolver prefers a
|
||||||
|
working tree when one is present (`PORTFOLIO_SHARED_ROOT` overrides both) — so an installed
|
||||||
|
distribution works without a checkout.
|
||||||
|
|
||||||
`uv sync` installs two commands: `portfolio-optimiser` (the CLI) and `portfolio-optimiser-demo`
|
`uv sync` installs two commands: `portfolio-optimiser` (the CLI) and `portfolio-optimiser-demo`
|
||||||
(the offline end-to-end proof). They are equivalent to the `python -m portfolio_optimiser.run` and
|
(the offline end-to-end proof). They are equivalent to the `python -m portfolio_optimiser.run` and
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,14 @@ build-backend = "hatchling.build"
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/portfolio_optimiser"]
|
packages = ["src/portfolio_optimiser"]
|
||||||
|
|
||||||
|
# Fase 4a: the wheel CARRIES shared/ (spec, persona skill, example bundles) as packaged data —
|
||||||
|
# a byte-identical mirror under portfolio_optimiser/_shared/, resolved by shared_root() only when
|
||||||
|
# no working tree is present (checkout wins; PORTFOLIO_SHARED_ROOT wins over both). This is what
|
||||||
|
# makes an installed wheel — and a container — work without a clone, while shared/ itself stays a
|
||||||
|
# pull-only subtree at the repo root. Pinned by tests/test_shared_packaged_data_loadbearing.py.
|
||||||
|
[tool.hatch.build.targets.wheel.force-include]
|
||||||
|
"shared" = "portfolio_optimiser/_shared"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
src = ["src", "tests", "spikes"]
|
src = ["src", "tests", "spikes"]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
"""Resolver for the location of the shared framework-neutral core (S3, R1-forberedelse).
|
"""Resolver for the location of the shared framework-neutral core (S3, R1-forberedelse; 4a pakket).
|
||||||
|
|
||||||
``shared/`` is today an in-repo directory, but is slated for extraction into its own commons repo
|
``shared/`` is a pull-only subtree of the commons repo at the repository root. Every MAF-side
|
||||||
(R1/S4). Every MAF-side runtime consumer (``persona``, ``simulation``) resolves its location through
|
runtime consumer (``persona``, ``simulation``) resolves its location through this ONE seam, so a
|
||||||
this ONE seam so the extraction is a re-point (env var), not a code change. Test fixtures pointing at
|
re-point is an env var, not a code change. Since Fase 4a the wheel also carries a byte-identical
|
||||||
the real in-repo bundle hardcode the path deliberately — they must not be redirected by a production
|
mirror of the tree as packaged data (``portfolio_optimiser/_shared/``, hatchling force-include in
|
||||||
env var. Pure stdlib — the shared core itself stays framework-free.
|
pyproject.toml), so an installed distribution — and a container built from it — works without a
|
||||||
|
checkout. Resolution order at CALL time: explicit env override → the working tree's ``shared/``
|
||||||
|
when present (a repo checkout stays authoritative, which is what keeps the subtree contract and
|
||||||
|
the byte-level goldens untouched) → the packaged copy. Test fixtures pointing at the real in-repo
|
||||||
|
bundle hardcode the path deliberately — they must not be redirected by a production env var. Pure
|
||||||
|
stdlib — the shared core itself stays framework-free.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -14,12 +19,20 @@ from pathlib import Path
|
||||||
|
|
||||||
ENV_VAR = "PORTFOLIO_SHARED_ROOT"
|
ENV_VAR = "PORTFOLIO_SHARED_ROOT"
|
||||||
|
|
||||||
_DEFAULT = Path(__file__).resolve().parents[2] / "shared"
|
_WORKTREE = Path(__file__).resolve().parents[2] / "shared"
|
||||||
|
_PACKAGED = Path(__file__).resolve().parent / "_shared"
|
||||||
|
|
||||||
|
|
||||||
def shared_root() -> Path:
|
def shared_root() -> Path:
|
||||||
"""Resolve the shared-core root at CALL time: ``PORTFOLIO_SHARED_ROOT`` if set (non-empty),
|
"""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
|
else the working tree's ``shared/`` when it exists (a checkout), else the packaged copy
|
||||||
extraction re-pointable without touching consumers."""
|
shipped inside the distribution. The call-time read is what keeps the override testable and
|
||||||
|
the ordering observable; when neither directory exists the packaged path is returned so the
|
||||||
|
consumer's own fail-fast names the installed distribution's gap, not a checkout that was
|
||||||
|
never there."""
|
||||||
override = os.environ.get(ENV_VAR)
|
override = os.environ.get(ENV_VAR)
|
||||||
return Path(override) if override else _DEFAULT
|
if override:
|
||||||
|
return Path(override)
|
||||||
|
if _WORKTREE.is_dir():
|
||||||
|
return _WORKTREE
|
||||||
|
return _PACKAGED
|
||||||
|
|
|
||||||
187
tests/test_shared_packaged_data_loadbearing.py
Normal file
187
tests/test_shared_packaged_data_loadbearing.py
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
"""Load-bearing tests for shared/ as packaged data with the working tree as override (Fase 4a).
|
||||||
|
|
||||||
|
README's install story is a clone because the shared spec, persona skill and example bundles under
|
||||||
|
``shared/`` were read from the WORKING TREE at run time — an installed wheel had no shared data at
|
||||||
|
all (measured 2026-08-13: the 1.0.0 wheel carried 58 files, zero of them under ``shared/``). Fase
|
||||||
|
4a packages the whole ``shared/`` tree into the wheel as data (``portfolio_optimiser/_shared/``,
|
||||||
|
hatchling force-include) and teaches ``shared_root()`` a call-time resolution order: env override →
|
||||||
|
working tree (when present) → packaged copy. The working tree stays authoritative in a checkout —
|
||||||
|
that is what keeps the pull-only subtree contract and the byte-level goldens untouched.
|
||||||
|
|
||||||
|
Three seams, each one measured by mutation:
|
||||||
|
|
||||||
|
- the wheel really CARRIES the tree, byte-identical to ``shared/`` (RED when the force-include is
|
||||||
|
dropped from pyproject — the packaged mirror silently disappears from every future wheel);
|
||||||
|
- an installed distribution RESOLVES to the packaged copy and a real consumer (the persona loader)
|
||||||
|
reads it (RED when the fallback in ``shared_root()`` is detached — the subprocess imports from
|
||||||
|
the unpacked wheel, never from the repo, so the working tree cannot mask the regression);
|
||||||
|
- the working tree still WINS when both copies exist (RED when the resolution order is flipped;
|
||||||
|
green both before and after the 4a change, so its discriminating power is proven by the flip
|
||||||
|
mutation, not by the fix itself — its control assert on the packaged copy is what made it red
|
||||||
|
pre-4a).
|
||||||
|
|
||||||
|
The wheel is built by the REAL build backend (``uv build``) once per session: the packaging config
|
||||||
|
is itself a seam under test, and nothing short of a genuine build can measure it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SHARED = REPO_ROOT / "shared"
|
||||||
|
PACKAGED_PREFIX = "portfolio_optimiser/_shared/"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def built_wheel(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||||
|
"""One genuine wheel build per test session. ``uv`` is the repo's documented toolchain (README
|
||||||
|
sends a fresh clone through ``uv sync``), so its absence is a broken environment — a hard
|
||||||
|
failure, never a skip: a skipped gate is no gate."""
|
||||||
|
out_dir = tmp_path_factory.mktemp("wheel-dist")
|
||||||
|
proc = subprocess.run(
|
||||||
|
["uv", "build", "--wheel", "--out-dir", str(out_dir)],
|
||||||
|
cwd=REPO_ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
assert proc.returncode == 0, f"uv build failed:\n{proc.stderr}"
|
||||||
|
wheels = sorted(out_dir.glob("*.whl"))
|
||||||
|
assert len(wheels) == 1, f"expected exactly one wheel, got {wheels}"
|
||||||
|
return wheels[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def installed_dist(built_wheel: Path, tmp_path: Path) -> Path:
|
||||||
|
"""A per-test unpacked copy of the wheel (an installed distribution is an unpacked wheel on
|
||||||
|
``sys.path``). Per test, NOT per session: the override test plants a ``shared/`` directory at
|
||||||
|
the working-tree position beside the copy, and a shared copy would leak that sibling into the
|
||||||
|
resolution test."""
|
||||||
|
site_dir = tmp_path / "installed"
|
||||||
|
with zipfile.ZipFile(built_wheel) as zf:
|
||||||
|
zf.extractall(site_dir)
|
||||||
|
return site_dir
|
||||||
|
|
||||||
|
|
||||||
|
def _child_env(site_dir: Path) -> dict[str, str]:
|
||||||
|
"""Subprocess env: the unpacked wheel FIRST on ``sys.path`` (before the dev venv's editable
|
||||||
|
install), and the production override unset so the resolver's own ordering is what gets
|
||||||
|
measured."""
|
||||||
|
env = dict(os.environ)
|
||||||
|
env.pop("PORTFOLIO_SHARED_ROOT", None)
|
||||||
|
env["PYTHONPATH"] = str(site_dir)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def _run_child(script: str, args: list[str], site_dir: Path, cwd: Path) -> str:
|
||||||
|
cwd.mkdir(exist_ok=True)
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, "-c", script, *args],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
env=_child_env(site_dir),
|
||||||
|
cwd=cwd,
|
||||||
|
)
|
||||||
|
assert proc.returncode == 0, f"child failed:\n{proc.stderr}"
|
||||||
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_carries_shared_tree_byte_identical(built_wheel: Path) -> None:
|
||||||
|
"""Every file under ``shared/`` lands in the wheel under ``portfolio_optimiser/_shared/`` —
|
||||||
|
same file set, same bytes. Byte identity is the property that lets the commons-owned goldens
|
||||||
|
keep gating the packaged copy: the mirror IS the subtree, never an edited derivative."""
|
||||||
|
worktree_files = sorted(
|
||||||
|
p.relative_to(SHARED).as_posix() for p in SHARED.rglob("*") if p.is_file()
|
||||||
|
)
|
||||||
|
assert worktree_files, "control: shared/ in the working tree must not be empty"
|
||||||
|
|
||||||
|
with zipfile.ZipFile(built_wheel) as zf:
|
||||||
|
packaged = {
|
||||||
|
name[len(PACKAGED_PREFIX) :]: zf.read(name)
|
||||||
|
for name in zf.namelist()
|
||||||
|
if name.startswith(PACKAGED_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert sorted(packaged) == worktree_files, (
|
||||||
|
"the wheel's packaged copy does not mirror shared/ — is the force-include still in "
|
||||||
|
"pyproject.toml?"
|
||||||
|
)
|
||||||
|
drifted = [rel for rel in worktree_files if packaged[rel] != (SHARED / rel).read_bytes()]
|
||||||
|
assert not drifted, f"byte drift between shared/ and the packaged copy: {drifted}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_installed_distribution_resolves_the_packaged_copy(
|
||||||
|
installed_dist: Path, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""From an installed distribution (no working tree anywhere above the package), the resolver
|
||||||
|
yields the packaged copy and the persona loader — a real consumer, not just the resolver —
|
||||||
|
reads it. The child first proves it imported the unpacked wheel (never the repo's editable
|
||||||
|
install), so a green here cannot be the working tree answering in the wheel's name."""
|
||||||
|
script = (
|
||||||
|
"import sys\n"
|
||||||
|
"from pathlib import Path\n"
|
||||||
|
"import portfolio_optimiser\n"
|
||||||
|
"from portfolio_optimiser.persona import load_persona_example\n"
|
||||||
|
"from portfolio_optimiser.shared_root import shared_root\n"
|
||||||
|
"site_dir = Path(sys.argv[1]).resolve()\n"
|
||||||
|
"pkg_file = Path(portfolio_optimiser.__file__).resolve()\n"
|
||||||
|
"assert pkg_file == site_dir / 'portfolio_optimiser' / '__init__.py', (\n"
|
||||||
|
" f'control: child imported {pkg_file}, not the unpacked wheel'\n"
|
||||||
|
")\n"
|
||||||
|
"root = shared_root()\n"
|
||||||
|
"expected = site_dir / 'portfolio_optimiser' / '_shared'\n"
|
||||||
|
"assert root == expected, f'resolved {root}, expected the packaged copy {expected}'\n"
|
||||||
|
"assert (root / 'method-spec.md').is_file(), f'packaged spec missing under {root}'\n"
|
||||||
|
"example = load_persona_example()\n"
|
||||||
|
"assert example.marker, 'persona example unreadable from the packaged copy'\n"
|
||||||
|
"print('PACKAGED-OK')\n"
|
||||||
|
)
|
||||||
|
stdout = _run_child(script, [str(installed_dist)], installed_dist, tmp_path / "cwd")
|
||||||
|
assert "PACKAGED-OK" in stdout, "child never reached its final assert"
|
||||||
|
|
||||||
|
|
||||||
|
def test_working_tree_overrides_the_packaged_copy(installed_dist: Path, tmp_path: Path) -> None:
|
||||||
|
"""When a ``shared/`` directory exists at the working-tree position (two levels above the
|
||||||
|
package — the layout of a repo checkout, and of this test's planted sibling), it wins over the
|
||||||
|
packaged copy. The control assert that the packaged copy EXISTS is load-bearing: without it,
|
||||||
|
an ordering flipped to packaged-first would still return the working tree whenever the
|
||||||
|
packaged copy is missing, and the assert below could not tell the two orderings apart."""
|
||||||
|
worktree_shared = (
|
||||||
|
tmp_path / "shared"
|
||||||
|
) # parents[2] of installed/portfolio_optimiser/shared_root.py
|
||||||
|
worktree_shared.mkdir()
|
||||||
|
(worktree_shared / "method-spec.md").write_text("worktree override marker\n", encoding="utf-8")
|
||||||
|
|
||||||
|
script = (
|
||||||
|
"import sys\n"
|
||||||
|
"from pathlib import Path\n"
|
||||||
|
"import portfolio_optimiser\n"
|
||||||
|
"from portfolio_optimiser.shared_root import shared_root\n"
|
||||||
|
"site_dir = Path(sys.argv[1]).resolve()\n"
|
||||||
|
"worktree = Path(sys.argv[2]).resolve()\n"
|
||||||
|
"pkg_file = Path(portfolio_optimiser.__file__).resolve()\n"
|
||||||
|
"assert pkg_file == site_dir / 'portfolio_optimiser' / '__init__.py', (\n"
|
||||||
|
" f'control: child imported {pkg_file}, not the unpacked wheel'\n"
|
||||||
|
")\n"
|
||||||
|
"packaged = site_dir / 'portfolio_optimiser' / '_shared'\n"
|
||||||
|
"assert packaged.is_dir(), (\n"
|
||||||
|
" 'control: the packaged copy must exist, or the ordering below is unmeasurable'\n"
|
||||||
|
")\n"
|
||||||
|
"root = shared_root()\n"
|
||||||
|
"assert root == worktree, (\n"
|
||||||
|
" f'resolved {root}, expected the working-tree override {worktree}'\n"
|
||||||
|
")\n"
|
||||||
|
"print('WORKTREE-OK')\n"
|
||||||
|
)
|
||||||
|
stdout = _run_child(
|
||||||
|
script, [str(installed_dist), str(worktree_shared)], installed_dist, tmp_path / "cwd"
|
||||||
|
)
|
||||||
|
assert "WORKTREE-OK" in stdout, "child never reached its final assert"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue