Skip to content

fix(reg)!: reject an out-of-range port in AppType.get, and claim each span by transport - #764

Merged
JarryShaw merged 1 commit into
mainfrom
fix-apptype-get-range-and-span-transport-758-760
Sep 25, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix-apptype-get-range-and-span-transport-758-760

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

What is the purpose of your pull request?

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

Description of your pull request and other information

Fixes #758. Fixes #760. One PR: both live in _missing_ and the crawler that emits it.

before after
AppType.get(-1, proto='tcp') PORT_-1_tcp, len(TCP) 6147→6148 ValueError: -1 is not a valid TCP
AppType.get(6010, proto='udp') <UDP.x11: 6010 [tcp]> <UDP.x11: 6010 [udp]>
AppType.get(6666, proto='udp') <UDP.ircu: 6666 [tcp]> <UDP.reserved: 6666 [udp]>
AppType.get(6010, proto='sctp') <SCTP.x11: 6010 [tcp]> PORT_6010_sctp

#758 — get raises rather than returns. _missing_'s None means "no row for this port"; its ValueError means "not a port". get caught the second and minted anyway, so it was more permissive than TCP(...). Dropping the except and testing for None makes get propagate _missing_'s own error verbatim — exact parity, not an approximation — and leaves the mint for a valid unassigned port alone, which is what get is documented for.

#760 — claim by transport, not merge. Merging needs one combined transport, and #732's TransportProtocol retype would then want a named combination for it: work to be undone. It is also wrong on the merits — 6665-6669 is ircu on TCP and IANA's reserved on UDP, two different services, so one merged branch has to discard one. So each branch naming a transport tests cls.__transport__. The 762 naming none are unchanged, and SCTP/DCCP are fixed too, which the duplicate only made visible on TCP/UDP.

breaking: yes. Transport._make_port (pcapkit/protocols/transport/transport.py:187) passes an unvalidated int, so TCP.make(srcport=99999) now raises where it minted junk. Parsing cannot reach it — the port fields unpack unsigned 16-bit. SCTP(6010) also raises now, exactly as SCTP(51) already did. Precedent: #677 carries the label for the same shape -- a registry gaining a guard that makes a previously-minting call raise. (#706 was cited here in error: it moves the opposite way, resolving an unassigned value instead of raising.)

Regeneration. Crawler re-run from one pinned IANA snapshot, and on 932cb48d1 the unmodified crawler reproduces all five const files byte-identically from it — so the entire diff is this change: 4 conditions in apptype.py, with tcp.py, udp.py, sctp.py and dccp.py byte-identical.

Evidence. 3 pins, each red on 932cb48d1 (12, 3 and 4 failures) and green here; the third asserts the invariant over all 766 branches rather than the 2 spans that collide today, which is what survives the next crawl. tests/const 55, transport 66, tests/vendor + tier guard 143, registry callers 36 — 300 tests / 2,519 subtests, pytest and unittest agreeing. Coverage of pcapkit/const/reg/apptype/apptype.py 51.516% → 51.752% from the same file. pylint 9.90/10 (unchanged; its duplicate-code reports are pre-existing), mypy and isort clean. Merged --no-ff onto 2146a54a6 in a scratch worktree: 274 tests / 2,436 subtests green.

One deliberate omission: a crawler-side unit test would want a new file under tests/vendor/ plus a gate in tests/_dependency_gates.py, which #755 owns, so the invariant is pinned on the generated output instead.

@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) breaking Breaks public-facing behaviour or API (apply alongside the type label) const Regenerated IANA or vendor constant tables; members keep their numeric values labels Sep 25, 2026
… span by transport

* `get`'s `except ValueError` caught the range rejection `_missing_` raises and
  minted regardless, so `AppType.get(-1, proto='tcp')` returned `PORT_-1_tcp` --
  a `__members__` key no attribute access can reach -- while `TCP(-1)` raised. It
  now tests `_missing_` for `None`, which is its "no row for this port" answer,
  and lets the rejection through, so both entry points raise the same
  `ValueError`. A valid but unassigned port still mints, which is what `get` is
  for. Caller-visible: `Transport._make_port` passes an unvalidated `int`, so
  `TCP.make(srcport=99999)` now raises where it minted junk.
* the crawler rendered one `_missing_` branch per registry row, so the two spans
  IANA registers on more than one transport emitted the same condition twice and
  the second copy was unreachable. Every registry answered with the first:
  `AppType.get(6010, proto='udp')` gave a UDP member whose `proto` read `tcp`,
  `6666/udp` answered `ircu` rather than `reserved`, and `proto='sctp'` minted
  `x11` where IANA assigns nothing. A branch naming a transport now tests
  `cls.__transport__`; the 762 naming none answer every registry as before.
* regenerated `pcapkit/const/reg/apptype/` from the crawler: 4 conditions changed
  in `apptype.py`, and `tcp.py`, `udp.py`, `sctp.py`, `dccp.py` byte-identical.

3 pins added, each red on 932cb48 (12, 3 and 4 failures) and green here.
tests/const 55 passed, transport 66, vendor + tier guard 143, registry callers
36, 2,519 subtests; pylint 9.90/10 unchanged, mypy clean, isort clean.
@JarryShaw
JarryShaw force-pushed the fix-apptype-get-range-and-span-transport-758-760 branch from 8395f08 to 2c2dcab Compare September 25, 2026 00:14
@JarryShaw JarryShaw added review: pending No verdict for the current head - never reviewed, or the head moved since the last one review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 25, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Cross-review verdict: GOOD TO GO (sonnet, a different model from the author). I re-derived the behavioural claims myself rather than relaying them.

On the fixed tree, against main:

SCTP.get(6010) -> <SCTP.unknown: 6010 [sctp]>     was <SCTP.x11: 6010 [tcp]>
DCCP.get(6010) -> <DCCP.unknown: 6010 [dccp]>     was <DCCP.x11: 6010 [tcp]>
UDP.get(6010)  -> <UDP.x11: 6010 [udp]>           proto now correct too
UDP.get(6666)  -> <UDP.reserved: 6666 [udp]>      was ircu; matches IANA's UDP row
TCP.get(6010) / TCP.get(6666)                     unchanged
TCP.get(-1 / 65536 / 999999) -> ValueError, byte-identical to TCP(-1)
80, 443, 5999, 6064, 6664, 6670                   unchanged

The regression-safety claim is exhaustive, not sampled. A full 262,144-cell sweep (65,536 ports × 4 registries × both trees) found exactly 207 changed pairs, all confined to 6000-6063 and 6665-6669, 69 each across UDP/SCTP/DCCP, zero outside. TCP shows 0 changes because it was the accidental first-branch winner before the fix as well.

Determinism is established, with one framing correction. There is no pinned snapshot artifact in the repo — that wording is not literally reproducible. What was done: IANA's CSV fetched once, further network access blocked, the same input reused throughout. The load-bearing check is that diffing tcp.py/udp.py/sctp.py/dccp.py produced by the old crawler against the new crawler on identical input gives four byte-identical files, so the diff is attributable to the code change rather than to input drift.

is None holds as the sole mint trigger: 262,144 real calls, no raise for any in-range value and no falsy-but-not-None return, with the source accounting for every exit — 2 raise ValueError, 766 return extend_enum(...), 1 terminal return super()._missing_(value). Counts confirmed by AST: 768 if statements, 766 range guards, 4 naming a transport. Red-without-fix reproduces at 12/3/4 under both pytest and python -m unittest — needed, because pytest's parent node reads PASSED for two of the three pins while their subTests fail.

breaking is right on both halves. Only four call sites reach the registry; construction via _make_port now raises where it minted junk, and all three parse sites are hard-masked to unsigned 16-bit, with boundary 0 and 65535 parsing clean end-to-end. The TransportProtocol IntFlag question is safe today — __transport__ is set by exactly four literal assignments and never composed — so #732's retype stays a future concern rather than a live bug here.

Two description defects found and fixed rather than left: the #706 precedent was cited in error (it moves the opposite way — resolving an unassigned value instead of raising), and an "8,478 unguarded ports" figure I quoted in the review brief turns out to appear nowhere in this PR; it came from a working note, so it is not a claim this PR makes and should not be attributed to it.

Unpublished and awaiting the owner.

@JarryShaw
JarryShaw merged commit 57b2c17 into main Sep 25, 2026
24 checks passed
@JarryShaw
JarryShaw deleted the fix-apptype-get-range-and-span-transport-758-760 branch September 25, 2026 01:36
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 25, 2026
JarryShaw added a commit that referenced this pull request Sep 25, 2026
…ting (#575)

- TCP/UDP/SCTP's PortEnumField and PCAP-NG's OptionEnumField called their
  registry's .get() directly on every parsed value, which mints a fresh
  member via aenum.extend_enum for any value no row or documented span
  covers. On http.pcap that is 111 calls and ~14% of extraction self time,
  entirely from unassigned ephemeral ports.
- All four now peek at what .get() would consult -- the registry's own
  rows, then AppType's declared IANA spans via _missing_, or OptionType's
  per-namespace membership -- and only call .get() once one of those
  already holds. A genuine miss falls through to the bounded, nameless
  pseudo-member EnumField.post_process already builds for #701, carrying
  .port/.svc/.proto (or .opt_name/.opt_value) so downstream attribute
  access keeps working.
- A bare ValueError (aenum's, or #764's out-of-range guard) is absorbed as
  before; a pcapkit.utilities.exceptions rejection still propagates
  unchanged. AppType.get() and every _missing_ are unmodified, so direct
  get() callers still mint.

extend_enum calls on http.pcap: 111 (13.6-14.1%) -> 0. many_interfaces.pcapng:
15 -> 2, both from the untouched _missing_ mechanism. New unit tests cover
the no-mint path, the in-library-rejection path, and the round trip; each
fails without this change. coverage run -m pytest and python -m unittest
agree; make isort clean.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
… calls

- apptype.py:717 dropped `: {self.NAME!r}` from each member's assignment.
  Every annotated member put a forward-ref string into AppType's
  __annotations__, and Sphinx's default autodoc re-resolves the whole
  class once per member -- 378.8s on CI, the largest stall in the docs
  build. Also drops the now-redundant `# mypy: disable-error-code=
  assignment` pragma that annotation forced on all five generated files.
- apptype.py's flag() now emits `TransportProtocol.tcp` instead of
  `TransportProtocol.get('tcp')` -- 21x cheaper per call, 23,941 of the
  23,942 call sites on this checkout rewritten (the 23,942nd, in
  _dispatch, resolves a caller-supplied string and stays). Also covers
  the 4 range-row conditions #764 added to records(). get() itself is
  untouched; other callers still use it.
- Regenerated all five files under pcapkit/const/reg/apptype/ from one
  pinned IANA snapshot. The unmodified crawler reproduced the current
  files byte-identically first; the diff after both changes is
  mechanical throughout.
- Updated the #760 regression test's regex for the new attribute form
  and tightened its trailing match, and added generator-level pins for
  both changes.

Fixes #744. Fixes #768.

Regenerated from one pinned IANA snapshot; unmodified crawler reproduced
the committed files byte-identically before either change landed.
Exhaustive member-set comparison (name/port/svc/proto) across all 12,391
members in AppType/TCP/UDP/SCTP/DCCP: identical. make isort clean.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
…ting (#575)

- TCP/UDP/SCTP's PortEnumField and PCAP-NG's OptionEnumField called their
  registry's .get() directly on every parsed value, which mints a fresh
  member via aenum.extend_enum for any value no row or documented span
  covers. On http.pcap that is 111 calls and ~14% of extraction self time,
  entirely from unassigned ephemeral ports.
- All four now peek at what .get() would consult -- the registry's own
  rows, then AppType's declared IANA spans via _missing_, or OptionType's
  per-namespace membership -- and only call .get() once one of those
  already holds. A genuine miss falls through to the bounded, nameless
  pseudo-member EnumField.post_process already builds for #701, carrying
  .port/.svc/.proto (or .opt_name/.opt_value) so downstream attribute
  access keeps working.
- The three port fields check the value against the field's own declared
  byte width (self.length) before consulting the registry at all, so a
  port outside that width still raises #764's deliberate rejection instead
  of being absorbed alongside a genuine in-range miss -- both are a bare
  ValueError with no way to tell them apart by type alone. A
  pcapkit.utilities.exceptions rejection still propagates unchanged.
  AppType.get() and every _missing_ are unmodified, so direct get()
  callers still mint.
- dictdumper.plist.PLIST (which 'xml' also maps to) writes <string>/<key>
  content with no entity escaping at all (JarryShaw/DictDumper#125,
  tracked as #772), so the pseudo-member's <unknown>/<unassigned> naming
  broke plist/xml reports. make_dumper's object_hook now escapes &, < and
  > for PLIST-rooted output only, leaving json/tree/text untouched.

extend_enum calls on http.pcap: 111 (13.6-14.1%) -> 0. many_interfaces.pcapng:
15 -> 2, both from the untouched _missing_ mechanism. New unit tests cover
the no-mint, out-of-width, in-library-rejection, round-trip and escaping
paths; each fails without its fix. coverage run -m pytest and python -m
unittest agree; make isort clean.

Fixes #575.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
… calls (#769)

- apptype.py:717 dropped `: {self.NAME!r}` from each member's assignment.
  Every annotated member put a forward-ref string into AppType's
  __annotations__, and Sphinx's default autodoc re-resolves the whole
  class once per member -- 378.8s on CI, the largest stall in the docs
  build. Also drops the now-redundant `# mypy: disable-error-code=
  assignment` pragma that annotation forced on all five generated files.
- apptype.py's flag() now emits `TransportProtocol.tcp` instead of
  `TransportProtocol.get('tcp')` -- 21x cheaper per call, 23,941 of the
  23,942 call sites on this checkout rewritten (the 23,942nd, in
  _dispatch, resolves a caller-supplied string and stays). Also covers
  the 4 range-row conditions #764 added to records(). get() itself is
  untouched; other callers still use it.
- Regenerated all five files under pcapkit/const/reg/apptype/ from one
  pinned IANA snapshot. The unmodified crawler reproduced the current
  files byte-identically first; the diff after both changes is
  mechanical throughout.
- Updated the #760 regression test's regex for the new attribute form
  and tightened its trailing match, and added generator-level pins for
  both changes.

Fixes #744. Fixes #768.

Regenerated from one pinned IANA snapshot; unmodified crawler reproduced
the committed files byte-identically before either change landed.
Exhaustive member-set comparison (name/port/svc/proto) across all 12,391
members in AppType/TCP/UDP/SCTP/DCCP: identical. make isort clean.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
#575)

- TCP/UDP/SCTP's PortEnumField and PCAP-NG's OptionEnumField called their
  registry's .get() directly on every parsed value, which mints a fresh
  member via aenum.extend_enum for any value no row or documented span
  covers. On http.pcap that is 111 calls and ~14% of extraction self time,
  entirely from unassigned ephemeral ports.
- All four now peek at what .get() would consult -- the registry's own
  rows, then AppType's declared IANA spans via _missing_, or OptionType's
  per-namespace membership -- and only call .get() once one already
  holds. A genuine miss gets EnumField._unregistered_member: an instance
  built by calling the registry's own storage base's __new__ (str or int,
  picked per namespace) directly, skipping the registry's own __new__ and
  the registration inside it, so isinstance holds and nothing is ever
  added to any lookup table. Per the owner's ruling, tracked more broadly
  as #775 and applied here only to these four call sites.
- The three port fields check the value against the field's own declared
  byte width (self.length) before consulting the registry at all, so a
  port outside that width still raises #764's deliberate rejection instead
  of being absorbed alongside a genuine in-range miss. A
  pcapkit.utilities.exceptions rejection still propagates unchanged.
  AppType.get() and every _missing_ are unmodified, so direct get()
  callers still mint.
- dictdumper.plist.PLIST (which 'xml' also maps to) writes <string>/<key>
  content with no entity escaping at all (JarryShaw/DictDumper#125,
  tracked as #772), so the unregistered member's rendering broke plist/xml
  reports. make_dumper's object_hook now escapes &, < and > for
  PLIST-rooted output only, leaving json/tree/text untouched.

extend_enum calls on http.pcap: 111 (13.6-14.1%) -> 0. many_interfaces.pcapng:
15 -> 2, both from the untouched _missing_ mechanism. New unit tests cover
no-mint, isinstance, out-of-width, equality-without-identity, value-lookup
still raising, copy/deepcopy, in-library-rejection, round-trip and escaping;
each fails without its fix. coverage run -m pytest and python -m unittest
agree; make isort clean.

Fixes #575.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
#575)

- TCP/UDP/SCTP's PortEnumField and PCAP-NG's OptionEnumField called their
  registry's .get() directly on every parsed value, which mints a fresh
  member via aenum.extend_enum for any value no row or documented span
  covers. On http.pcap that is 111 calls and ~14% of extraction self time,
  entirely from unassigned ephemeral ports.
- All four now peek at what .get() would consult -- the registry's own
  rows, then AppType's declared IANA spans via _missing_, or OptionType's
  per-namespace membership -- and only call .get() once one already
  holds. A genuine miss gets EnumField._unregistered_member: an instance
  built by calling the registry's own storage base's __new__ (str or int,
  picked per namespace) directly, skipping the registry's own __new__ and
  the registration inside it, so isinstance holds and nothing is ever
  added to any lookup table. Per the owner's ruling, tracked more broadly
  as #775 and applied here only to these four call sites.
- The three port fields check the value against the field's own declared
  byte width (self.length) before consulting the registry at all, so a
  port outside that width still raises #764's deliberate rejection instead
  of being absorbed alongside a genuine in-range miss. A
  pcapkit.utilities.exceptions rejection still propagates unchanged.
  AppType.get() and every _missing_ are unmodified, so direct get()
  callers still mint.
- dictdumper.plist.PLIST (which 'xml' also maps to) writes <string>/<key>
  content with no entity escaping at all (JarryShaw/DictDumper#125,
  tracked as #772), so the unregistered member's rendering broke plist/xml
  reports. make_dumper's object_hook now escapes &, < and > for
  PLIST-rooted output only, leaving json/tree/text untouched.

extend_enum calls on http.pcap: 111 (13.6-14.1%) -> 0. many_interfaces.pcapng:
15 -> 2, both from the untouched _missing_ mechanism. New unit tests cover
no-mint, isinstance, out-of-width, equality-without-identity, value-lookup
still raising, in-library-rejection, round-trip and escaping; each fails
without its fix. Also measured, not tested: copy/deepcopy of the returned
member work on Python 3.11+ (stdlib Enum.__copy__/__deepcopy__ return self
there) but raise on 3.10 exactly as pickle does on every version, since
neither is reachable from any reader in this package. coverage run -m
pytest and python -m unittest agree; make isort clean.

Fixes #575.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
#575)

- TCP/UDP/SCTP's PortEnumField and PCAP-NG's OptionEnumField called their
  registry's .get() directly on every parsed value, which mints a fresh
  member via aenum.extend_enum for any value no row or documented span
  covers. On http.pcap that is 111 calls and ~14% of extraction self time,
  entirely from unassigned ephemeral ports.
- All four now peek at what .get() would consult -- the registry's own
  rows, then AppType's declared IANA spans via _missing_, or OptionType's
  per-namespace membership -- and only call .get() once one already
  holds. A genuine miss gets EnumField._unregistered_member: an instance
  built by calling the registry's own storage base's __new__ (str or int,
  picked per namespace) directly, skipping the registry's own __new__ and
  the registration inside it, so isinstance holds and nothing is ever
  added to any lookup table. Per the owner's ruling, tracked more broadly
  as #775 and applied here only to these four call sites.
- The three port fields check the value against the field's own declared
  byte width (self.length) before consulting the registry at all, so a
  port outside that width still raises #764's deliberate rejection instead
  of being absorbed alongside a genuine in-range miss. A
  pcapkit.utilities.exceptions rejection still propagates unchanged.
  AppType.get() and every _missing_ are unmodified, so direct get()
  callers still mint.
- The extra attributes are passed in the order the registry's own __new__
  sets them -- svc, port, proto for AppType and opt_name, opt_value for
  OptionType -- because make_dumper renders a member's addon keys straight
  out of its __dict__ in insertion order. Any other order dumps an
  unassigned value's keys the opposite way round from every declared one's,
  in all four formats, while leaving every value correct: 1117 rendered
  port blocks of each kind on http.pcap alone.
- Enum.__reduce_ex__ reduces a member to (cls, (value,)), the one lookup
  an unregistered member is absent from, so _unregistered_member installs
  a __reduce_ex__ of its own that rebuilds an equivalent unregistered
  member. Without it, removing the mint would have taken away a pickle
  round-trip that worked before -- and taken it away on read-back, since
  dumps succeeds either way. copy/deepcopy reduce through it too on 3.10,
  where Enum.__copy__/__deepcopy__ do not exist.
- dictdumper.plist.PLIST (which 'xml' also maps to) writes <string>/<key>
  content with no entity escaping at all (JarryShaw/DictDumper#125,
  tracked as #772), so the unregistered member's rendering broke plist/xml
  reports. make_dumper's object_hook now escapes &, < and > for
  PLIST-rooted output only, leaving json/tree/text untouched.

extend_enum calls on http.pcap: 111 (13.6-14.1%) -> 0. many_interfaces.pcapng:
15 -> 2, both from the untouched _missing_ mechanism. Wall-clock
extract(nofile=True), 7 fresh processes per tree at host load 1.29 -> 1.27:
http.pcap 0.952 -> 0.679 s, many_interfaces.pcapng 0.130 -> 0.103 s. New unit
tests cover no-mint, isinstance, out-of-width, equality-without-identity,
value-lookup still raising, in-library-rejection, round-trip, escaping, addon
key order, _unregistered_member's int and TypeError branches, and the
pickle/copy round-trip; each fails without its fix. coverage run -m pytest and
python -m unittest agree.

Fixes #575.
JarryShaw added a commit that referenced this pull request Sep 25, 2026
…k included

`main` moved from 73f09ae to 4530424 while this PR sat open, and the 1.5.0
section cited none of the 25 commits in between. Ten new bullets cover thirteen
of them, appended in merge order, with the file's own `**a breaking change**`
lead sentence on the three that are breaking:

- #754 -- AppType split into per-transport registries; the 1,004 portless and
  704 transportless rows stop being members. Breaking.
- #764 -- an out-of-range port in `AppType.get` is refused, not minted, so
  `TCP.make(srcport=99999)` raises; per-transport `_missing_` spans. Breaking.
- #778 -- `@final` enforced at runtime on `Info`/`Schema`: bare `@final` raises
  `InfoError`/`SchemaError` at first construction, deriving from a finalised
  class raises, and `SchemaError` is a `ValueError` where a caller may have
  been catching `TypeError`. Breaking.
- #772 (with #790's docstring reword), #766, #759, #787, #794 (with #791's
  citation repoint), #792/#798 and #802 -- the remaining seven.

Also re-ran the citation sweep against `origin/main` rather than the checkout.
One stale line number fixed: the `httpv2.py` `header.length != 9` guard the
`#692` entry calls out moved from `:562` to `:650` under #789 and #802. The
preamble's "reaching #726" becomes #805, the new maximum reference. Verified
unmoved on 4530424: `protocol.py:1411`, `schema/internet/ipv4.py:336`,
`traceflow.py` 146/149/162/424, the four `:type:` fields in `engine.rst`,
`reassembly.rst` and `traceflow.rst`, and `EXPECTED_FAILURES` at 43 entries.

Carries the previous round's #651/#646 corrections unchanged. Two literals were
reflowed so no ``literal`` wraps a line, which the generator's residual guard
refuses. `changelog_md.py --check` exit 0; `test_changelog_md.py` 47 passed,
37 subtests.
JarryShaw added a commit that referenced this pull request Sep 26, 2026
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit
(relative to its parent, 4233555) now names all 20 bullets it carries, not
just the 8 this session added on top of the 10 already there -- a first
draft of this message named only its own 8 and left the other 10 silent,
which a cross-review caught. Four are breaking:

- #754 -- AppType split into per-transport registries; the 1,004 portless
  and 704 transportless rows stop being members.
- #764 -- an out-of-range port in `AppType.get` is refused, not minted.
- #778 -- `@final` enforced at runtime on `Info`/`Schema`.
- #575 -- four `.get()`-backed enum fields fall through to
  `_unregistered_member` instead of minting; 14 of 23 sample captures
  change output.
- #772 (with #790's docstring reword), #766, #759, #787, #794 (with #791's
  citation repoint), #792/#798, #802, #782 (a further #745-hazard instance),
  #704, #723, #739, #743/#746 (cross-dependent, one bullet each), #805
  (closes #802's own filed-as-out-of-scope), #796, #800 -- the other 16,
  non-breaking.

Also restores a measurement an earlier round in this same diff dropped
while updating an adjacent one: the #692 entry's "mypy is unmoved at 112
errors" silently lost its "and pylint ... 364 messages" half when
`EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence.
Restored to the last value earlier rounds signed off on rather than
re-measured, since this branch's own `pcapkit/` tree predates several
since-merged PRs and a fresh run would not be measuring the same thing the
original round measured. Unmoved, and not silently dropped this time:
`93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES`
44 to 43 in the several other places that already carried the fix.

On which PRs get a bullet: there is no clean "user-facing only" rule --
#766 and #791 are pure CI/test/lint-comment entries that are in, while
#773 and #763 are the same kind of thing and are out. The real pattern
across this file's 46 commits is closer to "each round's author judged it
worth a reader's time," which is inconsistent by construction. This round
leaves that inconsistency as found rather than trying to retrofit a rule,
but did add #782 on reconsideration -- its own PR body names it as sharing
#766's hazard, and pre-existing precedent already treats that hazard's
instances as bullet-worthy.

`changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0.
`test_changelog_md.py` 47 passed.
JarryShaw added a commit that referenced this pull request Sep 26, 2026
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit
(relative to its parent, 4233555) now names all 20 bullets it carries, not
just the 8 this session added on top of the 10 already there -- a first
draft of this message named only its own 8 and left the other 10 silent,
which a cross-review caught. Six are breaking, matching the crediting PRs'
own `breaking` label in each case:

- #754 -- AppType split into per-transport registries; the 1,004 portless
  and 704 transportless rows stop being members.
- #764 -- an out-of-range port in `AppType.get` is refused, not minted.
- #778 -- `@final` enforced at runtime on `Info`/`Schema`.
- #575 -- four `.get()`-backed enum fields fall through to
  `_unregistered_member` instead of minting; 14 of 23 sample captures
  change output.
- #759 -- `AppType._dispatch` on a multi-transport `proto` now raises
  `ProtocolError` instead of silently resolving to whichever transport
  owns the lowest set bit.
- #805 -- `FieldBase.length` on a negative resolved length now raises
  `ProtocolError` instead of letting a bare `struct.error` escape.
  A second cross-review caught both: their crediting PRs (#783, #811) both
  carry GitHub's own `breaking` label, and neither bullet said so.

- #772 (with #790's docstring reword), #766, #787, #794 (with #791's
  citation repoint), #792/#798, #802, #779 (via #782, a further
  #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one
  bullet each), #796, #800 -- the other 14, non-breaking.

Also restores a measurement an earlier round in this same diff dropped
while updating an adjacent one: the #692 entry's "mypy is unmoved at 112
errors" silently lost its "and pylint ... 364 messages" half when
`EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence.
Restored to the last value earlier rounds signed off on rather than
re-measured, since this branch's own `pcapkit/` tree predates several
since-merged PRs and a fresh run would not be measuring the same thing the
original round measured. Unmoved, and not silently dropped this time:
`93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES`
44 to 43 in the several other places that already carried the fix.

On which PRs get a bullet: there is no clean "user-facing only" rule --
#766 and #791 are pure CI/test/lint-comment entries that are in, while
#773 and #763 are the same kind of thing and are out. The real pattern
across this file's 46 commits is closer to "each round's author judged it
worth a reader's time," which is inconsistent by construction. This round
leaves that inconsistency as found rather than trying to retrofit a rule,
but did add #779 (via #782) on reconsideration -- its own PR body names it
as sharing #766's hazard, and pre-existing precedent already treats that
hazard's instances as bullet-worthy.

`changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0.
`test_changelog_md.py` 47 passed.
JarryShaw added a commit that referenced this pull request Sep 26, 2026
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit
(relative to its parent, 4233555) now names all 20 bullets it carries, not
just the 8 this session added on top of the 10 already there -- a first
draft of this message named only its own 8 and left the other 10 silent,
which a cross-review caught. Six are breaking, matching the crediting PRs'
own `breaking` label in each case:

- #754 -- AppType split into per-transport registries; the 1,004 portless
  and 704 transportless rows stop being members.
- #764 -- an out-of-range port in `AppType.get` is refused, not minted.
- #778 -- `@final` enforced at runtime on `Info`/`Schema`.
- #575 -- four `.get()`-backed enum fields fall through to
  `_unregistered_member` instead of minting; 14 of 23 sample captures
  change output.
- #759 -- `AppType._dispatch` on a multi-transport `proto` now raises
  `ProtocolError` instead of silently resolving to whichever transport
  owns the lowest set bit.
- #805 -- `FieldBase.length` on a negative resolved length now raises
  `ProtocolError` instead of letting a bare `struct.error` escape.
  A second cross-review caught both: their crediting PRs (#783, #811) both
  carry GitHub's own `breaking` label, and neither bullet said so.

- #772 (with #790's docstring reword), #766, #787, #794 (with #791's
  citation repoint), #792/#798, #802, #779 (via #782, a further
  #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one
  bullet each), #796, #800 -- the other 14, non-breaking.

Also restores a measurement an earlier round in this same diff dropped
while updating an adjacent one: the #692 entry's "mypy is unmoved at 112
errors" silently lost its "and pylint ... 364 messages" half when
`EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence.
Restored to the last value earlier rounds signed off on rather than
re-measured, since this branch's own `pcapkit/` tree predates several
since-merged PRs and a fresh run would not be measuring the same thing the
original round measured. Unmoved, and not silently dropped this time:
`93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES`
44 to 43 in the several other places that already carried the fix.

On which PRs get a bullet: there is no clean "user-facing only" rule --
#766 and #791 are pure CI/test/lint-comment entries that are in, while
#773 and #763 are the same kind of thing and are out. The real pattern
across this file's 46 commits is closer to "each round's author judged it
worth a reader's time," which is inconsistent by construction. This round
leaves that inconsistency as found rather than trying to retrofit a rule,
but did add #779 (via #782) on reconsideration -- its own PR body names it
as sharing #766's hazard, and pre-existing precedent already treats that
hazard's instances as bullet-worthy.

`changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0.
`test_changelog_md.py` 47 passed.
JarryShaw added a commit that referenced this pull request Sep 26, 2026
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit
(relative to its parent, 4233555) now names all 20 bullets it carries, not
just the 8 this session added on top of the 10 already there -- a first
draft of this message named only its own 8 and left the other 10 silent,
which a cross-review caught. Six are breaking, matching the crediting PRs'
own `breaking` label in each case:

- #754 -- AppType split into per-transport registries; the 1,004 portless
  and 704 transportless rows stop being members.
- #764 -- an out-of-range port in `AppType.get` is refused, not minted.
- #778 -- `@final` enforced at runtime on `Info`/`Schema`.
- #575 -- four `.get()`-backed enum fields fall through to
  `_unregistered_member` instead of minting; 14 of 23 sample captures
  change output.
- #759 -- `AppType._dispatch` on a multi-transport `proto` now raises
  `ProtocolError` instead of silently resolving to whichever transport
  owns the lowest set bit.
- #805 -- `FieldBase.length` on a negative resolved length now raises
  `ProtocolError` instead of letting a bare `struct.error` escape.
  A second cross-review caught both: their crediting PRs (#783, #811) both
  carry GitHub's own `breaking` label, and neither bullet said so.

- #772 (with #790's docstring reword), #766, #787, #794 (with #791's
  citation repoint), #792/#798, #802, #779 (via #782, a further
  #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one
  bullet each), #796, #800 -- the other 14, non-breaking.

Also restores a measurement an earlier round in this same diff dropped
while updating an adjacent one: the #692 entry's "mypy is unmoved at 112
errors" silently lost its "and pylint ... 364 messages" half when
`EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence.
Restored to the last value earlier rounds signed off on rather than
re-measured, since this branch's own `pcapkit/` tree predates several
since-merged PRs and a fresh run would not be measuring the same thing the
original round measured. Unmoved, and not silently dropped this time:
`93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES`
44 to 43 in the several other places that already carried the fix.

On which PRs get a bullet: there is no clean "user-facing only" rule --
#766 and #791 are pure CI/test/lint-comment entries that are in, while
#773 and #763 are the same kind of thing and are out. The real pattern
across this file's 46 commits is closer to "each round's author judged it
worth a reader's time," which is inconsistent by construction. This round
leaves that inconsistency as found rather than trying to retrofit a rule,
but did add #779 (via #782) on reconsideration -- its own PR body names it
as sharing #766's hazard, and pre-existing precedent already treats that
hazard's instances as bullet-worthy.

`changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0.
`test_changelog_md.py` 47 passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaks public-facing behaviour or API (apply alongside the type label) const Regenerated IANA or vendor constant tables; members keep their numeric values fix Pull requests that fix a defect (fix: subject prefix)

Projects

None yet

1 participant