"""Loader for the shared expert-reviewer persona artifact (målbilde §8). The persona lives in ``shared/skills/expert-reviewer/`` as a framework-neutral Agent Skill (SKILL.md + a canonical example verdict). ``shared/`` stays pure DATA — this loader is the MAF-side reader of it; the Claude-SDK sibling reads the same JSON with its own loader. This is what de-stubs the offline simulation: its persona judgement is sourced from the artifact instead of a hardcoded literal, so the shared persona is genuinely consumed (and cannot rot silently). Fail-fast on purpose: the example is REQUIRED input (contrast the tolerant async verdict inbox, ``verdicts.load_verdicts_from_dir``). A missing or malformed file raises rather than degrading — a broken shared artifact must surface, not pass silently. """ from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path 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) class PersonaExample: """The expert-reviewer persona's canonical example verdict: the judgement the persona renders for the reference measure. ``marker`` is a substring of ``rationale`` (the realization-rate payload the simulation traces across runs).""" decision: str rationale: str marker: str 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). 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"], marker=data["marker"], ) # --- The falsification-reviewer skill (Step 12, D4) ---------------------------------------------- # A SECOND shared Agent Skill, read the same way and for the same reason: it is authored in commons # and arrives by ``git subtree pull``, so a loader that consumes it is what keeps it from rotting # silently. Same call-time seam, same fail-fast discipline as the persona example above. _FALSIFICATION_EXAMPLE_PATH: Path | None = None _FALSIFICATION_SUBPATH = ( Path("skills") / "falsification-reviewer" / "references" / "example-evidence.json" ) def _falsification_example_path() -> Path: """The worked example's location: the test seam if set, else resolved under ``shared_root()``.""" if _FALSIFICATION_EXAMPLE_PATH is not None: return _FALSIFICATION_EXAMPLE_PATH return shared_root() / _FALSIFICATION_SUBPATH @dataclass(frozen=True) class FalsificationConcept: """One concept the worked example judged, with what the readers should derive from its bytes. ``frontmatter_verbatim`` is AUTHORITATIVE and is the only field a consumer should materialise from. The example also ships a line-oriented ``frontmatter`` projection, which is informative only: by construction it cannot carry a block form, so materialising from it would turn the unreadable case into an absent one — the tolerant read that widens the answer. """ concept_id: str #: The document's frontmatter block exactly as written, newlines and indentation included. frontmatter_verbatim: str state: str reason: str | None items_seen: int trust_tier: str adjudication: str #: Whether the verdict was allowed to rest on this concept. relied_on: bool @dataclass(frozen=True) class FalsificationExample: """The falsification reviewer's worked example: one claim, the concepts consulted, the verdict. ``judgement`` is ``undecided`` in the shipped example, and deliberately not ``survived``: the one concept that could have carried a refutation was unreadable, so the claim was never actually attacked. Returning ``survived`` there would convert a gap in the knowledge base into support for the claim — the inversion this role exists to prevent. """ claim: str judgement: str #: Who refuted the claim, or ``None`` when nobody did. A refutation that names no refuter is #: not a refutation (the ``verified``-without-an-actor rule, one layer up). refuter: str | None concepts: tuple[FalsificationConcept, ...] def load_falsification_example() -> FalsificationExample: """Read the falsification skill's worked example. Fail-fast: a missing file raises ``FileNotFoundError`` and a missing key raises ``KeyError`` (required input, contrast the tolerant Step-7 verdict inbox). Resolves the path at CALL time.""" data = json.loads(_falsification_example_path().read_text(encoding="utf-8")) return FalsificationExample( claim=data["claim"], judgement=data["judgement"], refuter=data["refuter"], concepts=tuple( FalsificationConcept( concept_id=c["concept_id"], frontmatter_verbatim=c["frontmatter_verbatim"], state=c["evidence"]["state"], reason=c["evidence"]["reason"], items_seen=c["evidence"]["items_seen"], trust_tier=c["derived"]["trust_tier"], adjudication=c["derived"]["adjudication"], relied_on=c["relied_on"], ) for c in data["concepts"] ), )