Skip to content

docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) - #657

Open
JarryShaw wants to merge 46 commits into
mainfrom
docs/changelog-1.5.0
Open

JarryShaw wants to merge 46 commits into
mainfrom
docs/changelog-1.5.0

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

This PR is deliberately long-lived. Please do not merge it yet.

It is the shared changelog for the 1.5.0 cycle. Code pull requests in this cycle
ship without their own changelog entry, and their entry is committed here instead.
It stays open and keeps accumulating commits as more code PRs land, and it merges
last, once the code PRs are settled.

If you have arrived here wondering whether it is stalled: it is not. An open,
growing diff is the intended steady state.

Why it exists

Five open PRs each added one bullet to docs/source/changelog/1.5.0.rst at the same
anchor and regenerated CHANGELOG.md beside it. That made them mutually exclusive
rather than independent — whichever merged first moved the anchor, and the other four
immediately re-conflicted on the two changelog files, even though no code file of
theirs overlapped at all
. That cycle had already cost this wave thirteen rebases.

Separating the changelog from the code breaks the cycle: the code PRs stop contending
for a shared file, and the contention is concentrated in one place — here — where it
is a sequence of appends rather than a conflict.

What it currently covers

One commit per code PR, so it is obvious what is and is not accounted for:

Entry Issue Code PR
TCP.read seeding its flag accumulator with a typing.cast no-op #616 #634
Frame.len / cap_len swapped between the PCAP and PCAP-NG readers #618 #635
Extractor closing the caller's stream and leaking its own #610 #636
extract(..., no_eof=True) never returning #620 #639
a construction keyword no signature declares now refused #617 #640

Every bullet is the verbatim block its own PR added — not reworded, not reflowed,
not trimmed, and not reordered internally. The entry-file diff is a single hunk of 197
added lines with zero deletions, which is the mechanical guarantee of that.

Two deliberate choices

It is not squashed, and should not be. The one-commit-per-PR convention does not
fit a PR that accumulates over days. A commit per code PR keeps it reviewable and
makes coverage self-evident. Please do not "tidy" it into a single commit.

The first commit is a fix, not an entry. 2c4212a02 regenerates CHANGELOG.md,
which has been drifted since 375e9d411 (#638) hand-inserted three lines into the
generated file instead of running util/changelog_md.py. That left a stale copy of
the #630 bullet sitting below its own correction, and stranded the #631 bullet after
#638.

That drift is why main is currently red, and it is unrelated to any of the five:
python util/changelog_md.py --check exits 1 on main, failing the Changelog drift
job and all twelve matrix jobs, the latter through the same assertion in
tests/project/test_changelog_md.py::RepositoryStateTests. Measured on run
35748204467 (a62aed134): 13 of 14 jobs failed.

It is kept as the first commit specifically so it can be cherry-picked ahead of the
rest of this branch
if you want main green before this PR merges:

git cherry-pick 2c4212a02

python util/changelog_md.py --check exits 0 at every one of the six commits here.

Still to come on this branch

  • An editorial pass restructuring the 1.5.0 entry — an executive summary up front,
    detail below — which is tracked separately and lands here as its own commit.
  • Further entries as more code PRs land.

The bullet #634 originally carried, moved here verbatim so that #634 touches only
`pcapkit/protocols/transport/tcp.py` and its two test files.

Covers: `TCP.read` seeding its connection-flag accumulator with a `typing.cast`
no-op rather than `Flags(0)`, so a flagless segment left `self._flags` a plain
`int`.

35 lines added to the entry file; `CHANGELOG.md` regenerated, not edited.
The bullet #635 originally carried, moved here verbatim so that #635 touches only
the two `Frame` modules, `pcapkit/toolkit/pcapng.py` and its two test files.

Covers: the breaking change to a public attribute -- `Frame.len` is the on-wire
length and `cap_len` the captured one, which the PCAP and PCAP-NG readers had
filled from opposite wire fields.

41 lines added to the entry file; `CHANGELOG.md` regenerated, not edited.
The bullet #636 originally carried, moved here verbatim so that #636 touches only
`pcapkit/foundation/extraction.py` and its two test files.

Covers: `Extractor` closing the caller's input stream and leaking the one it
opened itself, both handlers now reading a single `_owns_input` predicate.

23 lines added to the entry file; `CHANGELOG.md` regenerated, not edited.
The bullet #639 originally carried, moved here verbatim so that #639 touches only
`pcapkit/foundation/extraction.py`, `pcapkit/interface/core.py` and its three test
files.

Covers: `extract(..., no_eof=True)` never returning, and the progress check that
now ends it -- including the deliberate narrowing for a seekable input still being
appended to.

43 lines added to the entry file; `CHANGELOG.md` regenerated, not edited.
The bullet #640 originally carried, moved here verbatim so that #640 touches only
`pcapkit/protocols/protocol.py`, `pcapkit/protocols/application/http.py`,
`docs/source/ext.rst`, `examples/generators/dispatch.py` and its six test files.

Covers: the behaviour change to a public API -- building a protocol through its
constructor with a keyword no signature declares now raises `UnsupportedCall`
instead of discarding it.

55 lines added to the entry file; `CHANGELOG.md` regenerated, not edited.
JarryShaw added a commit that referenced this pull request Sep 22, 2026
…t values (#653, #654, #655)

`_make_param_puzzle` and `_make_param_solution` derived three wire-format
quantities from the payload value rather than taking them from the data model.
All three derivations were wrong, in the same two functions, and they are fixed
together because the width resolution is one expression that cannot be written
twice.

* The field width came from `int.bit_length()` and nothing else, so every
  leading zero octet was dropped on re-serialisation. A SOLUTION read with
  `Length = 20` rebuilt as `Length = 6`, a PUZZLE read with `Length = 12` as
  `Length = 5` -- silently, since the integers survive and nothing raises. Both
  data models now carry `rhash_len`, the field's on-wire width in bits, and both
  builders prefer it. This only became reachable end to end once #608 was fixed
  (#629); before that the undersized rebuild tripped the reader's parity guard
  first and failed loudly. (#653)
* SOLUTION's second contents octet is `Reserved`, "zero when sent, ignored when
  received" (RFC 7401 5.2.5, RFC 5201 5.2.5 identically) -- not a PUZZLE
  `Lifetime`, which only RFC 7401 5.2.4 defines. pcapkit wrote `0x20`, `0x21`,
  `0x25` or `0x2b` there and could not write the mandated zero at all: a
  conformant SOLUTION with `Reserved = 0x00` parsed to `timedelta(0)` and then
  escaped a bare `ValueError` from `math.log2(0.0)`. `Data_SolutionParameter`
  and `SolutionParameter` now carry `reserved` verbatim, so the conformant zero
  round-trips and a received non-zero octet is reproduced rather than
  re-derived. (#654)
* Neither builder read its own `version` keyword, so `version=1` and
  `version=2` computed identical lengths at every bit width. RFC 5201 5.2.4 and
  5.2.5 state both fields as literally 8 bytes and `Length` as literally 12 and
  20, and the readers enforce exactly that -- so under HIPv1 each builder
  accepted only a `bit_length()` of 57..64 and built, for everything else, a
  parameter this library's own reader rejects. Width now comes from the version
  under HIPv1. (#655)

The remaining `math.log2` sites, both in PUZZLE where a `Lifetime` really
lives, now raise `ProtocolError` rather than letting `ValueError` escape.
`ProtocolError(BaseError, ValueError)` is what the readers already raise for a
malformed PUZZLE or SOLUTION, and deriving from the builtin it replaces keeps
any caller written around today's bare `ValueError` working; `EnumError` is
`(BaseError, TypeError)` and would silently stop being caught. The same guard
covers the upper end, because `UInt8Field` wraps rather than raising --
measured, `300` packs as `0x2c` -- so an out-of-range lifetime would otherwise
be written as some other valid-looking duration. A plain `float` lifetime used
to escape an `AttributeError` from the `isinstance(lifetime, int)` branch; the
test keyed on `timedelta` instead found that.

`_make_param_solution` no longer accepts `lifetime=`. `reserved=` and
`rhash_len=` are both `None`-sentinelled, so an explicit value overrides what a
parsed parameter carries: `Data_SolutionParameter` is immutable, so without that
a caller holding a parsed parameter had no way to write the conformant zero over
a peer's non-conformant `Reserved`. The plain data fields still let `param` win,
as they did before. `rhash_len=` is also the only way a from-scratch HIPv2 build
can state a width, which genuinely varies with the Responder's HIT Suite
(RFC 7401 2.3, 5.2.10).

Five new tests, each verified to fail on 0c7f2b7 for the defect's own reason
and pass here: `12 != 5` and friends for #655, `expected a positive input` and
`'reserved' not found` for #654, `no attribute 'rhash_len'` for #653. The
widths exercised are 1, 9, 15, 17, 57 and 65 bits, where the candidate formulas
disagree, with the byte-aligned widths kept as controls -- at a multiple of 8
the correct `2 * ceil(b / 8)` coincides with #608's `ceil(b / 4)`, which is why
every byte-aligned fixture passed through that defect unharmed.

Two cases are deliberately accepted rather than rejected, and now say so in
`_make_puzzle_field_width`'s docstring: `rhash_len == 0`, which is what the
derived path yields for the default `random=0` and what a `Length = 4`
parameter parses back to; and a `version` other than 1, which is treated as
HIPv2 exactly as both readers' `version == 1` guards do.

Coverage holds at 100% statement and branch on all three changed modules, with
statements 1385 -> 1414 and branches 316 -> 338; 30 tests / 434 subtests ->
35 / 455. Scoped unit tier green: 291 passed, 1191 subtests. No
EXPECTED_FAILURES entry moved -- still 45, with the same four HIP entries.

No changelog entry: that is consolidated in #657.
JarryShaw added a commit that referenced this pull request Sep 22, 2026
…t values (#653, #654, #655)

`_make_param_puzzle` and `_make_param_solution` derived three wire-format
quantities from the payload value rather than taking them from the data model.
All three derivations were wrong, in the same two functions, and they are fixed
together because the width resolution is one expression that cannot be written
twice.

* The field width came from `int.bit_length()` and nothing else, so every
  leading zero octet was dropped on re-serialisation. A SOLUTION read with
  `Length = 20` rebuilt as `Length = 6`, a PUZZLE read with `Length = 12` as
  `Length = 5` -- silently, since the integers survive and nothing raises. Both
  data models now carry `rhash_len`, the field's on-wire width in bits, and both
  builders prefer it. This only became reachable end to end once #608 was fixed
  (#629); before that the undersized rebuild tripped the reader's parity guard
  first and failed loudly. (#653)
* SOLUTION's second contents octet is `Reserved`, "zero when sent, ignored when
  received" (RFC 7401 5.2.5, RFC 5201 5.2.5 identically) -- not a PUZZLE
  `Lifetime`, which only RFC 7401 5.2.4 defines. pcapkit wrote `0x20`, `0x21`,
  `0x25` or `0x2b` there and could not write the mandated zero at all: a
  conformant SOLUTION with `Reserved = 0x00` parsed to `timedelta(0)` and then
  escaped a bare `ValueError` from `math.log2(0.0)`. `Data_SolutionParameter`
  and `SolutionParameter` now carry `reserved` verbatim, so the conformant zero
  round-trips and a received non-zero octet is reproduced rather than
  re-derived. (#654)
* Neither builder read its own `version` keyword, so `version=1` and
  `version=2` computed identical lengths at every bit width. RFC 5201 5.2.4 and
  5.2.5 state both fields as literally 8 bytes and `Length` as literally 12 and
  20, and the readers enforce exactly that -- so under HIPv1 each builder
  accepted only a `bit_length()` of 57..64 and built, for everything else, a
  parameter this library's own reader rejects. Width now comes from the version
  under HIPv1. (#655)

The remaining `math.log2` sites, both in PUZZLE where a `Lifetime` really
lives, now raise `ProtocolError` rather than letting `ValueError` escape.
`ProtocolError(BaseError, ValueError)` is what the readers already raise for a
malformed PUZZLE or SOLUTION, and deriving from the builtin it replaces keeps
any caller written around today's bare `ValueError` working; `EnumError` is
`(BaseError, TypeError)` and would silently stop being caught. The same guard
covers the upper end, because `UInt8Field` wraps rather than raising --
measured, `300` packs as `0x2c` -- so an out-of-range lifetime would otherwise
be written as some other valid-looking duration. A plain `float` lifetime used
to escape an `AttributeError` from the `isinstance(lifetime, int)` branch; the
test keyed on `timedelta` instead found that.

`_make_param_solution` no longer accepts `lifetime=`. `reserved=` and
`rhash_len=` are both `None`-sentinelled, so an explicit value overrides what a
parsed parameter carries: `Data_SolutionParameter` is immutable, so without that
a caller holding a parsed parameter had no way to write the conformant zero over
a peer's non-conformant `Reserved`. The plain data fields still let `param` win,
as they did before. `rhash_len=` is also the only way a from-scratch HIPv2 build
can state a width, which genuinely varies with the Responder's HIT Suite
(RFC 7401 2.3, 5.2.10).

Five new tests, each verified to fail on 0c7f2b7 for the defect's own reason
and pass here: `12 != 5` and friends for #655, `expected a positive input` and
`'reserved' not found` for #654, `no attribute 'rhash_len'` for #653. The
widths exercised are 1, 9, 15, 17, 57 and 65 bits, where the candidate formulas
disagree, with the byte-aligned widths kept as controls -- at a multiple of 8
the correct `2 * ceil(b / 8)` coincides with #608's `ceil(b / 4)`, which is why
every byte-aligned fixture passed through that defect unharmed.

The byte-exact assertions compare the parameter without its trailing padding,
and then against the re-packed source schema rather than against a literal, so
they pin the `Length` field and the payload octets without encoding a padding
rule that #651/#664 is concurrently changing. Verified against a `git
merge-tree` of this branch and #664: both library files auto-merge with no
conflict, and all five new tests pass against the merged library.

Two cases are deliberately accepted rather than rejected, and now say so in
`_make_puzzle_field_width`'s docstring: `rhash_len == 0`, which is what the
derived path yields for the default `random=0` and what a `Length = 4`
parameter parses back to; and a `version` other than 1, which is treated as
HIPv2 exactly as both readers' `version == 1` guards do.

Coverage holds at 100% statement and branch on all three changed modules, with
statements 1385 -> 1414 and branches 316 -> 338; 30 tests / 434 subtests ->
35 / 455. Scoped unit tier green: 291 passed, 1191 subtests. No
EXPECTED_FAILURES entry moved -- still 45, with the same four HIP entries.

No changelog entry on this branch: that is consolidated in #657.
…ships in #665

The bullet #665 would otherwise have carried, kept here so that #665 touches only
`pcapkit/protocols/internet/hip.py`, `pcapkit/protocols/schema/internet/hip.py`,
`pcapkit/protocols/data/internet/hip.py` and `tests/protocols/internet/test_hip_unit.py`.

Covers all three as one bullet, because they are one root cause: the HIP `PUZZLE`
and `SOLUTION` builders derived the field width, the `Reserved` octet and the
version-dependent length from the payload value instead of from the data model.
Splitting the entry would tell the story three times and explain it none.

Two public data models change, so the bullet says so in bold and carries a
migration sentence: `SolutionParameter.lifetime` becomes `reserved` and an `int`
rather than a `timedelta`, both parameter models gain a required `rhash_len`, and
`_make_param_solution` no longer takes `lifetime=`.

46 lines added to the entry file; `CHANGELOG.md` regenerated with
`util/changelog_md.py`, not edited. `--check` exits 0 and `tests/project/` is green
at 96 passed, 469 subtests.

Committed from a detached HEAD on 69a6e13 and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree at a stale
f846523 and could not be taken here.
The bullet #664 would otherwise have carried, kept here so that #664 touches only
`pcapkit/protocols/internet/hip.py`, `pcapkit/protocols/schema/internet/hip.py`,
`tests/protocols/internet/test_hip_unit.py`,
`tests/protocols/test_option_roundtrip_unit.py`,
`examples/generators/options.py` and `docs/source/pcapkit/protocols/internet/hip.rst`.

One bullet, because it is one root cause in 95 places: every HIP padding site
aligned the parameter's *contents* to eight octets rather than the record,
ignoring the four-octet type-and-length header, so every parameter pcapkit wrote
was `4 (mod 8)` for every possible `Length`.

The bullet says in bold that both the emitted octets and the data model's
reported `length` change, and carries a migration sentence: a `SEQ` parameter's
`length` is 8 where it was 12, so code comparing stored output byte for byte or
asserting on `Data_*Parameter.length` sees different values.

It also records what was deliberately *not* changed, since both look like part of
the same defect and are not: `HIP.make`'s `len = total_length // 8 + 4`, which
RFC 7401 section 5.1.3 shows is correct and merely needed 8-aligned parameters;
and `HIP_COPIES`, which stays at two for `R1_COUNTER`'s four-octet `counter`
against section 5.2.3's eight -- a separate, still-unfiled defect this one had
been masking. The `EncryptedParameter.data` length callback is named as fixed in
the same change because the two four-octet errors cancelled at four of the eight
residues of `Length`, so correcting the padding alone would have regressed it.

41 lines added to the entry file; `CHANGELOG.md` regenerated with
`util/changelog_md.py`, not edited. `--check` exits 0 and `tests/project/` is
green at 96 passed, 469 subtests.

Committed from a detached HEAD on 2ce3687 and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree at a stale
f846523 and could not be taken here. Note 2ce3687, not the 69a6e13 I was
given: the branch had already moved on with #665's entry.
The bullet #667 would otherwise have carried, kept here so that #667 touches only
`pcapkit/corekit/multidict.py` and `tests/corekit/test_multidict.py`.

One bullet, because it is one convention gap in one class: `_Missing` behind
`MultiDict.pop` and `OrderedMultiDict.pop` lacked the `@final` and the falsy
`__bool__` that `NoValueType` in `pcapkit.corekit.fields.field` sets as the
package's convention for a marker of this kind.

The bullet says plainly that no behaviour changes, and says why rather than
asserting it: both `pop()` implementations decide by identity, never by
truthiness, and `pop()` structurally cannot return the marker -- it returns
`default` only on the branch where `default is not _missing`. It also names the
one way the old truthiness was observable, which is what justifies touching it at
all: `inspect.signature(MultiDict.pop).parameters['default'].default` hands the
marker to any caller who asks, and `if default:` on it reported "a default was
supplied" where none had been.

It closes by recording the disposition of the other two sites from the #640
sweep, so the entry is the whole story: site 1 needed nothing, and `_NOT_FOUND`
in `pcapkit.utilities.compat` stays a bare `object()` deliberately, being a
verbatim line of CPython's `functools.cached_property` inside a
`sys.version_info < (3, 8)` branch no supported interpreter reaches. The
reasoning behind that one is on #661, not here.

`:obj:` roles had to come out: `util/changelog_md.py` rejects them with
`ResidualMarkupError`, since its six conversion rules do not cover interpreted
text and `CHANGELOG.md` would carry the role through as literal text. Double
backticks instead, which is what the rest of the entry file uses.

20 lines added to the entry file; `CHANGELOG.md` regenerated with
`util/changelog_md.py`, not edited. `--check` exits 0 and `tests/project/` is
green at 96 passed, 469 subtests.

Committed from a detached HEAD on e55ba36 and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree at a stale
f846523 and could not be taken here. Note e55ba36, not the 69a6e13 I was
given: the branch had already moved on with #665's and #651's entries.

Refs #661
#669

One bullet, because it is one round trip with a defect on each side of it, in the
same two files: `_make_http_data` never read `frame.flags` on the construct side,
and `FrameType.post_process` seeded its accumulator with a bare `0` on the parse
side.

The bullet leads with what changes rather than with the mechanism, since both
halves alter output: the reconstructed DATA frame's flags octet, and the dumped
`__value__` of a flagless frame. It says why #650 was worth fixing at all, which
its issue had left as an open question -- the dump rendered `__value__` as a JSON
number for a flagless frame and a JSON string for every other frame in the same
capture, so the fix removes a type inconsistency rather than introducing one.

It also records two things a reader would otherwise be surprised by. The seed is
guarded rather than unconditional, because `FrameType.Flags` has no members and a
memberless `enum.Flag` subclass refuses `Flags(0)` -- the one-token fix the issue
proposed would have crashed six of the twelve frame schemas. And a DATA round trip
is still lossy after this, for the unrelated mis-parenthesised length callbacks
filed as #668, so the entry does not let the reader infer a clean round trip that
does not exist yet.

The `TypeError` message had to sit on one line: `util/changelog_md.py` rejects a
`` literal spanning a line break with `ResidualMarkupError`, since its six
conversion rules do not cover it.

37 lines added to the entry file; `CHANGELOG.md` regenerated with
`util/changelog_md.py`, not edited. `--check` exits 0 and `tests/project/` is green
at 96 passed, 469 subtests.

Committed from a detached HEAD on 367b6e6 and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree and could not be
taken here. Note 367b6e6, not the 69a6e13 I was given: the branch had already
moved on with #665's, #651's and #661's entries.

Refs #652
Refs #650
…s in #670 and #671

Two bullets, not one, because the two defects are unrelated: one changes what the
dumpers emit, the other only the text of four exception messages. They share a
file only by accident of being found in the same pass.

The #648 bullet leads with the output change and says so in bold, because that is
what a reader upgrading needs to see: a flag value with no declared bits dumped
as `Type::None [0]` in all six textual format names, out of both `Extractor` and
`TraceFlow`. It then justifies the *replacement* rather than just stating it,
since "render it as its decimal value" looks arbitrary until you know the
enumeration libraries already spell an undeclared residue that way -- and that a
decimal cannot collide with a member name where `None` can, `NONE` being a real
declared name elsewhere. Three things a reader would otherwise get wrong are
recorded: three sites carried the interpolation and not one, the guard is on
`name is None` rather than on zero because the defect never was about zero, and
it is not an `aenum` quirk since stdlib `enum.IntFlag` behaves identically.

It also corrects the issue on a point of fact. #648 said `Flags` was the only
registry nameless at zero; a sweep of all seven finds five, the four Mobility
Header flag registries included. And it states that the committed example dumps
do not move, which was measured by regenerating all three with and without the
change rather than assumed -- a reader of a bullet this emphatic will otherwise
wonder whether `examples/captures/` drifted.

The #649 bullet says "cosmetic" in its second sentence so nobody reads it as a
behavioural change, then gives the one reason it was worth doing at all: it is
the text a user sees when an option is rejected. It names all four sites, and the
28-against-4 count in the same file, because that count is what makes the correct
form a fact about the module rather than a preference.

Neither bullet claims a guard it does not have: #648's third site, the `addon`
branch, is not reachable from any registry in the library today, and the bullet
does not imply otherwise.

`CHANGELOG.md` regenerated with `util/changelog_md.py`, not edited by hand.
`--check` exits 0 and `tests/project/` is green at 96 passed, 469 subtests. Each
`` literal is kept on one line, since the generator rejects one spanning a line
break with `ResidualMarkupError`.

Committed from a detached HEAD on d14577d and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree and could not be
taken here. Note d14577d, not the 69a6e13 I was given: the branch had already
moved on with #665's, #651's, #661's and #652/#650's entries.

Refs #648
Refs #649
…entry

The bullet added in d14577d said a memberless `enum.Flag` subclass "refuses
`Flags(0)` outright", flatly. That is only true from Python 3.12, where the enum
rewrite made `EnumType.__call__` raise for an enum with no members; earlier
interpreters take the plain value-lookup path and hand back a pseudo-member.
Measured on the two available here:

    version 3.14.7  members: 0  Flags(0) -> TypeError: <flag 'Flags'> has no members
    version 3.7.16  members: 0  Flags(0) -> OK <Flags.0: 0>

`requires-python` is `>=3.6`, so the unqualified form overstated it. Three words
added, no other change to the bullet: the guard in #669 is correct on every
supported interpreter either way, being keyed on the memberless-ness rather than
on the refusal.

The same imprecision was corrected in #669's own source comment and PR body, and
its test now gates only the `TypeError` assertion behind
`sys.version_info >= (3, 12)` -- CI runs the unit tier on 3.10 through 3.15, so an
unconditional `assertRaises` would have gone red on the older two.

`CHANGELOG.md` regenerated with `util/changelog_md.py`, not edited; `--check` exits
0 and `tests/project/` is green at 96 passed, 469 subtests.

Committed from a detached HEAD on 6a956c4 and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree. The branch had
moved on with #648's and #649's entries since d14577d.

Refs #652
Refs #650
JarryShaw added a commit that referenced this pull request Sep 22, 2026
…a false packaging claim (#642)

Three names appeared in string annotations that their module never imported, and
they were mypy's complete set of ``name-defined`` findings for the package:

* ``pcapkit/utilities/logging.py:350`` used ``Any``; added to the
  ``TYPE_CHECKING`` block beside ``IO``, ``Optional`` and ``Union``.
* ``pcapkit/protocols/schema/internet/ipv6_route.py:136`` used ``Protocol`` and
  ``:271`` used ``Optional``; added, ``Protocol`` as
  ``ProtocolBase as Protocol``. Twenty-one sibling schema modules already spell
  it that way, in the same ``payload:`` stub; this makes twenty-two.

mypy 2.3.1 over ``pcapkit``: 3 ``name-defined`` errors before, 0 after; 115 total
errors before, 112 after, so nothing else moved.

``MANIFEST.in:14-17`` asserted that ``include README.md`` was "the only thing
that puts it in an sdist" and that an sdist without it "cannot be installed at
all". Both halves are false, and the file had contradicted itself since #631
wrote the correct mechanism seven lines below without correcting this. Deleting
the lines and rebuilding gives a byte-identical sdist listing -- empty diff --
that installs with exit 0: ``setuptools/command/sdist.py:59-60`` ships the README
unconditionally and ``setuptools/dist.py:460``'s default ``license_files`` glob
ships ``LICENSE``. Of the original three ``include`` lines only ``CHANGELOG.md``
is load-bearing. The comment now says that.

New ``tests/project/test_annotation_names.py`` resolves every string annotation
in the package -- following a nested forward reference such as
``'list["Nested"]'``, while treating ``Literal`` members and ``Annotated``
metadata as the values they are -- against the names its own module binds. It
reports the same three findings as mypy on the unfixed tree and none after.

That module named ``ast.TypeAlias`` and ``ast.TypeVar`` directly, and both are
PEP 695 nodes added in Python 3.12, so *every* test in it raised
``AttributeError`` on the 3.10 and 3.11 matrix jobs -- ``bound_names`` walks every
node of every file, so the attribute is reached whatever a test does. Both are now
resolved once at module scope through ``getattr(ast, ..., ())``, leaving the
``isinstance`` branches otherwise untouched: ``isinstance(x, ())`` is always
False, so the branches stay live on 3.12+ and are simply unreachable below it.
Chosen over a ``sys.version_info`` comparison because it writes no version number
down at all -- a comparison states 3.12 next to the attribute it guards, and the
two can then drift -- and over a per-node ``getattr`` because a module-level
constant lifts the lookup out of a loop that runs on every node of every file.

``ast.TypeVar`` is the branch that earns its keep: it carries its name as a bare
``str`` and emits no ``ast.Name`` node, so forcing ``_TYPE_VAR`` to ``()`` on
3.14.7 turns ``T`` and ``U`` into false findings. ``ast.TypeAlias`` is defensive
by comparison -- its name *is* an ``ast.Name`` in ``Store`` context, which the
preceding branch already catches -- and is left as it stands rather than removed.
A new ``test_a_pep695_type_parameter_is_in_scope`` pins both the guards and the
behaviour, skipped below 3.12 because its fixture source cannot parse there.

Measured on real interpreters rather than simulated. 3.10.21 and 3.11.15:
5 failed, exit 1 -> 5 passed, 1 skipped, exit 0. 3.14.7: all 6 pass, exit 0.
133 passed over ``tests/project`` and ``tests/utilities/test_logging.py``, exit 0,
subtests unchanged at 487.

No changelog entry on this branch. Per the rule that no code branch touches
``CHANGELOG.md`` or anything under ``docs/source/changelog/``, this change's entry
-- and the wording correction the ``MANIFEST.in`` claim implies for the #619
entry, plus the missing ``(#570)`` and ``(#577)`` citations -- go to the shared
changelog pull request #657 instead.
The `pypi` job's `environment: release` was commented out and `conda` never had
one, so a scheduled vendor bump could publish to PyPI and Anaconda unapproved.
Four jobs are now gated, one environment per credential, and the entry says
plainly that the gate is inert until the environments carry required reviewers.

Regenerated CHANGELOG.md with `util/changelog_md.py`; `--check` exits 0.
…11, not 3.12

b2ec64b qualified the claim with the wrong version. The refusal does not start
with a 3.12 change -- it starts at 3.11, and 3.12 only reworded the message. The
cross-review on #669 caught it; I had inferred 3.12 from the message text I
happened to measure on, which is the wrong evidence for a boundary.

Measured across every interpreter available here rather than inferred, with a bare
`class Flags(enum.IntFlag): pass`:

    3.8.20   Flags(0) -> OK <Flags.0: 0>
    3.9.25   Flags(0) -> OK <Flags.0: 0>
    3.10.21  Flags(0) -> OK <Flags.0: 0>
    3.11.15  Flags(0) -> TypeError: <flag 'Flags'> has no members defined
    3.12.13  Flags(0) -> TypeError: ... has no members; specify `names=()` ...
    3.14.7   Flags(0) -> TypeError: ... has no members; specify `names=()` ...

Confirmed in CPython's source, not just behaviourally. 3.11's `enum.py:1117`, inside
`Enum.__new__`, raises `TypeError("%r has no members defined" % cls)` when
`not cls._member_map_`, and it runs *before* the `_missing_` hook that manufactured
the pseudo-member on 3.10. 3.10's `enum.py` has no such raise -- its only "has no
members" occurrence is a comment at :616. 3.11 is also where the metaclass was
renamed (`class EnumType(type)` at :479 with `EnumMeta = EnumType` at :1052, against
3.10's `class EnumMeta(type)` at :161), so "the enum rewrite" is the 3.11 release.

One word in the bullet. #669 carries the matching correction to its source comment
and to its test's `sys.version_info` gate, which had been skipping the assertion on
3.11 -- a version the unit-test matrix runs -- even though 3.11 does raise.

`CHANGELOG.md` regenerated with `util/changelog_md.py`, not edited; `--check` exits 0
and `tests/project/` is green at 96 passed, 469 subtests.

Committed from a detached HEAD on b2ec64b and pushed to the branch ref, because
`docs/changelog-1.5.0` is checked out in another agent's worktree.

Refs #652
Refs #650
@JarryShaw

Copy link
Copy Markdown
Owner Author

Second-round fixes -- head 5301a3829 (Sonnet, self)

Fixed the eight defects the cross-review at #issuecomment-5801471135 found. New commits e88a604b1 (prose) and 5301a3829 (regeneration), on top of 2c0dbba43.

  1. Blocker deleted. 1.5.0.rst:1570-1588 claimed _Missing "carries @final and defines __bool__" -- fix(corekit): give the MultiDict _missing sentinel @final and __bool__ #667, which would have added those, was closed unmerged because _Missing is a verbatim werkzeug port that stays verbatim. multidict.py on origin/main has neither. The whole entry existed only to describe fix(corekit): give the MultiDict _missing sentinel @final and __bool__ #667's change, so it's removed rather than reworded (it can never become true).
  2. :2399 -- the __output__ #: block runs through line 149, not 148.
  3. :2400-2406 -- dropped the unsupported sphinx_autodoc_typehints attribution. Measured: TraceFlowBase.__dict__['__annotations__'] is empty, '__output__' in annotations is False, so nothing reads a runtime annotation for it. Kept the true observation (line 162 carries the same bare Type[Dumper]) and dropped "drives that signature".
  4. :2325 -- _dlink is documented on PCAP and three third-party engines (PyPCAP, PCAP_CT, PyPCAPFile), not built-in-only; narrowed "the third-party engines' own _backend" to the two that actually have one (PyPCAP, PCAP_CT).
  5. :2385 -- "5 of the 8 subtests" -> 4 of the 12 subtests, derived from test_capture_tracking.py's four TrackedCaptureTests methods against the pre-change (four-reports-restored) tree: 4 (test_no_rendered_report_is_tracked) + 2 (test_both_input_captures_are_tracked) + 6 (test_tracked_inputs_are_present_on_disk) = 12, of which 4 fail.
  6. :2288 -- docs(tests): stop stating how many captures are committed (#700) #703's body now reads "Fixes tests: three comments state a committed-capture count that is wrong or rots on the next change #700.\nFixes tests: test_every_tracked_name_exists_and_matches_git checks neither the index nor existence, so a hardcoded capture list would pass it #708." (edited after the previous fix landed), and docs(tests): stop stating how many captures are committed (#700) #703 carries tests/test_tier_guard.py +67/-3 -- not prose-only.
  7. :2367 -- reattributed the seven guarded registries: fix(const): give the six unguarded registries the same bare-ValueError guard #677 itself added three of the seven guards (CommandType, TransportProtocol, Flags); the four mh.* guards came from fix(mh): stop the four flag enums recursing on any non-member value (#623) #632 (a05f46149, predating fix(const): give the six unguarded registries the same bare-ValueError guard #677). fix(const): give the six unguarded registries the same bare-ValueError guard #677's own test_const_enum_builtin_parity.py sweeps all 123 registries, which is why it reaches all seven regardless of which PR guarded each. The "seven of seven" figure was already correct; only the attribution was wrong.
  8. Completeness gap -- zero entries added. Checked fix(tests): restore sys.modules after a test installs a stand-in module (#660) #662, fix(tests): put sys.modules back after a test loads modules from source (#674) #686, fix(tests): keep a faked version and hand-bound stand-ins inside the test that made them (#687, #688) #693, fix(tests): complete the warm-up list #693 left resting on someone else's imports #698, docs: say in SECURITY.md that hostile captures are a live risk, not a closed one #611, ci: wire the existing linters into CI as advisory checks #626 directly. All six are deliberate omissions, not oversights: fix(tests): put sys.modules back after a test loads modules from source (#674) #686, fix(tests): keep a faked version and hand-bound stand-ins inside the test that made them (#687, #688) #693 and fix(tests): complete the warm-up list #693 left resting on someone else's imports #698 each state in their own PR body "every entry in docs/source/changelog/1.5.0.rst corresponds to a pcapkit/ change" and name twelve tests/project/ tests fail with TypeError: type 'ProtocolBase' is not subscriptable depending on what ran first: the fake-module helpers purge on entry and never restore on exit #660/fix(tests): restore sys.modules after a test installs a stand-in module (#660) #662/tests/_support.py leaves bare stub modules in sys.modules, so tests/corekit + tests/project in one process fails three test_public_api tests #674/fix(tests): put sys.modules back after a test loads modules from source (#674) #686 as prior instances of the same call; docs: say in SECURITY.md that hostile captures are a live risk, not a closed one #611 says "No changelog bullet, following docs: correct documentation that stated untrue things (#546) #550, which rewrote this file wholesale and added none"; ci: wire the existing linters into CI as advisory checks #626 says "No changelog entry: CI and developer tooling, not user-visible." I'm not overriding those calls. This does leave a real tension the cross-review's own §7 also flagged: tests: three comments state a committed-capture count that is wrong or rots on the next change #700/tests: the nameless-enum sweep probes 65536 against a 16-bit flag registry, so main fails without showing red #702/tests: test_every_tracked_name_exists_and_matches_git checks neither the index nor existence, so a hardcoded capture list would pass it #708 got entries despite #702's and part of #684's text explicitly reading "No line under pcapkit/ changed" -- so the stated rule ("every entry corresponds to a pcapkit/ change") isn't actually followed uniformly by the file itself. I did not try to resolve that inconsistency by editing existing entries; it's outside this batch's scope.

Verification

check result
python util/changelog_md.py --check (final head) exit 0
scratch regen vs committed vs working tree, sha256 all three 5599fe25...9abe7d828a1fc23f -- identical
pytest tests/project/test_changelog_md.py -q 47 passed, 37 subtests passed
pcapkit.__file__ resolves inside this worktree
Sphinx 9.1.0, -b dummy -E full build build succeeded, 54 warnings, zero mentioning changelog/1.5.0

Read-only otherwise: no merge, no publish, no formal review. A fresh cross-review is needed since the head moved to 5301a3829.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored head 5301a3829: six of the eight fixes are right, but two of them replaced vague prose with false prose — #632 is credited with adding the four mh.* guards it did not add (git blame: 1f1399f6a1, 2023-04-17; and line 1144 of this same file already says #632 left that range guard "untouched", so the file now contradicts itself), and TraceFlowBase.__dict__['__annotations__'] is cited as "empty" when it raises KeyError and the class's annotations are non-empty — plus the #709 paragraph has rotted now that #709 is closed and #714 merged.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Cross-review detail — head 5301a3829 (this review: Opus; author: Sonnet). Third review of #657, independent of #issuecomment-5800589100 and #issuecomment-5801471135.

Provenance. Worktree detached at 5301a3829. pcapkit.__file__ = /local/home/jarryx/GitHub/PyPCAPKit/.claude/worktrees/agent-abcb84136ae51ccdd/pcapkit/__init__.py, Python 3.14.7, PYTHONSAFEPATH=1.
util/changelog_md.py --checkexit 0; CHANGELOG.md sha256 5599fe2515343633432825786e845577af85ba520341984f9abe7d828a1fc23f.
pytest tests/project/test_changelog_md.py -q47 passed, 37 subtests passed (0.18s).
Sphinx 9.1.0, PCAPKIT_SPHINX=1, real build: build succeeded, 53 warnings; zero warnings mention changelog/1.5.0 or 1.5.0. (Reported as 54 — on this head it is 53.)

Must fix

1. mh.* guard attribution is false, and contradicts line 1144 of this file. (New this round.)
Line 2354-5 says the four guards "are the mh.* flag guards #632 added for #623". git blame on pcapkit/const/mh/binding_ack_flag.py:

1f1399f6a1 pcapkit/const/mh/binding_ack.py  (2023-04-17 72)  raise ValueError('%r is not a valid %s' % ...)
a05f461490 pcapkit/const/mh/binding_ack_flag.py (2026-09-22 73)  return super()._missing_(value)

#632 changed line 73 (return cls(value)super()._missing_(value)); the guard on line 72 predates it by three years. Line 1144 of this same file already states it: "The range guard above it is untouched." Drop the #632 credit.

2. The __annotations__ evidence is false twice over. (New this round.)
Line 2388-90: "__output__ carries no runtime annotation (TraceFlowBase.__dict__['__annotations__'] is empty)". Measured:

TraceFlowBase.__dict__['__annotations__']  → raises KeyError: '__annotations__'   # absent, not empty
TraceFlowBase.__annotations__              → {'__cached__': 'dict[str, Any]'}     # non-empty
'__output__' in TraceFlowBase.__annotations__ → False

The conclusion is right — __output__ is unannotated — but neither reading of the cited expression supports it. Say __output__ is absent from TraceFlowBase.__annotations__, or drop the parenthetical.

3. The #709 paragraph has rotted. (Not new; false now.)
Lines 2394-2400 assert "This does not close #709." and "#709 stays open for them, though PR #714 covers exactly those four sites". Issue #709 is CLOSED (2026-09-23T21:09:08Z, by #712) and #714 is MERGED (21:10:41Z).

The eight fixes

Fix Verdict Evidence I obtained
148149 block range ✅ correct #: block is lines 146-149; # type: at 162 = 146+16 ✓
Drop sphinx_autodoc_typehints attribution ⚠️ right to drop, replaced with a false measurement see Must-fix 2
_backend "only two of six third-party engines" ✅ correct _backend in source only pypcap.py, pcap_ct.py; engines = PCAP, PCAPNG + 6 third-party ✓
_dlink not built-in-only ✅ correct _dlink in exactly pcap.py, pypcap.py, pcap_ct.py, pypcapfile.py — four, as stated
"4 of the 12 subtests" ✅ correct 5 tests; subTest loops of 4 (RENDERED_REPORTS), 2 (INPUT_CAPTURES), and one dynamic over sorted(self.tracked) → 4+2+2=8 post-change, 4+2+6=12 pre-change, the 4 report subtests failing. The prior "5 of the 8" was wrong; this is right
#703 names both #700 and #708 ✅ correct body carries Fixes #700. and Fixes #708.; 3 commits, fix in commits 2-3 as stated
#677 supplies 3 of 7 guards ✅ correct #677 touches ftp/command.py, reg/apptype.py, tcp/flags.py
#632 supplies the other 4 false see Must-fix 1

Also spot-checked and true: five classes named Type (l2tp/type.py, ftp.py, httpv1.py, arp.py, vendor/l2tp/type.py); exactly eight other files use ~typing.Type[; the 123-registry sweep in test_const_enum_builtin_parity.py does reach all seven guards with genuinely out-of-range values (-1 and 1<<70) — 20 passed, 549 subtests.

Deleting the _Missing entry was correct — not an over-deletion

Verified three ways: (a) source at this head has no __bool__ and no @final on _Missing (pcapkit/corekit/multidict.py:78-86, still the werkzeug-verbatim __repr__/__reduce__ pair); (b) #667 is CLOSED, unmerged (closed 2026-09-22T22:49:41Z), for two reasons, not one — verbatim werkzeug port and the added doc section breaking the private-member convention; (c) issue #661 is closed completed with no code change at all: site 1 "needed nothing", site 2 was #667, site 3 the owner ruled "leave it as is since it's a compat code from direct copy from stdlib".
Nothing was orphaned: #640, #661, #667, sentinel, NoValueType, _NOT_FOUND are now 0 occurrences in the .rst. The entry described a change that never landed; it had no place in a changelog.

Adjudicating the completeness pushback

On the six exclusions the worker is substantially right — adding zero entries was the correct call. Five of six carry an explicit written statement: #686 ("every entry … corresponds to a change in pcapkit/"), #693 ("checked rather than assumed"), #698, #611 ("No changelog bullet, following #550"), #626 ("CI and developer tooling, not user-visible"). Two corrections to its framing: the three citations run forward from #686 rather than "citing each other", and #662 has no stated rationale at all — zero mentions of "changelog" in its body or any comment — so "every one is a deliberate, documented exclusion" is wrong for that one.

The in-file inconsistency is real, and its attribution in the worker's report is wrong. The phrase "No line under pcapkit/ changed" sits on the #684 entry (line 2326) and the #702 entry (line 2358) — not on #700/#702/#708. Four entries describe work that touched nothing under pcapkit/: #684 → PR #694 (docs + CONTRIBUTING.md), #702 → PR #705 (one test file), #700 and #708 → PR #703 (three test files). So the rule #686/#693/#698 invoke is falsified by this file, and two entries say so in terms.

But note who broke it: #657 did. Those exclusions were written 02:00-18:00Z, when the claim was still true; #657 then added the four test/docs-only entries. And the rule is not the house rule anyway — CONTRIBUTING.md:30 says "Add a changelog entry if the change is user-visible", and the mirror case proves the path test wrong in both directions: #550 touches 20 pcapkit/*.py files and got no entry.
So: editorial, not factual. I would not block on it, and given #719 I would not expand the file — but the "every entry corresponds to a pcapkit/ change" precedent should stop being cited, because it is no longer true.

Could not verify

All seven previously-"premature" PRs (#696, #703, #706, #711, #712, #714, #716) merged today 21:08-21:14Z, so that finding is discharged — no entry now describes work that will never land. The _Missing entry was the last of those, and it is gone.

A third cross-review found the same failure mode again: the last pass
replaced vague prose with false prose. This round deletes rather than
corrects.

- #702 entry: dropped the attribution of the seven guard raise lines to
  specific PRs. "the four mh.* guards came from #632" was false and
  self-contradicts the file's own #623 entry three sections earlier
  ("The range guard above it is untouched" -- a05f461's own commit
  message says the same). The entry does not need to say which PR added
  which guard; it only needs to say the lines were already covered.
- #709 entry: dropped the `TraceFlowBase.__dict__['__annotations__']`
  citation. Measured: that lookup raises KeyError, not "is empty" --
  `TraceFlowBase.__annotations__` (the resolved, non-dict-shortcut form)
  is `{'__cached__': ...}`, non-empty. The conclusion (`__output__` is
  unannotated) holds, but the cited evidence does not, so it's dropped
  rather than replaced with a corrected measurement.
- #709 entry: rewritten to the present tense of a merged world. #709
  closed once #712 landed (the two traceflow.py sites this entry
  describes); the four docs/*.rst sites are fixed separately by #714,
  also merged. No more "does not close" / "stays open" hedging.

Refs #657
python util/changelog_md.py, in step with 1.5.0.rst at 9102675.

Refs #657
@JarryShaw

Copy link
Copy Markdown
Owner Author

Round 3 -- head 93a41263 (Sonnet, self)

Deleted the two wrong claims instead of re-fixing them; rewrote #709 to present tense.

Deleted:

Rewritten: :2394 -- #709 closed (#712 landed), four .rst sites fixed separately by #714 (also merged). No more "stays open" / "does not close".

check result
changelog_md.py --check exit 0
scratch/committed/working-tree sha256 all 590655041...c3dd8a7
pytest tests/project/test_changelog_md.py -q 47 passed, 37 subtests
Sphinx -b dummy -E 54 warnings, reproduced twice, 0 on changelog/1.5.0

53 vs 54: got 54 both times in this worktree, identical command. Can't reproduce 53 here.

Fresh cross-review needed.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored work at head 93a412636: round 4's rewrite of the #709 entry asserts "this pair of sites is what closes #709", which both #712 and #714 explicitly deny in their own bodies, and #684's "#709 tracks it" was left in the present tense against it.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored work at head 93a412636.

Supersedes my ❌ NEEDS CHANGES at #657 (comment), which referred to head 5301a3829.

The new defect: round 4 replaced a true sentence with an unsupported one

Round 4 is two commits (91026756f plus the regen 93a412636) and two rst hunks, not three deletions. #632 and the __annotations__ citation were deleted cleanly — both now have zero occurrences anywhere. The #709 item was not a deletion; it is a rewrite asserting two new positive claims.

New claim Verdict
"Four more bare Type sites … engine.rst:40, reassembly.rst:33 and :43, traceflow.rst:40 … are fixed separately, by #714" TRUE#714 merged 21:10:41Z, changed exactly those four lines and nothing else; all four line numbers byte-accurate
"This pair of sites is what closes #709." FALSE / unsupported

Sphinx: the number is 53, and the 53-vs-54 spread is settled

My own -E build (Sphinx 9.1.0, PCAPKIT_SPHINX=1, pcapkit.__file__ asserted into this tree): build succeeded, 53 warnings, exit 0. grep -c WARNING on the same log gives 55, because two lines are Scapy import chatter rather than Sphinx: WARNING: [bgp.py] use_2_bytes_asn: True and WARNING: CARP overwrites VRRP !. So 53 is real, 55 is the naive grep, and 54 is one of those two Scapy lines counted. No warning mentions changelog — the 1169 added lines are clean. Corollary worth one build at #694's base: #684's "55 → 56" looks like the contaminated form of 53 → 54.

Verified clean

Disputed, non-blocking

❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored work at head 93a412636.

…tale tense

Round 5: fixed the two defects the fourth cross-review found at head
93a4126 (NEEDS CHANGES).

- `:2389` -- "This pair of sites is what closes #709" was false: #712
  and #714 both state in their own bodies that they are `Part of #709`,
  not a close, and #709's closed timeline event carries `commit_id:
  null` (a manual close). Made non-causal: "This pair of sites is part
  of #709."
- `:2325` -- "#709 tracks it" was present tense describing an issue
  that is now closed, contradicting the #709 entry itself. Changed to
  past tense: "#709 tracked it."

Regenerated CHANGELOG.md from the edited entry.

| check | result |
|---|---|
| `changelog_md.py --check` | exit 0 |
| `pytest tests/project/test_changelog_md.py -q` | 47 passed, 37 subtests |
@JarryShaw

Copy link
Copy Markdown
Owner Author

Round 5 -- head 42335554e (Sonnet, self)

Fixed the two defects the fourth cross-review found at head 93a412636 (NEEDS CHANGES).

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

Swept the file for the same defect class elsewhere. One more candidate, not fixed pending your call: :2199 (#678 entry) reads "the #683 drift #685 tracks exactly as it was" -- #685 is now closed (by #697). Same present-tense-on-a-closed-issue pattern; left alone per brief.

Fresh cross-review needed.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Round 6 -- head 776d0ffbb (Sonnet, self)

Fixed the third stale claim flagged during round 5's own sweep, per owner request ahead of the next cross-review.

Checked #690 per owner note (closed via #721, 1aae1da30): the only reference, :2243 "filed as #690", is a scope statement (this fix doesn't touch that defect), not a present-tense open/tracked claim -- left as is.

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

Noticed, not fixed (outside this round's scope): :2244 "dropping it is #689, deferred" -- #689 is also closed now. Fresh cross-review needed.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 776d0ffbb — the #672/#679 entry at :2242-2245 still describes the world before c496d9cb and 1aae1da30 landed on main: HIP_COPIES is 1 not "two", the R1_Counter gap is gone not "stays", and EXPECTED_FAILURES is 43 not "44" (×3, also :2112, :2190).

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 776d0ffbb — the #672/#679 entry at :2242-2245 still describes the world before c496d9cb and 1aae1da30 landed on main: HIP_COPIES is 1 not "two", the R1_Counter gap is gone not "stays", and EXPECTED_FAILURES is 43 not "44" (×3, also :2112, :2190).

Fifth review; the 42335554e verdict is superseded. Not a fifth independent instance — all three are one drift, the same drift the four prior rounds each caught one symptom of. len(EXPECTED_FAILURES) by runtime import (a source count reads 17; the **{…} comprehensions at :453/:497 expand it):

tree len pcapng R1_Counter HIP_COPIES
main after #651 fix 0f2a2d081 44 35 present 2
branch 776d0ffbb (= merge base; its 46 commits touch only the 2 doc files) 45 35 present 2
origin/main bf57b4542 43 35 gone 1

So the prose was right when written against main at 0f2a2d081; then 1aae1da30 "register R1CounterParameter under HIPv1's code 128 too (#721)" (closes #690) and c496d9cb "drop HIP_COPIES to 1" (closes #689 — the only one of these closed by a commit) moved it. Required:

__version__ is 1.5.0b4 and no 1.5.1.rst exists, so both commits are 1.5.0 content and cannot be deferred to a later entry. The durable fix is not another grep: the branch's tree is 38 commits / 89 files / +16025 behind main while the file asserts measured present-tense facts throughout, so re-verify the measured claims against main immediately before merge.

Cleared on independent derivation, all 191 #nnn refs cross-referenced live: only #514 is open (4 sites, "sequenced ahead of rather than part of" — correct); #563/#571 are closed-unmerged and correctly framed ("Proposed by", "candidate fix", "what it was declined for"); :2199/:2325 past-tense "tracked" hold; "filed as #690/#701/#704" hold as provenance; #684's counts hold on both trees (5 classes named Type; main's 9th ~typing.Type[ file is traceflow.py itself, so "eight other files" is right). Sphinx -E + PCAPKIT_SPHINX=1: build succeeded, 53 warnings (raw grep 55; the 2 extra are use_2_bytes_asn: True and CARP overwrites VRRP !) and no warning mentions changelog. util/changelog_md.py --check exits 0, CHANGELOG.md byte-identical regenerated (sha256 101cdb6c…, not the 59065504… of two heads ago), tests/project/test_changelog_md.py 47 passed / 37 subtests.

Two notes, owner's call rather than changes: #251 at :46 is a Discussion, not an issue or PR (/issues/251 404s); and the preamble's "between #326 and #509" (:7-8) no longer bounds a body that cites up to #714 — worth knowing, since that framing is what earlier rounds used to rule uncited PRs out of scope.

❌ NEEDS CHANGES @ 776d0ffbb — the #672/#679 entry at :2242-2245 still describes the world before c496d9cb and 1aae1da30 landed on main: HIP_COPIES is 1 not "two", the R1_Counter gap is gone not "stays", and EXPECTED_FAILURES is 43 not "44" (×3, also :2112, :2190).

@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
Round 7: the fifth review found the branch 38 commits behind main, and
that drift had reached the #672/#679 entry's hard numbers, not just
tense.

- `:2242-2245` -- `HIP_COPIES` was "two", the `R1_Counter` registry gap
  "stays", and `EXPECTED_FAILURES` was "44" -- all true when written
  against `main` at `0f2a2d081`, all moved since by `c496d9cb` (HIP_COPIES
  -> 1, closes #689) and `1aae1da30` (R1CounterParameter now registers
  under code 128 too, closes #690). Rewrote past tense: the R1_Counter
  entry was deleted once #690 closed the registry gap, and HIP_COPIES was
  dropped to one once #672/#679 left it routing around nothing.
- Swept the file for the same class of drift and found two more sites
  citing the same stale "44": `:2055` and `:2112`/`:2190` also corrected
  to 43, measured by runtime import of `EXPECTED_FAILURES`
  (`tests/protocols/test_option_roundtrip_unit.py`), never by counting
  the `**{...}` comprehension in source.
- Left `:1566`'s HIP_COPIES mention alone: it explains why the count
  stayed at two right after #651, for a different, still-accurate reason
  at that point in the narrative, and the #672/#679 entry now carries the
  eventual resolution.

Regenerated CHANGELOG.md from the edited entries.

| check | result |
|---|---|
| `changelog_md.py --check` | exit 0, sha256 `08fd2e01f3...ed982` |
| `pytest tests/project/test_changelog_md.py -q` | 47 passed, 37 subtests |
| Sphinx `-b dummy -E`, `PYTHONPATH` pinned to this worktree | build succeeded, 53 warnings, 0 mentioning `changelog` |
@JarryShaw JarryShaw added review: pending No verdict for the current head - never reviewed, or the head moved since the last one and removed 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

Round 7 -- head 8f41e97d3 (Sonnet, self)

Fixed the fifth review's drift at the #672/#679 entry (:2242-2245) against current origin/main (bf57b4542), and swept for the same class elsewhere.

check result
changelog_md.py --check exit 0, CHANGELOG.md sha256 08fd2e01f32adb35ad8a084097222fc5e14238928b8a6798398333a24aedf982
pytest tests/project/test_changelog_md.py -q 47 passed, 37 subtests
Sphinx -b dummy -E, PYTHONPATH pinned to this worktree (root confirmed in log) build succeeded, 53 warnings, 0 mentioning changelog

Fresh cross-review needed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Pull requests that change documentation only (docs: subject prefix) review: pending No verdict for the current head - never reviewed, or the head moved since the last one

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant