FIX: escape literal apostrophes in DigitBijectionConverter to avoid case-marker collision - #2696
Open
Mallika Kalangi (bhargavikvmpl-2001) wants to merge 1 commit into
Conversation
…ase-marker collision decode() treats any literal "'" as the case marker that precedes an uppercase letter's digit token. Since the apostrophe is passed through unchanged at encode time and every letter always encodes to a digit token, a plaintext apostrophe immediately followed by a letter (e.g. "it's", "don't") is indistinguishable from a real case marker: decode() swallowed the apostrophe and force-uppercased the next letter instead of round-tripping it. Escape a literal case-marker character by doubling it on encode, and collapse the doubled marker back to one character on decode before checking for a real case marker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment on lines
+294
to
+295
| elif char == self._CASE_MARKER: | ||
| encoded += self._CASE_MARKER * 2 |
Contributor
There was a problem hiding this comment.
Could we update the digit converter's get_teaching_instructions() along with this change? It still tells the target to "preserve spaces/punctuation", without explaining that '' represents a literal apostrophe and ' before a digit token marks uppercase.
With the mapping in these tests, it's encodes to 1829''28. Following the current instructions literally would read that as it''s. In the other direction, a target encoding it's with unchanged punctuation would produce 1829'28, which decode() turns into itS.
Please teach both marker rules, include a contraction example, and add regression coverage for the generated instructions. Am I missing somewhere else these rules are already explained?
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.
Description
DigitBijectionConverteruses'as_CASE_MARKER, written before an encoded letter to record that the letter was uppercase. Because every letter always encodes to a digit token,decoderecognises a marker by the fact that a digit token follows it.A literal apostrophe in the plaintext — contractions and possessives such as
it's,don't,I'm— is not in the mapping, so it is passed through unchanged. When the next character is a letter, that letter encodes to a digit token, and the passed-through apostrophe becomes indistinguishable from a real case marker.decodeconsumes it as one: the apostrophe is dropped and the following letter is wrongly uppercased.On
main(mappinga→10…z→35):it's1829'28itSI'm'18'22IMdon't132423'29donTO'Brien's'24''1127181423'28O'BrienSRound-tripping is therefore lossy for any input containing an apostrophe followed by a letter, which covers most naturally occurring English prose. Since the bijection technique sends the encoded prompt to the target and decodes the response, a corrupted round trip silently alters text on both legs.
Changes
encode: double a literal_CASE_MARKERwhen it is not being emitted as a case marker.decode: collapse a doubled marker back to a single literal marker before the case-marker check, so the escape is removed and the following token is not misread as an uppercase letter._CASE_MARKERto record why the escape is needed.All four examples above round-trip exactly with this change.
Tests
tests/unit/converter/test_bijection_converter.py: 2 new tests — a lowercase letter after a literal apostrophe (it's) and an uppercase letter adjacent to one (I'm). Both were verified to fail against the pre-fix logic (assert "'18'22" == "'18''22") and to pass with this change.tests/unit/converter/test_bijection_converter.py→ 42 passed.test_seeded_converter_determinism.py(pinyin seeding) and reproduce identically on a cleanupstream/maincheckout (1562 passed, 52 failed, 34 skipped), so they are pre-existing and unrelated to this change.pre-commit run --files pyrit/converter/bijection_converter.py tests/unit/converter/test_bijection_converter.py→ all hooks pass (ruff format, ruff check, ty).git diff --check→ clean.No documentation change: this is an internal encoding correctness fix with no API surface change.
I did not find an existing issue covering this — happy to open one if you would prefer it tracked separately.
🤖 Generated with Claude Code