Skip to content

fix(vendor): derive each crawler's const/ path from vendor/'s own root - #741

Merged
JarryShaw merged 1 commit into
mainfrom
fix/vendor-nested-const-path
Sep 24, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/vendor-nested-const-path

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Please follow the guide below

What is the purpose of your pull request?

  • fix — corrects a defect
  • feat — adds a feature
  • perf — changes performance, not behaviour
  • refactor — changes neither behaviour nor performance
  • test — tests only
  • docs — documentation only
  • ci — workflows or build tooling
  • chore — anything else

Description of your pull request and other information

Prerequisite for #732: Vendor.__init__ derived each crawler's const/ path by splitting the module's absolute path into exactly two levels, assuming the module always sits one level under vendor/. vendor/reg/apptype/tcp.py — the shape #732's per-transport split needs — resolved to vendor/const/apptype/tcp.py, dropping reg and never reaching pcapkit/const/.

Extracted the derivation into Vendor._dest_path(), anchored on pcapkit.vendor.__file__ rather than a fixed split count, mirroring a module's path relative to vendor/ under const/ at any depth.

before after
vendor/reg/apptype.py const/reg/apptype.py const/reg/apptype.py (unchanged)
vendor/reg/apptype/tcp.py vendor/const/apptype/tcp.py (wrong tree) const/reg/apptype/tcp.py
module not under vendor/ at all n/a, never occurred before review: relpath's .. cancelled back to the module's own path, so __init__ overwrote it — now raises VendorPathNotFound instead

tests/vendor/test_vendor_dest_path_unit.py: all 117 crawlers on disk pin their existing const/ file; a synthetic nested case; and the escape case above, confirmed to fail (AssertionError: VendorPathNotFound not raised) against the unguarded body. Gated on requests+bs4 only — matches test_crawler_reachability_unit.py's CI-visible gate, so both classes run under .[test], no #738 dependency.

coverage run -m pytest tests/vendor/: 59 passed, 171 subtests. pcapkit/vendor/default.py coverage: 65% → 69% (66 → 58 statements missed).

Part of #732.

@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 24, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 935d50852 — _dest_path() resolves to the crawler's own source file for any module outside vendor_root and __init__ opens it 'w' (pcapkit/vendor/default.py:495); 'html5lib' at tests/vendor/test_vendor_dest_path_unit.py:101 needlessly keeps both new test classes out of CI.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 935d50852 — pcapkit/vendor/default.py:495 silently resolves to the crawler's own source file for any module outside vendor_root, and :449-451 opens it 'w'; tests/vendor/test_vendor_dest_path_unit.py:101 gates on html5lib needlessly.

Cross-review on a different model from the author's. The 117-crawler no-regression claim was independently verified upstream and is taken as given; I attacked the rest.

1. _dest_path() overwrites the crawler's own source when the trees split

const/ and vendor/ are siblings at equal depth, so join(const_root, relpath(m, vendor_root)) normalises to exactly m whenever m is not under vendor_root:

module new (:495) old
…/vendor/reg/apptype.py …/const/reg/apptype.py ✅ same ✅
…/vendor/reg/apptype/tcp.py …/const/reg/apptype/tcp.py ✅ …/vendor/const/apptype/tcp.py ← the bug being fixed
/other/pcapkit/vendor/reg/apptype.py /other/pcapkit/vendor/reg/apptype.py ⚠️ /other/pcapkit/const/reg/apptype.py ✅
/tmp/x/mycrawler.py /tmp/x/mycrawler.py ⚠️ /const/x/mycrawler.py (fails loudly)

__init__ then runs open(const_file, 'w') → the crawler's source is truncated and replaced with generated const content. Reachable, and measured: every crawler ends if __name__ == '__main__': sys.exit(Cls()) (reg/apptype.py:349), and a script run from a second checkout resolves pcapkit from the editable install rather than its own tree — sys.path[0]=/tmp/treeB/pcapkit/vendor/reg while pcapkit.vendor.__file__=/local/…/PyPCAPKit/pcapkit/vendor/__init__.py, giving _dest_path() → /tmp/treeB/pcapkit/vendor/reg/apptype.py. So this is not the old wart reshaped: the old formula was correct in that case and loud in the escape case; the new one is silent and destroys source. Reject a rel whose first component is os.pardir, or anchor on the module file's own vendor ancestor. No test covers it.

2. html5lib is the only reason CI skips these tests, and it is not needed

With html5lib blocked by a meta_path finder, import pcapkit.vendor, importing all six bs4 crawlers, and _dest_path() all still succeed — html5lib appears only as bs4.BeautifulSoup(text, 'html5lib') at crawl time. .[test] ships requests+beautifulsoup4 deliberately so vendor tests run (pyproject.toml:243-263), and test_crawler_reachability_unit.py:95 already gates on ('requests','bs4'). Dropping one token at :101 makes both classes run in CI now, with no dependency on #738. Relatedly, the docstring at :63-65 (and :100) claims this is "the same gate test_crawler_reachability_unit.py … use[s]" — that file uses the two-dep HAS_CRAWLER_DEPS, so the cited precedent is the CI-visible one.

Verified sound

claim result
local import necessary, not merely cautious ✅ module-scope pcapkit.vendor.__file__ genuinely raises during the vendor/__init__.py:33 cycle
namespace-package / missing __file__ risk ✅ 17 real __init__.py; PEP 660 finder delegates to PathFinder; SourceFileLoader throughout
tests at head, and merged into 9b2d927c2 ✅ 58 passed, 171 subtests both; merge clean, 3 files, no conflicts
CI @ 935d50852 ✅ 0 failed, 0 cancelled; 12/15 required green, 3 (Python 3.10/3.12/3.14) still running
.rst follows the _request convention ✅ and consistent with this repo's "document most _xxx" position

Nits: mergeStateStatus is BEHIND, not blocking; two intervening commits, not three, touching only .github/ — neither default.py nor tests/vendor/; the make test box is unticked though a test was added; test_old_algorithm_would_have_written_inside_vendor asserts against a hand-copied formula with nothing tying it to the deleted code, so it documents the motivation rather than evidencing the fix — the 117-crawler and nested cases carry the contract on their own.

❌ NEEDS CHANGES @ 935d50852 — guard the os.pardir escape at pcapkit/vendor/default.py:495 (it currently overwrites the crawler's own source); drop 'html5lib' from VENDOR_DEPS at tests/vendor/test_vendor_dest_path_unit.py:101.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 24, 2026
Vendor.__init__ split a crawler's module path into exactly two levels
(ROOT, STEM = os.path.split(temp)) and assumed STEM was always the
module's one-and-only position under vendor/. That holds for today's
flat vendor/<stem>/<file>.py layout, but breaks for deeper nesting:
traced by hand, vendor/reg/apptype/tcp.py resolved to
vendor/const/apptype/tcp.py -- inside vendor/ itself, never reaching
pcapkit/const/. #732 needs exactly that depth for AppType's planned
tcp.py/udp.py/dccp.py/sctp.py split.

- Extract the derivation into Vendor._dest_path(), anchored on
  pcapkit.vendor's own __file__ instead of a fixed split count, and
  mirror a module's path relative to vendor/ under const/ at any depth.
- Guard it: const/ and vendor/ are siblings at equal depth, so a module
  NOT under vendor_root produced a relpath whose leading '..' segments
  cancelled back out to the module's own path, and __init__ then opened
  that with 'w' -- silently truncating the crawler's own source instead
  of failing loudly. Reachable via the documented `sys.exit(Crawler())`
  invocation from a second checkout. Now raises the new
  VendorPathNotFound (pcapkit/utilities/exceptions.py) instead.
- Import pcapkit.vendor inside the method, not at module scope, since
  pcapkit.vendor.__init__ imports this module while still initialising.

tests/vendor/test_vendor_dest_path_unit.py: every crawler currently
discovered under pcapkit.vendor still resolves to its existing const/
file; a synthetic nested-module case built under a temp directory; and
the escape case above, pinned directly against the raise. Gated on
requests+bs4 only (html5lib is never touched by anything reachable
here), matching test_crawler_reachability_unit.py's CI-visible gate.

Part of #732.
@JarryShaw
JarryShaw force-pushed the fix/vendor-nested-const-path branch from 935d508 to c2a336f Compare September 24, 2026 15:28
@JarryShaw JarryShaw added review: pending No verdict for the current head - never reviewed, or the head moved since the last one fix Pull requests that fix a defect (fix: subject prefix) and removed fix Pull requests that fix a defect (fix: subject prefix) review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Sep 24, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Addressed both blockers at c2a336f96.

1. Self-truncation escape. Confirmed the trace: const//vendor/ being equal-depth siblings means relpath(m, vendor_root)'s leading ..s cancel back to m whenever m isn't under vendor_root — reproduced it directly (unrelated fake root, real stub module): dest normalizes to exactly module_file. _dest_path() now checks relpath.split(os.sep, 1)[0] == os.pardir and raises the new pcapkit.utilities.exceptions.VendorPathNotFound instead of returning a path that can equal the input. Added test_module_outside_vendor_root_raises_instead_of_self_truncating, confirmed it fails on the unguarded body (AssertionError: VendorPathNotFound not raised) and passes with the guard.

2. html5lib gate. Dropped it — VENDOR_DEPS = ('requests', 'bs4'), matching test_crawler_reachability_unit.py. Fixed the docstring's false claim about which gate that file uses. Both classes now run under .[test].

Verified: coverage run -m pytest tests/vendor/: 59 passed, 171 subtests. pcapkit/vendor/default.py coverage 65% → 69% (baseline recomputed with the guard's 3 extra lines counted). isort/pylint/mypy clean on all touched files (default.py's 3 pre-existing findings unchanged, exceptions.py 10/10).

@JarryShaw

Copy link
Copy Markdown
Owner Author

✅ GOOD TO MERGE @ c2a336f96 — guard rejects all 15 constructed escapes (0 self-truncating) and raises before makedirs/open; two-dep gate makes these tests run in CI (+4 passed, +117 subtests vs main).

@JarryShaw

Copy link
Copy Markdown
Owner Author

✅ GOOD TO MERGE @ c2a336f96 — the guard rejects every escape I could construct (15 cases, 0 self-truncating) and raises before makedirs/open; the two-dep gate makes these tests actually run in CI (+4 passed, +117 subtests vs main, skips unchanged).

Supersedes my NEEDS CHANGES @ 935d50852. Narrow re-check of the two fixes; the 117-crawler no-regression result is taken as given.

1. Guard completeness — attacked, holds. 0 self-truncating cases out of 15 constructed.

adversarial case guard does
second checkout · unrelated module · vendorx/ prefix overlap · relpath == bare '..' (no separator) raises ✅
symlink, module-via-link vs real vendor_root either way (abspath≠realpath measured) raises ✅ — loud, not destructive
vendor/reg2 → symlink out of the tree dest const/reg2/…, mirrored, not the module ✅
NT module on D:, vendor on C: bare ValueError from relpath before the guard — loud, non-destructive (nit)
legit dir literally named ..foo under vendor/ correctly not rejected — why split(os.sep,1)[0]==os.pardir beats startswith

Structural rather than luck: relpath output is normalised, so .. can only be a leading run — over 2800 synthetic paths, 0 relpaths with .. after a non-.. component and 0 guard-passing dests that escape const_root or equal the module. Testing the first component is therefore sufficient. Only residual truncation path: symlinking const→vendor.
Raise precedes every write: default.py:449 _dest_path() → :450 makedirs → :451 open(...,'w'); nothing earlier in __init__ writes. VendorPathNotFound conforms — exceptions.py:390 (BaseError, ValueError) like neighbour TruncateError:386, __all__:48 in the ValueError group, .rst autoexception in the ValueError category.
The test pins scoping, not just presence — measured with source-level mutants, since setUp's purge_modules defeats runtime patching: guard-deleted, == os.pardir-only, and last-component-instead-of-first all FAIL at :350; an over-broad startswith(os.pardir) passes, uncaught — its only victim is a hypothetical ..foo dir, so the gap is real but harmless.

2. html5lib — right, and CI-visible now. :130 ('requests','bs4') matches test_crawler_reachability_unit.py:96; .[test] ships plain requests+beautifulsoup4 (pyproject.toml:262-263) and CI installs .[test,DPKT]. Ran rather than skipped: main 9b2d927c2 = 1566 passed / 92 skipped / 5668 subtests → c2a336f96 = 1570 / 92 / 5785, i.e. +4 passed, +117 subtests, skips unchanged — exactly the new file alone. No #738 dependency.
⚠️ But :90-91's "appears nowhere at module scope" is false — pcapkit/vendor/__init__.py:27 imports it at module scope, inside try/except ModuleNotFoundError. That guard is what actually makes dropping it safe (verified: with html5lib blocked, import pcapkit.vendor succeeds and HAS_VENDOR_DEPS is True). Right conclusion, wrong stated reason — one-line docstring fix.

re-run / gate result
coverage run -m pytest tests/vendor/ · plain unittest 59 passed, 171 subtests · Ran 59, OK — both exit 0
pcapkit/vendor/default.py coverage 65% → 69% (66→58 of 210) reproduces exactly at the PR's stated scope; against origin/main it reads 67%→69% (202 stmts) — same result, different baseline, not a contradiction
merge into 9b2d927c2 → tests/vendor/ clean, 0 conflicts; 59 passed / Ran 59 OK, exit 0
CI · body 20 pass, 3 skipped-by-design gates, 0 failed, 0 cancelled; BEHIND = main moved · Part of #732. ✓, one type ticked (fix) ✓, make test unticked with counts in prose ✓

Nit: the guard fires after _request()'s IANA/Wikipedia fetch (:436 vs :449), so a misplaced crawler pays a network round trip before failing.

✅ GOOD TO MERGE @ c2a336f96 — guard is complete and fail-safe, the raise precedes every write, and the tests now execute in CI; only nits remain (:90-91 prose, post-fetch guard placement).

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 24, 2026
@JarryShaw
JarryShaw merged commit e6d6749 into main Sep 24, 2026
24 checks passed
@JarryShaw
JarryShaw deleted the fix/vendor-nested-const-path branch September 24, 2026 17:36
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…arning count

Round 8: ran the merge-base/changed-files/cited-path intersection to
completion (0c7f2b7..origin/main: 43 commits/124 files; 54 cited paths,
34 of them touched by that range) instead of trusting a tense grep.

- :1540/:1542 -- "93 of the 95 sites"/"48 of the 49 record lengths" ->
  94 of 95 / 49 of 49; zero old-expression sites remain on main.
- :1544-1546, :1566 -- "LOCATOR_SET keeps the old expression ... is
  currently right" / "is unchanged in both respects" -> past tense;
  #679 fixed both LOCATOR_SET sites.
- :1566 -- "HIP_COPIES stays at two" -> past tense; #679 fixed
  LOCATOR_SET's Length unit, #689 then dropped HIP_COPIES to one.
- :1976-1977 -- "pcapng.txt ... wants a separate refresh" -> past
  tense; #685 removed it from the index instead of regenerating it.
- :2327-2328 -- "55 warnings on main before this change, 56 after"
  (-b html) -> 53/54; the raw `grep -c WARNING:` double-counts two
  Scapy import lines as Sphinx warnings, confirmed live on this head
  with both -b dummy and -b html (real 36/raw 38 with const/reg.rst
  excluded, same +2 gap either way).
- :834 -- stale ``protocol.py:1016`` -> ``:1413`` (the actual
  ``self._file.read()`` call inside ``_read_fileng``).
- :1969 -- the #646 entry's coverage renumbering was wrong twice over
  (first ``1153 to 1265``, then ``1153 to 1443``, the latter being
  main's ``def`` line, which always executes and can never be the
  single miss). Corrected to ``1248 to 1360``, the ``warn(...)``
  statement's line before/after #646's own diff.

Regenerated CHANGELOG.md from the edited entries.

changelog_md.py --check: exit 0. pytest tests/project/test_changelog_md.py -q:
47 passed, 37 subtests.

Follow-up: origin/main advanced through #726/#740/#741/#742 (to 0a3abff)
and then #747/#748 (to 074c53e) while this sat at good-to-go; #726 moved
three more claims anchored on files it touched.

- :834 -- ``protocol.py:1413`` -> ``:1411``; #726 shifted the
  ``self._file.read()`` call in ``_read_fileng`` by -2 lines.
- :2382-83 -- traceflow.py "Line 406" -> "Line 424"; #742 inserted 18
  lines above the ``#: Type[Dumper]: Dumper class.`` comment.
- :2099-2104, :2187-89 -- the "seven code-keyed parser registrars" and
  ``Option.register`` are no longer presence-only. #726, fixing #718,
  gave all seven -- and ``Option.register`` itself -- the same identity
  guard ``register_protocol`` already had; reworded both passages to
  say so, confirmed against the guards' own current docstrings.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…arning count

Round 8: ran the merge-base/changed-files/cited-path intersection to
completion (0c7f2b7..origin/main: 43 commits/124 files; 54 cited paths,
34 of them touched by that range) instead of trusting a tense grep.

- :1540/:1542 -- "93 of the 95 sites"/"48 of the 49 record lengths" ->
  94 of 95 / 49 of 49; zero old-expression sites remain on main.
- :1544-1546, :1566 -- "LOCATOR_SET keeps the old expression ... is
  currently right" / "is unchanged in both respects" -> past tense;
  #679 fixed both LOCATOR_SET sites.
- :1566 -- "HIP_COPIES stays at two" -> past tense; #679 fixed
  LOCATOR_SET's Length unit, #689 then dropped HIP_COPIES to one.
- :1976-1977 -- "pcapng.txt ... wants a separate refresh" -> past
  tense; #685 removed it from the index instead of regenerating it.
- :2327-2328 -- "55 warnings on main before this change, 56 after"
  (-b html) -> 53/54; the raw `grep -c WARNING:` double-counts two
  Scapy import lines as Sphinx warnings, confirmed live on this head
  with both -b dummy and -b html (real 36/raw 38 with const/reg.rst
  excluded, same +2 gap either way).
- :834 -- stale ``protocol.py:1016`` -> ``:1413`` (the actual
  ``self._file.read()`` call inside ``_read_fileng``).
- :1969 -- the #646 entry's coverage renumbering was wrong twice over
  (first ``1153 to 1265``, then ``1153 to 1443``, the latter being
  main's ``def`` line, which always executes and can never be the
  single miss). Corrected to ``1248 to 1360``, the ``warn(...)``
  statement's line before/after #646's own diff.

Regenerated CHANGELOG.md from the edited entries.

changelog_md.py --check: exit 0. pytest tests/project/test_changelog_md.py -q:
47 passed, 37 subtests.

Follow-up: origin/main advanced through #726/#740/#741/#742 (to 0a3abff)
and then #747/#748 (to 074c53e) while this sat at good-to-go; #726 moved
three more claims anchored on files it touched.

- :834 -- ``protocol.py:1413`` -> ``:1411``; #726 shifted the
  ``self._file.read()`` call in ``_read_fileng`` by -2 lines.
- :2382-83 -- traceflow.py "Line 406" -> "Line 424"; #742 inserted 18
  lines above the ``#: Type[Dumper]: Dumper class.`` comment.
- :2099-2104, :2187-89 -- the "seven code-keyed parser registrars" and
  ``Option.register`` are no longer presence-only. #726, fixing #718,
  gave all seven -- and ``Option.register`` itself -- the same identity
  guard ``register_protocol`` already had; reworded both passages to
  say so, confirmed against the guards' own current docstrings.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at 948ac49 came back NEEDS CHANGES: the round-12 edit fixed two
sites of the harmonisation claim and left its twin, plus its own reasoning,
asserting the opposite; and four numbers anchored on files the merges touched
had drifted independently of #726.

- :1877-1878, :1883-1884 (#675) -- "carries the guarded ``if code in
  cls.__xxx__: warn(...)``" / "every sibling warns on mere presence" ->
  past tense, noting #726 later gave all seven the identity guard this
  entry's own comparison assumes they lack.
- :2100-2109 -- dropped the retained "yields two keys and never reaches
  one key twice" (false: ``Internet.register(TransType.TCP, TCP)`` warns
  once, incumbent.klass is TCP) and "leaves a different-class test
  undecidable" (contradicted by :2404-2406's own ``incumbent is not
  protocol`` definition); replaced with the actual false positive the
  guard has -- pre-seeded ``ModuleDescriptor`` incumbents never compare
  equal to the resolved class.
- :2192 -- reflowed the ``Option.register`` paragraph (orphan lines
  fixed alongside).
- :2387-2388 -- the ``Type[Dumper]`` quote now matches what is actually
  at line 424 (post-#709-fix), rather than the pre-fix bare form.
- :945 -- ``README.md`` (103) -> (102).
- :1136 -- "75 of the 117 modules" -> "77 ... after #647 below adds
  the same ending to three more" (drifted via #647, independent of the
  four merges).
- :2119 -- dropped the irreproducible pylint "364 messages" figure;
  kept mypy's 112, which does reproduce.
- :2119 -- "326 registry writes" -> 327 (``R1CounterParameter``'s
  second code, from #690).

Also fixed six false claims in the PR body (separate from the .rst):
hunk/line counts, six-commits -> 46, the 5-row table's implied total,
"not trimmed", main's red/green state, and the now-unreachable
cherry-pick target.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…arning count

Round 8: ran the merge-base/changed-files/cited-path intersection to
completion (0c7f2b7..origin/main: 43 commits/124 files; 54 cited paths,
34 of them touched by that range) instead of trusting a tense grep.

- :1540/:1542 -- "93 of the 95 sites"/"48 of the 49 record lengths" ->
  94 of 95 / 49 of 49; zero old-expression sites remain on main.
- :1544-1546, :1566 -- "LOCATOR_SET keeps the old expression ... is
  currently right" / "is unchanged in both respects" -> past tense;
  #679 fixed both LOCATOR_SET sites.
- :1566 -- "HIP_COPIES stays at two" -> past tense; #679 fixed
  LOCATOR_SET's Length unit, #689 then dropped HIP_COPIES to one.
- :1976-1977 -- "pcapng.txt ... wants a separate refresh" -> past
  tense; #685 removed it from the index instead of regenerating it.
- :2327-2328 -- "55 warnings on main before this change, 56 after"
  (-b html) -> 53/54; the raw `grep -c WARNING:` double-counts two
  Scapy import lines as Sphinx warnings, confirmed live on this head
  with both -b dummy and -b html (real 36/raw 38 with const/reg.rst
  excluded, same +2 gap either way).
- :834 -- stale ``protocol.py:1016`` -> ``:1413`` (the actual
  ``self._file.read()`` call inside ``_read_fileng``).
- :1969 -- the #646 entry's coverage renumbering was wrong twice over
  (first ``1153 to 1265``, then ``1153 to 1443``, the latter being
  main's ``def`` line, which always executes and can never be the
  single miss). Corrected to ``1248 to 1360``, the ``warn(...)``
  statement's line before/after #646's own diff.

Regenerated CHANGELOG.md from the edited entries.

changelog_md.py --check: exit 0. pytest tests/project/test_changelog_md.py -q:
47 passed, 37 subtests.

Follow-up: origin/main advanced through #726/#740/#741/#742 (to 0a3abff)
and then #747/#748 (to 074c53e) while this sat at good-to-go; #726 moved
three more claims anchored on files it touched.

- :834 -- ``protocol.py:1413`` -> ``:1411``; #726 shifted the
  ``self._file.read()`` call in ``_read_fileng`` by -2 lines.
- :2382-83 -- traceflow.py "Line 406" -> "Line 424"; #742 inserted 18
  lines above the ``#: Type[Dumper]: Dumper class.`` comment.
- :2099-2104, :2187-89 -- the "seven code-keyed parser registrars" and
  ``Option.register`` are no longer presence-only. #726, fixing #718,
  gave all seven -- and ``Option.register`` itself -- the same identity
  guard ``register_protocol`` already had; reworded both passages to
  say so, confirmed against the guards' own current docstrings.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at 948ac49 came back NEEDS CHANGES: the round-12 edit fixed two
sites of the harmonisation claim and left its twin, plus its own reasoning,
asserting the opposite; and four numbers anchored on files the merges touched
had drifted independently of #726.

- :1877-1878, :1883-1884 (#675) -- "carries the guarded ``if code in
  cls.__xxx__: warn(...)``" / "every sibling warns on mere presence" ->
  past tense, noting #726 later gave all seven the identity guard this
  entry's own comparison assumes they lack.
- :2100-2109 -- dropped the retained "yields two keys and never reaches
  one key twice" (false: ``Internet.register(TransType.TCP, TCP)`` warns
  once, incumbent.klass is TCP) and "leaves a different-class test
  undecidable" (contradicted by :2404-2406's own ``incumbent is not
  protocol`` definition); replaced with the actual false positive the
  guard has -- pre-seeded ``ModuleDescriptor`` incumbents never compare
  equal to the resolved class.
- :2192 -- reflowed the ``Option.register`` paragraph (orphan lines
  fixed alongside).
- :2387-2388 -- the ``Type[Dumper]`` quote now matches what is actually
  at line 424 (post-#709-fix), rather than the pre-fix bare form.
- :945 -- ``README.md`` (103) -> (102).
- :1136 -- "75 of the 117 modules" -> "77 ... after #647 below adds
  the same ending to three more" (drifted via #647, independent of the
  four merges).
- :2119 -- dropped the irreproducible pylint "364 messages" figure;
  kept mypy's 112, which does reproduce.
- :2119 -- "326 registry writes" -> 327 (``R1CounterParameter``'s
  second code, from #690).

Also fixed six false claims in the PR body (separate from the .rst):
hunk/line counts, six-commits -> 46, the 5-row table's implied total,
"not trimmed", main's red/green state, and the now-unreachable
cherry-pick target.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at 1749cc0 came back NEEDS CHANGES: round 13 fixed five of
the nine sites and introduced four new false claims doing it, including
two inside the flagship rewrite -- swapping one inaccuracy for another
is this document's recurring failure mode.

- :1136-37 -- "75 ... 77 now, after #647 ... adds ... three more" was
  internally inconsistent (75+3=78, not 77). Traced #647's own diff
  (fc32d1b): it adds ``_missing_`` to three IntFlag classes across
  only two *new* files -- ``tcp/flags.py`` and ``ftp/command.py`` --
  since the third, ``TransportProtocol``, shares ``reg/apptype.py``
  with the already-counted ``AppType``. Module delta is +2, matching
  75+2=77; reworded to say so.
- :1879-88 -- dropped "the comparison below assumes they still lack"
  it, which was false about text 8 lines below in the same diff
  (already past-tensed). Also reflowed three orphan lines this
  introduced (`passes, whereas`, `it twice with nothing`, `none of
  the`).
- :2107-19 -- "These tables also ship pre-seeded" over-generalised:
  verified live (``ProtocolBase.__proto__`` is 0 entries,
  ``Transport.__proto__ is ProtocolBase.__proto__`` -- True) that 2 of
  7 have nothing pre-seeded. Scoped to the five that do (Link 7,
  Internet 16, Frame 3, PCAPNG 3, SCTP 2). Also fixed "the guard
  resolves only the incoming class", which contradicts the guard's own
  docstring ("the comparison itself resolves nothing") -- resolution is
  the earlier ``isinstance(protocol, ModuleDescriptor)`` step, three
  lines above the guard, not something the guard does.
- :2129-30 -- dropped the invented "327th" ordinal (327 total stays;
  traced-write instrumentation via ``sys`` hooks found the seeding is
  literal dict construction, not ``.register()`` calls, so I could not
  reproduce an ordinal with confidence -- said "one of them" instead
  of guessing).
- :7-8 -- "between #326 and #509" now says the programme continued
  past it (verified: 193 distinct #nnn refs, max #726, 103 above 509).
- PR body -- "7 hunks, 1168+/11-" was the previous head's figure, not
  this one's; replaced with the actual command
  (``git diff --shortstat da697fa -- docs/source/changelog/1.5.0.rst``)
  and today's figure (9 hunks, 1152+/16-), since a hardcoded count here
  has now gone stale twice.

Left alone per this round's scope: :1969/:1974 (before/after claim,
not falsified by #726's later +1), mypy "112" (correct, re-ran with
the project's own flags), ":2122" 13-to-14 (correct at its delta
scope), and the other 121 cited paths (unaffected by main's one new
commit, #745, confirmed test-only).

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…arning count

Round 8: ran the merge-base/changed-files/cited-path intersection to
completion (0c7f2b7..origin/main: 43 commits/124 files; 54 cited paths,
34 of them touched by that range) instead of trusting a tense grep.

- :1540/:1542 -- "93 of the 95 sites"/"48 of the 49 record lengths" ->
  94 of 95 / 49 of 49; zero old-expression sites remain on main.
- :1544-1546, :1566 -- "LOCATOR_SET keeps the old expression ... is
  currently right" / "is unchanged in both respects" -> past tense;
  #679 fixed both LOCATOR_SET sites.
- :1566 -- "HIP_COPIES stays at two" -> past tense; #679 fixed
  LOCATOR_SET's Length unit, #689 then dropped HIP_COPIES to one.
- :1976-1977 -- "pcapng.txt ... wants a separate refresh" -> past
  tense; #685 removed it from the index instead of regenerating it.
- :2327-2328 -- "55 warnings on main before this change, 56 after"
  (-b html) -> 53/54; the raw `grep -c WARNING:` double-counts two
  Scapy import lines as Sphinx warnings, confirmed live on this head
  with both -b dummy and -b html (real 36/raw 38 with const/reg.rst
  excluded, same +2 gap either way).
- :834 -- stale ``protocol.py:1016`` -> ``:1413`` (the actual
  ``self._file.read()`` call inside ``_read_fileng``).
- :1969 -- the #646 entry's coverage renumbering was wrong twice over
  (first ``1153 to 1265``, then ``1153 to 1443``, the latter being
  main's ``def`` line, which always executes and can never be the
  single miss). Corrected to ``1248 to 1360``, the ``warn(...)``
  statement's line before/after #646's own diff.

Regenerated CHANGELOG.md from the edited entries.

changelog_md.py --check: exit 0. pytest tests/project/test_changelog_md.py -q:
47 passed, 37 subtests.

Follow-up: origin/main advanced through #726/#740/#741/#742 (to 0a3abff)
and then #747/#748 (to 074c53e) while this sat at good-to-go; #726 moved
three more claims anchored on files it touched.

- :834 -- ``protocol.py:1413`` -> ``:1411``; #726 shifted the
  ``self._file.read()`` call in ``_read_fileng`` by -2 lines.
- :2382-83 -- traceflow.py "Line 406" -> "Line 424"; #742 inserted 18
  lines above the ``#: Type[Dumper]: Dumper class.`` comment.
- :2099-2104, :2187-89 -- the "seven code-keyed parser registrars" and
  ``Option.register`` are no longer presence-only. #726, fixing #718,
  gave all seven -- and ``Option.register`` itself -- the same identity
  guard ``register_protocol`` already had; reworded both passages to
  say so, confirmed against the guards' own current docstrings.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at 948ac49 came back NEEDS CHANGES: the round-12 edit fixed two
sites of the harmonisation claim and left its twin, plus its own reasoning,
asserting the opposite; and four numbers anchored on files the merges touched
had drifted independently of #726.

- :1877-1878, :1883-1884 (#675) -- "carries the guarded ``if code in
  cls.__xxx__: warn(...)``" / "every sibling warns on mere presence" ->
  past tense, noting #726 later gave all seven the identity guard this
  entry's own comparison assumes they lack.
- :2100-2109 -- dropped the retained "yields two keys and never reaches
  one key twice" (false: ``Internet.register(TransType.TCP, TCP)`` warns
  once, incumbent.klass is TCP) and "leaves a different-class test
  undecidable" (contradicted by :2404-2406's own ``incumbent is not
  protocol`` definition); replaced with the actual false positive the
  guard has -- pre-seeded ``ModuleDescriptor`` incumbents never compare
  equal to the resolved class.
- :2192 -- reflowed the ``Option.register`` paragraph (orphan lines
  fixed alongside).
- :2387-2388 -- the ``Type[Dumper]`` quote now matches what is actually
  at line 424 (post-#709-fix), rather than the pre-fix bare form.
- :945 -- ``README.md`` (103) -> (102).
- :1136 -- "75 of the 117 modules" -> "77 ... after #647 below adds
  the same ending to three more" (drifted via #647, independent of the
  four merges).
- :2119 -- dropped the irreproducible pylint "364 messages" figure;
  kept mypy's 112, which does reproduce.
- :2119 -- "326 registry writes" -> 327 (``R1CounterParameter``'s
  second code, from #690).

Also fixed six false claims in the PR body (separate from the .rst):
hunk/line counts, six-commits -> 46, the 5-row table's implied total,
"not trimmed", main's red/green state, and the now-unreachable
cherry-pick target.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at 1749cc0 came back NEEDS CHANGES: round 13 fixed five of
the nine sites and introduced four new false claims doing it, including
two inside the flagship rewrite -- swapping one inaccuracy for another
is this document's recurring failure mode.

- :1136-37 -- "75 ... 77 now, after #647 ... adds ... three more" was
  internally inconsistent (75+3=78, not 77). Traced #647's own diff
  (fc32d1b): it adds ``_missing_`` to three IntFlag classes across
  only two *new* files -- ``tcp/flags.py`` and ``ftp/command.py`` --
  since the third, ``TransportProtocol``, shares ``reg/apptype.py``
  with the already-counted ``AppType``. Module delta is +2, matching
  75+2=77; reworded to say so.
- :1879-88 -- dropped "the comparison below assumes they still lack"
  it, which was false about text 8 lines below in the same diff
  (already past-tensed). Also reflowed three orphan lines this
  introduced (`passes, whereas`, `it twice with nothing`, `none of
  the`).
- :2107-19 -- "These tables also ship pre-seeded" over-generalised:
  verified live (``ProtocolBase.__proto__`` is 0 entries,
  ``Transport.__proto__ is ProtocolBase.__proto__`` -- True) that 2 of
  7 have nothing pre-seeded. Scoped to the five that do (Link 7,
  Internet 16, Frame 3, PCAPNG 3, SCTP 2). Also fixed "the guard
  resolves only the incoming class", which contradicts the guard's own
  docstring ("the comparison itself resolves nothing") -- resolution is
  the earlier ``isinstance(protocol, ModuleDescriptor)`` step, three
  lines above the guard, not something the guard does.
- :2129-30 -- dropped the invented "327th" ordinal (327 total stays;
  traced-write instrumentation via ``sys`` hooks found the seeding is
  literal dict construction, not ``.register()`` calls, so I could not
  reproduce an ordinal with confidence -- said "one of them" instead
  of guessing).
- :7-8 -- "between #326 and #509" now says the programme continued
  past it (verified: 193 distinct #nnn refs, max #726, 103 above 509).
- PR body -- "7 hunks, 1168+/11-" was the previous head's figure, not
  this one's; replaced with the actual command
  (``git diff --shortstat da697fa -- docs/source/changelog/1.5.0.rst``)
  and today's figure (9 hunks, 1152+/16-), since a hardcoded count here
  has now gone stale twice.

Left alone per this round's scope: :1969/:1974 (before/after claim,
not falsified by #726's later +1), mypy "112" (correct, re-ran with
the project's own flags), ":2122" 13-to-14 (correct at its delta
scope), and the other 121 cited paths (unaffected by main's one new
commit, #745, confirmed test-only).

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.

Cross-review at b2ac58b came back NEEDS CHANGES: round 15 fixed four
sites clean but swapped in two new inaccuracies, and left one
round-fourteen defect (body :26) unfixed.

- :7 -- "past #726" was wrong direction: #726 is the max ref in the
  document (193 distinct, min #251, max #726), not one exceeded ->
  "reaching #726".
- :2110-19 -- "ProtocolBase and Transport share one dict, starting and
  staying empty until a subclass registers" was false three ways,
  verified live against origin/main (pcapkit.__file__ asserted):
  Transport.register() itself raises UnsupportedCall (abstract); TCP
  and UDP keep their own separate __proto__ (4 and 3 entries), not the
  shared one, so registering on them leaves the shared dict at 0; only
  a direct ProtocolBase.register() call fills it. Narrowing to "five
  of these seven" also hid that TCP/UDP are pre-seeded too, which is
  exactly where the false positive bites in the transport family --
  restored that.
- body :26 -- "26 entry commits" -> 27 (commits whose subject starts
  "docs(changelog): the 1.5.0 entry/entries for", verified by grep),
  28 bullets added and 0 removed (verified via the .rst diff against
  da697fa; one commit, 6a956c4, adds two bullets for #648/#649).
- body :41 -- dropped the hardcoded "9 hunks, 1152+/16-" figure
  entirely (it had already drifted to 1154+ by the time of this
  commit) and named the second command needed for the hunk count,
  since --shortstat cannot print one.

On the ordinal question raised last round: dropping it was still right
(the asserted "327th" was wrong), but "no ordinal is derivable" does
not hold either -- the writes are at pcapkit/protocols/schema/schema.py,
not the 8 dict-literal registrar sites my instrumentation covered, and
they are traceable. Left the text as "one of them being
R1CounterParameter's second code" (no ordinal asserted, no false
derivability claim either) rather than reopen a site outside this
round's scope.

Regenerated CHANGELOG.md again. changelog_md.py --check: exit 0. pytest
tests/project/test_changelog_md.py -q: 47 passed, 37 subtests.
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Pull requests that fix a defect (fix: subject prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant