Skip to content

#3826: the zoned sign table follows the data's code page - #3842

Merged
squid-protocol merged 1 commit into
mainfrom
fix/3826-code-page-overpunch
Sep 26, 2026
Merged

squid-protocol merged 1 commit into
mainfrom
fix/3826-code-page-overpunch

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Closes #3826 (epic #3819).

Why

Zoned-decimal sign overpunch was hard-wired to the US table ({ABCDEFGHI / }JKLMNOPQR). Under a national EBCDIC code page, the sign bytes 0xC0 / 0xD0 are that page's own letters. So the generated Java codec threw on every value ending in 0: cp273 German ä/ü, cp278 Swedish/Finnish ä/å, cp277 æ/å, cp280 à/è, cp297 é/è.

Found along the way: Python ships no codec for cp277, cp278, cp280, cp284, cp285, cp297 or cp1047. A table built from bytes.decode() alone silently can't handle the Nordic pages at all. That's the family behind the ÆØÅ bug (#3520).

What

  • Config: java_target's new data.code_page (default cp037) is checked when the config loads.
  • One definition: zoned_sign_characters(). It uses Python's codec where one exists. Otherwise it uses the national zero signs from IBM CDRA; in every Latin EBCDIC page, 0xC1–C9 / 0xD1–D9 are A–I / J–R.
  • Generated runtime: CobolRecords takes POSITIVE/NEGATIVE from that definition. Non-ASCII characters are escaped (ä) so the Java source stays ASCII. cp037 output is byte-identical.
  • Equivalence harness: decode_field, encode_field and the input generator take the code page, and a case may declare "code_page". They use the same table, cached.

Tests (tests/cobol_mainframe/test_code_page_overpunch.py)

  • Each of 10 code pages has 20 distinct sign characters with the right zero signs.
  • cp273 +100.00 decodes and encodes back, and the US table misreads it.
  • A generated CobolRecords for cp273 carries ä / ü.
  • cp037 is unchanged.
  • An unknown code page fails when the config loads.

Verified

  • test_code_page_overpunch, test_equivalence, test_equivalence_inputs, test_java_target and test_vsam_repositories: 63 passed, 2 skipped.
  • ruff shows no new lint errors.

Drafted by a sandboxed Gemini fleet agent. In review I added the config-load check, the cache, the non-Python code pages (the agent's tests had skipped them), and a cleaned-up test file.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ZsVaAkb86P5r5JXDC2Y9g

Zoned-decimal overpunch was hard-wired to the US table ({ABCDEFGHI / }JKLMNOPQR). Under
a national EBCDIC page the sign bytes 0xC0 / 0xD0 are other letters (cp273 ä/ü, cp278
ä/å, cp277 æ/å, cp280 à/è, cp297 é/è), so the generated Java codec threw on every
value ending in 0.

- java_target: `data.code_page` (default cp037), validated when the config loads;
  zoned_sign_characters() is the one definition -- Python codecs where they exist, and
  the national zero signs (IBM CDRA) for cp277/278/280/284/285/297/1047, which Python
  ships no codec for
- repository forge: CobolRecords' POSITIVE / NEGATIVE come from it, non-ASCII escaped
  (ä) so the Java source stays ASCII; cp037 output is byte-identical
- equivalence harness: decode_field / encode_field / the input generator take the
  code page (a case may declare "code_page"), using the same table (cached)
- tests: 20 distinct signs on ten code pages, cp273 +100.00 round trip, generated
  CobolRecords for cp273, cp037 unchanged, unknown page fails at config load

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZsVaAkb86P5r5JXDC2Y9g
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

@squid-protocol
squid-protocol merged commit 7d9e1b3 into main Sep 26, 2026
37 checks passed
@squid-protocol
squid-protocol deleted the fix/3826-code-page-overpunch branch September 26, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zoned overpunch table is the US code page's: German / Nordic / French / Italian data throws on every value ending in 0

1 participant