fix: reach IPv6 collectors, and modernise the license metadata (F-10, F-15) - #54
Merged
Merged
Conversation
…ress Three defects that each made a run report less than it already knew. **F-12: the run stream was destructive.** Every consumer read the same queue.Queue, and `get()` removes the item, so two browser tabs on one run each received a random subset of the lines and neither saw the whole stream. Each consumer now gets its own queue. That also isolates a stalled reader: their queue fills while everyone else keeps up, instead of the backlog being shared. **A failed run reported nothing.** The orchestrator writes a manifest on every exit path (F-02) and re-raises the original exception unchanged, which is correct. But the web runner had no way to reach that manifest, so it published `manifest: None, count: 0` while a complete partial record sat on disk. Half of what F-02 guaranteed was invisible from the surface most likely to need it. The record is now attached to the exception rather than wrapped in a new one, because the whole point of re-raising unchanged is that the operator sees their real error. Attaching is best effort: a slotted builtin exception refuses new attributes, and a diagnostic that raises while reporting a failure would replace the operator's error with its own. **Progress never reported its last word.** The callback fired on multiples of 100, so a 250-event run reported 200 and stopped, leaving every consumer at 80% on a finished run. The final call is in `finally`, so a stopped or failed run also gets a true count rather than the last round number. One regression caught and fixed in the same change, worth recording because it was invisible in the design: the client starts a run and only THEN opens the stream, so a short run routinely finishes before any subscriber exists. The old shared queue handled that by accident, since nothing had drained it. Fan-out removed the accident and an existing test immediately received an empty body. Subscribers are now seeded with a bounded history, and both the replay and its bound have their own guards. Also fixed while typing it: inside RunHandle the field named `queue` shadows the `queue` module, so every later annotation in the class body resolved to the field. mypy caught it; it would have been a confusing runtime error later. 865 py. black, ruff, mypy clean.
**F-10, IPv6.** Every socket in the transport was created with a hardcoded
`socket.AF_INET`, so a collector with only an IPv6 address could not be reached
at all, and failed with an error naming neither the cause nor the fix.
Dual-stack SIEM deployments are ordinary.
`resolve_endpoint` asks `getaddrinfo` with AF_UNSPEC, so a literal 10.0.20.125,
a literal ::1 and a name all work with nothing to configure. Six call sites
converted: the route probe, both connect-test paths, and all three emitter
transports.
The resolved sockaddr is kept on the emitter rather than re-derived per send,
because an IPv6 sockaddr is a 4-tuple: rebuilding `(host, port)` for `sendto`
would have worked on v4 and silently broken v6, which is the kind of asymmetry
that only shows up in someone else's lab.
Safety rule 1 is unchanged: resolution asks about the configured collector and
no other host, and it is not egress to it. `route_for` and `segment_claim` stay
IPv4-only and already decline v6 rather than guessing, so the path disclosure
degrades to "route not determined" instead of lying.
Tests cover udp, tcp and the connect probe over ::1, and assert IPv4 is
unchanged, which is the regression that would matter most. They skip cleanly on
a host with no v6 loopback rather than failing.
**F-15, packaging metadata.** `license = { text = "Apache-2.0" }` is the
deprecated table form; setuptools stops accepting it on 2027-02-18. Now the
PEP 639 SPDX expression, which needs setuptools>=77 in build-system.requires,
plus `license-files` so LICENSE and NOTICE actually ship. Verified by building
a wheel and reading its METADATA: `License-Expression: Apache-2.0`,
Metadata-Version 2.4, both files present under dist-info/licenses/.
The now-redundant License classifier is removed, as PEP 639 requires.
**F-14, partially.** `npm audit fix` applied the one non-breaking upgrade
(postcss), 6 advisories down to 5. The rest are NOT done and are not mine to
decide: see the PR body.
871 py + 136 fe. black, ruff, mypy clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
F-10 IPv6
Every socket in the transport was created with a hardcoded
socket.AF_INET, so a collector withonly an IPv6 address could not be reached at all.
resolve_endpointnow asksgetaddrinfowithAF_UNSPEC, so a v4 literal, a v6 literal and a name all work with nothing to configure. Six callsites converted.
The resolved sockaddr is kept on the emitter rather than re-derived per send, because an IPv6
sockaddr is a 4-tuple: rebuilding
(host, port)forsendtowould have worked on v4 and silentlybroken v6.
Safety rule 1 unchanged: resolution asks about the configured collector and no other host.
route_forandsegment_claimstay IPv4-only and already decline v6 rather than guessing, so thepath disclosure degrades to "route not determined" instead of lying.
F-15 packaging metadata
The deprecated
license = { text = ... }table becomes the PEP 639 SPDX expression. Verified bybuilding a wheel and reading METADATA:
Note this raises
build-system.requirestosetuptools>=77, which is what PEP 639 needs. That isa build-time requirement only; it does not change
requires-python.F-14 — stopped deliberately, needs your decision
npm audit fixapplied the one non-breaking upgrade (postcss). 6 advisories down to 5.Production audit was and remains clean (
npm audit --omit=dev→ 0). Every remaining advisoryis dev-only, so this is a contributor-workstation risk, not a shipped one.
The five that remain all require major bumps:
Why I stopped. Both upgrades drop Node 18:
vite@8engines:^20.19.0 || >=22.12.0vitest@4engines:^20.0.0 || ^22.0.0 || >=24.0.0The CI matrix builds Node 18 and 20, and its own comment says "18 is the floor the installer
enforces". So taking these upgrades means raising the minimum Node an operator needs and changing
what
scripts/install.shaccepts. That is a supported-platform commitment, not a mechanical bump,and it is yours to make rather than mine to make at 3am.
Node 18 went end-of-life on 2025-04-30, so the case for dropping it is strong. But it should be a
decision with a changelog line, not a side effect of an audit fix.
If you say go, the change is: bump both deps, set the CI matrix to
["20", "22"], raise theinstaller's Node floor, and note it in the CHANGELOG.