Say which run compare chose when more than one matched - #58
Conversation
`readout` refuses at exit 2 when several runs match an arm and none was named (deepskandpal#13). `compare` silently took the newest, which carries the same reshaping risk: with a warm cache a re-run is free, so an arm accumulates runs quickly and nobody counts them. Refusing here would be wrong. `compare` sits outside gate two -- it is the exploratory command, and re-running an arm is exactly what you should be doing there. So this is a disclosure, not a refusal: exit code stays 0, and the warning names the run that was used, how many matched, and how many were not compared. The general principle, which the comment states where the next person will read it: inside a gate ambiguity is a refusal; outside one it is a disclosure. Silence is neither. Prose goes to stderr, so it costs an agent nothing -- agents read stdout, and stdout stays one pure JSON document. A test asserts that by parsing the whole of stdout and checking the warning's text is not in it, because "we write to stderr" is the kind of claim that quietly stops being true. Resolution goes through runs.for_experiment() rather than runs.latest(), per deepskandpal#12. latest() is a thin wrapper that returns found[0] and discards the count, and the count is the entire disclosure -- so calling it and then counting separately would have been the second resolution path the issue warns about. Four tests: the warning fires when two runs match, stdout stays pure JSON, an unambiguous compare stays silent, and naming --variant silences it -- that last one because naming the run is the remedy the message suggests, and a remedy that does not work is worse than no message.
|
Taking this one for #25. Two PRs arrived for the same issue, this one and #56, and that duplicate is our process failure rather than anyone's carelessness. On the code: treating One thing to know before you wait on a review. CI has not run on any of your three PRs. Workflows on fork PRs from first-time contributors are held pending approval, so #57, #58 and #56 are all sitting without checks. That is on us to release, not something wrong with your branches. |
|
Reviewed and accepted for #25. All five acceptance criteria are met: I ran the full The note in the code about One mechanical thing before merge: |
#57 and #67 landed on origin while #28, #31 and #54 were being merged locally. tests/test_flow.py conflicted: #28 added a per-criterion attribution test and #58 added four ambiguity-disclosure tests, and git tangled them because both open with identical fixture setup. Resolved as the union of both, reconstructed from each side's full version rather than hand-patched, so no assertion was silently dropped in the middle of a hunk.
Closes #25
comparenow names the run it chose when more than one matched. Exit code stays 0 — this is the disclosure side of the principle the issue states, not a refusal.The message
On stderr, via the existing
say(). It carries the three things a person needs: which run was used, how many matched, and what to do about it.Why a disclosure and not a refusal
readoutrefuses (#13) because it is gate two.compareis the exploratory command, and refusing there would break the ordinary use where re-running an arm is exactly what you want to be doing. The comment in the code states the rule so the next person does not have to re-derive it:for_experiment(), notlatest()— and why that matteredPer #12. The subtle part:
latest()is already a thin wrapper overfor_experiment()that returnsfound[0]and throws the count away — and the count is the entire disclosure. Keepinglatest()and counting separately would have created exactly the second resolution path the issue warns about, where the next version of this bug gets written. So the call site resolves once and reads both the choice and the count off the same list.Behaviour on the existing paths is unchanged: same run chosen (newest first, ids sort chronologically), same
no variant run to compareerror when there is nothing to compare or the newest is the baseline.Tests — all four AC lines
test_ambiguous_compare_names_the_run_it_chosevar-aandvar-bpresent,var-bis chosen and stderr names it, the count, and--varianttest_ambiguous_compare_keeps_stdout_pure_jsonjson.loads(result.out)parses the whole of stdout, and the warning text is not in ittest_unambiguous_compare_stays_silenttest_named_variant_is_never_ambiguous--variantsilences itThat second one is deliberately phrased as "parse all of stdout and assert the prose is absent" rather than "check it starts with
{" — "we write to stderr" is the kind of claim that quietly stops being true, and the contract has no--formatflag for this to become.The fourth exists because naming the run is the remedy the message suggests, and a remedy that does not work is worse than no message at all.
Exit code is asserted
Exit.OKin every one.Verification
Collected tests go 183 → 187 (+4). The single failure is
test_docs_in_sync.py::test_verify_script_is_executable, which fails identically on a cleanmainhere — Git for Windows does not preserve the executable bit onscripts/verify.sh. Unrelated to this branch; green on Linux CI.Held
area:cli/area:workspaceonly; #32 is onarea:judgein a separate branch, so no lane collision.