docs: sync readme, architecture and tasks with the russian report feature

This commit is contained in:
Andrew Pimenov 2026-08-09 11:14:56 +03:00
parent 2e84c434c0
commit b4c541aecf
5 changed files with 18 additions and 13 deletions

View file

@ -32,8 +32,8 @@ src/netcheck/
**File-size exceptions.** The ~200-line guideline (Global Constraints) held for most modules, but these grew past it during implementation and were kept as single files rather than split, because each one is a single cohesive responsibility that splitting would only scatter across more files with a thinner reason to draw the line anywhere in particular: **File-size exceptions.** The ~200-line guideline (Global Constraints) held for most modules, but these grew past it during implementation and were kept as single files rather than split, because each one is a single cohesive responsibility that splitting would only scatter across more files with a thinner reason to draw the line anywhere in particular:
- `exporter.py` (~475 lines) — JSON and Markdown are two renderers of the same report shape; keeping them together is what guarantees they can never drift on which sections exist. - `exporter.py` (~680 lines) — JSON and both Markdown languages are renderers of the same report shape; keeping them together is what guarantees the English and Russian reports can never drift on which sections or rows exist. `render_markdown(report, lang="en"|"ru")` and a handful of inline `_t(lang, en, ru)` calls at each string site render both languages from one code path, deliberately, instead of a second renderer file that could silently fall out of sync.
- `cli.py` (~465 lines) — the Typer app, every flag, and the full `diagnose()` phase pipeline; splitting flag parsing from what the flags toggle would just add an import hop. - `cli.py` (~475 lines) — the Typer app, every flag, and the full `diagnose()` phase pipeline; splitting flag parsing from what the flags toggle would just add an import hop.
- `models.py` (~330 lines) — every shared dataclass plus `to_jsonable()`; this is deliberately the one place that defines the report's vocabulary. - `models.py` (~330 lines) — every shared dataclass plus `to_jsonable()`; this is deliberately the one place that defines the report's vocabulary.
- `interpret.py` (~320 lines) — threshold bands, the latency/path/speed finding generators, VPN scoring and bufferbloat grading all reason over the same `Finding[]` shape. - `interpret.py` (~320 lines) — threshold bands, the latency/path/speed finding generators, VPN scoring and bufferbloat grading all reason over the same `Finding[]` shape.
- `bgp.py` (~305 lines) — four independent providers (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) plus the disk cache they share. - `bgp.py` (~305 lines) — four independent providers (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) plus the disk cache they share.
@ -57,7 +57,7 @@ src/netcheck/
| |
+-- interpret ---> Finding[] + overall verdict +-- interpret ---> Finding[] + overall verdict
| |
+-- exporter ---> logs/report_<ASN>_<ts>.md + .json +-- exporter ---> logs/report_<ASN>_<ts>.{md,ru.md,json}
``` ```
Two hard concurrency constraints: Two hard concurrency constraints:
@ -89,7 +89,7 @@ Two hard concurrency constraints:
## Storage ## Storage
- `./logs/report_<ASN>_<YYYYMMDDTHHMMSSZ>.{md,json}` — one pair per run. `<ASN>` is the target's subject in target mode (an ASN, IP or domain) and the local egress ASN in auto mode. Compact ISO timestamps because Windows forbids `:` in filenames. Falls back to `report_unknown_…` when the ASN lookup fails entirely. Written temp-file-plus-`os.replace()`, always atomic. Gitignored: reports contain the external IP, city, coordinates and ISP name. - `./logs/report_<ASN>_<YYYYMMDDTHHMMSSZ>.{md,ru.md,json}` — one triple per run: English Markdown, a full Russian translation with identical structure, and the JSON dump (English only — no `.ru.json`). The two Markdown files cross-link each other at the top, the same way `README.md`/`README.ru.md` do. `<ASN>` is the target's subject in target mode (an ASN, IP or domain) and the local egress ASN in auto mode. Compact ISO timestamps because Windows forbids `:` in filenames. Falls back to `report_unknown_…` when the ASN lookup fails entirely. Written temp-file-plus-`os.replace()`, always atomic. Gitignored: reports contain the external IP, city, coordinates and ISP name.
- `./logs/watch_<ASN>_<YYYYMMDDTHHMMSSZ>.json` — one time-series artifact per `--watch` session, not one report per tick. - `./logs/watch_<ASN>_<YYYYMMDDTHHMMSSZ>.json` — one time-series artifact per `--watch` session, not one report per tick.
- `./.cache/firehol/*.netset` — downloaded blocklists, refreshed per `providers.firehol_refresh_hours`. - `./.cache/firehol/*.netset` — downloaded blocklists, refreshed per `providers.firehol_refresh_hours`.
- `./.cache/pdb-net-<asn>.json`, `./.cache/pdb-netixlan-<net_id>.json` — PeeringDB responses, valid for `providers.peeringdb_cache_hours`. - `./.cache/pdb-net-<asn>.json`, `./.cache/pdb-netixlan-<net_id>.json` — PeeringDB responses, valid for `providers.peeringdb_cache_hours`.

View file

@ -32,8 +32,8 @@ src/netcheck/
**Исключения по размеру файла.** Лимит ~200 строк (Общие ограничения) выдержан для большинства модулей, но эти выросли за него в процессе реализации и остались одним файлом, а не были разбиты, потому что каждый — единая цельная ответственность, и разбиение только раскидало бы её по файлам без чёткой причины проводить границу где-то конкретно: **Исключения по размеру файла.** Лимит ~200 строк (Общие ограничения) выдержан для большинства модулей, но эти выросли за него в процессе реализации и остались одним файлом, а не были разбиты, потому что каждый — единая цельная ответственность, и разбиение только раскидало бы её по файлам без чёткой причины проводить границу где-то конкретно:
- `exporter.py` (~475 строк) — JSON и Markdown — два рендера одной и той же формы отчёта; то, что они вместе, гарантирует, что они никогда не разойдутся в том, какие разделы существуют. - `exporter.py` (~680 строк) — JSON и оба языка Markdown — рендеры одной и той же формы отчёта; то, что они вместе, гарантирует, что английский и русский отчёты никогда не разойдутся в том, какие разделы или строки существуют. `render_markdown(report, lang="en"|"ru")` и точечные вызовы `_t(lang, en, ru)` в местах со строками рендерят оба языка из одного пути кода — намеренно, вместо отдельного файла-рендера, который мог бы незаметно разойтись.
- `cli.py` (~465 строк) — приложение Typer, все флаги и весь конвейер фаз `diagnose()`; разделение разбора флагов и того, что они переключают, добавило бы только лишний импорт. - `cli.py` (~475 строк) — приложение Typer, все флаги и весь конвейер фаз `diagnose()`; разделение разбора флагов и того, что они переключают, добавило бы только лишний импорт.
- `models.py` (~330 строк) — все общие датаклассы плюс `to_jsonable()`; это намеренно единственное место, определяющее словарь отчёта. - `models.py` (~330 строк) — все общие датаклассы плюс `to_jsonable()`; это намеренно единственное место, определяющее словарь отчёта.
- `interpret.py` (~320 строк) — пороговые полосы, генераторы находок по задержке/маршруту/скорости, скоринг VPN и оценка bufferbloat — всё рассуждает над одной формой `Finding[]`. - `interpret.py` (~320 строк) — пороговые полосы, генераторы находок по задержке/маршруту/скорости, скоринг VPN и оценка bufferbloat — всё рассуждает над одной формой `Finding[]`.
- `bgp.py` (~305 строк) — четыре независимых источника (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) плюс общий для них дисковый кэш. - `bgp.py` (~305 строк) — четыре независимых источника (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) плюс общий для них дисковый кэш.
@ -57,7 +57,7 @@ src/netcheck/
| |
+-- interpret ---> Finding[] + общий вердикт +-- interpret ---> Finding[] + общий вердикт
| |
+-- exporter ---> logs/report_<ASN>_<ts>.md + .json +-- exporter ---> logs/report_<ASN>_<ts>.{md,ru.md,json}
``` ```
Два жёстких ограничения по конкурентности: Два жёстких ограничения по конкурентности:
@ -89,7 +89,7 @@ src/netcheck/
## Хранение ## Хранение
- `./logs/report_<ASN>_<YYYYMMDDTHHMMSSZ>.{md,json}` — пара файлов на прогон. `<ASN>` — это цель в режиме target (ASN, IP или домен) и локальный ASN исходящего адреса в авто-режиме. Компактный ISO-таймстамп, потому что Windows запрещает `:` в именах файлов. При полном провале определения ASN — `report_unknown_…`. Запись через временный файл и `os.replace()`, всегда атомарно. В gitignore: отчёты содержат внешний IP, город, координаты и название провайдера. - `./logs/report_<ASN>_<YYYYMMDDTHHMMSSZ>.{md,ru.md,json}` — тройка файлов на прогон: английский Markdown, его полный перевод на русский с идентичной структурой и JSON-дамп (только английский, `.ru.json` нет). Оба Markdown-файла ссылаются друг на друга вверху, так же как `README.md`/`README.ru.md`. `<ASN>` — это цель в режиме target (ASN, IP или домен) и локальный ASN исходящего адреса в авто-режиме. Компактный ISO-таймстамп, потому что Windows запрещает `:` в именах файлов. При полном провале определения ASN — `report_unknown_…`. Запись через временный файл и `os.replace()`, всегда атомарно. В gitignore: отчёты содержат внешний IP, город, координаты и название провайдера.
- `./logs/watch_<ASN>_<YYYYMMDDTHHMMSSZ>.json` — один тайм-ряд на сессию `--watch`, а не отдельный отчёт на каждый тик. - `./logs/watch_<ASN>_<YYYYMMDDTHHMMSSZ>.json` — один тайм-ряд на сессию `--watch`, а не отдельный отчёт на каждый тик.
- `./.cache/firehol/*.netset` — скачанные блоклисты, обновляются по `providers.firehol_refresh_hours`. - `./.cache/firehol/*.netset` — скачанные блоклисты, обновляются по `providers.firehol_refresh_hours`.
- `./.cache/pdb-net-<asn>.json`, `./.cache/pdb-netixlan-<net_id>.json` — ответы PeeringDB, действительны `providers.peeringdb_cache_hours`. - `./.cache/pdb-net-<asn>.json`, `./.cache/pdb-netixlan-<net_id>.json` — ответы PeeringDB, действительны `providers.peeringdb_cache_hours`.

View file

@ -9,10 +9,11 @@ $ netcheck --quick
netcheck 0.1.0 · auto mode · Windows netcheck 0.1.0 · auto mode · Windows
Verdict: ok (100/100) — No problems found on this connection. Verdict: ok (100/100) — No problems found on this connection.
Report written to logs/report_AS64500_20260808T191200Z.md Report written to logs/report_AS64500_20260808T191200Z.md
logs/report_AS64500_20260808T191200Z.ru.md
logs/report_AS64500_20260808T191200Z.json logs/report_AS64500_20260808T191200Z.json
``` ```
Everything else — egress ASN, VPN verdict, latency, path, speed — lives in the two files it just wrote, not on the console; the terminal only tells you the headline and where to look. Everything else — egress ASN, VPN verdict, latency, path, speed — lives in the three files it just wrote, not on the console; the terminal only tells you the headline and where to look.
## Install ## Install
@ -72,7 +73,7 @@ No configuration is required. Copy `.env.example` to `.env` only if you have opt
| `--ndt7` | Opt in to M-Lab NDT7 (publishes your measurement, including your IP, as CC0 open data). On an interactive terminal it prints the consent notice and asks first; on a non-interactive one it proceeds without asking | | `--ndt7` | Opt in to M-Lab NDT7 (publishes your measurement, including your IP, as CC0 open data). On an interactive terminal it prints the consent notice and asks first; on a non-interactive one it proceeds without asking |
| `--tcp-trace` | Opt in to a scapy TCP-SYN traceroute through ICMP-filtering middleboxes (needs the `tcptrace` extra plus Npcap on Windows or root on Unix). Falls through to the normal cascade when it cannot run | | `--tcp-trace` | Opt in to a scapy TCP-SYN traceroute through ICMP-filtering middleboxes (needs the `tcptrace` extra plus Npcap on Windows or root on Unix). Falls through to the normal cascade when it cannot run |
Every run writes two files into `./logs/`: a Markdown report to read and a JSON dump containing every raw provider response, untruncated. `./logs/` is gitignored — reports contain your external IP, city, coordinates and ISP name. Every run writes three files into `./logs/`: an English Markdown report, a full Russian translation of the same report (`.ru.md`, same sections, same tables, same numbers — cross-linked with the English one), and a JSON dump containing every raw provider response, untruncated. `./logs/` is gitignored — reports contain your external IP, city, coordinates and ISP name.
netcheck never requires administrator or root. Where privileges would give better data, it degrades to an unprivileged method and says so in the report. netcheck never requires administrator or root. Where privileges would give better data, it degrades to an unprivileged method and says so in the report.

View file

@ -9,10 +9,11 @@ $ netcheck --quick
netcheck 0.1.0 · auto mode · Windows netcheck 0.1.0 · auto mode · Windows
Verdict: ok (100/100) — No problems found on this connection. Verdict: ok (100/100) — No problems found on this connection.
Report written to logs/report_AS64500_20260808T191200Z.md Report written to logs/report_AS64500_20260808T191200Z.md
logs/report_AS64500_20260808T191200Z.ru.md
logs/report_AS64500_20260808T191200Z.json logs/report_AS64500_20260808T191200Z.json
``` ```
Всё остальное — ASN, вердикт по VPN, задержки, маршрут, скорость — попадает в два только что записанных файла, а не в консоль; терминал показывает только заголовок и куда смотреть дальше. Всё остальное — ASN, вердикт по VPN, задержки, маршрут, скорость — попадает в три только что записанных файла, а не в консоль; терминал показывает только заголовок и куда смотреть дальше.
## Установка ## Установка
@ -72,7 +73,7 @@ netcheck
| `--ndt7` | Включить M-Lab NDT7 (замер публикуется как открытые данные CC0, включая ваш IP). В интерактивном терминале сначала печатает уведомление о согласии и спрашивает подтверждение; в неинтерактивном — продолжает без вопроса | | `--ndt7` | Включить M-Lab NDT7 (замер публикуется как открытые данные CC0, включая ваш IP). В интерактивном терминале сначала печатает уведомление о согласии и спрашивает подтверждение; в неинтерактивном — продолжает без вопроса |
| `--tcp-trace` | Включить TCP-SYN-трассировку через scapy сквозь middlebox'ы, режущие ICMP (нужны extras `tcptrace` и Npcap на Windows или root на Unix). Если не может запуститься — откатывается на обычный каскад | | `--tcp-trace` | Включить TCP-SYN-трассировку через scapy сквозь middlebox'ы, режущие ICMP (нужны extras `tcptrace` и Npcap на Windows или root на Unix). Если не может запуститься — откатывается на обычный каскад |
Каждый прогон пишет в `./logs/` два файла: Markdown-отчёт для чтения и JSON-дамп со всеми сырыми ответами провайдеров без усечения. `./logs/` в gitignore — отчёты содержат ваш внешний IP, город, координаты и название провайдера. Каждый прогон пишет в `./logs/` три файла: Markdown-отчёт на английском, его полный перевод на русский (`.ru.md` — те же разделы, те же таблицы, те же числа, со взаимными ссылками) и JSON-дамп со всеми сырыми ответами провайдеров без усечения. `./logs/` в gitignore — отчёты содержат ваш внешний IP, город, координаты и название провайдера.
netcheck никогда не требует администратора или root. Там, где привилегии дали бы данные лучше, он деградирует до непривилегированного метода и пишет об этом в отчёте. netcheck никогда не требует администратора или root. Там, где привилегии дали бы данные лучше, он деградирует до непривилегированного метода и пишет об этом в отчёте.

View file

@ -24,6 +24,8 @@
- `watch.py` — monitoring loop with one time-series artifact per session - `watch.py` — monitoring loop with one time-series artifact per session
- End-to-end smoke test across `--quick`, `--full` and `--target` modes — caught and fixed five live-network defects (egress_v4 mislabeled on a dual-stack host, the same for asn-target identity lookups, target-mode report filenames using the local ASN instead of the target, cf-trace's own IP leaking into a target lookup, a skipped module's warning not reaching its Markdown placeholder) plus one flaky test (Windows mtime clock skew in the PeeringDB cache) - End-to-end smoke test across `--quick`, `--full` and `--target` modes — caught and fixed five live-network defects (egress_v4 mislabeled on a dual-stack host, the same for asn-target identity lookups, target-mode report filenames using the local ASN instead of the target, cf-trace's own IP leaking into a target lookup, a skipped module's warning not reaching its Markdown placeholder) plus one flaky test (Windows mtime clock skew in the PeeringDB cache)
- Documentation freshness pass against the final module and flag set - Documentation freshness pass against the final module and flag set
- License finalized as PolyForm Noncommercial 1.0.0 — Shodan InternetDB and the free Spamhaus mirrors are non-commercial-use-only, and this license keeps every downstream fork inside those same terms. Real license text landed in `LICENSE`, README/README.ru.md updated, `pyproject.toml` carries the SPDX identifier
- Russian Markdown report (`report_<ASN>_<ts>.ru.md`), written alongside the English one on every run, same structure, same tables, same numbers, cross-linked with its English sibling. `Finding`/`Reputation`/`DnsLeak` carry optional `_ru` sibling fields set alongside the English ones at each construction site; `exporter.py` renders both languages from one code path via `render_markdown(report, lang=...)` and inline `_t(lang, en, ru)` calls, so the two reports cannot structurally drift. Module skip/warning reasons are translated too (small exact-match dict in `exporter.py`)
## In Progress ## In Progress
@ -31,9 +33,10 @@
## Next ## Next
- Finalize the license: MIT vs Polyform Noncommercial (spec §18). Polyform is the recommendation, because Shodan InternetDB and the free Spamhaus mirrors are non-commercial-use-only and MIT would let a downstream commercial fork violate those terms. If MIT wins, add the caveat to both READMEs. Replace the placeholder `LICENSE` before the first public push.
- Consider publishing to PyPI: decide on the distribution name, add a release workflow, and confirm the `tcptrace`/`ndt7` extras install cleanly from a wheel rather than from the repo. - Consider publishing to PyPI: decide on the distribution name, add a release workflow, and confirm the `tcptrace`/`ndt7` extras install cleanly from a wheel rather than from the repo.
- Revisit the opt-in extras' UX. `--ndt7` currently prints its consent notice and prompts on every interactive run, and `--tcp-trace` prints its privilege warning unconditionally. Decide whether consent should be remembered (a flag in `config.yaml`, or a marker in `.cache/`) so a repeat user is not re-prompted, and what the right behaviour is when stdin is not a TTY. - Revisit the opt-in extras' UX. `--ndt7` currently prints its consent notice and prompts on every interactive run, and `--tcp-trace` prints its privilege warning unconditionally. Decide whether consent should be remembered (a flag in `config.yaml`, or a marker in `.cache/`) so a repeat user is not re-prompted, and what the right behaviour is when stdin is not a TTY.
- Fix adapter-name mojibake in the DNS-leak table: a non-ASCII Windows adapter name (seen on this dev machine: a Bluetooth virtual adapter) renders as cp1251/cp866-corrupted garbage in both the English and Russian reports. Pre-existing bug in `netinfo.py`'s per-adapter resolver enumeration, not introduced by the Russian-report work — needs its own investigation into what encoding Windows actually hands back for adapter friendly names.
- `dual_stack_note` and other free-text notes generated outside `interpret.py`/`reputation.py`/`probes/dns_leak.py` still render in English inside the Russian report (only `Finding`, `Reputation`, `DnsLeak` carry `_ru` fields today). Low-traffic edge case; extend the same inline-sibling pattern if it turns out to matter.
## Blocked ## Blocked