feat(discovery): add SelectionTrace + TraceEntry types (Phase 1 trace surface)
Adds in-memory trace types consumed by upcoming SelectPriorityWithTrace / SelectMultiHopWithTrace siblings on MOLSRelayPolicy and RelaySet, and by the metrics emitter. ClientHash carried for debug-log correlation only; LocalAddress intentionally absent (PII-leak surface). No callers in this commit; pure type addition; existing public API unchanged. Phase 1 (telemetry-only) per the discovery rationalization plan.
cognitive committed
Apr 30, 2026 at 04:06 UTC
3fea6deaea99fbe52f8b7f72c874dc93735b6ac6
1 file changed
+81
portal/discovery/trace.go
new
+81
@@ -0,0 +1,81 @@
1
+package discovery
2
+
3
+// SelectionTrace records observability data for a single relay-selection
4
+// invocation. The struct is populated by SelectPriorityWithTrace /
5
+// SelectMultiHopWithTrace on MOLSRelayPolicy and by the matching siblings on
6
+// RelaySet, and consumed by:
7
+//
8
+// - portal/discovery/metrics.go — emits low-cardinality fields to Prometheus
9
+// (no per-client labels: ClientHash never becomes a metric label).
10
+// - sampled debug logs (zerolog) — ClientHash carried; LocalAddress
11
+// intentionally NOT carried in the trace (PII-leak surface; ClientHash is
12
+// sufficient for log correlation).
13
+//
14
+// The trace is not part of the public RelaySet API; it is consumed in-process
15
+// only.
16
+//
17
+// See /home/alpha/.claude/plans/sophisticate-and-rationalize-discovery-rosy-parnas.md
18
+// (Phase 1 — Telemetry only) for the rationale.
19
+
20
+import "time"
21
+
22
+// SelectionTrace captures the inputs and outputs of one selection invocation.
23
+// All fields are populated by the *WithTrace methods; downstream consumers
24
+// (metrics emitter, debug logger) read but never mutate the trace.
25
+type SelectionTrace struct {
26
+ Timestamp time.Time
27
+
28
+ // ClientHash is hashToGF64(LocalAddress) — a single byte derived from the
29
+ // client identity. Used only for sampled debug-log correlation. Not a
30
+ // Prometheus label (would unbounded cardinality) and not a public field on
31
+ // any external API.
32
+ ClientHash uint8
33
+
34
+ // Mode is "priority" or "multihop", matching the calling method.
35
+ Mode string
36
+
37
+ // Pool snapshot at selection time.
38
+ PoolTotal int
39
+ PoolEligible int
40
+ PoolFallback int
41
+
42
+ // Suppressed lists URLs excluded from selection along with the reason map.
43
+ Suppressed []string
44
+ Reasons map[string]string
45
+
46
+ // Congested is true when the existing congestion-grid switch is active
47
+ // (RTT mean > molsCongestionRTTThreshold).
48
+ Congested bool
49
+
50
+ // NonLinear is true when the variant-grid multiplier flip is active
51
+ // (CV > molsCVThreshold).
52
+ NonLinear bool
53
+
54
+ // M1, M2 are the MOLS multipliers used for this selection.
55
+ M1, M2 uint8
56
+
57
+ // AvgRTT is the mean discovery RTT across the auto pool sample.
58
+ AvgRTT time.Duration
59
+
60
+ // CV is the coefficient of variation of per-relay discovery RTTs.
61
+ CV float64
62
+
63
+ // Ranked carries per-relay scoring detail for every candidate considered
64
+ // (excluded URLs appear in Suppressed/Reasons instead).
65
+ Ranked []TraceEntry
66
+
67
+ // OutputURLs is the final ordered list returned by the selection method.
68
+ OutputURLs []string
69
+
70
+ // SelectionTook is wall time spent in the selection method.
71
+ SelectionTook time.Duration
72
+}
73
+
74
+// TraceEntry captures per-relay scoring detail within a SelectionTrace.
75
+type TraceEntry struct {
76
+ URL string
77
+ Score int
78
+ Confirmed bool
79
+ RTT time.Duration
80
+ Demoted bool
81
+}