fix(explore): lukk KeyError:'navigator' i --explore --scripted-replies
_SCRIPTED_ROLES = ("proposer", "checker") var det ENESTE rollesettet
_load_scripted_replies validerte OG returnerte. explore()s tre ekstra
roller (manager/navigator/hypothesiser) ble filtrert bort selv når de
fantes i --scripted-replies-JSON-en, og manglet en av dem krasjet CLI-en
med en rå KeyError midt i eksplorasjonssløyfa i stedet for en ren
"run refused:"-linje (MAJOR-2, docs/2026-08-25-syretest-vei-ab.md).
_EXPLORATION_SCRIPTED_ROLES legges nå til kravet KUN når --explore er
satt, slik at en manglende rolle nektes VED NAVN ved døren, før
explore() kalles. En rein debattkjøring skal ikke måtte svare for
roller den aldri bruker.
Målt (ikke bare antatt): med alle fem roller scriptet fullfører CLI-en
uten krasj, men sløyfa er fortsatt delvis vakuøs (1 runde, 0 approaches)
som syretesten forutså — en konstant streng per rolle kan ikke svare
korrekt på magentic-managerens stadiespesifikke former.
1028 passed / 5 skipped (+3 nye tester, RØD→GRØNN). Golden
demo-transcript byte-uendret.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSHNrYvKxDR5uct1QoVZng
This commit is contained in:
parent
932ece345b
commit
444fea7e94
2 changed files with 172 additions and 7 deletions
|
|
@ -55,6 +55,9 @@ from portfolio_optimiser.datasource import (
|
|||
)
|
||||
from portfolio_optimiser.dimension import Dimension, admits, load_dimension
|
||||
from portfolio_optimiser.explore import (
|
||||
HYPOTHESISER_ROLE,
|
||||
MANAGER_ROLE,
|
||||
NAVIGATOR_ROLE,
|
||||
ExplorationContract,
|
||||
ExplorationResult,
|
||||
ExplorationTrace,
|
||||
|
|
@ -1530,6 +1533,12 @@ async def run_mandate_across_bundles(
|
|||
# caught at the door instead of mid-run.
|
||||
_SCRIPTED_ROLES = ("proposer", "checker")
|
||||
|
||||
# The THREE more roles ``explore()`` asks the same factory for (``explore.py:576``) — added to
|
||||
# ``_SCRIPTED_ROLES`` at the door only when ``--explore`` is in play (MAJOR-2,
|
||||
# docs/2026-08-25-syretest-vei-ab.md): a plain debate-only run must not be made to answer for
|
||||
# roles it never uses.
|
||||
_EXPLORATION_SCRIPTED_ROLES = (MANAGER_ROLE, NAVIGATOR_ROLE, HYPOTHESISER_ROLE)
|
||||
|
||||
# The honesty banner for the scripted door. It is a REQUIREMENT, not decoration (målbilde §1):
|
||||
# a scripted run that reads like a model run is worse than having no offline mode at all, so this
|
||||
# prints on every scripted invocation and mirrors ``simulation.main``'s banner.
|
||||
|
|
@ -1544,10 +1553,13 @@ _SCRIPTED_BANNER = (
|
|||
)
|
||||
|
||||
|
||||
def _load_scripted_replies(path: str) -> dict[str, str]:
|
||||
"""Load the caller's scripted answers, fail-fast. Every role the debate can ask for must be
|
||||
def _load_scripted_replies(
|
||||
path: str, required_roles: Sequence[str] = _SCRIPTED_ROLES
|
||||
) -> dict[str, str]:
|
||||
"""Load the caller's scripted answers, fail-fast. Every role ``required_roles`` names must be
|
||||
present AND a string: a missing role would otherwise surface as a ``KeyError`` deep inside
|
||||
``scripted_factory``'s lookup, mid-run, long after the run appeared to start cleanly."""
|
||||
``scripted_factory``'s lookup, mid-run, long after the run appeared to start cleanly (MAJOR-2:
|
||||
measured for the three ``explore()`` adds on top of the debate's own two)."""
|
||||
try:
|
||||
raw = json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
except FileNotFoundError as exc:
|
||||
|
|
@ -1556,13 +1568,13 @@ def _load_scripted_replies(path: str) -> dict[str, str]:
|
|||
raise ValueError(f"--scripted-replies is not valid JSON ({path}): {exc}") from exc
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError(f"--scripted-replies must be a JSON object of role -> reply ({path})")
|
||||
missing = [r for r in _SCRIPTED_ROLES if not isinstance(raw.get(r), str)]
|
||||
missing = [r for r in required_roles if not isinstance(raw.get(r), str)]
|
||||
if missing:
|
||||
raise ValueError(
|
||||
f"--scripted-replies needs a string reply for each of {', '.join(_SCRIPTED_ROLES)}; "
|
||||
f"--scripted-replies needs a string reply for each of {', '.join(required_roles)}; "
|
||||
f"missing or non-string: {', '.join(missing)} ({path})"
|
||||
)
|
||||
return {role: raw[role] for role in _SCRIPTED_ROLES}
|
||||
return {role: raw[role] for role in required_roles}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
|
|
@ -2032,8 +2044,14 @@ def main(argv: list[str] | None = None) -> int:
|
|||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
# ``--explore`` asks the same factory for three roles the debate never uses (MAJOR-2) — the
|
||||
# door must know about them BEFORE loading the file, or a missing one crashes deep inside
|
||||
# ``explore()`` instead of being refused here, at the door, by name.
|
||||
required_scripted_roles: Sequence[str] = _SCRIPTED_ROLES
|
||||
if args.explore is not None:
|
||||
required_scripted_roles = _SCRIPTED_ROLES + _EXPLORATION_SCRIPTED_ROLES
|
||||
try:
|
||||
replies = _load_scripted_replies(args.scripted_replies)
|
||||
replies = _load_scripted_replies(args.scripted_replies, required_scripted_roles)
|
||||
except (OSError, ValueError) as exc:
|
||||
print(f"run refused: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue