[skip-docs] — the invariant row for this plan lands in Step 13, after the mutations. Amendment 2 in the same commit: the framework-neutrality sweep covered only expert-reviewer, so the skill that arrived by subtree pull had no framework guard anywhere. GUARDED_SKILL_DIRS names both, and a fail-closed coverage arm turns red when a future pull brings a third skill that is not listed. Co-Authored-By: Claude <claude-opus-5>
140 lines
5.9 KiB
Python
140 lines
5.9 KiB
Python
"""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"]
|
|
),
|
|
)
|