netprotocols 2.2.1__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netprotocols-2.4.0/.claude/skills/steward/SKILL.md +26 -0
- netprotocols-2.4.0/.gitattributes +3 -0
- netprotocols-2.4.0/.github/CODEOWNERS +1 -0
- netprotocols-2.4.0/.github/dependabot.yml +65 -0
- netprotocols-2.4.0/.github/workflows/ci.yml +173 -0
- netprotocols-2.4.0/.github/workflows/codeql.yml +50 -0
- netprotocols-2.4.0/.github/workflows/docs-lint.yml +75 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/workflows/fuzz.yml +45 -4
- netprotocols-2.4.0/.github/workflows/mutation.yml +115 -0
- netprotocols-2.4.0/.github/workflows/pr-title-lint.yml +33 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/workflows/release.yml +65 -5
- netprotocols-2.4.0/.github/workflows/scorecard.yml +61 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.gitignore +6 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/ARCHITECTURE.md +234 -32
- {netprotocols-2.2.1 → netprotocols-2.4.0}/CHANGELOG.md +56 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/CONTRIBUTING.md +62 -7
- {netprotocols-2.2.1 → netprotocols-2.4.0}/PKG-INFO +124 -34
- {netprotocols-2.2.1 → netprotocols-2.4.0}/README.md +122 -32
- netprotocols-2.4.0/SECURITY.md +59 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/docs/CLAIMS.md +189 -27
- netprotocols-2.4.0/pyproject.toml +151 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/_pcap.py +2 -1
- netprotocols-2.4.0/scripts/capture_fixtures_http.sh +202 -0
- netprotocols-2.4.0/scripts/capture_fixtures_ipip.sh +161 -0
- netprotocols-2.4.0/scripts/capture_fixtures_ipsec.sh +124 -0
- netprotocols-2.4.0/scripts/capture_fixtures_sctp.sh +206 -0
- netprotocols-2.4.0/scripts/capture_fixtures_vxlan.sh +224 -0
- netprotocols-2.4.0/scripts/check_fixtures.py +753 -0
- netprotocols-2.4.0/scripts/check_mutmut_survivors.py +158 -0
- netprotocols-2.4.0/scripts/extract_http_streams.py +138 -0
- netprotocols-2.4.0/scripts/ipsec_fixture_packets.py +418 -0
- netprotocols-2.4.0/scripts/mutmut_survivors.txt +27 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/__init__.py +27 -2
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/_base.py +42 -3
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/_defaults.py +37 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/_enums.py +10 -0
- netprotocols-2.4.0/src/netprotocols/_tlv.py +125 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/checksum.py +83 -17
- netprotocols-2.4.0/src/netprotocols/flow.py +135 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer2/arp.py +5 -9
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer2/ethernet.py +10 -4
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer2/vlan.py +6 -5
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/__init__.py +5 -0
- netprotocols-2.4.0/src/netprotocols/layer3/ah.py +245 -0
- netprotocols-2.4.0/src/netprotocols/layer3/esp.py +116 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/gre.py +8 -7
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/icmp.py +2 -2
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/igmp.py +2 -2
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/ip.py +39 -56
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer3/ipv6_ext.py +22 -45
- netprotocols-2.4.0/src/netprotocols/layer4/__init__.py +5 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer4/_ports.py +4 -3
- netprotocols-2.4.0/src/netprotocols/layer4/sctp.py +378 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer4/tcp.py +17 -42
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer4/udp.py +2 -2
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer7/__init__.py +16 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer7/dns.py +2 -2
- netprotocols-2.4.0/src/netprotocols/layer7/http.py +1426 -0
- netprotocols-2.4.0/src/netprotocols/layer7/vxlan.py +190 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/packet.py +29 -19
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/registry.py +15 -7
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/utils/exceptions.py +6 -1
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/walk.py +9 -4
- netprotocols-2.4.0/tests/conftest.py +204 -0
- netprotocols-2.4.0/tests/fixtures/MANIFEST.md +260 -0
- netprotocols-2.4.0/tests/fixtures/ah.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/esp.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/http/01.request +5 -0
- netprotocols-2.4.0/tests/fixtures/http/01.response +8 -0
- netprotocols-2.4.0/tests/fixtures/http/02.request +5 -0
- netprotocols-2.4.0/tests/fixtures/http/02.response +7 -0
- netprotocols-2.4.0/tests/fixtures/http/03.request +6 -0
- netprotocols-2.4.0/tests/fixtures/http/03.response +5 -0
- netprotocols-2.4.0/tests/fixtures/http/04.request +8 -0
- netprotocols-2.4.0/tests/fixtures/http/04.response +7 -0
- netprotocols-2.4.0/tests/fixtures/http/05.request +5 -0
- netprotocols-2.4.0/tests/fixtures/http/05.response +17 -0
- netprotocols-2.4.0/tests/fixtures/http/06.request +5 -0
- netprotocols-2.4.0/tests/fixtures/http/06.response +20 -0
- netprotocols-2.4.0/tests/fixtures/http/07.request +10 -0
- netprotocols-2.4.0/tests/fixtures/http/07.response +32 -0
- netprotocols-2.4.0/tests/fixtures/http/http.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/ipip.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/sctp.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/ah.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/esp.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/http.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/ipip.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/sctp.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/staging/vxlan.pcap +0 -0
- netprotocols-2.4.0/tests/fixtures/vxlan.pcap +0 -0
- netprotocols-2.4.0/tests/fuzz/fuzz_decode_frame.py +77 -0
- netprotocols-2.4.0/tests/fuzz/fuzz_http_decode.py +121 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/strategies.py +220 -2
- netprotocols-2.4.0/tests/test_ah.py +545 -0
- netprotocols-2.4.0/tests/test_checksum.py +602 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_contract.py +59 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_corpus.py +17 -5
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_diagnostics.py +44 -1
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_dns.py +188 -14
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_docs.py +87 -4
- netprotocols-2.4.0/tests/test_enums.py +22 -0
- netprotocols-2.4.0/tests/test_esp.py +307 -0
- netprotocols-2.4.0/tests/test_extract_http_streams.py +178 -0
- netprotocols-2.4.0/tests/test_flow.py +356 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_fuzz.py +262 -15
- netprotocols-2.4.0/tests/test_http.py +2059 -0
- netprotocols-2.4.0/tests/test_ipip.py +250 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_ipv6_ext.py +12 -18
- netprotocols-2.4.0/tests/test_mutmut_gate.py +184 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_registry.py +43 -13
- netprotocols-2.4.0/tests/test_sctp.py +573 -0
- netprotocols-2.4.0/tests/test_tlv.py +259 -0
- netprotocols-2.4.0/tests/test_vxlan.py +562 -0
- netprotocols-2.4.0/tests/test_workflows.py +797 -0
- netprotocols-2.4.0/uv.lock +886 -0
- netprotocols-2.2.1/.github/workflows/ci.yml +0 -114
- netprotocols-2.2.1/pyproject.toml +0 -85
- netprotocols-2.2.1/scripts/check_fixtures.py +0 -332
- netprotocols-2.2.1/src/netprotocols/flow.py +0 -96
- netprotocols-2.2.1/src/netprotocols/layer4/__init__.py +0 -4
- netprotocols-2.2.1/tests/conftest.py +0 -103
- netprotocols-2.2.1/tests/fixtures/MANIFEST.md +0 -96
- netprotocols-2.2.1/tests/test_checksum.py +0 -322
- netprotocols-2.2.1/tests/test_flow.py +0 -174
- netprotocols-2.2.1/tests/test_workflows.py +0 -118
- netprotocols-2.2.1/uv.lock +0 -584
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/ISSUE_TEMPLATE/protocol_request.yml +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/.github/pull_request_template.md +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/LICENSE +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/benchmarks/baseline.json +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/benchmark.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/benchmark_encode.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/benchmark_import.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_dhcp.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_dns_tcp.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_gre.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_igmp.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_supplement.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/capture_fixtures_vlan.sh +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/pyodide/check_in_pyodide.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/pyodide/package-lock.json +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/pyodide/package.json +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/scripts/pyodide/run_in_pyodide.mjs +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer2/__init__.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/layer7/dhcp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/pcap.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/py.typed +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/utils/__init__.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/utils/ipv4.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/src/netprotocols/utils/mac.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/arp_exchange.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/dhcp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/dns_tcp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/gre.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/icmpv4_external.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/igmp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/ipv4_fragments.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/ipv6_fragments.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/ipv6_mld.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/arp_exchange.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/dhcp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/dns_tcp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/gre.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/igmp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/tcp_http.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/tcp_https.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/udp_dns.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/staging/vlan_icmp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/tcp_http.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/tcp_https.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/udp_dns.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/fixtures/vlan_icmp.pcap +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_arp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_dhcp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_ethernet.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_exports.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_gre.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_icmp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_igmp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_ip.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_packet.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_pattern_matching.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_pcap.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_tcp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_udp.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_utils.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_version.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_vlan.py +0 -0
- {netprotocols-2.2.1 → netprotocols-2.4.0}/tests/test_walk.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Steward: driving PRs to green in this repo
|
|
2
|
+
|
|
3
|
+
Repo-specific guidance for any Claude Code session babysitting, driving-to-green, or otherwise acting on an open pull request in this repository. This takes precedence over generic PR-babysitting conventions on the points below; it does not expand access or let a session skip anything those generic rules mark as "never."
|
|
4
|
+
|
|
5
|
+
## Independent implementation and review for CI/CodeRabbit-reported bugs
|
|
6
|
+
|
|
7
|
+
**Rule 1 — independent implementation.** A fix to a bug reported by CI (a failing check, a CodeRabbit finding, a bot review comment) must be implemented by an independent session or sub-agent — never by the same session/agent that authored the code the bug was found in.
|
|
8
|
+
|
|
9
|
+
**Rule 2 — independent review.** Each such fix, once implemented, must be reviewed by another independent agent before proceeding (pushing, resolving the finding's thread, moving to the next item). This review is a separate pass from whichever agent wrote the fix.
|
|
10
|
+
|
|
11
|
+
### Why this exists
|
|
12
|
+
|
|
13
|
+
Observed directly on [PR #158](https://github.com/EONRaider/NETProtocols/pull/158): the same session that authored a refactor (source dedup, test hardening, new CI workflows) also implemented every fix for what CodeRabbit found in that code. This repeated the exact failure mode being fixed at least once — a regression test written to guard `mutation.yml`'s DNS mutation-testing scope checked `pattern in text` against the whole file rather than the actual executed command block, the same "checks vocabulary, not behavior" bug already fixed twice earlier in the same review pass (a `uses:` line-matching regex, and a reproducible-build test that checked for the word "reproducible" rather than an actual hash comparison). The author's own blind spots — the same assumptions and habits that produced the original bug — carried straight into the fix, and an external reviewer had to catch it again on the next round.
|
|
14
|
+
|
|
15
|
+
An agent fixing its own bug tends to re-apply the same mental model that produced it. A fresh session or sub-agent, with no investment in the original approach, is more likely to notice when a fix is narrower than the problem, or repeats the original mistake in a new shape.
|
|
16
|
+
|
|
17
|
+
### How to apply this in practice
|
|
18
|
+
|
|
19
|
+
When a CI check fails or CodeRabbit (or any other bot/reviewer) reports a finding on a PR in this repo:
|
|
20
|
+
|
|
21
|
+
1. **Diagnose** in the current session — reading the failure, the diff, and the relevant code is fine and doesn't need to be delegated.
|
|
22
|
+
2. **Delegate the fix itself** to an independent sub-agent (e.g. via the Agent tool), giving it the finding and enough context to act, but without it inheriting the current session's authorship of the original code — it should approach the fix fresh, not defend or extend its own prior work.
|
|
23
|
+
3. **Delegate review of that fix** to a second, separate sub-agent before pushing — distinct from the agent that implemented the fix. This agent should verify the fix actually closes the gap (ideally by constructing a case that would have slipped past the old code and confirming the new code catches it, not just that the reported symptom is gone), and check it didn't introduce the same class of bug elsewhere.
|
|
24
|
+
4. Only after both steps pass does the fix get pushed and the finding marked resolved.
|
|
25
|
+
|
|
26
|
+
This applies to fixes for bugs reported by CI, CodeRabbit, another bot, or a human reviewer — the fix-a-reported-bug loop, where the risk of an author's blind spots recurring is highest. It does not require every commit or every piece of new feature work in this repo to go through this two-step delegation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @EONRaider
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
# --- Python tooling, managed by uv --------------------------------
|
|
5
|
+
#
|
|
6
|
+
# This project's dependencies are declared exclusively via PEP 735
|
|
7
|
+
# [dependency-groups] in pyproject.toml (see uv.lock), not the older
|
|
8
|
+
# [project.optional-dependencies] table. Dependabot's "pip" ecosystem
|
|
9
|
+
# does not parse [dependency-groups]; only the native "uv" ecosystem
|
|
10
|
+
# does (GA since March 2025, with dependency-groups parsing landing
|
|
11
|
+
# in dependabot/dependabot-core#11796), so "uv" is used here rather
|
|
12
|
+
# than "pip".
|
|
13
|
+
- package-ecosystem: "uv"
|
|
14
|
+
directory: "/"
|
|
15
|
+
schedule:
|
|
16
|
+
interval: "weekly"
|
|
17
|
+
commit-message:
|
|
18
|
+
prefix: "chore"
|
|
19
|
+
groups:
|
|
20
|
+
# netprotocols ships zero runtime dependencies today, so every
|
|
21
|
+
# update currently comes from the "dev" dependency-group (pytest,
|
|
22
|
+
# mypy, ruff, etc.) -- kept current here as its own group rather
|
|
23
|
+
# than left to open one PR per package. Matched by explicit
|
|
24
|
+
# package name instead of `dependency-type: development`: as of
|
|
25
|
+
# this writing Dependabot's dependency-type classifier does not
|
|
26
|
+
# work correctly for the uv ecosystem and reports every
|
|
27
|
+
# dependency as "production" regardless of where it's declared
|
|
28
|
+
# (see dependabot/dependabot-core#13202), which would leave a
|
|
29
|
+
# `dependency-type: development` group empty -- or, used the
|
|
30
|
+
# other way around, wrongly sweep this same dev tooling into a
|
|
31
|
+
# "runtime" group. A future runtime dependency added under
|
|
32
|
+
# [project.dependencies] is intentionally left out of this list,
|
|
33
|
+
# so it keeps updating on its own outside this group rather than
|
|
34
|
+
# being bundled in with dev tooling.
|
|
35
|
+
dev-dependencies:
|
|
36
|
+
patterns:
|
|
37
|
+
- "hypothesis"
|
|
38
|
+
- "mypy"
|
|
39
|
+
- "pytest"
|
|
40
|
+
- "pytest-cov"
|
|
41
|
+
- "ruff"
|
|
42
|
+
|
|
43
|
+
# --- GitHub Actions -------------------------------------------------
|
|
44
|
+
#
|
|
45
|
+
# Keeps the SHA-pinned `uses:` references in .github/workflows current
|
|
46
|
+
# (Dependabot updates both the pinned SHA and its trailing `# vX.Y.Z`
|
|
47
|
+
# comment).
|
|
48
|
+
- package-ecosystem: "github-actions"
|
|
49
|
+
directory: "/"
|
|
50
|
+
schedule:
|
|
51
|
+
interval: "weekly"
|
|
52
|
+
commit-message:
|
|
53
|
+
prefix: "ci"
|
|
54
|
+
groups:
|
|
55
|
+
# github/codeql-action ships init, analyze, and upload-sarif as
|
|
56
|
+
# subdirectories of one repository, but Dependabot tracks each
|
|
57
|
+
# `uses:` path as its own dependency and opens one PR per path.
|
|
58
|
+
# init and analyze must run the same release -- analyze refuses a
|
|
59
|
+
# config file written by a different init version ("Loaded a
|
|
60
|
+
# configuration file for version '4.37.9', but running version
|
|
61
|
+
# '4.38.1'") -- so every split PR fails CodeQL on its own and
|
|
62
|
+
# none can merge. Grouping them bumps all three together.
|
|
63
|
+
codeql-action:
|
|
64
|
+
patterns:
|
|
65
|
+
- "github/codeql-action*"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
# Callable so the release workflow can run this exact ladder against a
|
|
9
|
+
# tagged commit before publishing (see release.yml). Keeping one
|
|
10
|
+
# definition means the release gate cannot drift from the PR gate.
|
|
11
|
+
workflow_call:
|
|
12
|
+
|
|
13
|
+
# Cancel a stale run the moment a newer one starts for the same branch
|
|
14
|
+
# or pull request, so a quick follow-up push doesn't leave two ladders
|
|
15
|
+
# racing for runners. Keyed on the PR number when this is a pull_request
|
|
16
|
+
# run and on the ref otherwise. The group name is the literal "ci"
|
|
17
|
+
# prefix, not ${{ github.workflow }}: inside a workflow_call, that
|
|
18
|
+
# expression is the CALLER's workflow name, not this one's. Using it
|
|
19
|
+
# here made this group collide with release.yml's own top-level
|
|
20
|
+
# "Release-<ref>" group on a tag push, and GitHub cancelled the whole
|
|
21
|
+
# run as a self-deadlock.
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ci-${{ github.event.pull_request.number || github.ref }}
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
# No job here needs to write anything -- lint/typecheck/test/build/pyodide/
|
|
27
|
+
# benchmark all read the checked-out tree and call out to local tools.
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
lint:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
36
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.12"
|
|
39
|
+
- run: uv run ruff check
|
|
40
|
+
- run: uv run ruff format --check
|
|
41
|
+
|
|
42
|
+
typecheck:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
- run: uv run mypy
|
|
50
|
+
|
|
51
|
+
test:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
strategy:
|
|
54
|
+
matrix:
|
|
55
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
58
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
59
|
+
with:
|
|
60
|
+
python-version: ${{ matrix.python-version }}
|
|
61
|
+
- run: uv run --frozen pytest --cov=netprotocols --cov-report=term
|
|
62
|
+
|
|
63
|
+
# Scans the dev dependency group (the only third-party code CI itself
|
|
64
|
+
# pulls in) for known vulnerabilities. Exported with --no-emit-project
|
|
65
|
+
# so the audit sees pinned, hashed third-party requirements only --
|
|
66
|
+
# not an unhashable `-e .` reference to this repo's own in-tree
|
|
67
|
+
# package, which pip-audit cannot install under --require-hashes
|
|
68
|
+
# (implied automatically once any requirement carries a hash).
|
|
69
|
+
dependency-scan:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
73
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
74
|
+
with:
|
|
75
|
+
python-version: "3.12"
|
|
76
|
+
- name: Export dev dependencies
|
|
77
|
+
run: >-
|
|
78
|
+
uv export --group dev --format requirements-txt --no-emit-project
|
|
79
|
+
-o requirements-dev.txt
|
|
80
|
+
- name: Audit dev dependencies for known vulnerabilities
|
|
81
|
+
run: >-
|
|
82
|
+
uv run --with pip-audit --no-project
|
|
83
|
+
pip-audit -r requirements-dev.txt
|
|
84
|
+
|
|
85
|
+
build:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
89
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
90
|
+
- run: uv build
|
|
91
|
+
- run: uv run --isolated --no-project --with dist/*.whl python -c "import netprotocols; print(netprotocols.__version__)"
|
|
92
|
+
# Builds are supposed to be a pure function of the source tree, so
|
|
93
|
+
# two runs back-to-back must emit byte-identical wheels. Diffing
|
|
94
|
+
# their sha256 (rather than the files directly) catches drift
|
|
95
|
+
# from things like embedded timestamps or non-deterministic
|
|
96
|
+
# archive member ordering without needing a full binary diff.
|
|
97
|
+
- name: Verify the wheel build is reproducible
|
|
98
|
+
run: |
|
|
99
|
+
set -euo pipefail
|
|
100
|
+
uv build -o dist-repro-1
|
|
101
|
+
uv build -o dist-repro-2
|
|
102
|
+
hash1=$(sha256sum dist-repro-1/*.whl | cut -d ' ' -f1)
|
|
103
|
+
hash2=$(sha256sum dist-repro-2/*.whl | cut -d ' ' -f1)
|
|
104
|
+
echo "Build 1 sha256: $hash1"
|
|
105
|
+
echo "Build 2 sha256: $hash2"
|
|
106
|
+
if [ "$hash1" != "$hash2" ]; then
|
|
107
|
+
echo "::error::Wheel build is not reproducible: sha256 differs between two successive 'uv build' runs."
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
echo "Wheel build is reproducible."
|
|
111
|
+
|
|
112
|
+
# Proves the README's browser/WASM claim (#99) against the real
|
|
113
|
+
# thing, not a simulation of it: boots an actual Pyodide runtime
|
|
114
|
+
# (scripts/pyodide/run_in_pyodide.mjs, via Node — no browser needed,
|
|
115
|
+
# same WebAssembly build either way) and decodes the whole
|
|
116
|
+
# real-capture corpus with the wheel this job just built. See
|
|
117
|
+
# scripts/pyodide/check_in_pyodide.py for what it checks and why.
|
|
118
|
+
pyodide:
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
122
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
123
|
+
with:
|
|
124
|
+
python-version: "3.12"
|
|
125
|
+
- run: uv build
|
|
126
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
127
|
+
with:
|
|
128
|
+
node-version: "22"
|
|
129
|
+
- run: npm ci
|
|
130
|
+
working-directory: scripts/pyodide
|
|
131
|
+
- run: >-
|
|
132
|
+
node scripts/pyodide/run_in_pyodide.mjs
|
|
133
|
+
"$GITHUB_WORKSPACE" scripts/pyodide/check_in_pyodide.py
|
|
134
|
+
|
|
135
|
+
# Blocking, at 15% below the committed baseline. The threshold has
|
|
136
|
+
# runner data behind it: this job's first run measured 3.7% away from
|
|
137
|
+
# a baseline recorded on entirely different hardware, and run-to-run
|
|
138
|
+
# spread on one machine is ~2%. 15% is therefore roughly four times
|
|
139
|
+
# the noise it has to tolerate, which leaves real regressions (Tier 1
|
|
140
|
+
# moved this number by 30-90% a step) nowhere to hide.
|
|
141
|
+
#
|
|
142
|
+
# A failure re-measures once before failing, because a shared runner
|
|
143
|
+
# can lose a slice of CPU to a neighbour: a genuine regression fails
|
|
144
|
+
# both attempts, a noisy one usually does not.
|
|
145
|
+
#
|
|
146
|
+
# Pinned to the baseline's interpreter on purpose: normalization
|
|
147
|
+
# cancels how fast the machine is, not which CPython it runs (see the
|
|
148
|
+
# module docstring in scripts/benchmark.py). If a change is a
|
|
149
|
+
# deliberate trade, re-record with --update-baseline and say why.
|
|
150
|
+
benchmark:
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
154
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
155
|
+
with:
|
|
156
|
+
python-version: "3.12"
|
|
157
|
+
- name: Corpus decode throughput
|
|
158
|
+
shell: bash
|
|
159
|
+
run: |
|
|
160
|
+
# Keep the benchmark's own exit status: piping the whole thing
|
|
161
|
+
# into tee would report tee's, and wrapping it in a { } group
|
|
162
|
+
# would report the trailing echo's -- either way the gate
|
|
163
|
+
# would pass while the check failed.
|
|
164
|
+
status=0
|
|
165
|
+
uv run --frozen --group bench python scripts/benchmark.py \
|
|
166
|
+
--check --threshold 15 --compare >benchmark.out 2>&1 || status=$?
|
|
167
|
+
{
|
|
168
|
+
echo '```'
|
|
169
|
+
cat benchmark.out
|
|
170
|
+
echo '```'
|
|
171
|
+
} >>"$GITHUB_STEP_SUMMARY"
|
|
172
|
+
cat benchmark.out
|
|
173
|
+
exit "$status"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
# Advanced-setup workflow (a checked-in file) rather than GitHub's
|
|
4
|
+
# built-in "default setup", so this analysis lives in version control
|
|
5
|
+
# alongside the rest of this repository's CI (ci.yml, fuzz.yml,
|
|
6
|
+
# release.yml, mutation.yml, scorecard.yml) and its action pins are
|
|
7
|
+
# reviewable the same way theirs are. Default setup is configured
|
|
8
|
+
# through repository settings instead of a file, so it isn't available
|
|
9
|
+
# to author from here.
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [master]
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [master]
|
|
15
|
+
schedule:
|
|
16
|
+
# Arbitrary; weekly on Sundays, clear of fuzz.yml's and mutation.yml's
|
|
17
|
+
# daily 03:00/04:00 UTC slots and scorecard.yml's own Sunday 05:00
|
|
18
|
+
# UTC slot.
|
|
19
|
+
- cron: "0 6 * * 0"
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
26
|
+
cancel-in-progress: true
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
analyze:
|
|
30
|
+
name: Analyze (python)
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
actions: read
|
|
34
|
+
contents: read
|
|
35
|
+
security-events: write
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
38
|
+
|
|
39
|
+
# Python is interpreted, so there is nothing for autobuild (or a
|
|
40
|
+
# manual build step) to do: build-mode: none has CodeQL extract
|
|
41
|
+
# the database straight from source instead. This is the build
|
|
42
|
+
# mode the CodeQL Action's own docs recommend for interpreted
|
|
43
|
+
# languages, and it sidesteps the question of an autobuild step
|
|
44
|
+
# fitting this project's uv-based build at all.
|
|
45
|
+
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
46
|
+
with:
|
|
47
|
+
languages: python
|
|
48
|
+
build-mode: none
|
|
49
|
+
|
|
50
|
+
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Docs lint
|
|
2
|
+
|
|
3
|
+
# Runs lycheeverse/lychee (a link checker) over the small set of
|
|
4
|
+
# hand-maintained docs that carry the project's external claims and
|
|
5
|
+
# cross-references: README.md, ARCHITECTURE.md, CONTRIBUTING.md,
|
|
6
|
+
# CHANGELOG.md, and docs/CLAIMS.md. Two triggers, for two different
|
|
7
|
+
# failure modes: the pull_request path filter catches a broken link
|
|
8
|
+
# introduced *by* a docs edit, and the weekly schedule catches a link
|
|
9
|
+
# that was fine when written but has since gone dead out from under an
|
|
10
|
+
# otherwise-unchanged file (a renamed issue, a retired badge service, a
|
|
11
|
+
# moved external page) -- which no docs PR would ever surface.
|
|
12
|
+
on:
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [master]
|
|
15
|
+
paths:
|
|
16
|
+
- "README.md"
|
|
17
|
+
- "ARCHITECTURE.md"
|
|
18
|
+
- "CONTRIBUTING.md"
|
|
19
|
+
- "CHANGELOG.md"
|
|
20
|
+
- "docs/CLAIMS.md"
|
|
21
|
+
schedule:
|
|
22
|
+
# Arbitrary; weekly on Mondays, clear of fuzz.yml's/mutation.yml's
|
|
23
|
+
# daily 03:00/04:00 UTC slots and scorecard.yml's Sunday 05:00 slot.
|
|
24
|
+
- cron: "0 6 * * 1"
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
concurrency:
|
|
28
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
29
|
+
cancel-in-progress: true
|
|
30
|
+
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
lychee:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
39
|
+
|
|
40
|
+
# lychee-action's `token` input defaults to `${{ github.token }}`,
|
|
41
|
+
# which it exports as GITHUB_TOKEN; lychee reads that itself to
|
|
42
|
+
# check github.com links through the API instead of scraping the
|
|
43
|
+
# page. That's what keeps the ~15 github.com/EONRaider/NETProtocols
|
|
44
|
+
# issue/release links in these docs from tripping an anonymous
|
|
45
|
+
# rate limit -- most relevant on the weekly schedule run, which
|
|
46
|
+
# has no pull request author's own browsing to blend in with.
|
|
47
|
+
#
|
|
48
|
+
# --timeout/--max-retries/--retry-wait-time are more generous
|
|
49
|
+
# than lychee's own defaults (20s/3/1s) to absorb a slow or
|
|
50
|
+
# momentarily rate-limited external host without failing the
|
|
51
|
+
# build outright.
|
|
52
|
+
#
|
|
53
|
+
# --exclude covers img.shields.io, which serves this project's
|
|
54
|
+
# three static README badges: a shared, heavily-trafficked CDN
|
|
55
|
+
# known to intermittently rate-limit or time out under bulk/CI
|
|
56
|
+
# traffic across many unrelated repos, independent of whether
|
|
57
|
+
# any given badge URL is actually broken. Every other file was
|
|
58
|
+
# checked by hand for placeholder/example domains (example.com,
|
|
59
|
+
# localhost, and the like) before this workflow was added; none
|
|
60
|
+
# were found, so no other entries are excluded here.
|
|
61
|
+
- name: Check links in the project's hand-maintained docs
|
|
62
|
+
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
|
63
|
+
with:
|
|
64
|
+
args: >-
|
|
65
|
+
--no-progress
|
|
66
|
+
--verbose
|
|
67
|
+
--timeout 30
|
|
68
|
+
--max-retries 3
|
|
69
|
+
--retry-wait-time 10
|
|
70
|
+
--exclude '^https://img\.shields\.io'
|
|
71
|
+
README.md
|
|
72
|
+
ARCHITECTURE.md
|
|
73
|
+
CONTRIBUTING.md
|
|
74
|
+
CHANGELOG.md
|
|
75
|
+
docs/CLAIMS.md
|
|
@@ -12,6 +12,14 @@ on:
|
|
|
12
12
|
- cron: "0 3 * * *"
|
|
13
13
|
workflow_dispatch:
|
|
14
14
|
|
|
15
|
+
# Keyed on the triggering event too, so the nightly cron can never
|
|
16
|
+
# cancel a manually dispatched run on the same ref (the release
|
|
17
|
+
# checklist in CONTRIBUTING.md relies on one) -- runs of the same kind
|
|
18
|
+
# still supersede each other.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
15
23
|
permissions:
|
|
16
24
|
contents: read
|
|
17
25
|
|
|
@@ -29,8 +37,8 @@ jobs:
|
|
|
29
37
|
fuzz:
|
|
30
38
|
runs-on: ubuntu-latest
|
|
31
39
|
steps:
|
|
32
|
-
- uses: actions/checkout@
|
|
33
|
-
- uses: astral-sh/setup-uv@
|
|
40
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
41
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
34
42
|
with:
|
|
35
43
|
python-version: "3.12"
|
|
36
44
|
# Keyed by run id with a prefix restore-key: a fixed key would
|
|
@@ -40,7 +48,7 @@ jobs:
|
|
|
40
48
|
# today's as a new entry, so Hypothesis's example database grows
|
|
41
49
|
# night over night instead of starting cold every time.
|
|
42
50
|
- name: Restore accumulated Hypothesis examples
|
|
43
|
-
uses: actions/cache@
|
|
51
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
44
52
|
with:
|
|
45
53
|
path: .hypothesis
|
|
46
54
|
key: hypothesis-nightly-${{ github.run_id }}
|
|
@@ -57,8 +65,41 @@ jobs:
|
|
|
57
65
|
# repo write access to re-run the job locally.
|
|
58
66
|
- name: Upload the failing example database
|
|
59
67
|
if: failure()
|
|
60
|
-
uses: actions/upload-artifact@
|
|
68
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
61
69
|
with:
|
|
62
70
|
name: hypothesis-nightly-failure-${{ github.run_id }}
|
|
63
71
|
path: .hypothesis
|
|
64
72
|
retention-days: 30
|
|
73
|
+
|
|
74
|
+
# Coverage-guided fuzzing of the same target, via Atheris rather than
|
|
75
|
+
# Hypothesis's example strategies above -- it drives decode_frame with
|
|
76
|
+
# libFuzzer's mutation engine instead of Hypothesis's, so it explores
|
|
77
|
+
# a different part of the input space. HTTP.decode gets a harness of
|
|
78
|
+
# its own because decode_frame never reaches it: HTTP is not
|
|
79
|
+
# port-dispatched. Pinned to Python 3.12 only,
|
|
80
|
+
# like the "fuzz" job above: Atheris hooks CPython internals directly
|
|
81
|
+
# and tends to lag new interpreter releases, so it isn't given the
|
|
82
|
+
# full 3.12/3.13/3.14 matrix the compatibility-gate "test" job in
|
|
83
|
+
# ci.yml runs.
|
|
84
|
+
atheris:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
88
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
89
|
+
with:
|
|
90
|
+
python-version: "3.12"
|
|
91
|
+
- name: Run Atheris against decode_frame
|
|
92
|
+
run: |
|
|
93
|
+
uv run --frozen --with atheris \
|
|
94
|
+
python tests/fuzz/fuzz_decode_frame.py -max_total_time=300
|
|
95
|
+
- name: Run Atheris against HTTP.decode
|
|
96
|
+
run: |
|
|
97
|
+
uv run --frozen --with atheris \
|
|
98
|
+
python tests/fuzz/fuzz_http_decode.py -max_total_time=300
|
|
99
|
+
- name: Upload crash artifacts
|
|
100
|
+
if: failure()
|
|
101
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
102
|
+
with:
|
|
103
|
+
name: atheris-crash-${{ github.run_id }}
|
|
104
|
+
path: crash-*
|
|
105
|
+
retention-days: 30
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
name: Nightly mutation testing
|
|
2
|
+
|
|
3
|
+
# Separate from ci.yml on purpose, same reasoning as fuzz.yml: ci.yml is
|
|
4
|
+
# reused via workflow_call by release.yml, so folding a schedule trigger
|
|
5
|
+
# into it would fire mutation testing on every release too, not just
|
|
6
|
+
# once a night.
|
|
7
|
+
on:
|
|
8
|
+
schedule:
|
|
9
|
+
# Arbitrary; offset an hour past fuzz.yml's 03:00 UTC slot so the
|
|
10
|
+
# two nightly jobs don't contend for the same runner pool.
|
|
11
|
+
- cron: "0 4 * * *"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
# Keyed on the triggering event too, so the nightly cron can never
|
|
15
|
+
# cancel a manually dispatched run on the same ref (the release
|
|
16
|
+
# checklist in CONTRIBUTING.md relies on one) -- runs of the same kind
|
|
17
|
+
# still supersede each other.
|
|
18
|
+
concurrency:
|
|
19
|
+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
# Mutation testing itself is exploratory/maintenance, not a
|
|
27
|
+
# compatibility gate, so this is pinned to one interpreter — same
|
|
28
|
+
# reasoning as fuzz.yml's "fuzz" job.
|
|
29
|
+
mutation:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
# mutmut hashes each mutated file plus the tests that cover it and
|
|
37
|
+
# skips re-running mutants whose inputs haven't changed since the
|
|
38
|
+
# last recorded result, so carrying its cache forward night over
|
|
39
|
+
# night saves real work — same accumulation pattern as fuzz.yml's
|
|
40
|
+
# Hypothesis example database. Keyed by run id with a prefix
|
|
41
|
+
# restore-key for the same reason: a fixed key would restore
|
|
42
|
+
# forever but never save a new entry.
|
|
43
|
+
- name: Restore accumulated mutmut cache
|
|
44
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
45
|
+
with:
|
|
46
|
+
path: |
|
|
47
|
+
.mutmut-cache
|
|
48
|
+
mutants
|
|
49
|
+
key: mutmut-nightly-${{ github.run_id }}
|
|
50
|
+
restore-keys: |
|
|
51
|
+
mutmut-nightly-
|
|
52
|
+
# The restored cache also carries mutants/mutmut-stats.json, the
|
|
53
|
+
# map from each function to the tests that call it. When that map
|
|
54
|
+
# exists, mutmut 3.8 re-traces only tests it has never seen, so a
|
|
55
|
+
# function added since the cached run (a new helper called only
|
|
56
|
+
# by existing tests) maps to no tests and every one of its
|
|
57
|
+
# mutants reads "no tests"; so does an existing test edited to
|
|
58
|
+
# call a function it didn't before. Without the map, mutmut
|
|
59
|
+
# traces the configured test selection in full, which costs one
|
|
60
|
+
# traced pytest pass. Nothing else is lost: with the positional
|
|
61
|
+
# filters below, mutmut re-runs every in-scope mutant regardless
|
|
62
|
+
# of a cached result.
|
|
63
|
+
- name: Drop the cached function-to-test map
|
|
64
|
+
run: rm -f mutants/mutmut-stats.json
|
|
65
|
+
- name: Install the project with dev dependencies
|
|
66
|
+
run: uv sync --group dev --frozen
|
|
67
|
+
# File-level scope (src/netprotocols/checksum.py, _tlv.py,
|
|
68
|
+
# layer7/dns.py and layer7/http.py) comes from the project's mutmut config (pyproject.toml's
|
|
69
|
+
# [tool.mutmut] table) via only_mutate, not hardcoded here.
|
|
70
|
+
# dns.py's in-scope surface is narrower still -- only the
|
|
71
|
+
# name-compression byte walk, not the record/RDATA/dataclass code
|
|
72
|
+
# the rest of the file holds -- and mutmut has no config-level way
|
|
73
|
+
# to express that, so these positional filters (kept in lockstep
|
|
74
|
+
# with the ones documented next to only_mutate in pyproject.toml)
|
|
75
|
+
# are what actually narrow it; a filter added or renamed there
|
|
76
|
+
# without a matching update here is exactly the kind of drift this
|
|
77
|
+
# comment can't prevent on its own.
|
|
78
|
+
- name: Run mutmut against the scoped targets
|
|
79
|
+
run: |
|
|
80
|
+
uv run --frozen mutmut run \
|
|
81
|
+
'netprotocols.checksum.*' \
|
|
82
|
+
'netprotocols._tlv.*' \
|
|
83
|
+
'netprotocols.layer7.dns.x__read_name*' \
|
|
84
|
+
'netprotocols.layer7.dns.x__labels*' \
|
|
85
|
+
'netprotocols.layer7.http.x__*'
|
|
86
|
+
# `mutmut run` exits 0 however many mutants survive, so it can
|
|
87
|
+
# never turn this job red on its own. This step is what does: it
|
|
88
|
+
# fails on any undetected mutant not in the triage record
|
|
89
|
+
# (scripts/mutmut_survivors.txt), on a stale entry there, and on
|
|
90
|
+
# a scoped file with no checked mutant at all.
|
|
91
|
+
- name: Fail on survivors outside the triage record
|
|
92
|
+
run: |
|
|
93
|
+
uv run --frozen mutmut results --all true > mutmut-results.txt
|
|
94
|
+
uv run --frozen python scripts/check_mutmut_survivors.py \
|
|
95
|
+
mutmut-results.txt
|
|
96
|
+
# A failed scheduled run already notifies repo watchers by
|
|
97
|
+
# default (GitHub's standard behavior), and with the step above a
|
|
98
|
+
# red run means an untriaged survivor or a broken run; nothing
|
|
99
|
+
# here files an issue on top of it. This makes the results
|
|
100
|
+
# recoverable without needing repo write access to re-run mutmut
|
|
101
|
+
# locally. Covers the status listing plus both the sqlite-file
|
|
102
|
+
# cache and the mutated-copies directory, since either of the
|
|
103
|
+
# latter may be what a given mutmut version writes; whatever
|
|
104
|
+
# doesn't exist is silently skipped.
|
|
105
|
+
- name: Upload mutmut results on failure
|
|
106
|
+
if: failure()
|
|
107
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
108
|
+
with:
|
|
109
|
+
name: mutmut-nightly-failure-${{ github.run_id }}
|
|
110
|
+
path: |
|
|
111
|
+
mutmut-results.txt
|
|
112
|
+
.mutmut-cache
|
|
113
|
+
mutants
|
|
114
|
+
if-no-files-found: ignore
|
|
115
|
+
retention-days: 30
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: PR Title Lint
|
|
2
|
+
|
|
3
|
+
# Enforces Conventional Commits shape (https://www.conventionalcommits.org/)
|
|
4
|
+
# on the *pull request title* only — e.g. `feat: ...`, `fix: ...`,
|
|
5
|
+
# `docs: ...`, `chore: ...`. This is unrelated to this repo's guidance on
|
|
6
|
+
# individual commit messages within a PR (see CONTRIBUTING.md); nothing
|
|
7
|
+
# here checks or rewrites those.
|
|
8
|
+
#
|
|
9
|
+
# Runs on `pull_request` (not `pull_request_target`) so it only ever sees
|
|
10
|
+
# the PR's own metadata with a read-only, non-elevated token — no need for
|
|
11
|
+
# the elevated-permissions trigger the action's own docs suggest for
|
|
12
|
+
# repos that also want its optional "[WIP]" title support, which this
|
|
13
|
+
# repo doesn't use.
|
|
14
|
+
on:
|
|
15
|
+
pull_request:
|
|
16
|
+
branches: [master]
|
|
17
|
+
types: [opened, edited, synchronize]
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
pull-requests: read
|
|
21
|
+
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
lint-pr-title:
|
|
28
|
+
name: Validate PR title
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
|
32
|
+
env:
|
|
33
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|