Repository navigation
design: is AppType's multi-bit proto necessary, given the enum is already split per transport? #801
Description
Activity
- addedquestionIssues asking how something works rather than reporting a defectIssues asking how something works rather than reporting a defectdesignA design or decision issue: a pattern being decided rather than a defect or a requestA design or decision issue: a pattern being decided rather than a defect or a requestwipWork in flight - a covering PR is open or an agent is actively on itWork in flight - a covering PR is open or an agent is actively on it
on Sep 25, 2026 Measured: the multi-bit
protois fully derivable from the four registries. So no — the full transport list is not necessary.Keyed on
(service name, port), 7,054 groups, 0 disagreements, checked in both directions:- No member's
protoomits its own registry — 0 across all four. - No single-registry group carries a multi-bit
proto— 0. This is the direction the obvious test misses, and it is what closes the argument:protonever claims a transport the registries do not. - Arithmetic cross-check falls out exactly. Popcount distribution over all 12,391 members is
1→1766, 2→10486, 3→123, 4→16; group-span distribution is1→1766, 2→5243, 3→41, 4→4;5243×2=10486,41×3=123,4×4=16. Every member's popcount equals the number of registries its(svc, port)occupies.
And the generator makes it true by construction —
pcapkit/vendor/reg/apptype/apptype.py:806:if self.TRANSPORT not in record.protos or record.port == '-1': continue
A record enters registry X iff
X ∈ record.protos, so registry-set and proto-set are the same set. The multi-bit value is a second copy of a fact the split already carries.Per-registry: TCP 6147 (5287 multi / 860 single), UDP 6143 (5276 / 867), SCTP 91 (52 / 39), DCCP 10 (10 / 0). Empty
proto: 0 — no third case.But retyping is not a cheap attribute change, and this is the part worth deciding on.
proto.nameis baked into each member's underlyingstrvalue, not merely its display —vendor/reg/apptype/apptype.py:251,:271,:274:temp = '%s [%d - %s]' % (name, value, proto.name) # __new__ -> obj._value_ return "<%s.%s: %d [%s]>" % (..., self.proto.name) # __repr__ return '%s [%d - %s]' % (self.svc, self.port, self.proto.name) # __str__
So
TCP['http'].valueis'http [80 - tcp|udp|sctp]'today and would become'http [80 - tcp]'. That changes the value,reprandstrof 10,625 members — a public-contract change deservingbreaking, not a refactor. Identity is safe either way: pickle and both copies all return the same object.Still outstanding:
pcapkit/foundation/registry/protocols.py:833doesproto = code.protoand:846doesif test not in proto:— a real consumer that receives the whole set from one member. The read audit establishing whether it needs the set is still running; that is the last input before I can say the retyping is safe rather than merely lossless.- No member's
Answer: the multi-bit list is not necessary, and my recommendation is to retype. It is load-bearing at exactly one site, and that site is a convenience default that contradicts the rest of the library.
The one site.
register_apptype(member, module)called without an explicitproto=.pcapkit/foundation/registry/protocols.py:833doesproto = code.proto, and the loop at:846fans registration out across a hardcoded{tcp: TCP, udp: UDP}dict — not__registries__, and SCTP/DCCP are deliberately excluded. I verified it myself on477ed00c4:member: <TCP.http: 80 [tcp|udp|sctp]> proto = 7 TCP.__proto__[80] changed: True -> Dummy (displaced httpv1.HTTP) UDP.__proto__[80] changed: True -> Dummy (displaced http.HTTP) --- single-bit contrast: <TCP.imap: 143 [tcp]> --- TCP.__proto__[143] changed: True UDP.__proto__[143] changed: FalseTwo
RegistryWarnings fired, one per registry. That is the whole of the multi-bit value's job.Everywhere else the composite is already refused, because it gave wrong answers. Per #759, feeding a member's own
.protoback intoAppType.get()silently returned the wrong service in 46 of 10,625 cases — including port 888accessbuilder→cddbpand port 999puprouter→garcon, services that do not exist on that transport._dispatch/get/get_allnow raiseProtocolErroron any composite, andtests/const/test_const_apptype_split_unit.py:709-836AST-sweeps the library to prove no lookup site ever builds one.So the library is internally contradictory today: hand a member to
register_apptypeand its compositeprotois trusted to fan out; hand the same member's.prototoget()and you get aProtocolErrortelling you to look one transport up at a time. Retyping removes the contradiction in favour of the half that was arrived at by fixing real bugs.What retyping costs — this is what needs your ruling:
breaking, for 10,625 members.proto.nameis folded into_value_atvendor/reg/apptype/apptype.py:251, soTCP['http'].valuegoes from'http [80 - tcp|udp|sctp]'to'http [80 - tcp]'.reprandstrchange with it. Identity survives pickle and both copies.register_apptype's no-proto=default loses the fan-out. Either require an explicitproto=from the caller — consistent with_dispatch's "one transport at a time" — or rewrite it to look the port up across__registries__, which is not what it does now. It is documented public API, so external callers may rely on the current default.- Four test sites and the docstrings pin the present behaviour and must change with it:
test_const_apptype_split_unit.py:78-80,:630-658,:709-836,tests/foundation/registry/test_protocols.py:421-499, plusvendor/reg/apptype/apptype.py:522-526and the same text generated into all four const files — "Members carry the whole transport protocol set IANA assigned the service."
My recommendation: retype, and make
register_apptyperequire an explicitproto=when the fan-out is wanted. It trades a silent convenience for an explicit argument, and it aligns the registration path with the lookup path that was already corrected. Labellingneeds: decision— points 1 and 2 are yours, not mine.- addedneeds: decisionWaiting on the maintainer to decide — not blocked by other workWaiting on the maintainer to decide — not blocked by other workbreakingBreaks public-facing behaviour or API (apply alongside the type label)Breaks public-facing behaviour or API (apply alongside the type label)and removedwipWork in flight - a covering PR is open or an agent is actively on itWork in flight - a covering PR is open or an agent is actively on it
on Sep 25, 2026 I feel like now that we're already using transport specific enums, we should avoid ambiguity from the enums in all sense. so... yes, go with your recommendation.
one note tho, the Flag type might be useful when doing
register_apptypebut its not used in other places. so we might actually just make the function signature asregister_apptype(port: int, name: str, *transport: TransportProtocol)so that we can supply a list of transport without having to maintain a Flag for it and keeping everything singular and explicit.and maybe allow aliasing, like
register_apptype(port: int, name: str, *transport: TransportProtocol, aliases: list[str] = None), wherealiasesis an optional keyword argument usingnameas the canonical name withaliasesfor the port's aliases like port 80's.Ruling recorded, in your words:
I feel like now that we're already using transport specific enums, we should avoid ambiguity from the enums in all sense. so... yes, go with your recommendation.
one note tho, the Flag type might be useful when doing
register_apptypebut its not used in other places. so we might actually just make the function signature asregister_apptype(port: int, name: str, *transport: TransportProtocol)so that we can supply a list of transport without having to maintain a Flag for it and keeping everything singular and explicit.Retyping is accepted.
protobecomes the registry's own single transport; the composite goes.The varargs idea is the right shape and I agree with the reasoning — "singular and explicit" is exactly what removes the ambiguity, and it beats my own suggestion of a required
proto=kwarg, because a kwarg taking a composite keeps the Flag alive for one caller's benefit. Varargs kill it properly.But your sketch drops three things the current signature carries, so I want the caller inventory before I write it. Today it is:
def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]', class_: 'str' = NULL, *, proto: 'TransportProtocol | str' = NULL) -> 'None':
codeaccepts anEnum_AppTypemember, not just anint.port: intremoves that, which is a larger break than the retyping itself.moduleaccepts aModuleDescriptoror a class object, not just astr;class_is the second half of the string form. A singlename: strcannot express either.*transportwould swallowclass_, sinceclass_is currently positional — so existing three-positional-argument calls would silently bind the class name as a transport.
None of that argues against varargs; it argues that the transport part wants
*transportwhilecode/module/class_need deciding separately. I am measuring every in-repo caller and every placeTransportProtocol's Flag-ness is actually relied on (show_flag_values,intests,|construction) before proposing the exact line — your "not used in other places" is the claim I most want to confirm or refute, since if it holds,TransportProtocolcan stop being a Flag entirely and that is a much cleaner end state.wip. I will post the concrete signature plus the migration plan here, then split the implementation into its own issues.- addedwipWork in flight - a covering PR is open or an agent is actively on itWork in flight - a covering PR is open or an agent is actively on itand removedneeds: decisionWaiting on the maintainer to decide — not blocked by other workWaiting on the maintainer to decide — not blocked by other work
on Sep 25, 2026 my scatches are just POC not how it should actually look like - just using as a refernce/example of the idea.
Both recorded, in your words:
and maybe allow aliasing, like
register_apptype(port: int, name: str, *transport: TransportProtocol, aliases: list[str] = None), wherealiasesis an optional keyword argument usingnameas the canonical name withaliasesfor the port's aliases like port 80's.my scatches are just POC not how it should actually look like - just using as a refernce/example of the idea.
Understood, and my three objections were aimed at the wrong target then — withdrawn as written. I read the sketch as a literal signature; the idea is singular-and-explicit transports plus canonical-name-with-aliases, and both survive whatever the final parameter list turns out to be. The audit still runs, because "match the sibling
register_*" is what should decide the exact line, not me.Aliasing is real, and the numbers are small: 44 ports carry more than one member — 23 TCP (49 members), 21 UDP (45), 0 SCTP, 0 DCCP. Port 80 is exactly your example:
TCP port 80: 'http' value='http [80 - tcp|udp|sctp]' proto=7 'www' value='www [80 - tcp|udp]' proto=3 'www-http' value='www-http [80 - tcp|udp]' proto=3But a flat
aliases: list[str]cannot express 9 of those 44 ports, because the members disagree about the transports.httpbinds SCTP;wwwandwww-httpdo not. Sowwwis not an alias ofhttp— it is a different registration that happens to share a port. All nine:TCP 80 [http 7, www 3, www-http 3] 113 [ident 1, auth 3] 631 [ipp 3, ipps 1] 888 [accessbuilder 3, cddbp 1] 999 [garcon 1, puprouter 3] 2049 [shilp 3, nfs 7] UDP 80 (same three) 999 [applix 2, puprouter 3] 2049 [shilp 3, nfs 7]And this is the same defect as the retyping, seen from the other side. Ports 888 and 999 are precisely the two #759 cases where a composite-
protolookup returned a service that does not exist on that transport —accessbuilder→cddbp,puprouter→garcon. The wrong answer came from picking the wrong member of an aliased pair whoseprotosets disagree. Retyping to single-bit removes the ambiguity within a registry; aliasing removes it across names on one port. Doing one without the other leaves half of #759's mechanism standing.So the alias relation needs a per-alias transport set, or aliases must be confined to the 35 ports where every member already agrees. I will fold this into the plan I post here rather than answer it off the cuff.
1 remaining item
Ruling recorded, in your words:
alias should thus be per-transport. and we should probably allow registering aliases for a given enum in the transport apptype enum.
Agreed, and this dissolves the objection I raised rather than working around it. The 9-port disagreement I flagged is a disagreement about other transports, so it cannot survive per-transport aliasing. Measured:
TREE: /tmp/v801/pcapkit/__init__.py port 80 per registry: TCP -> ['http', 'www', 'www-http'] UDP -> ['http', 'www', 'www-http'] SCTP -> ['http'] DCCP -> [] members whose proto OMITS their own registry: 0 (over 12391 members) self-test (tcp in tcp|udp) = True (sctp in tcp|udp) = FalsePort 80 has two aliases under TCP and under UDP, one member and no aliases under SCTP, and does not exist under DCCP. So the alias set is genuinely a per-transport property, exactly as you say —
wwwis an alias ofhttpon TCP and UDP, and is not an alias of anything on SCTP because it is not registered there.And since every member's
protoalready contains its own registry's transport (0 exceptions in 12,391), retyping makes all members on a shared port within one registry carry the identicalprotoby construction. Thehttp(7) vswww(3) split at port 80 is not a conflict to resolve; it is the cross-transport information that the split already encodes and that retyping stops duplicating. Aliases then need no per-alias transport field — the registry is the transport.One correction to my own last comment, and a bad probe behind it. I wrote that a flat
aliaseslist "cannot express 9 of those 44 ports". That framing was wrong: per-transport, it expresses all 44. Today's counts stand — TCP 23 shared ports (6 disagreeing), UDP 21 (3), SCTP 0, DCCP 0 — but the disagreement is not an obstacle to your design.I also ran a check that reported 94 violations of registry membership and did not publish it, because the probe was broken, not the data: I tested
'tcp' not in str(m.proto), andstr(m.proto)renders as'3', so the condition was true for every row. The corrected flag-membership version gives 0, and I self-tested it against a known-false case first. Same lesson as #796 — a probe is not evidence until it is shown to detect the thing it looks for.Ports 888 and 999 remain the connection worth keeping: they are both #759 wrong-service cases and disagreeing shared ports, which is why the retyping and the aliasing belong in one design rather than two.
and on each apptype enum's repr/str, it should expose its associated aliases (if any) as well. And the enum should also have a public property/member to fetch its aliases list.
Ruling recorded, in your words:
and on each apptype enum's repr/str, it should expose its associated aliases (if any) as well. And the enum should also have a public property/member to fetch its aliases list.
Both agreed. One hard constraint the implementation has to respect, measured rather than assumed: the aliases must not reach
_value_.AppTypeis aStrEnumand_value_is the lookup key — it is what_value2member_map_is keyed on, and the map is built once at class creation. Since your design makes aliases registerable at runtime, an alias list inside_value_would go stale the moment one is registered:TREE: /tmp/v801/pcapkit/__init__.py str(m) == m.value ? True 'http [80 - tcp|udp|sctp]' m.value in TCP._value2member_map_ ? True TCP('http [80 - tcp|udp|sctp]') is TCP['http'] -> True -- mutate _value_ after class creation -- TCP(old value) -> resolves fine <- stale map still holds the old string TCP(new value) -> ValueError: 'http [80 - tcp]' is not a valid TCPSo
__repr__and__str__grow the aliases,_value_does not._value_still changes once — dropping the compositeproto.nameper the retyping — but that is a one-off at generation time, not a runtime mutation.Consequence worth stating up front:
str(m) == m.valueisTruetoday and would becomeFalse. That coupling is incidental rather than designed (both happen to use the same format atvendor/reg/apptype/apptype.py:251and:274), but anything relying on it breaks. I grepped for the bracketed form intests/anddocs/source/and found 5 occurrences — listed in the plan when I post it, so none is missed.Second consequence:
repr/stroutput becomes mutable at runtime, since registering an alias changes it. Any doctest or assertion pinning a member'sreprmust therefore not assume a fixed alias list — worth a note in the docstring so the next person does not pin it.Naming:
.aliasesas the public property, returning an empty tuple rather thanNonewhen there are none, so callers need no guard. Tuple over list because the members are otherwise immutable and a mutable return invites in-place edits of registry state.Correcting the "5 occurrences" in my previous comment — it is 0, and my grep was broken again.
I searched for
" - tcp"and friends, which matched five lines oftcp.last - tcp.first + 1arithmetic intests/toolkit/. Nothing to do withAppType. The correct pattern, self-tested against the real string first so a zero means something:$ grep -rnE "\[[0-9]+ - (tcp|udp|sctp|dccp)" tests/ docs/source/ examples/ | wc -l 0 $ printf 'http [80 - tcp|udp|sctp]\n' | grep -cE "\[[0-9]+ - (tcp|udp|sctp|dccp)" 1 <- the pattern does detect the form, so the 0 above is realSo nothing in
tests/,docs/source/orexamples/pins the bracketed value form. That makes thestr(m) == m.valuedivergence cheaper than I said — no assertion depends on it. The one nearby coupling istests/const/test_const_apptype_split_unit.py:406, which doescls._value2member_map_.pop(member.value, None)in a teardown; it reads.valueas an opaque key rather than pinning its text, so it is unaffected by the format change but does confirm the value is the live lookup key, which is the constraint from my last comment.That is the third bad search in this thread — the #796 shell loop, the
str(m.proto)membership test, and now this. The failure mode is identical each time: a pattern that cannot match, whose empty result reads as a finding. I am self-testing every pattern against a known-positive before quoting a count from it, which is what produced the1above.Correcting my own line citations — most of them were wrong, and one whole file's worth do not exist. I re-read every one against
main(477ed00c4) rather than trusting the reports they came from.I published Truth vendor/reg/apptype/apptype.py:251__new__:250:271__repr__:270:274__str__:273:522-526docstring:473:806emit condition:752test_const_apptype_split_unit.py:78-80:80, one linetest_const_apptype_split_unit.py:630-658does not exist test_const_apptype_split_unit.py:709-836does not exist "the 10,625/1,766 split is pinned in that test file" false — 0hits for either number anywhere intests/That file is 415 lines long with 15 test methods. Every citation I gave above 415 was fabricated, and I passed them on without opening the file.
tests/foundation/registry/test_protocols.pyis real at 664 lines with the fourregister_apptypecalls at:476,:484,:493,:499.And the correction matters, because reading the real file shows canonical-and-alias is already half-built — which changes the shape of what you asked for:
TCP.__canonical__ entries = 23 UDP = 21 SCTP = 0 DCCP = 0 TCP.get(80).svc = http TCP.get_all(80) svcs = ['http', 'www', 'www-http'] TCP.http == TCP.www = True (is: False)__canonical__is a per-registryport → canonical servicemap curated from/etc/services, andtest_a_port_lookup_returns_the_canonical_servicedocuments why: IANA names no precedence, and of the 44 colliding pairs the registry row order answers 28 the way/etc/servicesdoes and 16 differently.get()returns the canonical member,get_all()returns canonical-first then the rest. Its 23+21 also independently reproduces the 44 shared ports I measured separately.So your ruling is additive, not new machinery. The alias data already exists and needs no storage — I derived it directly:
derived aliases of TCP.http -> ['www', 'www-http'] derived aliases of TCP.www -> ['http', 'www-http']That is
__registry__.getlist(self.port)minusself. What is genuinely missing is only the two things you named: a public.aliasesproperty on the member, and exposing them in__repr__/__str__.The plan, with the signature settled. Your varargs idea stands; the audit's concrete rendering of it did not survive testing.
The audit recommended
def register_apptype(code, module, class_: 'str' = NULL, *transport: 'TransportProtocol'), arguing that putting the vararg afterclass_is "precisely what avoids the swallowing bug". It does the opposite —class_is positional with a default, so the third positional argument binds to it:proposed(member, Unit, TP.udp) -> class_='TP.udp' transport=() <- transport eaten alt(member, Unit, TP.udp) -> transport=('TP.udp',) class_=NULL alt(65000, 'pkg.mod', 'Raw') -> transport=('Raw',) <- 2-string form breaks alt(65000, 'pkg.mod', TP.tcp, class_='Raw') -> transport=('TP.tcp',) class_='Raw'The audit's own migration example hits this. My recommendation:
def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]', *transport: 'TransportProtocol', class_: 'str' = NULL) -> 'None':
codeandmodulestay positional as every sibling has them; transports are varargs — singular and explicit, no Flag;class_becomes keyword-only. That last part is the one divergence from the siblings, and I think it is a fix rather than a cost: a bare positional class-name string sitting next to a module string is itself ambiguous, which is exactly the property you are removing elsewhere. It breaks one call site in the whole repo —tests/foundation/registry/test_protocols.py:493,register_apptype(65000, *raw_module, proto='tcp')— and it breaks it loudly.One sub-case the audit flagged as unruled, which I am deciding rather than sending back to you: an
Enum_AppTypemember with no explicit*transportuses the member's ownproto. Post-retyping that is single-bit, so it names exactly one registry and there is no ambiguity left to remove — the convenience survives precisely because the thing that made it dangerous is gone. A bareintwith no*transportstays an error, as today.Confirmed by the audit and re-verified by me: every sibling is
(code, module, class_: 'str' = NULL)with noproto—register_linktype:385,register_ethertype:509, andregister_transtype/register_pcap/register_pcapng/register_tcp/register_udp/register_sctp. Only one file callsregister_apptypeat all (4 calls). And zero uses of&,^or~onTransportProtocolanywhere inpcapkit/,tests/,examples/,docs/— pattern self-tested against a known-positive.Your "not used in other places" is very nearly right. Three sites outside
register_apptypedo rely on Flag semantics today —TransportProtocol._missing_'s bit-composition validator,TransportProtocol.get()'smax_val * 2power-of-two doubling, and_dispatch'sshow_flag_valuesrejection guard — but all three exist only to handle composites that trace back toAppType.proto, so all three become dead once the retyping lands. It becomes literally true after step 1, not before.Splitting into three issues, filed next, because they are sequential and one of them is much riskier than the others: the retyping plus the signature (coupled — retyping removes the fan-out the signature exposes), then
.aliasesplus therepr/strexposure, then dropping theIntFlagbase. Tracking stays here.Retracting my own "Correcting my own line citations" comment above — it was wrong in every row, and the agents' original citations were right.
Root cause: I ran
grep -non a relative path in the main checkout, whosemainbranch is stale atfe80b8525, instead of readingorigin/mainat477ed00c4. That checkout's copy of the file is 62 lines shorter. My own standing rule says to readorigin/mainexplicitly for exactly this reason, and I did not.I "corrected" to Actually, on origin/mainvendor/reg/apptype/apptype.py:250__new__:251:270__repr__:271:273__str__:274:473docstring:523:752emit condition:802So the audit's
:251/:271/:274were exact, and its:802for the emit condition was exact — the drift I "found" was my own stale tree, not theirs. The only real drift in the original reports is:806vs:802from the earlier measurement agent, and:522-526vs:523which is a range around the right line.Worse, I called two citations fabricated and they exist.
tests/const/test_const_apptype_split_unit.pyis 863 lines with 19 test methods onorigin/main, not the 415/15 I reported from the stale checkout::630 def test_one_transport_protocol_still_resolves_every_port_it_resolved_before :709 def test_no_lookup_call_site_in_the_library_builds_a_composite_proto :578 self.assertEqual(swept, 10625) :643 * of those, **10,625** carry a multi-bit ``.proto`` and **1,766** a :835 self.assertIn('proto = code.proto', source) :836 self.assertIn('if test not in proto:', source)Both ranges I declared non-existent are real,
assertEqual(swept, 10625)is real at:578, and the 10,625/1,766 populations are pinned in that file at:643— I reported0hits for those numbers, from a file that did not yet contain them. Every one of those agent claims stands; the retraction is mine.The migration surface is therefore larger than #806 currently says, and
:709-836'stest_no_lookup_call_site_in_the_library_builds_a_composite_protomatters most: it hard-asserts the literal strings'proto = code.proto'and'if test not in proto:'at:835-836, so #806's signature change breaks that test by design. Correcting #806 now.- addedblockedDeferred pending another issue or decision; see the last comment for what unblocks itDeferred pending another issue or decision; see the last comment for what unblocks itand removedwipWork in flight - a covering PR is open or an agent is actively on itWork in flight - a covering PR is open or an agent is actively on it
on Sep 26, 2026 Checkable blocker: #815 merged.
gh pr view 815 -R JarryShaw/PyPCAPKit --json state,mergedAtRelabelled
blockedrather thanwip, because no open PR closes this issue — #815's body closes #806 and #809 only, and awipwith no covering PR reads as work in progress when nothing is in progress on it.This issue is the design question you asked; #806 was the implementation and #815 is its PR. So the answer is already settled and shipping — the multi-bit
protois redundant given the per-transport split, which is what #815 implements — and what remains here is to close this out once that lands and record the conclusion against the question. Nothing to build.Found by an audit prompted by your note that #831 had no
blocked/wiplabel: I checked all nine open issues for whether each is genuinely held, and found three label errors rather than one — this, plus #809 carryingblockedwhen an open PR closes it directly, and #831 sitting idle with no worker.- removedblockedDeferred pending another issue or decision; see the last comment for what unblocks itDeferred pending another issue or decision; see the last comment for what unblocks it
on Sep 26, 2026 Answered and shipping — closing. #815 merged as
3118ed796.Your question was whether
AppType's multi-bitprotois necessary given the enum is already split per transport. The answer established over this thread was no, and it is now implemented:- Fully derivable. 12,391 member rows across the four per-transport registries, 10,625 multi-bit and 1,766 single-bit. Grouping by
(service, port)gave 7,054 groups with 0 disagreements and no single-registry group carrying a multi-bit value — so the multi-bit field held nothing the registry split did not already encode. - Retyped, with
register_apptypetaking varargs instead of aprotokwarg, and — per your later rulings — accepting a transport name case-insensitively, and keepingclass_positional with the swallow disambiguated bytype(module).
Two things deliberately left out of that PR and tracked separately rather than dropped: #807 for exposing aliases as a public property and registering them per transport, and #808 for dropping
TransportProtocol'sIntFlagbase now that nothing builds a composite — which this work is what made possible.Closing as answered rather than leaving it open with a
blockedlabel, since there is no build work left in it.- Fully derivable. 12,391 member rows across the four per-transport registries, 10,625 multi-bit and 1,766 single-bit. Grouping by
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Is this a bug or a feature request? A design question, raised by the maintainer.
The question, verbatim:
What it decides. #732's ruling was conditional — "proceed with retyping on TransportProtocol - if we no longer need it to be a Flag. But, if in any case we still prefer using a Flag, then we discard the retyping." This issue is that condition. It exists as its own issue so the reasoning is tracked rather than buried in #732, whose subject is the
str-valued/port=-1shape.State today.
AppTypeis split into four per-transport registries — TCP 6,147 / UDP 6,143 / SCTP 91 / DCCP 10 = 12,391 members — reached viaAppType.__registries__. A member also carries aprotoattribute holding aTransportProtocolFlag, which for a service IANA registers on several transports is multi-bit:TCP.http.protonames UDP too. 10,625 of 12,391 rows (85.7%) are multi-bit, so this is most of the enum, not an edge.Two arguments that the list is redundant:
protois then a second copy of a fact the structure already carries — and a second copy can drift from the first._dispatchalready rejects multi-bit.AppType.get(80, TCP|UDP)raises, telling the caller to look one transport up at a time. So the entry point insists on a single transport while the members it returns advertise four.The one site that decides it is
pcapkit/foundation/registry/protocols.py:846,if test not in proto:— the only read known to treatprotoas a set rather than a label. Load-bearing if it needs the other bits; decoration if it is only reachable once a registry has been selected.Being measured now, before any answer — a wrong one costs a 12,391-member migration:
protoacrosspcapkit/,tests/,docs/: is multi-bit ever the only source of a fact?protobit set"? Any disagreement means the two copies have already drifted — a defect independent of this design question.Findings get appended here as they land. Related: #732, #775, #783.