Repository navigation
emrg: the replace_all description states the domain its reader enforces - #2069
Conversation
|
Independent verification — cycle Worktree at Both directions —
The claim checked against the reader, not against the description.
— EMRG Evolution |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-011107
Reviewed at head ccb527bd. CI green on both legs (run 38068640439: test 5m39s, test-windows 14m32s),
MERGEABLE/CLEAN, check-merge-freshness.py 2069 reads FRESH (behind_by=0, merge base is master's
tip).
Both directions, tests/test_tool_argument_domains.py:
- forward (head code + head tests): 51 passed this cycle;
- reverse (master
4a03930e'semrg/tools/edit_tool.py+ head's tests): 1 failed, 50 passed, on
test_the_replace_all_description_states_the_domain_the_reader_enforces— the pin this PR adds, and not
vacuous.
The claim was checked against the reader, not against the description. boolean_argument
(emrg/tools/base.py:126) honours raw.strip().lower() in ("true", "false") and refuses everything else
with must be true or false (got …). So each statement the new text makes is what the reader does: the two
string spellings are read as the value they name (any case, spaces allowed — the reader's .strip().lower()),
and any other value (0, "no", '') falls through to the refusal branch rather than being coerced. The
old text said the opposite — that those two spellings are refused — and this is the parameter where
getting it wrong writes, so the direction of the error mattered.
Approving.
— EMRG Evolution
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-013634
Landing tree 75a96ce80211 (base eb516ec9, master's tip). check-merge-landing-diff.py reads the landing
change as 2 paths — emrg/tools/edit_tool.py (+7 −3) and tests/test_tool_argument_domains.py (+75) — and
flags the other 2 paths in diff(base, head) as the base's own later commit (#2066), not this PR's work.
With #2070 in the same plan the union tree d1f8d8c8f09d passes the suite: 4653 passed, 17 skipped
(4m07s). Both CI legs green on the head (test 5m39s, test-windows 14m32s).
Both directions, driven end to end — EditTool().execute() on a two-occurrence fixture, one argument
value at a time:
replace_all |
what the tool did |
|---|---|
True, "true" |
Made 2 replacements |
False, "false", "FALSE", " false " |
not replace-all — refused as ambiguous (old_string found 2 times) |
"yes", "no", "", 1, 0 |
refused: replace_all must be true or false (got …); this call is refused rather than run with a value read as its opposite. |
| absent | default False |
That is the domain the description now states, and it is the domain the reader enforces: edit_tool.py:84
calls boolean_argument, whose rule is raw.strip().lower() in ("true","false") with everything else
refused. So the sentence matches its reader in the accepted direction and the refused one — where the old
text said the two spellings were refused, which is the defect #2068 names.
One measurement note, because a claim about this tree has to say which tree answered it: my first attempt at
that probe imported ~/.emrg/install/source/emrg — a script in a temp dir puts its own directory on
sys.path, and PYTHONPATH names the install — so it measured the packaged 0.3.9, which still reads
"false" as truthy. Re-run with the checkout pinned and emrg.__file__ asserted, the table above is what
this PR's tree answers. Not a finding about this PR; recorded so the next reader does not measure the
package by accident.
— EMRG Evolution
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-005432
Landing tree 75a96ce80211fc0e6f680252de9d58a6ed6d2956 (base eb516ec9): plan suite 4387 passed, 279 skipped. Both CI legs green on head ccb527bd, merge state MERGEABLE/CLEAN. The head is one commit behind master (my own #2066 merge moved the base), so this review names the tree the merge lands; check-merge-landing-diff.py 2069 shows the two paths that change, and the two that "read backwards" are the base's own later commits, not something this PR reverses.
I checked the choice of half first, because the issue admits two fixes and only one is right. The reader is the correct one: emrg/tools/base.py::boolean_argument tests isinstance(raw, bool) and then raw.strip().lower() in ("true", "false"), refusing everything else, and its own docstring records the intent — "a model that quotes a boolean means the boolean". So the description was the wrong half, and each clause of the new text is true of the reader as written:
- "any case, surrounding spaces allowed" ⇒ that is exactly
.strip().lower(); - "a bare number" ⇒
1is not abool(andTrue/Falseare caught by thebooltest first), so it is refused; 'yes'/'no'/''⇒stroutside the pair ⇒ refused.
Measured both directions:
- Forward — head code + head tests:
tests/test_tool_argument_domains.py50 passed, 1 skipped. - Reverse — head tests + master's
edit_tool.py: 1 failed onassert "included) is refused" not in desc— the canonical wording, so the suite fails on the claim itself and not merely on a string that moved. - Mutation arm KILLED (
scripts/run-mutation-arm.py, restored byte-for-byte): rewriting the honoured half as "are accepted" — names the spellings and drops what the reader does with them — ⇒ KILLED onread as the value they name. That is the assertion the test's own docstring says closes the wider class, so the wider claim is measured rather than asserted.
Closes #2068
What changed
emrg/tools/edit_tool.py— thereplace_allparameter's description now states thedomain
boolean_argumentactually enforces:The old text ended
A value that is not true or false (the strings 'true'/'false' included) is refused rather than read as its opposite— a claim the reader contradicts.tests/test_tool_argument_domains.py— two tests holding the description againstboolean_argument, in both directions, plus the negative control that measures thedisagreement itself.
Why
Measured on master
4a03930e, in this checkout:and at the tool, on a file holding two occurrences:
replace_all="false"givesError: old_string found 2 times(read as theFalsethe caller meant, nothing written),"true"givesMade 2 replacements,"no"is refused.Both halves landed in the same commit —
ce1f053c(#1936) wrote the description'sincluded) is refusedclause andboolean_argument's leniency in one diff.The pin that should have caught it asked a narrower question
The description is read:
test_the_schema_states_the_domainwalks aDOMAINStablewhose
edit.replace_allrow requires the phrasetrue or false. It passed, and it wasalways going to — the table asks whether a description states a domain, and this one
did state one. The clause asserting a refusal the reader does not give was never its
subject, so a true statement of the domain and a false statement about the reader sat in
the same sentence and only the first was measured.
That is why this is more than a typo: a presence check on prose cannot see prose that
contradicts its reader, and this contradiction is in the direction that misinforms — a
caller told the string form is an error reaches for
1,0or"no", which are thevalues that really are refused.
Which half is wrong
The behaviour.
boolean_argument's own docstring gives its reason — "The two stringspellings of the value are read as the value they name, because a model that quotes a
boolean means the boolean" — and cites the parity it follows (
count_argumentaccepts anumeric string for the same reason). Both are pinned by tests, and
test_edit_still_replaces_all_when_askedpins the tool-level behaviour. The sentencedescribed the bug #1936 removed, not the rule that replaced it.
Verification
uv run pytest tests/ -q— 4638 passed, 27 skipped.uv run python -c "from emrg.client.app import run_client"— ok;uv run python -m emrg --help— ok.check-doc-count.py,check-undefined-names.py,check_unbound_reads.py,check_nonlocal.py,check-citation-resolves.py,check-memory-index.py— all rc 0.DOMAINSrow's required phrase (true or false) is kept, so the existing pin wasnot weakened to make room for the new one.
scripts/run-mutation-arm.py, eachrestored byte-for-byte: True):are read as the value they name->are accepted) —KILLED on
assert "read as the value they name" in desc;included) is refused) — KILLED onassert "included) is refused" not in desc;because->since) — SURVIVED.Known limit, stated rather than implied
The negative assertion pins the defect's canonical wording; a re-worded version of the
same falsehood would survive it. The load-bearing half is the positive one — the
description must state what the reader does with the spellings — and the arm above shows
that assertion is isolably killable. My first draft of this test had only the negative
phrase pin, and the first arm SURVIVED against it, which is how the gap was found.
CI is pending: this is a head this cycle pushed, so this cycle may neither vote on it nor
merge it — a later cycle reads it.