Prove the browser claim under a real Pyodide runtime (#99) - #140
Merged
Conversation
Adds a blocking CI job that boots actual Pyodide (WebAssembly CPython) under Node, installs the wheel this job just built, and decodes the whole real-capture fixture corpus with it — real proof the library runs where scapy/dpkt-with-fcntl cannot, not a simulation of it via a sys.meta_path blocklist. That run surfaced a genuine bug: IPv6 decode/encode went through socket.inet_ntop/inet_pton(AF_INET6, ...), and Pyodide's CPython build has AF_INET6 sockets disabled, so every IPv6 frame failed under it. Fixed by reimplementing _base.py's ipv6_to_bytes/bytes_to_ipv6 in pure Python (ipaddress for parsing; a hand-rolled RFC 5952 canonicalizer, differentially verified against glibc's inet_ntop across 500,000+ random addresses plus both of its dotted-quad special cases, for formatting) — str(ipaddress.IPv6Address) was tried first and rejected because it disagrees with glibc on IPv4-mapped addresses and disagrees with itself between Python 3.11 and 3.12. Exception type and message are preserved, so this is a pure implementation swap, not a behavior change on any platform that already worked. Also flips docs/CLAIMS.md 3.1's status now that the capability is CI-verified (the COMPARATIVE — HELD embargo on the claim itself is unchanged), and 5.3's stale "gate pending #79" language — #79 already shipped fail_under=98. Closes #99. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDTcK51CWcy6PrNetN213
This was referenced Sep 4, 2026
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.
Summary
Tier 4 (#106), item 1 of 3: proves the README's browser/WASM claim under a real Pyodide runtime rather than a simulation of it. Closes #99.
What's included
pyodidein.github/workflows/ci.yml): builds the wheel, boots actual Pyodide (WebAssembly CPython) under Node — no browser needed, same WASM build either way — installs the wheel, and decodes the entire real-capture fixture corpus with it.scripts/pyodide/run_in_pyodide.mjs: generic Node driver that mounts the checked-out repo into Pyodide's virtual filesystem and runs a Python script inside it.scripts/pyodide/check_in_pyodide.py: the actual check — re-assertsfcntl/termios/resource/grp/pwdare genuinely unavailable under Pyodide (not just blocklisted), then decodes every corpus frame, failing on anything butProtocolError.IPv6.decode()/bytes(IPv6(...))went throughsocket.inet_ntop/inet_pton(AF_INET6, ...). Pyodide's CPython build hasAF_INET6sockets disabled, so every IPv6 frame failed to decode there before this fix._base.py'sipv6_to_bytes/bytes_to_ipv6are now pure Python:ipaddress.IPv6Addressfor parsing, and a hand-rolled RFC 5952 canonicalizer for formatting —str(ipaddress.IPv6Address(...))was tried first and rejected because it disagrees with glibc'sinet_ntopon IPv4-mapped addresses (::ffff:a.b.c.d), and disagrees with itself between Python 3.11 and 3.12. The replacement is differentially verified againstsocket.inet_ntopacross 500,000+ random addresses plus an exhaustive sweep of both dotted-quad special cases (seetests/test_ip.py's new Hypothesis properties) — zero divergence. IPv4 addressing is untouched (itsAF_INETsocket calls work fine under Pyodide). The invalid-input exception type (OSError) and message are preserved, so this is a pure implementation swap on every existing platform.docs/CLAIMS.md3.1's status flipped fromGATED ON #99to reflect the capability is now CI-verified — theCOMPARATIVE — HELDembargo on the claim itself (naming scapy) is untouched, per the roadmap's standing embargo through the rest of Roadmap: post-1.3.0 — performance, platform, typing, proof #107.docs/CLAIMS.md5.3's stale "gate pending Enforce the coverage level already achieved #79" language corrected — Enforce the coverage level already achieved #79 already shipped (fail_under = 98), and the statement/miss counts were out of date.CHANGELOG.mdentry under## [Unreleased].Verification
uv run ruff checkanduv run ruff format --checkare cleanuv run mypyis clean (strict)uv run pytestpasses locally — 1036 passedCHANGELOG.mdhas an entry under## [Unreleased]uv run --group bench python scripts/benchmark.py --check --threshold 15— within threshold (-10.5% normalized vs. baseline)pyodidejob itself, run locally the same way CI will:netprotocols 2.0.0 imported and decoded the real 97-frame corpus under a real Pyodide runtime (97 decoded cleanly, 0 raised ProtocolError, 0 raised anything else)Notes
Scope note for reviewers: the issue offered a cheaper alternative (just
sys.meta_path-block the POSIX-only modules and run the existing suite under CPython) that would have missed theAF_INET6bug entirely — the real-runtime approach was chosen deliberately for the stronger proof, at the cost of the extra Node/Pyodide CI plumbing.Closes #99.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MGDTcK51CWcy6PrNetN213
Generated by Claude Code