docs: hxprobe plans, summaries, explanations, usage, and changelog

Plans (docs/plans/):
- 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design
- 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage
  doc + Makefile, file input (-f), simplification pass, run-summary footer

Summaries (docs/summaries/): one per completed feature, recording what
was actually built, deviations from the plan, and verification steps

Explanations (docs/explanations/): two deep-dives written during review —
hxprobe concurrency model and worst-exit-code + render-loop analysis

Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md
for the full runnable reference

Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the
latprobe Python package for interview / code-review context

CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc,
file input, simplification, run-summary footer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 13:22:59 +02:00
parent 13a562e966
commit f487a4b1bd
19 changed files with 2367 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# hxprobe simplification — summary
Plan: `docs/plans/2026-07-02-12-05-hxprobe-simplification.md` (five targeted
removals of speculative scaffolding / redundant state / duplicated logic, no
behavior change). Trigger: a question about a dead `file=` parameter on
`_Parser.print_help`/`print_usage` in `hxprobe/hxprobe/cli.py`, which led to a
broader pass over the whole package for the same pattern.
## What changed
1. **`hxprobe/hxprobe/cli.py` — deleted the `_Parser` subclass and `_ArgExit`
exception** (~32 lines). It re-implemented, by hand, behavior the standard
library already provides: `argparse.ArgumentParser` already writes help to
`sys.stdout` and usage/errors to `sys.stderr`, and already raises
`SystemExit` rather than hard-exiting. `run()` now builds a plain
`argparse.ArgumentParser`, wraps the parse + validation calls in
`contextlib.redirect_stdout(stdout)` / `redirect_stderr(stderr)`, and
catches `SystemExit` at a single site:
```python
except SystemExit as exc:
return EXIT_OK if not exc.code else EXIT_USAGE
```
Deviation from the original plan sketch: the plan's snippet used
`EXIT_OK if not exc.code else EXIT_USAGE`, matching what was actually
implemented (equivalent to, but slightly more defensive than, the
`exc.code == 0` check first drafted, since argparse can in principle pass
`None`).
2. **`hxprobe/hxprobe/probe.py` — trimmed `_TimingStream.get_extra_info`** to
the single branch actually consumed on the request path (`ssl_object`).
Verified by grepping the installed `httpcore`/`httpx` packages: the
`server_addr`/`client_addr` branches were only ever queried by `httpx`'s own
`_main.py` (the `httpx` CLI command), never by anything hxprobe's request
path touches.
3. **`hxprobe/hxprobe/cli.py` — simplified `_load_urls`**: dropped the
`line.split()[0]` "forward-compatible with future `url key=value`
annotations" scaffolding; a stripped line is used directly. Docstring
updated to describe only what the function does today.
4. **`hxprobe/hxprobe/probe.py` — removed `_dns_set`/`_connect_set`/
`_tls_set`** from `_Trace`. These booleans duplicated `Phase.present` on
`self.dns`/`self.connect`/`self.tls` (each starts as `Phase()`, i.e.
`present=False`). Guards rewritten from `if not self._dns_set:` to
`if not self.dns.present:` (and analogously for connect/tls) — identical
first-hop-wins semantics for redirects, one less piece of parallel state.
5. **`hxprobe/hxprobe/cli.py` — extracted `_summarize_failures`**, a shared
helper that dedupes `failed: list[Result]` into `[(phase, message, count),
...]` in first-seen order. `_print_failure_summary` (text rendering) and
`_build_json_entry` (JSON rendering) previously each carried an identical
~12-line dedup loop; both now call the helper and only differ in how they
format the tuple.
## Not changed (considered, kept — per the plan)
- The custom httpcore backend (`_TimingBackend`/`_TimingStream`/
`_TimingTransport`) — this is the tool's actual reason to exist.
- `ThreadPoolExecutor` concurrency — backs the shipped `-c/--concurrency` and
multi-URL/`-f` features.
- Explicit per-phase dataclass fields in `probe.py`/`aggregate.py`.
- `CertInfo.sans` — unused in text output but real (emitted in JSON verbose
output).
## Verification
- `hxprobe/.venv/bin/python -m pytest tests/test_cli.py tests/test_probe.py -q`
→ **33 passed**, no test file edits.
- `hxprobe/.venv/bin/ruff check hxprobe/` → **all checks passed**.
- Manual smoke tests (`python -m hxprobe -h`, no-args, and a live
`https://example.com -v` request):
- `-h` → help text on stdout, exit `0`.
- No args → usage + `no URLs given` error on **stderr only** (confirmed via
separate stdout/stderr redirection to files — stdout was empty), exit `1`.
- Live verbose request → correct 6-phase timing, resolved IP, HTTP/2
protocol, TLS version/cipher, and certificate detail — confirms the
`get_extra_info` trim didn't break `ssl_object` retrieval and the
`_Trace` refactor didn't break phase/first-hop-wins tracking.
Net: `cli.py` and `probe.py` are shorter and carry less parallel/duplicated
state; no observable behavior changed.