fix(s31): close 1 review BLOCKER — refuse --semantic-retrieval when it cannot take effect
This commit is contained in:
parent
fc69285f2c
commit
8e9f6603d7
2 changed files with 98 additions and 2 deletions
|
|
@ -859,6 +859,33 @@ def main(argv: list[str] | None = None) -> int:
|
|||
)
|
||||
return 1
|
||||
|
||||
# --semantic-retrieval is refused, never silently ignored (the repo's flag contract). In
|
||||
# single-project mode it can only do observable work with BOTH of these: the Step-1 fold is
|
||||
# gated on ``bundle_dir``, and ``--verdict-dir`` is the only route by which ``main()`` can hand
|
||||
# ``run_project`` a non-empty store (``main()`` never passes ``store=``, and ``run_project``
|
||||
# never seeds one). Without them the flag would rank nothing that reaches a prompt, and
|
||||
# ``RunResult.retrieved`` never leaves the process — ``main()`` prints one outcome line only.
|
||||
#
|
||||
# DELIBERATELY STATIC. There is no runtime "refuse if the store ends up empty" check: a
|
||||
# missing, empty or partially-skipped inbox is the Steg-7 tolerant-load contract, so refusing
|
||||
# there would fire on a legitimate first run. The refusal is therefore necessary, not
|
||||
# sufficient — it catches the configuration that CANNOT work, not every run that finds nothing.
|
||||
#
|
||||
# main() only. As a library API, ``run_project(semantic_retrieval=True, store=…)`` with a
|
||||
# caller-supplied store stays legitimate — that is the path the tests drive. Portfolio mode is
|
||||
# unaffected: ``run_portfolio`` always resolves a store and populates it by cross-project capture.
|
||||
if args.semantic_retrieval:
|
||||
required = {"--bundle-dir": args.bundle_dir, "--verdict-dir": args.verdict_dir}
|
||||
missing = [name for name, value in required.items() if not value]
|
||||
if missing:
|
||||
print(
|
||||
f"run refused: --semantic-retrieval requires {' and '.join(missing)} in "
|
||||
"single-project mode (the Step-1 fold is bundle-path-only, and --verdict-dir is "
|
||||
"the only route to a non-empty store)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
if args.live_dry_run:
|
||||
# S4.2 drill (comparison protocol §4 pkt 2/3): walk the offline path, STOP before the first
|
||||
# model call, print the run-config. A misconfigured profile (e.g. AZURE with a
|
||||
|
|
|
|||
|
|
@ -518,8 +518,53 @@ def _generation_prompts(sink: list[str]) -> list[str]:
|
|||
return [p for p in sink if "SavingsProposal" in p]
|
||||
|
||||
|
||||
def test_semantic_retrieval_flag_parses_offline(capsys) -> None:
|
||||
"""(a) the flag is accepted in single-project mode and the run still stops offline."""
|
||||
def test_semantic_retrieval_flag_parses_offline(tmp_path, capsys) -> None:
|
||||
"""(a) the flag is accepted in single-project mode and the run still stops offline.
|
||||
|
||||
Carries ``--verdict-dir`` because the flag is now refused without it (Step 3) — the inbox path
|
||||
need not exist, since the Steg-7 load is deliberately tolerant of a missing folder."""
|
||||
rc = run.main(
|
||||
[
|
||||
_PID,
|
||||
"--docs-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--bundle-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--verdict-dir",
|
||||
str(tmp_path / "inbox"),
|
||||
"--semantic-retrieval",
|
||||
"--live-dry-run",
|
||||
]
|
||||
)
|
||||
assert rc == 0
|
||||
assert "LIVE-DRY-RUN OK" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_semantic_retrieval_without_bundle_dir_is_refused(tmp_path, capsys) -> None:
|
||||
"""The flag is REFUSED, never ignored: without ``--bundle-dir`` the Step-1 fold never runs, so
|
||||
the opt-in could not affect anything the run emits. The refusal names the missing flag verbatim
|
||||
(the repo's offender-naming idiom) and carries no traceback."""
|
||||
rc = run.main(
|
||||
[
|
||||
_PID,
|
||||
"--docs-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--verdict-dir",
|
||||
str(tmp_path / "inbox"),
|
||||
"--semantic-retrieval",
|
||||
]
|
||||
)
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 1
|
||||
assert "run refused:" in err
|
||||
assert "--bundle-dir" in err
|
||||
assert "Traceback" not in err
|
||||
|
||||
|
||||
def test_semantic_retrieval_without_verdict_dir_is_refused(capsys) -> None:
|
||||
"""Same contract on the other half: ``--verdict-dir`` is the ONLY route by which ``main()`` can
|
||||
hand ``run_project`` a non-empty store, so without it the fold's ``store.verdicts`` guard
|
||||
short-circuits and the flag is inert."""
|
||||
rc = run.main(
|
||||
[
|
||||
_PID,
|
||||
|
|
@ -528,11 +573,35 @@ def test_semantic_retrieval_flag_parses_offline(capsys) -> None:
|
|||
"--bundle-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--semantic-retrieval",
|
||||
]
|
||||
)
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 1
|
||||
assert "run refused:" in err
|
||||
assert "--verdict-dir" in err
|
||||
assert "Traceback" not in err
|
||||
|
||||
|
||||
def test_semantic_retrieval_with_both_dirs_is_not_refused(tmp_path, capsys) -> None:
|
||||
"""The refusal must be exactly as wide as the condition that makes the flag inert — with both
|
||||
flags present the run proceeds (here to the offline dry-run return), not to a refusal."""
|
||||
rc = run.main(
|
||||
[
|
||||
_PID,
|
||||
"--docs-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--bundle-dir",
|
||||
str(BUNDLE_DIR),
|
||||
"--verdict-dir",
|
||||
str(tmp_path / "inbox"),
|
||||
"--semantic-retrieval",
|
||||
"--live-dry-run",
|
||||
]
|
||||
)
|
||||
out = capsys.readouterr()
|
||||
assert rc == 0
|
||||
assert "LIVE-DRY-RUN OK" in capsys.readouterr().out
|
||||
assert "LIVE-DRY-RUN OK" in out.out
|
||||
assert "--semantic-retrieval" not in out.err
|
||||
|
||||
|
||||
def test_semantic_retrieval_is_not_refused_in_portfolio_mode(capsys) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue