fix(vendor): send a descriptive User-Agent and retarget the dead pcapng draft - #524
Conversation
…ng draft Closes #518. - default.py: add get_user_agent(), built from importlib.metadata rather than hard-coded, and pass it at both the direct and the proxy call site. Wikipedia answers requests' default User-Agent with 403, which took out four crawlers. Not a browser spoof: it names the distribution, version and repository. - pcapng/{block_type,option_type,record_type}.py: retarget from the dead ietf.org/staging draft to the -03 archive. The issue's proposed -02 URLs are both wrong -- that revision renders its registries as preformatted ASCII art with one table and no table-N ids, so the crawlers fetch 200 and then raise IndexError. Only -03 reproduces all three files; -04 onward diverge and the renamed draft-ietf-opsawg-pcapng raises on a 0x0A0D0AXX wildcard row. - ipx/packet.py: retire the scrape. The table was deleted from the article on 2026-08-25 and the registry is closed, so DATA is hand-maintained with per-entry citations and the class defines no LINK. - ftp/return_code.py: skip cell-less rows. The article now ends its table with an empty <tr>, so line[0] raised IndexError -- which is why this constant file had never been regenerated, and why regenerating it now adds CODE_335 (RFC 2228's ADAT response). 53 -> 54 members, nothing removed. New tests/vendor/test_user_agent_unit.py, test_ipx_packet_unit.py and test_ftp_return_code_unit.py: 25 tests, 35 subtests. 115 of 117 constant files regenerate byte-identically; 87 of 92 response bodies are unchanged by the header, the five that differ being the four 403s and the dead 404.
Cross-review: GOOD TO GOReviewed at head Corrections to this PR's own text
Independently verifiedBoth new tests hold up, and the negative test genuinely constrains. The adversarial push was to mutate the label comparison to something looser rather than stricter — The FTP fixture genuinely reproduces the defect through the real parse path, re-parsed independently with the installed bs4 4.15.0 + html5lib 1.1: the trailing
The FTP regeneration is mechanical, re-derived against the live page with no retries and no warnings: md5 Roughly 18 of the 115 constant files were independently regenerated and diffed byte-for-byte across the four agents — including 9 IANA CSV crawlers, Both judgement calls endorsed: retiring a scrape against a closed registry trades no freshness for removed fragility, and bundling the regenerated constant file is right because Not fully verified, stated plainly
Of the PR's claimed 14-of-14 caught mutations, only the 2 header-drop mutations were independently re-exercised; the other 12 were not reproduced. One reviewer-side finding worth recordingOne of the four subagents reported that the proxy call site lacks the header. That is wrong — line 489 reads |
|
✅ GOOD TO MERGE — cross-reviewed clean; 87 of 92 crawler responses byte-identical with the new User-Agent, and the regenerated FTP constant file confirmed as mechanical crawler output rather than a hand-edit. |
The reason all three scraping crawlers read draft revision -03 lived only in a `#` comment block, and the docs pull in nothing but autoclass output, so Sphinx never rendered any of it. Meanwhile the rendered pages still cited the dead -02 staging URL, so the docs pointed at a 404 while the code fetched -03. - add a note to the pcapng vendor docs stating once that BlockType, OptionType and RecordType all read -03, that the -02 staging URL is dead, and that the table-1 through table-10 ids they select on first exist in -03 - retarget staging/draft-tuexen-opsawg-pcapng-02.html at archive/id/draft-tuexen-opsawg-pcapng-03.html in all 31 places that cited it across the vendor, const and protocols docs and sources; all 8 anchor fragments were confirmed present in -03 - leave 3 deliberately historical -02 references intact: the "which is dead" URL in block_type.py, and two draft-ietf-opsawg-pcapng-02 section citations in protocols/misc/pcapng.py that quote that revision's own text - trim the block_type.py comment to the implementer-facing measurements, the reader-facing rationale now being in the rendered note Sphinx 9.1.0 builds clean: 83 warnings, warning set byte-identical to the parent commit, and no clickable -02 link left anywhere in the output. Refs #518.
|
✅ GOOD TO MERGE — the |
Re-review of the delta (previous verdict covered
|
Closes #530. - `_hopopt_option_length` quoted "the length of the Option Data field of this option, in octets" and attributed it to RFC 8200 section 4.3. That sentence is the `Opt Data Len` definition from section 4.2; section 4.3 defines no `Opt Data Len` at all and its only length field is `Hdr Ext Len`, the whole header in 8-octet units. Now cites 4.2 for the quote and keeps 4.3 as the header this class implements, matching the two-part treatment #528 landed for the IPv6-Opts sibling. - `tests/test_docstring_contract.py` grows a fourth property: every verbatim RFC 8200 sentence this package quotes must be introduced by a citation naming the section that contains it. Keyed on the quote rather than the file, so it covers the next copy-paste of these paragraphs. - Drops the rotted `KNOWN_DEFECTS` entry for `pcapkit/vendor/ipx/packet.py`, whose `process` now takes and documents `data`. Unrelated to #530: #524 renamed the parameter and #535 added the rot guard four commits later, so `main` has been red on that subtest since the guard landed. No behavioural change: the docstring is prose, and `_hopopt_option_length` still returns `schema_len + 2`. Verified on .venv python 3.14.7 with PYTHONSAFEPATH=1 and pcapkit.__file__ asserted inside the worktree. The new check fails on unfixed hopopt.py (1 defect) and passes with the fix (0). Suite now fully green, exit code read from a file rather than a pipe: 66 passed, 449 subtests passed, EXIT=0.
…#530) (#538) Closes #530. - `_hopopt_option_length` quoted "the length of the Option Data field of this option, in octets" and attributed it to RFC 8200 section 4.3. That sentence is the `Opt Data Len` definition from section 4.2; section 4.3 defines no `Opt Data Len` at all and its only length field is `Hdr Ext Len`, the whole header in 8-octet units. Now cites 4.2 for the quote and keeps 4.3 as the header this class implements, matching the two-part treatment #528 landed for the IPv6-Opts sibling. - `tests/test_docstring_contract.py` grows a fourth property: every verbatim RFC 8200 sentence this package quotes must be introduced by a citation naming the section that contains it. Keyed on the quote rather than the file, so it covers the next copy-paste of these paragraphs. - Drops the rotted `KNOWN_DEFECTS` entry for `pcapkit/vendor/ipx/packet.py`, whose `process` now takes and documents `data`. Unrelated to #530: #524 renamed the parameter and #535 added the rot guard four commits later, so `main` has been red on that subtest since the guard landed. No behavioural change: the docstring is prose, and `_hopopt_option_length` still returns `schema_len + 2`. Verified on .venv python 3.14.7 with PYTHONSAFEPATH=1 and pcapkit.__file__ asserted inside the worktree. The new check fails on unfixed hopopt.py (1 defect) and passes with the fix (0). Suite now fully green, exit code read from a file rather than a pipe: 66 passed, 449 subtests passed, EXIT=0.
Closes #518.
What was broken
Four crawlers took a 403 on
requests' default User-Agent — Wikipedia now requires a descriptive one — and three pointed at anietf.org/stagingURL that 404s.The User-Agent
New
get_user_agent()inpcapkit/vendor/default.py, built fromimportlib.metadatarather than hard-coded, applied at both the direct and the proxy call site. It is not a browser spoof: it names the distribution, version and repository, per Wikimedia's robot policy.Proven metadata-driven rather than literal: faking the metadata to a different name and repository URL makes the function follow it, and the label match is case-insensitive.
The argument that the header cannot change generated output: of the 92 distinct crawler URLs, 87 return byte-identical bodies with and without it. The only five that differ are the four Wikipedia 403→200 and the one dead 404. Identical bodies imply identical output — stronger than running 117 generators twice.
The pcapng draft — both URLs the issue proposed are wrong
draft-tuexen-opsawg-pcapng-02renders its registries as preformatted ASCII art: exactly one HTML table (the running header) and zerotable-Nids, while all three crawlers select tables by id. So both-02URLs fetch 200 and then raiseIndexError. Measured by running the realBlockType.process()against the fetched page.Only
-03reproduces all three constant files byte-identically.-04,-05and ietf-00diverge onblock_type; ietf-01/-02on all three; ietf-03onward raiseValueErroron a0x0A0D0AXXwildcard row — so migrating to the renameddraft-ietf-opsawg-pcapngis real work, not a URL swap, and is left as a follow-up.Chose the static
www.ietf.org/archive/id/rendering over datatracker: datatracker renders per request, sends noLast-Modified, and wraps the document in chrome adding three unrelated tables to a document the crawlers index tables out of.ipx/packet.py— scrape retiredThe table was deleted from the article on 2026-08-25 and the registry is closed, so
DATAis hand-maintained with per-entry citations and the class defines noLINK.pcapkit/const/ipx/packet.pyregenerates byte-identically (md50655b561ccdca4981ef6f7974ebeb66f).ftp/return_code.py— a second defect, and why a data file changesThe article now ends its table with a cell-less
<tr class="mw-empty-elt">, so indexing the first cell raisedIndexError. That is why this constant file had never been regenerated. With the guard in place it regenerates — and gainsCODE_335, RFC 2228's ADAT response. 53 → 54 members, nothing removed (verified withcomm).To be explicit, since a data change inside an infrastructure fix deserves it: this is not a hand-edit. Running the unmodified
request()/count()/process()/context()pipeline against today's live page reproduces the committed file byte-for-byte,CODE_335included. The only other differences are doc comments — the enum values for codes 450 and 452 are byte-identical either side.Tests — 25 tests, 35 subtests, all three suites new
Mutation-tested by mutating files on disk with hash-checked restore. 14 of 14 mutations caught, including dropping the header from each call site independently.
Proven to fail without their fixes
IndexErroratvendor/ftp/return_code.py:221SUBFAILEDonRepository,REPOSITORY,RePoSiToRyThe one test that survives the guard removal is the fixture-shape assertion, correctly — it exists so that a fixture which stopped reproducing the defect would be caught, rather than silently making the others pass.
115 of 117 constant files regenerate byte-identically. The two exceptions are
ipx.socket, whose crawler #511 retires, andmh.handover_initiate_status— see below.Found while doing this, reported not fixed
pcapkit/const/mh/handover_initiate_status.pyis the only one of 117 constant files missing the generated-file marker, and the cause is a duplicated export.MH_HandoverACKStatusappears twice in__all__at line 196 of bothpcapkit/vendor/mh/__init__.pyandpcapkit/const/mh/__init__.py, andMH_HandoverInitiateStatushas no import line at all. Sincepcapkit/vendor/__main__.py:87builds its target list from__all__, that crawler has never run — 117 defined, 116 reachable. Not failing; unwired. Confirmed three independent ways. Its own file, so a separate change.A vendor-reproduction CI gate is drafted but deliberately not included: two of its three steps fail today by design until that typo is fixed.
Also out of scope here: the dead docs URL still appears 15 times across 3 files, and #522 (the
_request()fallback loop) was filed from a failure hit while testing this.