HydraIssues

Feature Request: In-client network-quality measurement (j...
open unclassified Project: Parent: #695 Reporter: anonymous 18 Jul 2026 10:36

Description

BUILT 2026-09-15, NOT RELEASED

hydraprobe daca4da and hydraheadflatscreen 6c9867a, both on main, neither tagged.

The engine is now shared

hydraprobe's measurement engine moved from internal/ to pkg/ so a head can import it: probe.Run, the B1/B2 profile table, and crucially result.FromStats, which is the single RawStats to PLAN 4.6 mapping. The CLI and the head now use the SAME mapping, so they cannot drift. PLAN 4.9's "one method, one schema, two contexts" is now enforced by the code rather than by intent. result.Tier was added for the two service tiers (ruling 2026-09-14c), and Result gained an omitempty district field so a region-qualification document still renders the exact 4.6 field set.

The head measures

  • hydraheadflatscreen probe --target host:port --hmac-key-file <path> --profile b1|b2 --duration ..., in-process so it works when the agent is down, key by file only because exec logs command lines, non-zero exit on a run that measured nothing.
  • POST /api/v1/probe on the local API, returning the 4.6 YAML document.
  • Labels come from the head's OWN config: venue, district, head name as client. Only region, transport and gateway are operator input. An operator cannot mislabel a run by retyping it wrong.
  • Path tag is derived, not guessed, from the address discoverBody actually chose: loopback or the body LAN address means lan; inside the mesh /16 means tunnel, split into tunnel-direct or tunnel-hub by reading this head's own hydraguard-air peers (a peer covering the target more specifically than the hub catch-all means direct); anything else means wan. --path overrides. This is exactly what #402 asked for and it is now automatic.

Pre-flight is OFF by default, deliberately

probe_preflight, default false. A museum visitor pressing start must not wait on telemetry. When enabled it is a 2 s B1 run (configurable) fired alongside pairing, hard boxed at duration plus 3 s, and every failure path returns nil: telemetry never gates or delays a stream. A zero-reply run is DROPPED rather than published as an all-loss fail, because no reflector runs on a body today and that is indistinguishable from a dead path.

DiagnosticsResult gained an omitempty Probe summary (RTT p50/p99, IPDV p99, downstream loss, tier, verdict, run id, path). With no probe the 5-check JSON that hydraheadipad mirrors is byte identical, and the probe never moves passed.

Verified

Build, vet and tests green in both repos. New tests cover the path tag across LAN, WireGuard hub, WireGuard direct peer and unreadable config, pre-flight off by default, the diagnostics JSON shape being unchanged, and a full RunProbe against a loopback reflector asserting the head-config labels. The darwin cgo build passes in CI on the self-hosted macOS ARM64 runner (Build run 35017335408 green), which closes the one gap the implementer could not check from Linux.

NOT verified, and why

Nothing has run on a real head. Heads are macOS arm64 and cannot be cross-compiled from a Linux box (pkg/client/micrelay_darwin.go is cgo), and the Build workflow publishes no artifact. Validating on a real head therefore requires tagging a release, and heads AUTO-UPDATE from releases.experiencenet.com/hydrahead/ every 6 hours and restart. That deploys to every live venue kiosk, so it is an owner decision, not an implementer one.

Risk if released: the visitor-facing path is unchanged (pre-flight off, the new surface is additive), the engine is the same code already run standalone on those exact Macs on 2026-09-14, and CI is green including darwin. The cost is a service restart per kiosk.

Out of scope, tracked

Reporting to HydraNeck: #745. HydraNeck has no diagnostics ingest at all today, so #397's "report to HydraNeck" was never buildable as written. The seam is one call in Client.runPreflightProbe, where the shaped Result and its summary exist together.

The iPad probe is a separate port: hydraheadipad is Swift and cannot import this engine. sint-niklaas can only ever be measured that way (#732).

Related: #402, #714, #724, #732, #745.