@a4anthony/proctorkit-sdk 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,14 +93,13 @@ export interface RequiredCapabilitiesConfig {
93
93
  */
94
94
  export interface ThresholdsConfig {
95
95
  /**
96
- * Minimum download bandwidth (Mbps) for the connection check to
97
- * pass. A measurement below this trips `slow-connection`.
98
- * Default: 2.
96
+ * Download-bandwidth reference used for internal quality telemetry. Browser
97
+ * Mbps estimates do not decide candidate eligibility. Default: 2.
99
98
  */
100
99
  minBandwidthMbps?: number;
101
100
  /**
102
- * Minimum application-path upload bandwidth (Mbps). Upload is the primary
103
- * gate for continuous webcam and screen recordings. Default: 2.
101
+ * Upload-bandwidth reference used for internal quality telemetry. Recording
102
+ * readiness is derived from the enabled media workload instead. Default: 2.
104
103
  */
105
104
  minUploadBandwidthMbps?: number;
106
105
  /**
@@ -133,6 +132,13 @@ export interface ThresholdsConfig {
133
132
  */
134
133
  allowMobile?: boolean;
135
134
  }
135
+ export interface ConnectionReadinessCheckResult {
136
+ mode: "direct" | "segments" | "post";
137
+ payloadBytes: number;
138
+ completionWindowMs: number;
139
+ sampleDurationsMs: number[];
140
+ medianCompletionMs: number;
141
+ }
136
142
  export interface SystemCheckOptions {
137
143
  /**
138
144
  * Mode controls the row list and the short-circuit behaviour:
@@ -191,6 +197,11 @@ export interface SystemCheckOptions {
191
197
  * SDK consumers.
192
198
  */
193
199
  connectionTest?: HttpConnectionProbeOptions & ConnectionQualityOptions;
200
+ /**
201
+ * Optional workload-aware recording-path check. When supplied, this is the
202
+ * candidate eligibility signal; numeric speed tests remain telemetry only.
203
+ */
204
+ connectionReadiness?: () => Promise<ConnectionReadinessCheckResult>;
194
205
  }
195
206
  /** JSON-serialisable final report. No class instances, safe to log + persist. */
196
207
  export interface PreflightReport {
@@ -1 +1 @@
1
- {"version":3,"file":"system-check.d.ts","sourceRoot":"","sources":["../../src/preflight/system-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EASL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAE1E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,kBAAkB;IACjC,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qBAAqB;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,4EAA4E;AAC5E,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oEAAoE;IACpE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,qFAAqF;IACrF,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC;IAC3D;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,0BAA0B,GAAG,wBAAwB,CAAC;CACxE;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,6CAA6C;AAC7C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;AA8B1E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,gBAAgB,CAOzD,CAAC;AAyBF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,kBAAuB;IAsB5C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAKb;;;;;;OAMG;IACH,OAAO,IAAI,aAAa,CAAC,QAAQ,CAAC;IAMlC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAQpD;;;;;OAKG;IACI,GAAG,IAAI,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC;IA8D5E,0EAA0E;IACpE,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC;IAYzC;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;YAmD/D,OAAO;YAoCP,MAAM;YAUN,aAAa;IAyD3B;;;;;OAKG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrC;;;;;;OAMG;YACW,sBAAsB;IAiCpC,wEAAwE;IACxE,OAAO,CAAC,WAAW;IAMnB,kEAAkE;IAClE,OAAO,CAAC,WAAW;IASnB;;;;;;OAMG;IACG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhD;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IASnC;;;;OAIG;IACG,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE;QAAE,YAAY,EAAE,gBAAgB,CAAA;KAAE,GAClD,OAAO,CAAC;QACT;;;;;;WAMG;QACH,QAAQ,EAAE,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAAC;KAChE,CAAC;IAiBF;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,gBAAgB,CAAC,QAAQ,GAAE,OAAc,GAAG,OAAO,CAAC,IAAI,CAAC;YAiCjD,QAAQ;IAoDtB;;;;OAIG;YACW,kBAAkB;IAuFhC,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,KAAK;CAId;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,UAAS,kBAAuB,KAC/B,OAAO,CAAC,eAAe,CAAuC,CAAC"}
1
+ {"version":3,"file":"system-check.d.ts","sourceRoot":"","sources":["../../src/preflight/system-check.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,31 @@
1
- interface RecordingReadinessConfig {
1
+ export interface RecordingReadinessConfig {
2
2
  sessionId: string;
3
3
  ingestUrl: string;
4
4
  appId?: string;
5
5
  }
6
+ export interface RecordingDeliveryMeasurementConfig extends RecordingReadinessConfig {
7
+ /** Representative bytes produced by the enabled recorders in one workload window. */
8
+ payloadBytes: number;
9
+ /** Maximum acceptable upload completion time for the representative payload. */
10
+ completionWindowMs: number;
11
+ /** Number of scored uploads. Default: 3; bounded to 1..5. */
12
+ samples?: number;
13
+ /** Injectable monotonic clock for deterministic tests. */
14
+ now?: () => number;
15
+ }
16
+ export interface RecordingDeliveryMeasurement {
17
+ mode: "direct" | "segments" | "post";
18
+ payloadBytes: number;
19
+ completionWindowMs: number;
20
+ sampleDurationsMs: number[];
21
+ medianCompletionMs: number;
22
+ }
6
23
  /** Verify the server-selected recording path. No host-facing controls exist. */
7
24
  export declare function verifyRecordingDelivery(config: RecordingReadinessConfig): Promise<"direct" | "segments" | "post">;
8
- export {};
25
+ /**
26
+ * Measure whether the real recording destination can absorb the configured
27
+ * media workload. A disposable warm-up is excluded, then the median of the
28
+ * scored uploads is compared with the workload completion window.
29
+ */
30
+ export declare function measureRecordingDeliveryReadiness(config: RecordingDeliveryMeasurementConfig): Promise<RecordingDeliveryMeasurement>;
9
31
  //# sourceMappingURL=recording-delivery-readiness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recording-delivery-readiness.d.ts","sourceRoot":"","sources":["../src/recording-delivery-readiness.ts"],"names":[],"mappings":"AAKA,UAAU,wBAAwB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD,gFAAgF;AAChF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC,CAgDzC"}
1
+ {"version":3,"file":"recording-delivery-readiness.d.ts","sourceRoot":"","sources":["../src/recording-delivery-readiness.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kCAAmC,SAAQ,wBAAwB;IAClF,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AASD,gFAAgF;AAChF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC,CAgDzC;AAED;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,4BAA4B,CAAC,CA4EvC"}
package/dist/worker.js CHANGED
@@ -504,11 +504,14 @@ function indexedDbOpenFailureReason(err) {
504
504
  class DeliveryReadinessError extends Error {
505
505
  code;
506
506
  status;
507
+ /** Structured operator diagnostics. Never render this object in candidate UI. */
508
+ details;
507
509
  constructor(code, message, options) {
508
510
  super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
509
511
  this.name = "DeliveryReadinessError";
510
512
  this.code = code;
511
513
  this.status = options?.status;
514
+ this.details = options?.details;
512
515
  }
513
516
  }
514
517
  function newId(prefix) {
@@ -873,8 +876,8 @@ function sleep(ms, signal) {
873
876
  );
874
877
  });
875
878
  }
876
- const SDK_VERSION = "0.1.1";
877
- const SDK_BUILD_SHA = "de98da0125619b3b2d4486f45c10d4a8f7a85113";
879
+ const SDK_VERSION = "0.2.1";
880
+ const SDK_BUILD_SHA = "03bc2909b70164d21dfa7be02df4f4dc5bef8691";
878
881
  const SDK_BUILD_INFO = Object.freeze({
879
882
  sdkVersion: SDK_VERSION,
880
883
  buildSha: SDK_BUILD_SHA
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a4anthony/proctorkit-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Framework-agnostic browser SDK for the proctoring platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "idb": "^8.0.3",
29
29
  "rrweb": "2.1.1",
30
- "@a4anthony/proctorkit-types": "0.2.0"
30
+ "@a4anthony/proctorkit-types": "0.4.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@tailwindcss/vite": "^4.3.0",
@@ -10,15 +10,22 @@ export type { DeliveryReadinessErrorCode } from "@a4anthony/proctorkit-types";
10
10
  export class DeliveryReadinessError extends Error {
11
11
  readonly code: DeliveryReadinessErrorCode;
12
12
  readonly status: number | undefined;
13
+ /** Structured operator diagnostics. Never render this object in candidate UI. */
14
+ readonly details: Readonly<Record<string, string | number>> | undefined;
13
15
 
14
16
  constructor(
15
17
  code: DeliveryReadinessErrorCode,
16
18
  message: string,
17
- options?: { cause?: unknown; status?: number },
19
+ options?: {
20
+ cause?: unknown;
21
+ status?: number;
22
+ details?: Readonly<Record<string, string | number>>;
23
+ },
18
24
  ) {
19
25
  super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
20
26
  this.name = "DeliveryReadinessError";
21
27
  this.code = code;
22
28
  this.status = options?.status;
29
+ this.details = options?.details;
23
30
  }
24
31
  }
package/src/index.ts CHANGED
@@ -101,6 +101,12 @@ export type {
101
101
  WorkerToMainMessage,
102
102
  } from "@a4anthony/proctorkit-types";
103
103
  export { DeliveryReadinessError } from "./delivery-readiness.js";
104
+ export { measureRecordingDeliveryReadiness } from "./recording-delivery-readiness.js";
105
+ export type {
106
+ RecordingDeliveryMeasurement,
107
+ RecordingDeliveryMeasurementConfig,
108
+ RecordingReadinessConfig,
109
+ } from "./recording-delivery-readiness.js";
104
110
  export type { DomObserversConfig } from "./observers/dom-observers.js";
105
111
  export type {
106
112
  RequestScreenShareOptions,
@@ -3759,6 +3765,7 @@ export type {
3759
3765
  CheckOverrides,
3760
3766
  CheckRow,
3761
3767
  CheckState,
3768
+ ConnectionReadinessCheckResult,
3762
3769
  FailCode,
3763
3770
  MediaChecksConfig,
3764
3771
  PreflightReport,
@@ -66,8 +66,7 @@ export const RETRYABLE_FAIL_CODES: ReadonlySet<FailCode> = new Set([
66
66
  ]);
67
67
 
68
68
  /** True when a "Retry" could plausibly clear this failure without a reload. */
69
- export const isRetryableFailCode = (code: FailCode): boolean =>
70
- RETRYABLE_FAIL_CODES.has(code);
69
+ export const isRetryableFailCode = (code: FailCode): boolean => RETRYABLE_FAIL_CODES.has(code);
71
70
 
72
71
  /** Per-check state. Each row stores its own state plus any kind-specific payload. */
73
72
  export type CheckState =
@@ -107,6 +106,16 @@ export interface CheckRow {
107
106
  latencyMs?: number;
108
107
  /** Median variation between latency samples in milliseconds. Connection row only. */
109
108
  jitterMs?: number;
109
+ /** Server-selected recording upload mode verified by the readiness canary. */
110
+ recordingUploadMode?: "direct" | "segments" | "post";
111
+ /** Representative readiness payload size. Internal telemetry only. */
112
+ readinessPayloadBytes?: number;
113
+ /** Median representative upload completion time. Internal telemetry only. */
114
+ readinessMedianMs?: number;
115
+ /** Workload-derived completion window. Internal telemetry only. */
116
+ readinessWindowMs?: number;
117
+ /** Number of successful representative readiness samples. Internal telemetry only. */
118
+ readinessSampleCount?: number;
110
119
  /**
111
120
  * Plain-language quality bucket for a PASSING connection row (Fair / Good /
112
121
  * Excellent), relative to the configured floor. Absent on fail rows and on
@@ -133,13 +142,13 @@ export interface CheckOverrides {
133
142
  screenShareSupported?: boolean;
134
143
  }
135
144
 
136
- /** Minimum acceptable downlink speed in megabits/sec. */
145
+ /** Default reference floor for downlink quality telemetry in megabits/sec. */
137
146
  export const MIN_SPEED_MBPS = 2;
138
147
 
139
148
  /**
140
- * Plain-language quality bucket shown to the candidate INSTEAD of a raw Mbps
141
- * figure. Below the floor is not a band that's the `slow-connection` fail —
142
- * so a passing connection is always one of these three.
149
+ * Plain-language quality bucket for measured connection telemetry. A value
150
+ * below the reference floor has no band; measured Mbps never decides candidate
151
+ * eligibility.
143
152
  */
144
153
  export type ConnectionBand = "fair" | "good" | "excellent";
145
154
 
@@ -154,21 +163,11 @@ const BAND_GOOD_MULTIPLE = 1.5;
154
163
  const BAND_EXCELLENT_MULTIPLE = 3;
155
164
 
156
165
  /**
157
- * Bucket a measured speed into a {@link ConnectionBand} relative to the floor.
158
- * Returns null below the floor (the fail path owns that) or for a non-finite /
159
- * non-positive input or floor. Pure — the single source of truth for both the
160
- * application-path and legacy download-only rows.
166
+ * Bucket a measured speed into a {@link ConnectionBand} relative to the
167
+ * reference floor. Returns null below the floor or for invalid inputs.
161
168
  */
162
- export const connectionQualityBand = (
163
- mbps: number,
164
- floorMbps: number,
165
- ): ConnectionBand | null => {
166
- if (
167
- !Number.isFinite(mbps) ||
168
- !Number.isFinite(floorMbps) ||
169
- floorMbps <= 0 ||
170
- mbps < floorMbps
171
- ) {
169
+ export const connectionQualityBand = (mbps: number, floorMbps: number): ConnectionBand | null => {
170
+ if (!Number.isFinite(mbps) || !Number.isFinite(floorMbps) || floorMbps <= 0 || mbps < floorMbps) {
172
171
  return null;
173
172
  }
174
173
  if (mbps >= floorMbps * BAND_EXCELLENT_MULTIPLE) return "excellent";
@@ -228,9 +227,7 @@ export const checkBrowser = (overrides?: CheckOverrides): CheckRow => {
228
227
  // Safari is a separate beast (no getDisplayMedia, OS interruptions
229
228
  // mid-session) and stays as `unsupported-browser`.
230
229
  const code: FailCode =
231
- browser.name === "Chrome" ||
232
- browser.name === "Edge" ||
233
- browser.name === "Safari"
230
+ browser.name === "Chrome" || browser.name === "Edge" || browser.name === "Safari"
234
231
  ? "outdated-browser"
235
232
  : "unsupported-browser";
236
233
 
@@ -271,14 +268,12 @@ export const checkDevice = (
271
268
  const lowMem = overrides?.isLowMemory ?? isLowMemoryDevice();
272
269
  // Non-iOS mobile gate. iOS is already covered by `iosBlocked`
273
270
  // above, so we only check the remaining mobile case here.
274
- const mobileBlocked =
275
- !allowMobile && !iosBlocked && isMobileOrTablet() && !isIOS();
271
+ const mobileBlocked = !allowMobile && !iosBlocked && isMobileOrTablet() && !isIOS();
276
272
  const screenShareSupported =
277
273
  overrides?.screenShareSupported ??
278
274
  (typeof navigator !== "undefined" &&
279
275
  typeof navigator.mediaDevices?.getDisplayMedia === "function");
280
- const requiredScreenShareUnavailable =
281
- requireScreenShareSupport && !screenShareSupported;
276
+ const requiredScreenShareUnavailable = requireScreenShareSupport && !screenShareSupported;
282
277
 
283
278
  if (iosBlocked) {
284
279
  return {
@@ -464,10 +459,7 @@ const measureLatency = (): Promise<number | null> =>
464
459
  ping();
465
460
  });
466
461
 
467
- const downloadTest = (
468
- bytes: number,
469
- timeoutMs: number,
470
- ): Promise<number | null> =>
462
+ const downloadTest = (bytes: number, timeoutMs: number): Promise<number | null> =>
471
463
  new Promise((resolve) => {
472
464
  const xhr = new XMLHttpRequest();
473
465
  const url = `${CLOUDFLARE_SPEED_URL}?bytes=${bytes}&_=${Date.now()}${Math.random()}`;
@@ -551,11 +543,9 @@ export const checkConnection = (overrides?: CheckOverrides): CheckRow | null =>
551
543
  };
552
544
 
553
545
  /**
554
- * Build the connection row from a measured speed (or null on
555
- * measurement failure). The `minMbps` threshold defaults to the
556
- * module-level `MIN_SPEED_MBPS` so existing call sites work
557
- * unchanged; the engine threads the policy override through when
558
- * present.
546
+ * Build the connection row from a measured speed (or null on measurement
547
+ * failure). Numeric speed is retained as telemetry, but never gates the
548
+ * candidate because short browser probes are dominated by request latency.
559
549
  */
560
550
  export const applySpeedToResult = (
561
551
  speed: number | null,
@@ -571,17 +561,6 @@ export const applySpeedToResult = (
571
561
  },
572
562
  };
573
563
  }
574
- if (speed < minMbps) {
575
- return {
576
- kind: "connection",
577
- state: {
578
- kind: "fail",
579
- code: "slow-connection",
580
- detail: `${speed} Mbps`,
581
- },
582
- mbps: speed,
583
- };
584
- }
585
564
  const band = connectionQualityBand(speed, minMbps);
586
565
  return {
587
566
  kind: "connection",
@@ -592,10 +571,9 @@ export const applySpeedToResult = (
592
571
  };
593
572
 
594
573
  /**
595
- * Apply policy to the richer application-path result. Upload is evaluated
596
- * first because recordings must leave the browser faster than MediaRecorder
597
- * produces them. The existing `slow-connection` code remains the stable
598
- * public contract; direction-specific values are carried on the row.
574
+ * Preserve application-path measurements as internal diagnostics. A completed
575
+ * measurement passes regardless of the estimated Mbps; candidate eligibility
576
+ * is decided separately by the workload-aware recording-storage canary.
599
577
  */
600
578
  export const applyConnectionQualityToResult = (
601
579
  result: ConnectionQualityResult,
@@ -622,42 +600,16 @@ export const applyConnectionQualityToResult = (
622
600
  ...fields,
623
601
  };
624
602
  }
625
- if (upload < minUploadMbps) {
626
- return {
627
- kind: "connection",
628
- state: {
629
- kind: "fail",
630
- code: "slow-connection",
631
- detail: `Upload ${result.uploadMbps ?? upload} Mbps`,
632
- },
633
- ...fields,
634
- };
635
- }
636
- if (download < minDownloadMbps) {
637
- return {
638
- kind: "connection",
639
- state: {
640
- kind: "fail",
641
- code: "slow-connection",
642
- detail: `Download ${result.downloadMbps ?? download} Mbps`,
643
- },
644
- ...fields,
645
- };
646
- }
647
- // Band the WORST of the two directions off the conservative p25 (the same
648
- // value the gate above used), so the label reflects the binding constraint
649
- // and can never sit above the pass/fail line.
650
- const band = worseBand(
651
- connectionQualityBand(upload, minUploadMbps),
652
- connectionQualityBand(download, minDownloadMbps),
653
- );
603
+ // Band the worst direction only when both values meet the configured
604
+ // reference floors. The band is display telemetry, never a gate.
605
+ const uploadBand = connectionQualityBand(upload, minUploadMbps);
606
+ const downloadBand = connectionQualityBand(download, minDownloadMbps);
607
+ const band = uploadBand && downloadBand ? worseBand(uploadBand, downloadBand) : null;
654
608
  return {
655
609
  kind: "connection",
656
610
  state: {
657
611
  kind: "pass",
658
- detail: band
659
- ? BAND_LABELS[band]
660
- : `Up ${result.uploadMbps} · Down ${result.downloadMbps} Mbps`,
612
+ detail: band ? BAND_LABELS[band] : "Connection available",
661
613
  },
662
614
  ...fields,
663
615
  ...(band ? { band } : {}),
@@ -676,10 +628,7 @@ const BAND_RANK: Record<ConnectionBand, number> = {
676
628
  * this branch — the gate already failed the row — so treat null as "no
677
629
  * opinion" and fall back to the other direction.
678
630
  */
679
- function worseBand(
680
- a: ConnectionBand | null,
681
- b: ConnectionBand | null,
682
- ): ConnectionBand | null {
631
+ function worseBand(a: ConnectionBand | null, b: ConnectionBand | null): ConnectionBand | null {
683
632
  if (a === null) return b;
684
633
  if (b === null) return a;
685
634
  return BAND_RANK[a] <= BAND_RANK[b] ? a : b;