Refuse to convert when the bytes do not identify the encoding - #45
Merged
Conversation
The audit's largest remaining risk category: 262 of 5,078 files where several encodings read the bytes and disagree about what they say. Single-byte code pages map 256 values independently, so a file valid in windows-1252 is equally valid in iso-8859-1 and nothing in the bytes decides. Detection answers anyway, and on short or ASCII-heavy input that answer is close to a guess - which EC then acted on, rewriting the file into one of several readings without saying so. EC now classifies each detection and refuses only where it must: Unambiguous one codec reads these bytes convert TextEquivalent several read them, all agree on text convert TextChanging several read them and disagree refuse The middle case matters as much as the last. A pure-ASCII file is ambiguous in label and identical in text; refusing it would protect nothing. The test is whether the candidates produce different Unicode, not whether more than one can decode. The candidate set comes from EC's supported encodings, deduplicated by code page, never from any corpus - the audit already demonstrated that deriving candidates from test data makes the answer depend on what the tests happened to contain. That list moved from MainForm to TextEncoding so the engine can reach it. The decision is made beside the detection that produced it and carried on the entry, so the conversion path reads a verdict rather than re-deriving one, and a caller who supplies the source encoding instead of detecting it is unaffected. There is nothing ambiguous about an answer somebody gave. Two mistakes on the way to the discriminator, both caught by the existing tests: Counting rival readings alone refused every UTF-8 file, because iso-8859-1 "reads" it too. A codec that cannot refuse anything is not offering an alternative. Fixed by requiring the detected codec to have no hold on the bytes before rivals count. Measuring that hold by bit flips alone refused UTF-16, which survives nearly any flip - most produce another valid character. Fixed by also deleting a byte, which tests alignment, the only structure a fixed-width encoding has. The measurement is deterministic: evenly-spaced probes rather than random ones, after an earlier version gave answers that moved with the seed on short files. Whether a conversion is refused should not depend on a random draw. The threshold sits just above zero because the separation is not a matter of degree: unconstrained single-byte pages reject exactly nothing, and every structured encoding measured on real text lands at 0.111 or above. The refusal names the encodings in conflict and the next step, rather than reporting low confidence. 345 tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The refusal tells a user to specify the source encoding. Until now the CLI had no way to do that, which made a safety feature issue instructions its own interface could not follow. EncodingChecker.exe -BasePath . -Include "*.txt" -From "windows-1252" -Target "utf-8" -From replaces detection and nothing else. It answers "which encoding is this?", not "convert it regardless", so every guarantee from the conversion engine still applies: the bytes must strictly decode as the named encoding, the output must re-decode to exactly the same text, a failed backup still aborts, and the recovery record is still written before anything is installed. Verified by tests for each of those paths rather than asserted. It also genuinely changes the interpretation rather than granting permission: naming koi8-r for windows-1252 bytes produces koi8-r text, which is the point - the user is choosing a reading, not waiving a check. Rejected where it cannot mean anything: -DetectOnly and -Validate report what the detector finds, so overriding the detector there would make the result a tautology. Also adds AmbiguityReason - SingleCandidate, StructurallyDetermined, MultipleCodecsSameText, MultipleCodecsDifferentText, ExplicitlySpecified - and records on each entry whether the encoding was detected or specified. Those are different claims, since detection can be wrong in ways an explicit choice cannot, and the journal will need to say which produced a conversion. The Phase C contract now has an end-to-end regression suite: ambiguous detection refused, same-text alternatives allowed, explicit source converts, and explicit source still refused on undecodable bytes, on content the target cannot hold, and on backup failure. 354 tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Phase C status: CLI-complete, GUI override pending.
The GUI plumbing exists ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The audit's largest remaining risk category: 262 of 5,078 files where several encodings read the bytes and disagree about what they say. Single-byte code pages map 256 values independently, so a file valid in
windows-1252is equally valid iniso-8859-1, and nothing in the bytes decides. Detection answers anyway — and on short or ASCII-heavy input that answer is close to a guess, which EC then acted on.Three outcomes, not two
UnambiguousTextEquivalentTextChangingThe middle case matters as much as the last. A pure-ASCII file is ambiguous in label and identical in text — refusing it would protect nothing. The test is whether the candidates produce different Unicode, not whether more than one can decode.
Observed end to end:
Candidate set and placement
Candidates come from EC's supported encodings, deduplicated by code page — never from a corpus. The audit already demonstrated that deriving candidates from test data makes the answer depend on what the tests happened to contain. That list moved from
MainFormtoTextEncodingso the engine can reach it.The decision is made beside the detection that produced it and carried on the entry, so the conversion path reads a verdict rather than re-deriving one. A caller who supplies the source encoding rather than detecting it is unaffected — there is nothing ambiguous about an answer somebody gave.
Two mistakes on the way, both caught by the existing tests
Counting rival readings alone refused every UTF-8 file, because
iso-8859-1"reads" it too. A codec that cannot refuse anything is not offering an alternative. Fixed by requiring the detected codec to have no hold on the bytes before rivals count.Measuring that hold by bit flips alone refused UTF-16, which survives nearly any flip — most produce another valid character. Fixed by also deleting a byte, which tests alignment, the only structure a fixed-width encoding has.
The measurement is deterministic — evenly-spaced probes, not random ones — after an earlier version gave answers that moved with the seed on short files. Whether a conversion is refused should not depend on a random draw.
The threshold sits just above zero because the separation is not a matter of degree: unconstrained single-byte pages reject exactly nothing, and every structured encoding measured on real text lands at 0.111 or above.
The message
Rather than "low confidence", which gives a user nothing to act on.
345 tests passing (was 330).
🤖 Generated with Claude Code
Update:
-Fromfolded inThe refusal told users to specify the source encoding, and the CLI had no way to do it — a safety feature issuing instructions its own interface could not follow. Fixed before merge:
It replaces detection and nothing else. Verified end to end:
Error+ advice-From windows-1252Converted, text intact-From euc-jpon undecodable bytesSourceDecodeError-From+ target that cannot hold the content-From+ failing backup-From+-DetectOnly/-ValidateIt changes the interpretation, not the permission: naming
koi8-rfor windows-1252 bytes produces koi8-r text. The user is choosing a reading, not waiving a check. The recovery record is still written.Also adds
AmbiguityReason(SingleCandidate,StructurallyDetermined,MultipleCodecsSameText,MultipleCodecsDifferentText,ExplicitlySpecified) and records per entry whether the encoding was detected or specified — different claims, since detection can be wrong in ways an explicit choice cannot.354 tests passing (was 345).