-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.ts
More file actions
174 lines (160 loc) · 5.38 KB
/
Copy pathtypes.ts
File metadata and controls
174 lines (160 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import type {
CaptivePortalResult,
DnsIntegrityResult,
DualStackResult,
EdgePathResult,
MeasurementFailure,
PingResult,
SpeedTestResult,
} from '../types';
/**
* The answer layer's vocabulary.
*
* Everything here is deterministic and offline. There is no model, no API call
* and no scoring heuristic hidden behind a friendly sentence: a finding exists
* because a named predicate over named measurements returned true, and it
* carries the measurements that made it true. If the inputs a rule needs were
* not measured, the rule does not fire — it is skipped, and the gap is reported.
*/
/**
* Where in the path a finding places the fault.
*
* Ordered from the user outwards, which is also the order in which a person can
* actually do something about it.
*/
export type Layer =
/** The browser or this machine. */
| 'this-device'
/** Wi-Fi, cabling, the router, anything up to the demarcation point. */
| 'local-network'
/** The access network: the ISP link and whatever it is subscribed to. */
| 'isp'
/** The public internet between the ISP and the destination. */
| 'internet'
/** The specific service being reached. */
| 'destination';
/**
* How firmly the evidence supports the finding.
*
* Deliberately ordinal rather than a percentage. A number like "83% confident"
* would be exactly the kind of invented figure this project exists to keep out:
* there is no calculation behind it. These three levels each have a stated
* meaning, and every rule declares which one it is claiming.
*
* - `confirmed` — the measurement *is* the finding. Nothing is inferred.
* - `likely` — the observed pattern has one dominant cause, but a browser
* cannot see the cause directly.
* - `possible` — consistent with the finding, and with other explanations too.
*/
export type Confidence = 'confirmed' | 'likely' | 'possible';
/** How much the finding matters, independent of how sure we are of it. */
export type Severity = 'blocking' | 'degrading' | 'informational';
/** A measurement that supports a finding, quoted rather than summarised. */
export interface Evidence {
/** Dotted path into the snapshot, e.g. `speed.loadedPing`. Matches the rule's
* declared `consumes`, so a claim can always be traced to its inputs. */
metric: string;
/** The observed value in words. Only ever describes something measured. */
observation: string;
}
/** What a rule returns when it fires. */
export interface RuleHit {
confidence: Confidence;
severity: Severity;
/** One sentence stating what is wrong, in plain language. */
verdict: string;
/** Concrete actions, most useful first. */
remediation: string[];
evidence: Evidence[];
}
export interface Finding extends RuleHit {
ruleId: string;
title: string;
layer: Layer;
}
/**
* A rule.
*
* `consumes` is not decoration. It is the declared contract of what the rule
* reads, it is rendered in the UI so a user can see why a rule did or did not
* apply, and it is checked by a test against the evidence each rule actually
* cites.
*/
export interface Rule {
id: string;
title: string;
layer: Layer;
consumes: string[];
/** Null when the rule does not apply, or when its inputs were not measured.
* A rule must never substitute a value for an absent input. */
evaluate: (snapshot: TriageSnapshot) => RuleHit | null;
}
export type TriageStepId =
| 'browser-online'
| 'lan-gateway'
| 'dns'
| 'captive-portal'
| 'dual-stack'
| 'cdn-reach'
| 'bandwidth'
| 'bufferbloat';
export type TriageStepStatus =
| 'pending'
| 'running'
| 'pass'
| 'fail'
/** Ran, but the result does not support a conclusion either way. */
| 'inconclusive'
/** Did not run. `note` says why — never left to look like a pass. */
| 'skipped';
export interface TriageStep {
id: TriageStepId;
label: string;
/** What this step can actually establish, shown next to the result. */
question: string;
status: TriageStepStatus;
note: string | null;
}
/**
* Everything one triage run observed.
*
* Every field is nullable and null means "not measured". Rules read this
* structure and nothing else, which is what makes them testable without a
* network and auditable after the fact.
*/
export interface TriageSnapshot {
startedAt: number;
browserOnline: boolean;
/** `location.protocol`, which decides whether plaintext probes are possible. */
pageProtocol: string;
dns: DnsIntegrityResult | null;
portal: CaptivePortalResult | null;
dualStack: DualStackResult | null;
/** Reachability and phase timings across four independent CDNs, plus the
* HTTP/3 evidence. Produced by the Edge Path Explorer, reused whole. */
edge: EdgePathResult | null;
speed: SpeedTestResult | null;
ping: PingResult | null;
}
export type Attribution =
| Layer
/** Checks ran and none of them found a fault. */
| 'no-fault-found'
/** Too little was measured to attribute anything. Distinct from "fine". */
| 'indeterminate';
export interface TriageVerdict {
id: string;
timestamp: number;
attribution: Attribution;
/** The one-line answer to "is it me or the internet?". */
headline: string;
/** A short paragraph explaining the headline. */
summary: string;
/** Ranked, most actionable first. */
findings: Finding[];
steps: TriageStep[];
/** Everything the run could not determine, and why. */
failures: MeasurementFailure[];
snapshot: TriageSnapshot;
totalTimeMs: number;
}