docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) - #657
docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620)#657JarryShaw wants to merge 46 commits into
Conversation
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.
f846523 to
69a6e13
Compare
…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.
…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
…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
Second-round fixes -- head
|
| 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.
|
❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored head |
|
Cross-review detail — head Provenance. Worktree detached at Must fix1. #632 changed line 73 ( 2. The The conclusion is right — 3. The #709 paragraph has rotted. (Not new; false now.) The eight fixes
Also spot-checked and true: five classes named Deleting the
|
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
Round 3 -- head
|
| 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.
|
❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored work at head Supersedes my The new defect: round 4 replaced a true sentence with an unsupported oneRound 4 is two commits (
Sphinx: the number is 53, and the 53-vs-54 spread is settledMy own Verified clean
Disputed, non-blocking
❌ NEEDS CHANGES — cross-review on Opus of Sonnet-authored work at head |
…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 |
Round 6 -- head
|
| 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.
|
❌ NEEDS CHANGES @ |
|
❌ NEEDS CHANGES @ Fifth review; the
So the prose was right when written against
Cleared on independent derivation, all 191 Two notes, owner's call rather than changes: ❌ NEEDS CHANGES @ |
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` |
776d0ff to
8f41e97
Compare
Round 7 -- head
|
| 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 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.rstat the sameanchor and regenerated
CHANGELOG.mdbeside it. That made them mutually exclusiverather 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:
TCP.readseeding its flag accumulator with atyping.castno-opFrame.len/cap_lenswapped between the PCAP and PCAP-NG readersExtractorclosing the caller's stream and leaking its ownextract(..., no_eof=True)never returningEvery 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.
2c4212a02regeneratesCHANGELOG.md,which has been drifted since
375e9d411(#638) hand-inserted three lines into thegenerated file instead of running
util/changelog_md.py. That left a stale copy ofthe #630 bullet sitting below its own correction, and stranded the #631 bullet after
#638.
That drift is why
mainis currently red, and it is unrelated to any of the five:python util/changelog_md.py --checkexits 1 onmain, failing theChangelog driftjob and all twelve matrix jobs, the latter through the same assertion in
tests/project/test_changelog_md.py::RepositoryStateTests. Measured on run35748204467(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
maingreen before this PR merges:python util/changelog_md.py --checkexits 0 at every one of the six commits here.Still to come on this branch
detail below — which is tracked separately and lands here as its own commit.