Skip to content

fix(registry): add register_protocol's identity guard to sibling registrars - #726

Merged
JarryShaw merged 1 commit into
mainfrom
fix/718-sibling-registry-identity-guard
Sep 24, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/718-sibling-registry-identity-guard

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

What is the purpose of your pull request?

  • fix — corrects a defect

Description of your pull request and other information

Ten code-keyed registrars warned on presence alone, so re-registering the exact same class read as "overwriting X with X" — worse than #710/#711, since nothing distinguished that no-op from a real collision. Added register_protocol's identity guard (incumbent is not None and incumbent is not X) to all ten; message text unchanged.

File Line Method
protocol.py 801 ProtocolBase.register
internet.py 166 Internet.register
link.py 146 Link.register
transport.py 117 Transport.register
sctp.py 630 SCTP.register
frame.py 153 Frame.register
protocols/misc/pcapng.py 888 PCAPNG.register
schema.py 1131 EnumSchema.__init_subclass__ (reachable via ordinary subclassing, not symmetry-only: a repeated or aliased member in code=[...] hits the same key twice with cls on both sides)
schema.py 1186 EnumSchema.register
schema/misc/pcapng.py 767 Option.register

Option.register was the one holdout, now closed. It fans one registration out across a namespace dict-of-dicts rather than a single mapping, so it was left on presence-only with an explicit docstring admission that __init_subclass__'s code=[...] loop could reach it twice with the same class. It can, and the presence-only guard warned on the second call about the class overwriting itself. Same identity criterion as the other nine, adapted to check each namespace targets reaches via .get() rather than in; docstring rewritten to match and drop the admission.

Step 2 (#711's id() disambiguation) — not applied. #711's own cross-review found the id() suffix defeats __warningregistry__ dedup, with a nondeterministic delivered-warning count under GC pressure (199, 191, then 3 of 200 in one measurement). Once step 1 removes the dominant "X with X" cause, the residual case — two different classes sharing a repr — is mostly a dynamic-factory/test-class shape, not a real-usage concern. Paying that cost ten more times isn't worth it; the identity guard alone is the fix.

Falsification: one same-object no-op test per site (9 total), each shown failing against the unfixed guard with the literal "overwriting X with X" text, passing after. Also fixed 6 pre-existing tests that used a same-object re-registration as their "overwrite" case, incl. test_sibling_registries_still_warn_on_an_identical_re_registration, which pinned the old behaviour by name. Added a tenth test, test_a_repeated_code_in_the_declaration_list_stays_quiet, covering schema.py's guard through ordinary class X(Base, code=[A, A]) syntax and an enum-alias pair — the prior per-site test only called __init_subclass__() directly. For Option.register: two more tests, both shown failing against the unfixed guard — a code=[b, b] registration of the same class (0 warnings expected, guard produced 1) and the same shape displacing a genuinely different class (1 warning expected, guard produced 2).

Coverage (9 files, targeted suite): 3294→3301 stmts, miss and BrPart unchanged (180/97) — every new statement/branch fully exercised. pylint/mypy message counts unchanged in content (364/21) after retargeting two # type: ignore comments mypy flagged as stale. schema/misc/pcapng.py (targeted test_pcapng_unit.py run): 538 stmts/72 branches, 100% both before and after — the two new tests close a behavioural gap coverage numbers alone don't show.

Could not verify: full make test; CI (not run).

Fixes #718.

@JarryShaw JarryShaw added bug Issues reporting a defect (set by the bug report template; a default, not an assessment) fix Pull requests that fix a defect (fix: subject prefix) and removed bug Issues reporting a defect (set by the bug report template; a default, not an assessment) labels Sep 23, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ d12c3b441 — EnumSchema.__init_subclass__'s guard is reachable through ordinary code=[X, X] / enum-alias syntax (measured: warns pre-fix, silent post-fix), so schema.py:1125-1130, the test_declaring_the_same_class_twice_stays_quiet docstring and the PR table all assert the opposite of what it does — correct all three and test that path.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ d12c3b441 — EnumSchema.__init_subclass__'s guard is reachable through ordinary code=[X, X] / enum-alias syntax, so schema.py:1125-1130, the test_declaring_the_same_class_twice_stays_quiet docstring and the PR table all assert the opposite of what it does — correct all three and test that path.

Independent cross-review, Opus (author: Sonnet). Verified on d12c3b441 and on a clean merge of origin/main.

Blocker. code= is an Iterable and the loop at schema.py:1124 visits every element, so a repeated member reaches the same key twice with cls on both sides. With that hunk reverted:

code=[Code.one, Code.one]                          -> schema 1 already registered, overwriting <class 'abc.Dup'> with <class 'abc.Dup'>
code=[Code.one, Code.uno]  (Code.uno is Code.one)  -> same
code=[Code.one, Code.two]  (control)               -> silent

Post-fix all three are silent, so the guard is live rather than symmetry-only. Not a contrived shape either: #721 just put that list form into production (R1CounterParameter(Parameter, code=[R1_Counter, R1_COUNTER]) — 128/129, distinct, so it does not fire), and 4 pcapkit.const enums carry real aliases, reg.linktype.LinkType (I2C_LINUX) and esp.cipher.Cipher (30) among them. Please cover it too: the only test for this site calls __init_subclass__() directly, which its own docstring calls "the only way" — the claim being corrected.

Also. pcapkit/protocols/schema/misc/pcapng.py:725-733 (Option.register, untouched here) still defends its presence-only guard with "which is what the seven sibling register methods on ProtocolBase and friends already assume". This PR falsifies that premise and leaves the prose asserting it; it is a 10th sibling in the family, still presence-only.

claim verdict what I derived independently
9 sites, none pre-guarded ✅ 9 presence checks removed / 9 incumbent is not None and incumbent is not X added, across 8 files
same guard as register_protocol ✅ condition byte-identical to protocols.py:221; #711's id() half deferred, as #718 itself asks
identity, not equality ✅ is not throughout, and correct — ProtocolBase.__eq__ (protocol.py:1325) is name-based against str, so == could hide the very #710 collision the warning exists for
schema.py opt-out intact ✅ if code is not None: unchanged; no-code= and explicit code=None → 0 warnings, registry unchanged. dict.get never calls __missing__, so #555's non-recording miss still holds
tests fail without the fix ✅ revert transport.py → Expected 'warn' to not have been called. Called 1 times. … "port 80 already registered, overwriting <class '…Raw'> with <class '…Raw'>" (1 failed/10 passed). revert schema.py → both new tests fail on "schema 1 already registered, overwriting <…Twice'> with <…Twice'>" (2 failed/10 passed)

Reach is narrower than the body implies. Seeded registries hold ModuleDescriptors, so re-registering a built-in under its own built-in code still warns (overwriting ModuleDescriptor(module='pcapkit.protocols.link.arp', name='ARP') with <class …ARP>) — the guard only helps once the incumbent is already a class. A walk_packages sweep of all of pcapkit emits 0 RegistryWarnings pre- and post-fix: latent, not user-visible.

breaking label — your call, not a blocker. Against: the registry write is identical on both paths and the package's own imports are unaffected. For: 6 pre-existing tests had to change, one pinning the old behaviour by name. I read it as not breaking, since the guard only ever fired where nothing was displaced.

Runs (3.14.7, pcapkit proven to load from the worktree; fixtures generated, 0 skipped). Branch, 9 test files: 203 passed, 1930 subtests, exit 0. Merged tree — origin/main is now 1aae1da30, not daa953d1b — merge exit 0 with 0 conflicts; those 9 files plus the 3 main just touched: 250 passed, 2468 subtests, exit 0. mypy: 21 errors in the 8 touched files (matching the body), none on a changed line, and no unused-ignore at schema.py:1131/1186, so the retargeted codes are right under warn_unused_ignores = True.

#726 vs #728: disjoint library files, and the shared test_pcapng_unit.py hunks sit ~2700 lines apart, so no conflict either way. I would land #728 first — #726 needs another revision regardless, so its mandatory rebase absorbs #728 rather than the reverse, and the Option.register fix above would pull #726 into schema/misc/pcapng.py, which #728 rewrites.

❌ NEEDS CHANGES @ d12c3b441 — correct the three reachability claims, add a code=[X, X]/alias test, and update schema/misc/pcapng.py:725-733's now-false cross-reference.

@JarryShaw
JarryShaw force-pushed the fix/718-sibling-registry-identity-guard branch from d12c3b4 to 6634e22 Compare September 24, 2026 02:00
@JarryShaw JarryShaw added the review: pending No verdict for the current head - never reviewed, or the head moved since the last one label Sep 24, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 6634e2283 — correct schema/misc/pcapng.py:725-734's two now-false sentences (my d12c3b441 third demand, unaddressed; Option.register is a 10th sibling I measured still self-displacing) and the body's coverage pair (116/80) → (180/97).

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 6634e2283 — correct schema/misc/pcapng.py:725-734's two now-false sentences, and the body's (116/80) → (180/97).

Independent cross-review, Opus (author: Sonnet). Supersedes my d12c3b441 verdict, which made three demands: two are done. The reachability corrections are accurate rather than merely softened — schema.py:1125-1132, the test_declaring_the_same_class_twice_stays_quiet docstring and the body table now each state positively that a repeated or aliased member in code=[...] reaches the guard through ordinary syntax. The third demand is untouched.

Blocker 1 — Option.register (pcapkit/protocols/schema/misc/pcapng.py:725-734), still presence-only, prose now false in two ways.

  • "__init_subclass__ passes each code exactly once per subclass" — disproved. Option.__init_subclass__:700-702 loops an iterable code calling Option.register per element, the same shape as the site you just fixed. Measured at this head in namespace dsb on a code absent from it: control code=a → 0 warnings; code=[b, b] → 1 warning, option already registered in namespace(s) 'dsb', overwriting with <class '…RepOption'> — self-displacement, the exact text RegistryWarning: nine sibling registrars share #710's repr collision, and guard on presence rather than difference #718 exists to remove.
  • "which is what the seven sibling register methods on ProtocolBase and friends already assume" — this PR removes that assumption, so the sentence cites the nine methods you just changed as authority for a premise they no longer hold. Left as-is, the package asserts the opposite of this PR's own new comment two files away.

Correcting those two sentences is enough to clear this; the guard itself is fair follow-up, since #728 rewrites that file.

Blocker 2 — the body's coverage pair does not reproduce. Under the body's own scope (nine modified test files as the pytest selection, eight modified library files as the coverage scope), both sides measured: 4391dc77b → 3294/180/1292/97, 6634e2283 → 3301/180/1292/97. So "3294→3301 stmts" is exact and "miss and BrPart unchanged" is exactly true — but the pair is 180/97. (116/80) reproduced under no scope tried. "Every new statement/branch fully exercised" is independently confirmed: +7 statements, 0 new branches, none missed.

claim verdict what I derived independently
three assertions corrected, not softened ✅ all three affirmative; the direct-call test now cross-references the ordinary-syntax one
new test fails without the library hunk ✅ reverted schema.py to 4391dc77b → 3 failed / 10 passed, exit 1 (you reported 2/11); RepeatedMember and AliasPair each warn displacing themselves; restored → 13/13
nine registrars, all previously defective ✅ exactly 9 presence-only guards at 4391dc77b, exactly 9 identity guards at head, condition matching protocols.py:221; registry write stays outside the guard at all nine, so nothing is dropped
is not == ✅ code, ❌ my stated reason my d12c3b441 rationale was wrong. __eq__ is a classmethod, so Class == Class resolves on the metaclass, and ProtocolMeta/SchemaMeta/EnumMeta all inherit object.__eq__ — two distinct classes with identical reprs compare == False. == would not have hidden #710 at these sites. is is still correct, just for a different reason.
schema.py opt-out survives ✅ no code= and explicit code=None → 0 RegistryWarnings, registry unchanged; _EnumRegistry overrides only __missing__, so .get() really returns None
breaking — your call already recorded as no; not re-litigated

Also measured. Merged into current main (bf57b4542) with --no-ff: 0 conflicts, 207 passed, exit 0; the one auto-merged file is ~3,600 lines apart from your hunks. Branch-only: 204 passed, 1930 subtests, exit 0, 0 skips — matching the amended message, so the old "249 passed, 1 skipped" is gone. No test was weakened: 10 added, 0 removed (one rename), and real-collision coverage grew from 3 registrars to 8. After #727, CI's test job still runs all 11 new tests (verified fixture-less: 206 passed, 1 pre-existing skip). [index]→[arg-type] is the right retarget — 0 unused-ignore in schema.py under warn_unused_ignores. One body-accuracy note: reach is narrower than the body implies — all 7 Link.__proto__ entries are seeded ModuleDescriptors, and registering the very class one names still warns; the guard only helps once the incumbent is already a class.

❌ NEEDS CHANGES @ 6634e2283 — the two Option.register sentences, and (116/80) → (180/97).

@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
@JarryShaw
JarryShaw force-pushed the fix/718-sibling-registry-identity-guard branch from 6634e22 to 22b5739 Compare September 24, 2026 04:29
@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

Addressed both remaining items from the last review:

  • Option.register's docstring (pcapkit/protocols/schema/misc/pcapng.py:717-735): corrected the two now-false sentences. It no longer claims __init_subclass__ passes each code exactly once per subclass (it loops over a code list, so a repeated/aliased member reaches this method twice with the same class, same as the failure just fixed elsewhere), and no longer cites the seven ProtocolBase-family siblings as still assuming that -- they were the ones just fixed away from it. The "seven" count itself is correct; only the claim about their current behavior was wrong. Also tightened the #681 cross-reference in the first paragraph, which called it "the same guard" while the next paragraph says otherwise.
  • PR body coverage figure corrected: (116/80) -> (180/97).

Amended onto the existing commit, force-pushed. New head: 22b57397e.

@JarryShaw
JarryShaw force-pushed the fix/718-sibling-registry-identity-guard branch from 22b5739 to 63dce26 Compare September 24, 2026 13:31
@JarryShaw JarryShaw changed the title fix(registry): add register_protocol's identity guard to nine sibling registrars fix(registry): add register_protocol's identity guard to sibling registrars Sep 24, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Closed the one holdout: Option.register in schema/misc/pcapng.py was left on
presence-only, with a docstring admitting __init_subclass__'s code=[...] loop
could call it twice with the same class. It did, and the old guard warned about the
class overwriting itself. Now identity-based, matching the other nine — checks each
namespace targets reaches via .get() rather than in, so no defaultdict insert.
Docstring's flaw admission is gone; #681 provenance kept.

Two new tests, both shown failing against the unfixed guard before the fix:

  • code=[b, b] registering the same class: expected 0 warnings, guard gave 1.
  • Same shape displacing a different class: expected 1 warning, guard gave 2.

tests/protocols/misc/test_pcapng_unit.py: 81 passed (was 79), 1753 subtests.
schema/misc/pcapng.py: 538 stmts / 72 branches, 100% coverage, unchanged by the fix.

PR title/body and table updated to ten registrars.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 63dce26ad — three now-false prose sites: pcapkit/foundation/registry/protocols.py:171-186, tests/protocols/misc/test_pcapng_unit.py:4428-4433, tests/protocols/schema/test_enum_schema_registry_unit.py:421; implementation itself is sound.

@JarryShaw

JarryShaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 63dce26ad — three now-false prose sites: pcapkit/foundation/registry/protocols.py:171-186, tests/protocols/misc/test_pcapng_unit.py:4428-4433, tests/protocols/schema/test_enum_schema_registry_unit.py:421.

Independent cross-review, Opus (author: Sonnet). Supersedes my 6634e2283 verdict — both its blockers are cleared: the Option.register docstring is corrected and the guard itself fixed (more than I asked), and the body reads 180/97. No behavioural defect at any of the ten sites; every finding below is prose.

  • Blocker 1 — register_protocol's own docstring still asserts the guard this PR removes. foundation/registry/protocols.py:172 "every sibling registry warns on mere presence"; :173-175 "a repeat call is a caller mistake worth reporting even when the value is identical" — the reasoning RegistryWarning: nine sibling registrars share #710's repr collision, and guard on presence rather than difference #718 rejected, present tense; :181-186 then argues at length against what you just did ten times. You deleted the near-verbatim copy of this paragraph from tests/foundation/registry/test_protocols.py:172-176 and missed the library original it paraphrased, so protocol.py:781's new "The guard now matches register_protocol's" walks a reader into the contradiction. This is the class you blocked me on: the package asserting the opposite of the PR's own comment two files away.
  • Blocker 2 — the premise blocked at 6634e2283 survives verbatim, in a file this PR edits. tests/protocols/misc/test_pcapng_unit.py:4428 "Presence alone is the test, as it is for the seven sibling registrars" and :4431-4433 "__init_subclass__ passes exactly once per subclass … which is what the ProtocolBase, Frame, Internet, PCAPNG, SCTP, Transport and Link registrars already do" — ~130 lines above test_repeated_code_list_registering_the_same_class_is_silent, whose whole point is that it does not. Third round on that sentence. Body is correct and passes.
  • Blocker 3 (minor) — test_enum_schema_registry_unit.py:421 "what makes a presence-only guard safe here"; summary line only, body still catches a EnumSchema registries retain every looked-up code, so a lookup miss leaks and contaminates later parses #555 regression under the identity guard.
claim verdict evidence I obtained
new tests fail on the unfixed guard ✅ guard hunk alone reverted → 2 failed, exit 1, verbatim AssertionError: Lists differ: […option already registered in namespace(s) 'if', overwriting with …_RepeatedCodeOption"…] != [] and AssertionError: 2 != 1; restored → 2 passed. All 9 library files reverted → 15 failures across 9 test files, so every new test is load-bearing, not only these two.
79 → 81 passed, 1753 subtests ✅ 81 passed, 1753 subtests passed, exit 0, 0 skips (after make_samples.py). Not disputing "79": the true merge base 4391dc77b measures 78, so 79 is right at the prior revision's scope.
schema/misc/pcapng.py 538/72, 100% both sides ✅ head 538 / Miss 0 / 72 / BrPart 0 / 100%; structure identical at 4391dc77b and head (686 stmts, 318 branch destinations via coverage.parser), so Stmts/Branch cannot differ. Measure against 4391dc77b, not origin/main — main's #728 adds +71/−7 to this same file and makes it read 551/78. coverage run -m pytest, never pytest-cov.
docstring drops presence-only, keeps #681, in→.get() ✅ :723 keeps #681, :749 reads "Membership is tested with .get()"; no site retains the old rationale.
body says ten, Option.register its own row ✅ "Ten code-keyed registrars", 10-row table. Nits: title carries no count; the two schema.py rows are 2 lines stale (actual 1133/1188); and "(9 files) 3294→3301" is an 8-file pair — schema/misc/pcapng.py is 538 at both ends, so the 9-file pair is 3832→3839.
ten sites genuinely consistent ✅ exactly 10 guard conversions in 9 files (EnumSchema carries two), each comparing against the object it then stores, registry write outside the guard at all ten. Nine siblings + Option.register = ten — "seven" is the docstring this PR fixes.

Insertion invariant — holds, in both directions. _EnumRegistry.__missing__ hands back the 'opt' inner dict by identity without inserting (outer 7→7, key absent after); inner .get() miss → None (22→22); inner […] miss inserts UnknownOption (22→23) — on head and base. Every length and insert flag identical across the two, so in→.get() is insertion-neutral; and inside if not fresh every targets key is already present, so the outer is never even missed. Worth knowing: test_the_collision_check_does_not_insert_a_default passes under a deliberately subscripted guard — it does not discriminate. Your new test_repeated_code_list_registering_the_same_class_is_silent is what actually catches that mutant.

ModuleDescriptor incumbent — real and reachable, but not a regression. Every seeded protocol-layer entry is a descriptor, zero classes (Internet 16, Link 7, Frame 3, TCP 4, UDP 3, PCAPNG 3, SCTP 2), so the first register of the class a seeded descriptor names warns — byte-identical message on head and base — and only the second is silent at head. Head never warns more than base. The guard therefore starts helping from the second registration of any shipped code, which protocol.py:781-791 now states correctly. Fan-out is also right: mixed state (A in five namespaces, B in if/epb), register(code, A, ns='opt') → head 1 warning naming 'if', 'epb', base 1 naming all seven; same class twice → 0.

One real "warns less than it should". EnumSchema.register and Option.register have no runtime type check on the value (the protocol-layer ones raise RegistryError), so register(code, None) stores None and .get() then conflates absent with present-None: the next genuine registration is silent at head, warned at base (0 vs 1, measured at all three layers). No shipped registry holds a None, so it needs misuse to reach — a caveat, not a blocker.

Follow-up, not a blocker. Class-valued presence-only registrars survive outside #718's "renders {incumbent!r}" criterion: Extractor.register_engine (extraction.py:412), register_reassembly (:436) and register_traceflow (:460) each store a bare class and warn on presence, and each is the funnel for an auto-registering __init_subclass__ (engines/engine.py:319, reassembly/reassembly.py:539) — so declaring a subclass and then calling the registrar with it warns about nothing displaced, #718 case 2 exactly. register_dumper (:386) is class-valued too but stores (dumper, ext), so identity there needs an elementwise compare.

Merged into current main — clean and green. Worth checking because the PR is behind main (base 4391dc77b) and main has since changed schema/misc/pcapng.py (+71/−7, #728) and tests/protocols/misc/test_pcapng_unit.py (+234) — both files this PR edits. Merged into 55513f69e with --no-ff: 0 conflicts, both contested files auto-merged by ort; the nine selections then give 214 passed, 1930 subtests, 0 failures, 0 skips (195.75s), and the two Option tests pass on the merged tree. The rebase will still move the 538/72 figure, since #728 grows that file. Could not verify: the 180/97 Miss/BrPart pair, which needs the wider suite than any selection I ran.

❌ NEEDS CHANGES @ 63dce26ad — correct foundation/registry/protocols.py:171-186, test_pcapng_unit.py:4428-4433 and test_enum_schema_registry_unit.py:421; the implementation itself is sound and needs no change.

@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
@JarryShaw
JarryShaw force-pushed the fix/718-sibling-registry-identity-guard branch from 63dce26 to 3bbc4e6 Compare September 24, 2026 14:40
@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

Addressed cross-review's three prose sites (head 3bbc4e65e):

  1. pcapkit/foundation/registry/protocols.py:171-186 — register_protocol's own
    docstring argued "every sibling warns on mere presence" and "a repeat call is
    a caller mistake even when identical". Rewrote: guard still reads "present
    and different", but now notes the siblings share the same identity criterion
    as of RegistryWarning: nine sibling registrars share #710's repr collision, and guard on presence rather than difference #718, not the opposite.
  2. tests/protocols/misc/test_pcapng_unit.py:4428 — test docstring claimed
    "the seven sibling registrars" use presence alone and __init_subclass__
    "passes exactly once per subclass". Both false post-fix; rewrote to describe
    the case as a genuine displacement the identity guard still reports.
  3. tests/protocols/schema/test_enum_schema_registry_unit.py:421 — summary line
    said "a presence-only guard"; changed to "the identity guard".

Grepped all 9 test files this PR touches for the same phrasing (presence alone, mere presence, exactly once per subclass, seven/nine sibling) —
no further live instances; remaining "presence alone" hits are accurate
"before the fix" history.

Re-ran the three affected test files: 104 passed, 1838 subtests, exit 0.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 3bbc4e65e — pcapkit/foundation/registry/protocols.py:186-187's new clause "which is no longer the case anywhere in the package" is false: 23 presence-only guards are live, four class-valued in foundation/extraction.py, and #739 tracks exactly those. Six words; the other two fixes read true.

@JarryShaw

Copy link
Copy Markdown
Owner Author

❌ NEEDS CHANGES @ 3bbc4e65e — pcapkit/foundation/registry/protocols.py:186-187: the new clause "which is no longer the case anywhere in the package" is false. Six words; nothing else.

Independent cross-review, Opus (author: Sonnet). Supersedes my 63dce26ad verdict. Prose-only delta, exactly the three files I named, no guard logic touched — so everything I verified at 63dce26ad carries forward unchanged and was not redone.

The blocker. Fix 1 removed the two false sentences correctly, but its replacement ends on a package-wide universal that does not hold. 23 presence-only already registered guards are live at this head, four of them class-valued — foundation/extraction.py:413 register_engine, :437 register_reassembly, :461 register_traceflow, :386 register_dumper — plus foundation/traceflow/traceflow.py:223 register_dumper; and #739 exists to track exactly those. So the docstring now asserts the negation of an open issue in the same repo. The preceding sentence is true and precise: I checked every survivor and none is a method named register (they are register_engine, register_block, register_chunk, register_option, …), so "the sibling register methods … apply the same identity criterion as of #718" is exactly right. Only the trailing generalisation overreaches. Fix: delete , which is no longer the case anywhere in the package, or scope it — …before that they warned on presence alone; none of them does now.

fix reads true? what I checked
protocols.py:171-187 ⚠️ partly "every sibling registry warns on mere presence" and "worth reporting even when the value is identical" are gone ✅; derived-key + wrapper-funnel rationale kept ✅; new trailing clause false ❌
test_pcapng_unit.py:4428-4434 ✅ now "presence alone is not the test, only whether the incumbent differs from the replacement"; the false "exactly once per subclass" and "seven sibling registrars" are gone, and the body still pins a genuine displacement (if_name → UnknownOption)
test_enum_schema_registry_unit.py:421 ✅ "presence-only guard safe" → "identity guard safe"; body unchanged and still catches a #555 regression

Sweep spot-checked, not trusted. Your five terms (seven/nine sibling, mere presence, caller mistake worth, exactly once per subclass, presence-only guard safe) return zero hits across pcapkit/, tests/ and docs/ — confirmed. I widened it to presence alone|presence-only|on mere|deliberately departs|warns on presence|presence is the test: 18 hits, and every one is accurate — correct "Before the fix…" history, or a correct hypothetical (protocols.py:179, test_protocols.py:183). The single false statement on this topic anywhere in the package is the clause this revision added. Two earlier rounds each missed a site; this sweep did not.

CI, read directly. 22 pass / 0 fail / 0 cancelled. The three skipping marks are by design — Compat Python 3.15 is scheduled-only (b7f51401b dropped it from the blocking matrix), and Docs test gate / Gate (full suite, Python 3.14) chain off Unit Tests (bea54df28). Rollup reads PENDING solely because deploy-pages has not finished; no test, lint or integration job is outstanding. BEHIND is main moving to 9b2d927c2, not a conflict — still MERGEABLE.

One provenance caveat on my own evidence. The "15 failures across 9 files on full revert" figure remains single-sourced from my measurement agent; I have not re-derived it personally. Its composition, so it is checkable: 6 failures in test_pcapng_unit.py + test_enum_schema_registry_unit.py, and 9 across the seven protocol-layer files. It still applies at this head because the only pcapkit/ change since 63dce26ad is a docstring.

❌ NEEDS CHANGES @ 3bbc4e65e — delete or scope protocols.py:186-187's "which is no longer the case anywhere in the package"; the implementation and the other two fixes are sound, and that clause is the only thing standing between this and good-to-go.

…strars

- Ten code-keyed registrars (ProtocolBase, Internet, Link, Transport, SCTP,
  Frame, PCAPNG, EnumSchema's register + __init_subclass__, and pcapng.py's
  Option.register) warned on mere presence, so re-registering the exact same
  class under the same code emitted a misleading "overwriting X with X".
  Guard each on presence AND identity, matching register_protocol's guard
  from #681/#711.
- Option.register needed its own fix: __init_subclass__ loops over a code
  list with no deduplication, so code=[b, b] reached it twice with the same
  class and warned about a self-overwrite. Rewrote its docstring, dropping
  the now-false admission that this could not happen.
- Updated each site's docstring: the "fires on presence alone, deliberate"
  rationale (added by #695) no longer holds now the guard changed.
- Repurposed test_sibling_registries_still_warn_on_an_identical_re_registration
  (tests/foundation/registry/test_protocols.py) and fixed five other
  pre-existing tests that re-registered a literal same object as their
  "overwrite" case.
- Added one same-object no-op test per site (nine total), plus a tenth
  covering EnumSchema.__init_subclass__'s class-declaration path, and two
  more for Option.register's own code=[b, b] shape (silent on the same
  class, still warns once on a genuine displacement).
- Cross-review found three prose sites that still argued the rejected
  reasoning: register_protocol's own docstring (foundation/registry/
  protocols.py) claiming every sibling warns on mere presence, a
  test_pcapng_unit.py test docstring claiming __init_subclass__ passes each
  code exactly once, and a one-line summary in
  test_enum_schema_registry_unit.py calling the guard presence-only. Fixed
  all three; grepped every test file this PR touches for the same phrasing,
  no further instances.

Did not apply #711's id() disambiguation to the siblings -- see PR body.
Build: targeted pytest run (9 files), 204 passed, 1930 subtests, exit 0.
Option.register: test_pcapng_unit.py, 81 passed, 1753 subtests, pcapng.py at
100% line/branch coverage, exit 0. Re-verified with the two other touched
test files: 104 passed, 1838 subtests, exit 0.

Fixes #718.
@JarryShaw
JarryShaw force-pushed the fix/718-sibling-registry-identity-guard branch from 3bbc4e6 to 0f84641 Compare September 24, 2026 14:55
@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

✅ GOOD TO MERGE @ 0f8464154 — the overclaiming clause is gone and "none of them does now" reads true: scoped to the nine code-keyed register methods, all nine carry the identity guard, and #739's five live presence-only guards now sit outside the sentence rather than contradicting it. CI 20 pass / 0 fail.

@JarryShaw

Copy link
Copy Markdown
Owner Author

✅ GOOD TO MERGE @ 0f8464154 — the overclaiming clause is gone and its replacement reads true; no behavioural defect at any of the ten sites.

Independent cross-review, Opus (author: Sonnet). Supersedes my 3bbc4e65e verdict. The delta is one line in one file (1 insertion, 2 deletions), touching only the paragraph I blocked on, so everything verified at 63dce26ad and re-confirmed at 3bbc4e65e carries forward untouched and was not redone.

The replacement is true, and I checked the scope rather than the wording. It now reads "…apply the same identity criterion as of GitHub issue #718; before that they warned on presence alone, and none of them does now", whose subject is explicitly "the sibling register methods … each keyed on a caller-supplied code". That set is twelve methods named register in pcapkit/, of which nine are the code-keyed sibling registrars — and all nine carry the identity guard at this head (11 identity guards across 10 files: this PR's 10 sites plus register_protocol's own from #681). The other three are outside the qualifier and do not warn on presence anyway: corekit/context.py:125 raises RegistryError instead of warning, and reassembly/reassembly.py:397 and internet/esp.py:916 take a callback and an SA, not a code. So "none of them does now" holds, and the five live presence-only guards in #739 — foundation/extraction.py:386, :413, :437, :461 and foundation/traceflow/traceflow.py:223 — now sit outside the sentence's scope rather than being contradicted by it. That is the whole of what I asked for.

Sweep re-run, not assumed. Your five terms (seven/nine sibling, mere presence, caller mistake worth, exactly once per subclass, presence-only guard safe) return zero hits across pcapkit/, tests/ and docs/. My widened six patterns return 18 hits, unchanged from last round and every one accurate — correct "Before the fix…" history, or a correct hypothetical at protocols.py:179 and test_protocols.py:183. "anywhere in the package" now survives only at corekit/fields/numbers.py:571, in an unrelated sentence about in-library guards. There is no false statement left in the package on this topic.

CI, read directly at this head. 20 pass, 0 fail, 0 cancelled, 3 skipping, 1 pending. All five unit-test matrix jobs (3.10–3.14) green, all five integration jobs green, plus Lint, CodeQL, Analyze, Changelog drift and safety-ci. The three skips are by design — Compat Python 3.15 is scheduled-only per b7f51401b, and Docs test gate / Gate (full suite, Python 3.14) chain off Unit Tests per bea54df28. The only pending job is deploy-pages, a docs deployment that gates nothing. BEHIND is main having moved, not a conflict; still MERGEABLE.

Three caveats that survive to merge, none blocking. The 180/97 Miss/BrPart pair in the body is still unverified — it needs a wider suite than any selection I ran. The "15 failures across 9 files on full revert" figure remains single-sourced from my measurement agent rather than independently re-derived; its composition, so it stays checkable, is 6 failures in test_pcapng_unit.py + test_enum_schema_registry_unit.py and 9 across the seven protocol-layer files, and it still applies because no guard logic has changed since. And the mandatory rebase will move the body's 538/72, since main's #728 grows schema/misc/pcapng.py — worth re-reading the coverage line after rebasing, though I measured the claim true at the PR's own base.

✅ GOOD TO MERGE @ 0f8464154 — three rounds of prose findings are all closed, the implementation was never in question, and #739 carries the out-of-scope registrars I turned up along the way.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 24, 2026
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…t missed

register_engine, register_reassembly, register_traceflow, and both
register_dumper copies (Extractor and TraceFlowBase) warned on mere key
presence, so re-registering the exact same class or descriptor read as
"overwriting X" even though nothing was displaced. #718 fixed this for ten
code-keyed registrars but excluded these five because their message wording
falls outside its criterion, even though they share the defect.

- Replace each `if key in cls.__dict__:` guard with `incumbent = cls.__dict__
  .get(key)` + `incumbent is not None and incumbent is not value`, matching
  the identity-guard shape #726 gives the ten sibling registrars. Message
  text is unchanged.
- register_engine/reassembly/traceflow: all three seed dicts hold only
  ModuleDescriptors, never a class, so the *first* real registration
  (descriptor -> class) still legitimately warns; only a *repeat*
  registration is now silent. Plain dicts, so `.get()`/`in` are both
  non-inserting.
- register_dumper (both copies): __output__ maps format -> (dumper, ext), so
  the identity check compares the incumbent dumper (index 0), not the pair --
  a re-registration that only changes ext still counts as a different entry.
  __output__ is a `collections.defaultdict`; measured that `.get()` does not
  invoke the default factory the way a subscript access would (confirmed:
  len() unchanged on a `.get()`/`in` miss, +1 on a `d[missing]` subscript).
- Add tests pinning, per registrar: same-object re-registration is silent,
  different-object replacement still warns with the same message, and the
  registry write happens on both paths. Verified all five fail against the
  unfixed guard.

Ran tests/foundation/test_extraction.py + tests/foundation/traceflow/
test_traceflow_base.py: 19 -> 21 passed (28 subtests). coverage:
extraction.py 370 -> 372 stmts (miss unchanged at 7, 96%), traceflow.py
121 -> 123 stmts (0 miss, 100%) -- all new statements fully exercised.

Fixes #739.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
Library modules imported a *Base class aliased to its public name, so the
source read `class PCAP(Engine)` while actually inheriting `EngineBase`. Per
the ruling on #514 the split is permanent and library classes inherit the
base, so the base is now imported under its own name.

* rewrote 28 of the 82 alias imports -- EngineBase 8 of 8, ReassemblyBase 3
  of 3, DumperBase 2 of 2, TraceFlowBase 2 of 2, ProtocolBase 13 of 67
* renamed the code and annotation references that followed, including
  `cast()` targets, `TypeVar(bound=)` and `# type:` comments, but not the
  string values inside `Literal[...]`
* corrected four `docs/source` index pages that named the public class while
  their own class diagram roots the hierarchy at the base
* added `tests/test_base_class_contract.py`, pinning the contract per suite

The four non-protocols families are complete. The remaining 54 sites are all
`ProtocolBase` ones in paths #726 and #742 own, recorded as prefixes in
`PENDING_ALIAS_PATHS` so the entry fails once its pull request lands.

No behaviour change: name registry 38 keys before and after,
`descendants(Public)` 0 in all five suites, 45 dispatches identical and still
identical after clearing the name registry, mypy at its 112-error baseline
with none introduced.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…t missed

register_engine, register_reassembly, register_traceflow, and both
register_dumper copies (Extractor and TraceFlowBase) warned on mere key
presence, so re-registering the exact same class or descriptor read as
"overwriting X" even though nothing was displaced. #718 fixed this for ten
code-keyed registrars but excluded these five because their message wording
falls outside its criterion, even though they share the defect.

- Replace each `if key in cls.__dict__:` guard with `incumbent = cls.__dict__
  .get(key)` + `incumbent is not None and incumbent is not value`, matching
  the identity-guard shape #726 gives the ten sibling registrars. Message
  text is unchanged.
- register_engine/reassembly/traceflow: all three seed dicts hold only
  ModuleDescriptors, never a class, so the *first* real registration
  (descriptor -> class) still legitimately warns; only a *repeat*
  registration is now silent. Plain dicts, so `.get()`/`in` are both
  non-inserting.
- register_dumper (both copies): __output__ maps format -> (dumper, ext), so
  the identity check compares the incumbent dumper (index 0), not the pair --
  a re-registration that only changes ext is still identity-equal on the
  dumper and stays silent. __output__ is a `collections.defaultdict`;
  measured that `.get()` does not invoke the default factory the way a
  subscript access would (confirmed: len() unchanged on a `.get()`/`in`
  miss, +1 on a `d[missing]` subscript). `incumbent_entry[0]` is safe on a
  factory-produced miss too, since the factory itself returns a 2-tuple.
- Add tests pinning, per registrar: same-object re-registration is silent,
  different-object replacement still warns with the same message, and the
  registry write happens on both paths. Verified all five fail against the
  unfixed guard.

Ran tests/foundation/test_extraction.py + tests/foundation/traceflow/
test_traceflow_base.py: 16 -> 21 passed (28 subtests, unchanged). coverage:
extraction.py 367 -> 372 stmts (miss unchanged at 7, 96%), traceflow.py
121 -> 123 stmts (0 miss, 100%) -- all new statements fully exercised.

Fixes #739.
@JarryShaw
JarryShaw merged commit c63c830 into main Sep 24, 2026
24 checks passed
@JarryShaw
JarryShaw deleted the fix/718-sibling-registry-identity-guard branch September 24, 2026 17:35
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…t missed (#742)

register_engine, register_reassembly, register_traceflow, and both
register_dumper copies (Extractor and TraceFlowBase) warned on mere key
presence, so re-registering the exact same class or descriptor read as
"overwriting X" even though nothing was displaced. #718 fixed this for ten
code-keyed registrars but excluded these five because their message wording
falls outside its criterion, even though they share the defect.

- Replace each `if key in cls.__dict__:` guard with `incumbent = cls.__dict__
  .get(key)` + `incumbent is not None and incumbent is not value`, matching
  the identity-guard shape #726 gives the ten sibling registrars. Message
  text is unchanged.
- register_engine/reassembly/traceflow: all three seed dicts hold only
  ModuleDescriptors, never a class, so the *first* real registration
  (descriptor -> class) still legitimately warns; only a *repeat*
  registration is now silent. Plain dicts, so `.get()`/`in` are both
  non-inserting.
- register_dumper (both copies): __output__ maps format -> (dumper, ext), so
  the identity check compares the incumbent dumper (index 0), not the pair --
  a re-registration that only changes ext is still identity-equal on the
  dumper and stays silent. __output__ is a `collections.defaultdict`;
  measured that `.get()` does not invoke the default factory the way a
  subscript access would (confirmed: len() unchanged on a `.get()`/`in`
  miss, +1 on a `d[missing]` subscript). `incumbent_entry[0]` is safe on a
  factory-produced miss too, since the factory itself returns a 2-tuple.
- Add tests pinning, per registrar: same-object re-registration is silent,
  different-object replacement still warns with the same message, and the
  registry write happens on both paths. Verified all five fail against the
  unfixed guard.

Ran tests/foundation/test_extraction.py + tests/foundation/traceflow/
test_traceflow_base.py: 16 -> 21 passed (28 subtests, unchanged). coverage:
extraction.py 367 -> 372 stmts (miss unchanged at 7, 96%), traceflow.py
121 -> 123 stmts (0 miss, 100%) -- all new statements fully exercised.

Fixes #739.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…one (#750)

Library modules imported a *Base class aliased to its public name, so the
source read `class PCAP(Engine)` while actually inheriting `EngineBase`. Per
the ruling on #514 the split is permanent and library classes inherit the
base, so the base is now imported under its own name.

* rewrote 28 of the 82 alias imports -- EngineBase 8 of 8, ReassemblyBase 3
  of 3, DumperBase 2 of 2, TraceFlowBase 2 of 2, ProtocolBase 13 of 67
* renamed the code and annotation references that followed, including
  `cast()` targets, `TypeVar(bound=)` and `# type:` comments, but not the
  string values inside `Literal[...]`
* corrected four `docs/source` index pages that named the public class while
  their own class diagram roots the hierarchy at the base
* added `tests/test_base_class_contract.py`, pinning the contract per suite

The four non-protocols families are complete. The remaining 54 sites are all
`ProtocolBase` ones in paths #726 and #742 own, recorded as prefixes in
`PENDING_ALIAS_PATHS` so the entry fails once its pull request lands.

No behaviour change: name registry 38 keys before and after,
`descendants(Public)` 0 in all five suites, 45 dispatches identical and still
identical after clearing the name registry, mypy at its 112-error baseline
with none introduced.
JarryShaw added a commit that referenced this pull request Sep 24, 2026
…arning count

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

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

Regenerated CHANGELOG.md from the edited entries.

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

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

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

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

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

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

Regenerated CHANGELOG.md from the edited entries.

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

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

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

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

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

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

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

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

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

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

Regenerated CHANGELOG.md from the edited entries.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Regenerated CHANGELOG.md from the edited entries.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Completes #514 part (c). #750 renamed 28 of 82 `ProtocolBase as Protocol`
alias imports and deferred the remaining 54 -- all `ProtocolBase` -- to paths
#726 and #742 owned. Both have merged, so the deferral is over.

* renamed the alias import at all 54 sites (51 under pcapkit/protocols/, 3
  under pcapkit/foundation/) and every in-file reference that used the local
  alias: class headers, annotations, cast(), isinstance/issubclass checks,
  # type: comments, and the bracketed part of a handful of Sphinx #: doc
  comments -- 191 lines changed, no statements added
* merged two now-unaliased same-module imports per isort in 4 files
  (application.py, internet.py, link.py, transport.py), removing 4 statements
* left descriptive prose, protocol-name string literals, error-message text,
  and fully-qualified :class:/:meth:/:rtype: cross-references to the real
  public Protocol class untouched, matching #750's own precedent
* emptied tests/test_base_class_contract.py's PENDING_ALIAS_PATHS, its
  documented end state, and updated the stale docstring narrative

No behaviour change: __mro__/__bases__/__module__ identical across 18 classes
spanning every family, __proto__ registry 38 keys before and after,
descendants(Protocol) 0 in both. mypy stays at the 112-error baseline.
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 24, 2026
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 4530424 to 3cbdf89 while this PR sat open. Four are the
new PRs merged in that window (#811-#814); the other four are older defects
(#704, #723, #739, #743/#746) whose fixes had merged earlier but were never
cited. Eight new bullets cover them, appended in merge order:

- #704 -- `SystemdJournalExportBlock.post_process` skipped a binary field's
  trailing newline by reading to EOF, discarding every field behind it.
- #723 -- the same block split entries on a bare `b'\n\n'`, shredding binary
  data that contains that byte pair; fixed alongside an independent
  trailing-separator/EOF ambiguity.
- #739 -- five more registrars (`register_engine`/`_reassembly`/`_traceflow`,
  `register_dumper` x2) sat outside #718/#726's identity guard.
- #743, #746 -- `pypcapfile`'s `IP.src`/`.dst` are dotted-decimal text, not
  packed bytes, and its frames need un-hexlifying before decoding; the two
  fixes are cross-dependent and landed together.
- #805 -- `FieldBase.length`'s `struct.calcsize` on a negative resolved
  length raised a bare `struct.error`; now `ProtocolError`. Closes the
  follow-on #802's own entry filed as out of scope.
- #796 -- thirteen `re.sub` sites under `pcapkit/vendor/` passed
  `re.MULTILINE` positionally as `count`, not as `flags=`.
- #800 -- `httpv2._guess_version` now identifies a connection preface before
  parsing it, rather than by trial and error.

Derived the gap by diffing `git log 73f09ae..origin/main` against
`gh pr view --json state,mergedAt` for every candidate number, not from
commit-subject text alone. `CHANGELOG.md` regenerated with
`util/changelog_md.py`; `--check` exit 0 and `test_changelog_md.py`'s 47
tests pass. Sphinx's full-site build did not finish inside budget --
`pcapkit.const.reg`'s autodoc page is slow regardless of this change --
so verified instead with `docutils --report=1`, which parses the updated
file with zero messages.
@JarryShaw JarryShaw added this to the 1.5 milestone Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

RegistryWarning: nine sibling registrars share #710's repr collision, and guard on presence rather than difference

1 participant