@observertc/observer-js 1.0.0-beta.15 → 1.0.0-beta.17

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.
package/dist/index.d.mts CHANGED
@@ -2376,8 +2376,117 @@ type OperationSystem = {
2376
2376
  };
2377
2377
 
2378
2378
  /**
2379
- * A finding raised **by the observer** — `observedCall.addIssue()` / `observer.addIssue()`, surfaced
2380
- * on the bus as `call-issue` / `observer-issue`.
2379
+ * Turning a correlation into a **conclusion**.
2380
+ *
2381
+ * Every detector in this library ultimately reports the same shape of observation: *N clients have
2382
+ * issue X open at once, and here is what they have in common*. That is useful but not yet
2383
+ * actionable — someone still has to know that congestion spread across unrelated calls means the
2384
+ * server, while CPU limitation spread across unrelated calls means a bad client release. This module
2385
+ * holds that interpretation step so it is stated once, consistently, instead of being re-derived by
2386
+ * whoever reads the alert at 3am.
2387
+ *
2388
+ * ### Two functions, because there are two questions
2389
+ *
2390
+ * A detector already knows its scope — it was constructed with an `ObservedCall` or with the
2391
+ * `Observer`. Handing that scope back to a single generic function meant every caller supplied
2392
+ * fields the other scope needed and its own scope ignored: a call-scoped detector passing
2393
+ * `affectedCalls: 1, totalCalls: 1` forever, an observer-scoped one passing a participant ratio that
2394
+ * was deliberately never read. Placeholders like that are a standing invitation to read them as if
2395
+ * they meant something.
2396
+ *
2397
+ * So there are two entry points, each taking only the facts its scope actually has:
2398
+ *
2399
+ * - {@link concludeCallIssue} — within one call. The axis is *how much of the meeting*, and whether
2400
+ * the affected clients all subscribe to one published track.
2401
+ * - {@link concludeObserverIssue} — across calls. The axis is *how many independent calls*, which is
2402
+ * the only thing that separates "one bad room" from "our infrastructure".
2403
+ *
2404
+ * Neither the issue family nor the spread concludes anything alone: congestion in one call is a
2405
+ * meeting problem, congestion in six calls is an infrastructure problem, and the issue type is
2406
+ * identical in both.
2407
+ */
2408
+ /** Where the fault most likely sits, given who is affected. */
2409
+ type IssueFaultDomain =
2410
+ /** Independent calls affected at once — they share only the servers and the network. */
2411
+ 'infrastructure'
2412
+ /** One call, broadly affected — something that call shares (its SFU worker, room, or host). */
2413
+ | 'call'
2414
+ /** The subscribers of one published track — the publisher's path or the forwarding of it. */
2415
+ | 'published-track'
2416
+ /** A single endpoint — its own device or last mile. */
2417
+ | 'endpoint'
2418
+ /** Independent calls affected, but by something endpoints own — a client build, not a server. */
2419
+ | 'client-population'
2420
+ /** Not enough signal to attribute. */
2421
+ | 'unknown';
2422
+ /** A stated verdict, attached to the raised issue payload. */
2423
+ type IssueConclusion = {
2424
+ /** Where to look. */
2425
+ faultDomain: IssueFaultDomain;
2426
+ /** One line, written to be readable in an alert without opening a dashboard. */
2427
+ summary: string;
2428
+ /** What to check first. Omitted when the issue family is unknown to this module. */
2429
+ recommendation?: string;
2430
+ /**
2431
+ * How much the spread alone justifies the verdict, `0..1`. Not a probability — a coarse ranking
2432
+ * so alerting can threshold on it. More independent calls, or a tighter onset, means higher.
2433
+ */
2434
+ confidence: number;
2435
+ };
2436
+ /** The facts a **call-scoped** conclusion is drawn from. */
2437
+ type CallIssueSpread = {
2438
+ issueType: string;
2439
+ /** Distinct clients of this call with the issue open. */
2440
+ affectedClients: number;
2441
+ /** Participants in the call — the denominator. */
2442
+ totalClients: number;
2443
+ /** True when the onsets clustered — a shared trigger rather than drift. */
2444
+ onsetBurst: boolean;
2445
+ /**
2446
+ * Set when the affected clients are the subscriber set of **one published track**.
2447
+ *
2448
+ * The strongest call-scoped statement available: those clients share a publisher and nothing
2449
+ * else, so the receivers are exonerated and the source's path is implicated.
2450
+ */
2451
+ publishedTrackId?: string;
2452
+ };
2453
+ /** The facts an **observer-scoped** conclusion is drawn from. */
2454
+ type ObserverIssueSpread = {
2455
+ issueType: string;
2456
+ /** Distinct clients across the fleet with the issue open. */
2457
+ affectedClients: number;
2458
+ /** Clients in the fleet. Reported for context; it does not gate anything at this scope. */
2459
+ totalClients: number;
2460
+ /** Distinct calls containing at least one affected client. The dimension that matters here. */
2461
+ affectedCalls: number;
2462
+ /** Calls in flight. */
2463
+ totalCalls: number;
2464
+ /** True when the onsets clustered. */
2465
+ onsetBurst: boolean;
2466
+ };
2467
+ /**
2468
+ * Draw the conclusion for a group of clients **within one call**.
2469
+ *
2470
+ * Ordered most-to-least specific: a track-scoped group is a stronger statement than a call-wide one,
2471
+ * and a single affected endpoint is not a statement about the call at all.
2472
+ */
2473
+ declare function concludeCallIssue(spread: CallIssueSpread): IssueConclusion;
2474
+ /**
2475
+ * Draw the conclusion for a group of clients spanning **several calls**.
2476
+ *
2477
+ * One affected call is not an observer-scoped finding — it has an obvious local explanation and the
2478
+ * call-scoped detector has already reported it — so that case returns `call` and says so rather than
2479
+ * dressing it up as a fleet event.
2480
+ *
2481
+ * Which domain breadth implicates depends on the family, and this is the whole reason the module
2482
+ * exists: `congestion` across unrelated calls points at the servers, `cpulimitation` across unrelated
2483
+ * calls points at what those *endpoints* share — a client release, a browser version, shared
2484
+ * virtualised hardware — and pointing an SFU team at the second one wastes a night.
2485
+ */
2486
+ declare function concludeObserverIssue(spread: ObserverIssueSpread): IssueConclusion;
2487
+
2488
+ /**
2489
+ * What every server-raised finding carries, whatever raised it.
2381
2490
  *
2382
2491
  * ### Why this is not `ClientIssue`
2383
2492
  *
@@ -2385,38 +2494,209 @@ type OperationSystem = {
2385
2494
  * string. Server-raised findings were reusing it, which forced every detector to `JSON.stringify` a
2386
2495
  * perfectly good object on the way out and every handler to `JSON.parse` it back on the way in —
2387
2496
  * paying serialisation on a path where nothing is ever serialised, and losing type information in
2388
- * both directions.
2389
- *
2390
- * An observer issue goes straight to an in-process event handler, so it carries the object.
2497
+ * both directions. These go straight to an in-process handler, so they carry the object.
2391
2498
  */
2392
- type ObserverIssue = {
2499
+ type IssueBase = {
2393
2500
  /** What was found, e.g. `'CROSS_CALL_ISSUE_ONSET_BURST'`. */
2394
2501
  type: string;
2395
2502
  /** Observer clock, when the finding was raised. */
2396
2503
  timestamp: number;
2397
2504
  /**
2398
- * The evidence behind the finding.
2505
+ * What the finding *means* — where to look, and how much the evidence justifies it.
2399
2506
  *
2400
- * Prefer an object — that is the point of this type. `string` is still accepted so an application
2401
- * can forward a payload it already has serialised (e.g. relaying a `ClientIssue`) without a
2402
- * pointless parse-then-restringify round trip. Use {@link issuePayloadOf} to read either form.
2507
+ * A first-class field rather than a key inside {@link payload}, because it is the one part every
2508
+ * finding has in common and the one part an alerting rule reads. Burying it in the evidence made
2509
+ * `payload.conclusion.faultDomain` the path to the most important thing in the object.
2510
+ */
2511
+ conclusion?: IssueConclusion;
2512
+ /**
2513
+ * The evidence, and **only** the evidence.
2514
+ *
2515
+ * Deliberately does not repeat `type`, `scope`, or the ids already present on the event that
2516
+ * delivers it. A payload that restates its envelope invites the two to disagree — and they did,
2517
+ * because nothing kept them in step.
2518
+ */
2519
+ payload?: Record<string, unknown>;
2520
+ };
2521
+ /**
2522
+ * A finding about **one call**, raised by `observedCall.addIssue()` and delivered as `call-issue`.
2523
+ *
2524
+ * The call is the event's scope (`{ observedCall, observer }`), so the payload does not carry a
2525
+ * `callId` — read it from the event.
2526
+ */
2527
+ type CallIssue = IssueBase & {
2528
+ scope: 'call';
2529
+ };
2530
+ /**
2531
+ * A finding about the **fleet**, raised by `observer.addIssue()` and delivered as `observer-issue`.
2532
+ *
2533
+ * Raised by detectors and validators that reason across calls, so no single call owns it. Where the
2534
+ * finding does concern specific calls — a cross-call correlation, say — they are named in the
2535
+ * evidence, because that *is* the evidence.
2536
+ */
2537
+ type ObserverIssue = IssueBase & {
2538
+ scope: 'observer';
2539
+ };
2540
+ /**
2541
+ * Either kind, discriminated by {@link IssueBase} + `scope`.
2542
+ *
2543
+ * `scope` is on the issue and not merely implied by which event fired, so a finding stays
2544
+ * self-describing once it leaves the bus — funnelled into one handler, a log line, or a queue.
2545
+ */
2546
+ type Issue = CallIssue | ObserverIssue;
2547
+ /**
2548
+ * The payload as a JSON string, for the boundaries that genuinely need text — a log line, an HTTP
2549
+ * body, a message queue.
2550
+ *
2551
+ * Returns `undefined` for a missing payload, or for one that cannot be serialised (a circular
2552
+ * reference from something an application attached): the caller wanted text, not an exception.
2553
+ */
2554
+ declare function issuePayloadAsString(issue: Pick<IssueBase, 'payload'>): string | undefined;
2555
+
2556
+ /**
2557
+ * The bus events that carry an `observedCall`, i.e. the ones an enricher can attribute to a summary.
2558
+ *
2559
+ * Derived from the event map rather than listed by hand, so it cannot drift: adding a call-scoped
2560
+ * event makes it enrichable automatically, and an enricher on an observer-scoped event
2561
+ * (`observer-issue`, `validation-ready`) will not compile — there is no single call it belongs to,
2562
+ * and quietly writing a fleet-wide fact into every open summary would be worse than a type error.
2563
+ */
2564
+ type CallScopedEventName = {
2565
+ [K in keyof ObserverEvents]: ObserverEvents[K][0] extends ObservedCallScope ? K : never;
2566
+ }[keyof ObserverEvents];
2567
+ /** A function that folds one event into the summary. Runs on every occurrence, for its own call. */
2568
+ type CallSummaryEnricher<K extends CallScopedEventName> = (summary: CallSummary, ...args: ObserverEvents[K]) => void;
2569
+ /** The enricher map: any subset of the call-scoped events, each fully typed against its payload. */
2570
+ type CallSummaryEnrichers = {
2571
+ [K in CallScopedEventName]?: CallSummaryEnricher<K>;
2572
+ };
2573
+ /** The built-in sections. Ask for what you want; anything absent is simply not collected. */
2574
+ type CallSummarySection = 'clients' | 'issues' | 'turnServers' | 'scores';
2575
+ type CallSummaryConfig = {
2576
+ /**
2577
+ * Which built-in sections to accumulate. Empty (the default) collects none of them — a summary
2578
+ * with only `enrich` is a perfectly good summary.
2403
2579
  */
2404
- payload?: string | Record<string, unknown>;
2580
+ include: CallSummarySection[];
2581
+ /** Fold arbitrary state in from any call-scoped event. See {@link CallSummaryEnrichers}. */
2582
+ enrich?: CallSummaryEnrichers;
2583
+ /** Cap on the retained issue log. Default `500`. See the note on truncation. */
2584
+ maxIssues: number;
2585
+ /** Cap on retained client ids. Default `10_000`. */
2586
+ maxClientIds: number;
2405
2587
  };
2406
2588
  /**
2407
- * The payload of an issue as an object, parsing it only if it happens to be a string.
2589
+ * Who was in the call over its whole life — not just who is in it now.
2408
2590
  *
2409
- * Handlers shouldn't have to care which form arrived. Returns `undefined` for a missing payload or a
2410
- * string that isn't valid JSON — reading evidence must never throw inside an issue handler.
2591
+ * Deliberately identifiers and counts only. Anything *about* a client — browser, platform, region —
2592
+ * is already on `observedClient` while the call is live, and belongs in `attachments` via an enricher
2593
+ * if you want it kept. Duplicating it here would mean the library deciding which client attributes
2594
+ * matter, and it would mean reading the highest-frequency event on the bus to do it.
2411
2595
  */
2412
- declare function issuePayloadOf(issue: Pick<ObserverIssue, 'payload'>): Record<string, unknown> | undefined;
2596
+ type CallSummaryClients = {
2597
+ /** Every client id seen, in join order, capped by `maxClientIds`. */
2598
+ clientIds: string[];
2599
+ /** The most participants present at any one moment. */
2600
+ peak: number;
2601
+ joined: number;
2602
+ left: number;
2603
+ };
2604
+ /** Which TURN relays carried this call's media. */
2605
+ type CallSummaryTurnServers = {
2606
+ serverUrls: string[];
2607
+ /** Distinct clients seen relaying through any of them. */
2608
+ clientsRelayed: number;
2609
+ };
2610
+ /** The call score over time. Percentiles, not a mean — see `utils/stats`. */
2611
+ type CallSummaryScores = {
2612
+ min?: number;
2613
+ max?: number;
2614
+ median?: number;
2615
+ /** How many score readings went into the above. `0` means nothing was measured. */
2616
+ samples: number;
2617
+ };
2618
+ /** What had to be dropped to stay within the caps. Absent when nothing was. */
2619
+ type CallSummaryTruncation = {
2620
+ issues?: number;
2621
+ clientIds?: number;
2622
+ };
2413
2623
  /**
2414
- * The payload as a JSON string, serialising it only if it is an object.
2624
+ * An accumulating record of one call's life, finalised when the call closes.
2625
+ *
2626
+ * ### Why this exists
2415
2627
  *
2416
- * For the boundaries that genuinely need text — a log line, an HTTP body, a message queue. Keep it at
2417
- * the edge rather than in the detector, so in-process handlers never pay for it.
2628
+ * Everything else in this library is about *now*. Detectors answer "is something wrong right now",
2629
+ * validators answer a structural question once, and both read state that the call throws away when
2630
+ * it ends. Nothing kept the answer to *"what happened in that meeting?"* — who was in it, what was
2631
+ * raised, how it scored — and that is the question asked after the call, by support, by billing, by
2632
+ * whoever is writing the incident note.
2633
+ *
2634
+ * ### It is opt-in, and its sections are opt-in
2635
+ *
2636
+ * `observedCall.summary` is `undefined` unless a summary was configured, and each section is present
2637
+ * only if it was requested. **An absent section means "not collected", never "nothing happened"** —
2638
+ * the same rule as `inconclusive` on a validator. Reading `summary.issues` as "this call had no
2639
+ * issues" when `'issues'` was never in `include` is the one misreading this type invites, so it does
2640
+ * not offer a default-empty section to make it easy.
2641
+ *
2642
+ * ### Read it live, receive it once
2643
+ *
2644
+ * The object is live: read `observedCall.summary` at any point during the call. It is also delivered
2645
+ * on the `call-summary` event, emitted inside `close()` while the call is still reachable — after
2646
+ * that the call is gone from `observer.observedCalls` and there is nothing left to ask.
2418
2647
  */
2419
- declare function issuePayloadAsString(issue: Pick<ObserverIssue, 'payload'>): string | undefined;
2648
+ type CallSummary = {
2649
+ callId: string;
2650
+ /** First client join (client clock), as `ObservedCall` computed it. */
2651
+ startedAt?: number;
2652
+ /** Last client leave. */
2653
+ endedAt?: number;
2654
+ /** `endedAt - startedAt`, when both are known. */
2655
+ durationInMs?: number;
2656
+ /** When the summary itself was finalised (observer clock). Set by `close()`. */
2657
+ closedAt?: number;
2658
+ clients?: CallSummaryClients;
2659
+ /**
2660
+ * Every issue raised against this call, in the order they were raised, capped by `maxIssues`.
2661
+ *
2662
+ * Just the issues — no derived tallies. A count is `issues.length`, a per-type count is one
2663
+ * `filter`, and either is cheaper to write at the call site than to keep correct here. The one
2664
+ * thing you cannot derive is what the cap discarded, which is why `truncated.issues` exists:
2665
+ * the issues actually raised is `issues.length + (truncated?.issues ?? 0)`.
2666
+ */
2667
+ issues?: CallIssue[];
2668
+ turnServers?: CallSummaryTurnServers;
2669
+ scores?: CallSummaryScores;
2670
+ /**
2671
+ * Whatever your enrichers put here. The library never writes to it, so it cannot collide with a
2672
+ * section added in a future version.
2673
+ *
2674
+ * **`attachments`, not `appData`, and the distinction is load-bearing.** `appData` is the live
2675
+ * working state an application hangs off an entity for the entity's lifetime, and it may hold
2676
+ * references that cannot be serialised — a mediasoup router, an `RTCPeerConnection`, a socket. A
2677
+ * summary is the opposite: it outlives the call precisely so it can be *shipped* — archived,
2678
+ * queued, written to a column — and it is handed to you on `call-summary` at the moment the call
2679
+ * it came from is being torn down. Anything unserialisable in it is a reference to something
2680
+ * already gone.
2681
+ *
2682
+ * So put serialisable facts here, the same contract as `attachments` on a `ClientSample`. If you
2683
+ * need the live object, read it off `observedCall` / `observedClient` inside the enricher and
2684
+ * attach what you can serialise: the router's `id`, not the router.
2685
+ */
2686
+ attachments: Record<string, unknown>;
2687
+ /**
2688
+ * What the caps discarded, and how much.
2689
+ *
2690
+ * Present **only** when something was actually dropped. A silently truncated summary is worse
2691
+ * than no summary — someone will count `log.length` and report it as the issue count — so the
2692
+ * shortfall is stated rather than left to be inferred from a suspiciously round number.
2693
+ */
2694
+ truncated?: CallSummaryTruncation;
2695
+ };
2696
+ /** The defaults a summary is created with. Caps are generous but finite; see {@link CallSummary}. */
2697
+ declare const defaultCallSummaryConfig: CallSummaryConfig;
2698
+ /** A fresh summary for `callId`, with only the requested sections present. */
2699
+ declare function createCallSummary(callId: string, config: CallSummaryConfig): CallSummary;
2420
2700
 
2421
2701
  /**
2422
2702
  * What a validator concluded, once it is done.
@@ -2468,8 +2748,13 @@ interface Validator<S extends Record<string, unknown> = Record<string, unknown>>
2468
2748
  onDone: (report: ValidationReport<S>) => void;
2469
2749
  /** Gather evidence; decide if there is now enough. Called on every `observer.update()`. */
2470
2750
  update(): void;
2471
- /** Give up without a verdict. Finishes with `inconclusive`, so a caller waiting on it is freed. */
2472
- cancel: () => void;
2751
+ /**
2752
+ * Give up without a verdict. Finishes with `inconclusive`, so a caller waiting on it is freed.
2753
+ *
2754
+ * `reason` is carried into the report. Worth passing something specific — "cancelled" tells the
2755
+ * reader nothing, whereas "sfu redeployed" explains why a check that was running has no verdict.
2756
+ */
2757
+ cancel: (reason?: string) => void;
2473
2758
  }
2474
2759
  /**
2475
2760
  * The part of a validator the observer needs in order to drive it.
@@ -2959,7 +3244,15 @@ type ObserverEvents = {
2959
3244
  'call-empty': [ObservedCallScope];
2960
3245
  'call-not-empty': [ObservedCallScope];
2961
3246
  'call-issue': [ObservedCallScope & {
2962
- issue: ObserverIssue;
3247
+ issue: CallIssue;
3248
+ }];
3249
+ /**
3250
+ * A call's summary was finalised. Emitted from inside `close()`, while the call is still in
3251
+ * `observer.observedCalls` — after that there is nothing left to ask. Only fires for calls that
3252
+ * had a summary configured.
3253
+ */
3254
+ 'call-summary': [ObservedCallScope & {
3255
+ summary: CallSummary;
2963
3256
  }];
2964
3257
  'client-added': [ObservedClientScope];
2965
3258
  'client-sink-created': [ObservedClientScope & {
@@ -4412,11 +4705,48 @@ type AvailableDetectorsConfigs = AvailableObserverScopeDetectorsConfigs | Availa
4412
4705
  declare class Detectors {
4413
4706
  private _detectors;
4414
4707
  constructor(...detectors: Detector[]);
4708
+ /**
4709
+ * Every registered detector, in registration order.
4710
+ *
4711
+ * This is **the** way to get hold of an instance: `addDetector` / `addObserverDetector` are
4712
+ * chainable and return the owning entity, so the registry is where instances live. Read it to
4713
+ * inspect a detector's state, or to pick one out and {@link remove} it.
4714
+ *
4715
+ * A copy, not the live array — a caller iterating this while removing would otherwise skip
4716
+ * entries, and that is exactly what "remove the ones that look like X" does.
4717
+ */
4718
+ get instances(): Detector[];
4719
+ /** Iterate the registry directly: `for (const detector of call.detectors)`. */
4720
+ [Symbol.iterator](): IterableIterator<Detector>;
4721
+ /** The names in registration order. Duplicates are meaningful — see {@link getAll}. */
4415
4722
  get listOfNames(): string[];
4416
4723
  get size(): number;
4417
4724
  add(detector: Detector): void;
4725
+ /** The first detector registered under `name`. See {@link getAll} when several can share one. */
4418
4726
  get(name: string): Detector | undefined;
4419
- remove(detector: Detector): void;
4727
+ /**
4728
+ * Every detector registered under `name`.
4729
+ *
4730
+ * More than one is legitimate: `ClientPopulationIssueDetector` is meant to be added once per
4731
+ * `groupBy` axis, and two instances of it share a name.
4732
+ */
4733
+ getAll(name: string): Detector[];
4734
+ has(name: string): boolean;
4735
+ /** Remove one specific instance. Returns `false` if it was not registered here. */
4736
+ remove(detector: Detector): boolean;
4737
+ /**
4738
+ * Remove **every** detector registered under `name`, returning how many were removed.
4739
+ *
4740
+ * All of them rather than the first, because a name can legitimately be registered more than once
4741
+ * (see {@link getAll}) and "remove the `client-population-issue-detector`" cannot sensibly mean
4742
+ * "remove whichever axis happens to be first in the array". Removing all of them is the only
4743
+ * behaviour that leaves the registry in a state the caller can predict from the name alone.
4744
+ *
4745
+ * Each removed detector gets `close()`, so trackers unsubscribe from the issue registry, bus
4746
+ * listeners drop, and timers clear — a detector removed without closing keeps being fed issues
4747
+ * forever.
4748
+ */
4749
+ removeByName(name: string): number;
4420
4750
  update(): void;
4421
4751
  clear(): void;
4422
4752
  private _close;
@@ -4533,6 +4863,14 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
4533
4863
  readonly clientsUsedTurn: Set<string>;
4534
4864
  readonly calculatedScore: CalculatedScore;
4535
4865
  remoteTrackResolver?: RemoteTrackResolver;
4866
+ /**
4867
+ * The accumulating record of this call's life, or `undefined` when no summary was configured.
4868
+ *
4869
+ * Live — read it at any point during the call. It is also delivered once on `call-summary` when
4870
+ * the call closes. See `CallSummary`: an absent section means "not collected", never "nothing
4871
+ * happened".
4872
+ */
4873
+ summary?: CallSummary;
4536
4874
  /**
4537
4875
  * Published tracks that currently have **no** subscriber linked to them.
4538
4876
  *
@@ -4565,14 +4903,53 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
4565
4903
  constructor(settings: ObservedCallSettings<AppData>, observer: Observer, activeIssuesRegistry: ActiveIssuesRegistry);
4566
4904
  get numberOfClients(): number;
4567
4905
  get score(): number | undefined;
4906
+ /**
4907
+ * Build a call-scoped detector onto this call. Chainable.
4908
+ *
4909
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
4910
+ * it back off the registry: `call.detectors.getAll(name)`, or `call.detectors.instances`.
4911
+ */
4568
4912
  addDetector<K extends keyof AvailableCallScopeDetectorsConfigs>(name: K, config?: Partial<AvailableCallScopeDetectorsConfigs[K]>): this;
4913
+ /**
4914
+ * Start accumulating this call's summary, if the observer was configured for summaries.
4915
+ *
4916
+ * Called by `createObservedCall`; you should not need it. It takes no configuration of its own on
4917
+ * purpose: the collector subscribes to exactly the events the observer's `include` requires, so a
4918
+ * per-call section outside that set would be created and then never written to — an empty section
4919
+ * that reads as "nothing happened". One shape per observer is the only shape that can be filled.
4920
+ *
4921
+ * The collector builds it rather than this method, so the resolved configuration never has to
4922
+ * leave the one object that owns it. Returns `undefined` when summaries are off, and is
4923
+ * idempotent: an existing summary is kept, not restarted.
4924
+ */
4925
+ enableSummary(): CallSummary | undefined;
4926
+ /**
4927
+ * Remove a detector from **this call** by name, returning how many were removed.
4928
+ *
4929
+ * **Every** instance under the name goes — a name can legitimately be registered more than once.
4930
+ * When you want one of them specifically, go through the registry, which deals in instances:
4931
+ *
4932
+ * ```ts
4933
+ * const [ first ] = call.detectors.getAll('issue-fan-out-detector');
4934
+ *
4935
+ * call.detectors.remove(first);
4936
+ * ```
4937
+ *
4938
+ * Either route `close()`s the detector, so it unsubscribes from `activeIssuesRegistry` — without
4939
+ * that the registry keeps feeding a detector nobody is running any more, and its tracked set grows
4940
+ * for the life of the call.
4941
+ *
4942
+ * To stop building it on *future* calls too, use `observer.removeCallDetector(name)`.
4943
+ */
4944
+ removeDetector(name: keyof AvailableCallScopeDetectorsConfigs): number;
4569
4945
  /**
4570
4946
  * Raise a call-level (server-side) finding; surfaced on the Observer bus as `call-issue`.
4571
4947
  *
4572
- * `payload` takes an **object** — it is delivered to in-process handlers, so there is nothing to
4573
- * serialise for. Pass a string only if you already have one.
4948
+ * `payload` is an **object** and holds evidence only — it is delivered to an in-process handler,
4949
+ * so there is nothing to serialise for. `scope` is stamped here, and the `callId` is already on
4950
+ * the event, so neither belongs in the payload. Put the interpretation in `conclusion`.
4574
4951
  */
4575
- addIssue(issue: ObserverIssue): void;
4952
+ addIssue(issue: Omit<CallIssue, 'scope'>): void;
4576
4953
  close(): void;
4577
4954
  getObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(clientId: string): ObservedClient<ClientAppData> | undefined;
4578
4955
  createObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedClientSettings<ClientAppData>): ObservedClient<ClientAppData> | undefined;
@@ -4973,6 +5350,62 @@ type AvailableValidatorConfigs = {
4973
5350
  /** A validator name that can be started. */
4974
5351
  type ValidatorName = keyof AvailableValidatorConfigs;
4975
5352
 
5353
+ /**
5354
+ * Keeps every configured `CallSummary` up to date, from **observer-level** bus subscriptions.
5355
+ *
5356
+ * ### Why one collector and not one per call
5357
+ *
5358
+ * The obvious implementation subscribes each call's summary to the events it needs. But the bus is
5359
+ * observer-wide: a listener attached for call A is invoked for every event of every call, so that
5360
+ * design costs `calls × events` listeners *and* `calls` invocations per event — quadratic in the
5361
+ * thing most likely to be large. At 500 concurrent calls and eight subscribed events that is 4 000
5362
+ * listeners doing 500 no-op calls each, per event.
5363
+ *
5364
+ * So the collector attaches **one listener per event type, once**, and routes each event to the
5365
+ * summary of the call it names. Cost is O(subscribed event types), independent of how many calls are
5366
+ * in flight, and an event for a call with no summary costs one `undefined` check.
5367
+ *
5368
+ * ### Only call-scoped events
5369
+ *
5370
+ * Routing needs `observedCall` on the payload, which is exactly what `CallScopedEventName` selects.
5371
+ * Observer-scoped events have no single call to attribute to; see that type for why fanning them out
5372
+ * to every open summary would be worse than refusing.
5373
+ */
5374
+ declare class CallSummaryCollector {
5375
+ private readonly _observer;
5376
+ private readonly _config;
5377
+ private readonly _scratch;
5378
+ private readonly _listeners;
5379
+ private _closed;
5380
+ constructor(_observer: Observer, _config: CallSummaryConfig);
5381
+ /**
5382
+ * Build a summary for `callId` and start tracking it.
5383
+ *
5384
+ * Creating it here, rather than letting the call create one and hand it over, keeps the resolved
5385
+ * configuration inside the single object that owns it — and makes it impossible to end up with a
5386
+ * summary whose sections nobody subscribed to fill.
5387
+ */
5388
+ createSummary(callId: string): CallSummary;
5389
+ /**
5390
+ * Finalise `call`'s summary: fold in what only makes sense once, and stamp the closing times.
5391
+ *
5392
+ * Percentiles are computed here rather than on every update — a median recomputed per tick over a
5393
+ * growing array is quadratic work to produce a number nobody reads until the end.
5394
+ */
5395
+ finalise(call: ObservedCall): void;
5396
+ /** Drop every bus subscription. Called when the observer closes. */
5397
+ close(): void;
5398
+ /**
5399
+ * Subscribe `listener` to `event`, routed to the summary of the call the event names.
5400
+ *
5401
+ * The `observedCall` is read off the payload rather than closed over, which is what lets one
5402
+ * subscription serve every call.
5403
+ */
5404
+ private _on;
5405
+ private _subscribeBuiltIns;
5406
+ private _subscribeEnrichers;
5407
+ }
5408
+
4976
5409
  type SampleRejectedReason = 'observer-closed' | 'missing-callId' | 'missing-clientId';
4977
5410
 
4978
5411
  /**
@@ -5016,6 +5449,36 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
5016
5449
  * yourself, and note that observer-scoped detectors and validators run *nowhere else*.
5017
5450
  */
5018
5451
  autoUpdateOnCallUpdate?: boolean;
5452
+ /**
5453
+ * Accumulate a {@link CallSummary} on every call this observer creates.
5454
+ *
5455
+ * **Absent or `null` means no summaries at all** — no accumulation, and not one bus subscription.
5456
+ * Pass an object (`{}` is valid) to switch it on; anything you leave out takes its default from
5457
+ * `defaultCallSummaryConfig`, including `include: []`, which collects *no* built-in section. A
5458
+ * summary that only runs `enrich` is a perfectly good summary.
5459
+ *
5460
+ * ```ts
5461
+ * const observer = new Observer({
5462
+ * callSummary: {
5463
+ * include: [ 'clients', 'issues' ],
5464
+ * enrich: {
5465
+ * 'client-joined': (summary, { observedClient }) => {
5466
+ * ((summary.attachments.regions ??= []) as string[]).push(String(observedClient.appData.region));
5467
+ * },
5468
+ * },
5469
+ * },
5470
+ * });
5471
+ *
5472
+ * observer.on('call-summary', ({ summary }) => archive(summary));
5473
+ * ```
5474
+ *
5475
+ * This is construction-time and fixed for the observer's life, unlike detectors, which are added
5476
+ * per call as an application decides what to watch. A summary is a record of what happened, and a
5477
+ * record you can turn on halfway through is a record with a hole in it — calls that started
5478
+ * earlier would carry different sections from calls that started later, with nothing on either to
5479
+ * say which. One shape for every call, or none.
5480
+ */
5481
+ callSummary?: Partial<CallSummaryConfig> | null;
5019
5482
  inboundTrackDegradationThresholds?: {
5020
5483
  deltaFreezeCount: number;
5021
5484
  framesDroppedRatio: number;
@@ -5110,9 +5573,23 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
5110
5573
  * ```
5111
5574
  */
5112
5575
  readonly callDetectorConfigs: Map<keyof AvailableCallScopeDetectorsConfigs, Partial<UnconsumedTrackDetectorConfig | TrackDeliveryMismatchDetectorConfig | CallConcurrentIssueDetectorConfig | IssueFanOutDetectorConfig | PublisherFaultCorroborationDetectorConfig>>;
5576
+ /**
5577
+ * Owns every call's summary: the resolved `config.callSummary`, the bus subscriptions that keep
5578
+ * the summaries current (one per event type, not one per call), and the summaries themselves.
5579
+ *
5580
+ * `undefined` when `config.callSummary` was absent or `null` — so its presence *is* the answer to
5581
+ * "are summaries on", and nothing is subscribed to anything.
5582
+ */
5583
+ readonly callSummaryCollector?: CallSummaryCollector;
5113
5584
  constructor(config?: Partial<ObserverConfig<AppData>>);
5114
5585
  get numberOfCalls(): number;
5115
5586
  get appData(): AppData | undefined;
5587
+ /**
5588
+ * Build a cross-call detector onto `observer.detectors`. Chainable.
5589
+ *
5590
+ * To get a handle on what was built — to inspect it, or to remove that exact instance later — read
5591
+ * it back off the registry: `observer.detectors.getAll(name)`, or `observer.detectors.instances`.
5592
+ */
5116
5593
  addObserverDetector<K extends keyof AvailableObserverScopeDetectorsConfigs>(name: K, config?: Partial<AvailableObserverScopeDetectorsConfigs[K]>): this;
5117
5594
  /**
5118
5595
  * Enable a call-scoped detector for calls created **from now on**.
@@ -5121,8 +5598,36 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
5121
5598
  * built with. To add one to an existing call, use `observedCall.addDetector(...)` directly.
5122
5599
  */
5123
5600
  addCallDetector<K extends keyof AvailableCallScopeDetectorsConfigs>(name: K, config?: Partial<AvailableCallScopeDetectorsConfigs[K]>): this;
5124
- /** Stop building `name` on calls created from now on. Calls already open are untouched. */
5125
- removeCallDetector(name: keyof AvailableCallScopeDetectorsConfigs): this;
5601
+ /**
5602
+ * Remove an observer-scoped detector by name, returning how many were removed.
5603
+ *
5604
+ * **Every** instance registered under the name goes, since a name can legitimately be registered
5605
+ * more than once (`ClientPopulationIssueDetector` is meant to be added once per `groupBy` axis).
5606
+ * When you want one of them specifically, go through the registry, which deals in instances:
5607
+ *
5608
+ * ```ts
5609
+ * const [ byBrowser, byOs ] = observer.detectors.getAll('client-population-issue-detector');
5610
+ *
5611
+ * observer.detectors.remove(byOs); // keeps the browser axis running
5612
+ * ```
5613
+ *
5614
+ * Either route `close()`s the detector, so it unsubscribes from the issue registry and drops any
5615
+ * timers or bus listeners it held.
5616
+ */
5617
+ removeObserverDetector(name: keyof AvailableObserverScopeDetectorsConfigs): number;
5618
+ /**
5619
+ * Stop building `name` on calls created from now on.
5620
+ *
5621
+ * By default this also removes it from the calls **already open**, so that "remove this detector"
5622
+ * means the same thing whether you say it before or after a call started — the alternative leaves
5623
+ * a fleet where the detector is live on some calls and not others, decided by join time. Pass
5624
+ * `{ includeOpenCalls: false }` to change only what future calls are built with.
5625
+ *
5626
+ * Returns the number of live detector instances removed (`0` when only the config changed).
5627
+ */
5628
+ removeCallDetector(name: keyof AvailableCallScopeDetectorsConfigs, { includeOpenCalls }?: {
5629
+ includeOpenCalls?: boolean;
5630
+ }): number;
5126
5631
  /**
5127
5632
  * Start a structural check. It runs on each `observer.update()` until it can decide, reports once
5128
5633
  * on `validation-ready`, and removes itself.
@@ -5136,6 +5641,25 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
5136
5641
  * elapsed time is what makes a structural verdict stale.
5137
5642
  */
5138
5643
  addValidator<K extends keyof AvailableValidatorConfigs>(name: K, config?: Partial<AvailableValidatorConfigs[K]>): this;
5644
+ /**
5645
+ * Stop a running validation, by name or by instance. Returns how many were cancelled.
5646
+ *
5647
+ * Cancelling is **not** silent discarding. The validator finishes with `inconclusive` and the given
5648
+ * `reason`, emits `validation-ready` like any other completion, and removes itself. That matters
5649
+ * because anything waiting on the verdict — a deploy gate, a dashboard, a promise — would otherwise
5650
+ * wait forever, and because "we stopped asking" is a materially different outcome from "we asked
5651
+ * and learned nothing", which is exactly what `inconclusive` with a reason records.
5652
+ *
5653
+ * ```ts
5654
+ * observer.cancelValidator('simulcast-receivers', 'sfu redeployed');
5655
+ *
5656
+ * // or one specific instance — `observer.validators` holds what is running
5657
+ * for (const validator of observer.validators) observer.cancelValidator(validator, 'shutting down');
5658
+ * ```
5659
+ *
5660
+ * Pass a real reason. The default tells the reader nothing they could not already infer.
5661
+ */
5662
+ cancelValidator(target: keyof AvailableValidatorConfigs | RunningValidator, reason?: string): number;
5139
5663
  getObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(callId: string): ObservedCall<T> | undefined;
5140
5664
  createObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
5141
5665
  getOrCreateObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
@@ -5151,7 +5675,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
5151
5675
  *
5152
5676
  * `payload` takes an **object**; see `ObserverIssue`.
5153
5677
  */
5154
- addIssue(issue: ObserverIssue): void;
5678
+ addIssue(issue: Omit<ObserverIssue, 'scope'>): void;
5155
5679
  /** Emit an Observer-bus event. */
5156
5680
  private _notify;
5157
5681
  }
@@ -5266,116 +5790,6 @@ declare class CallHealthAggregator {
5266
5790
  private _clientHealth;
5267
5791
  }
5268
5792
 
5269
- /**
5270
- * Turning a correlation into a **conclusion**.
5271
- *
5272
- * Every detector in this library ultimately reports the same shape of observation: *N clients have
5273
- * issue X open at once, and here is what they have in common*. That is useful but not yet
5274
- * actionable — someone still has to know that congestion spread across unrelated calls means the
5275
- * server, while CPU limitation spread across unrelated calls means a bad client release. This module
5276
- * holds that interpretation step so it is stated once, consistently, instead of being re-derived by
5277
- * whoever reads the alert at 3am.
5278
- *
5279
- * ### Two functions, because there are two questions
5280
- *
5281
- * A detector already knows its scope — it was constructed with an `ObservedCall` or with the
5282
- * `Observer`. Handing that scope back to a single generic function meant every caller supplied
5283
- * fields the other scope needed and its own scope ignored: a call-scoped detector passing
5284
- * `affectedCalls: 1, totalCalls: 1` forever, an observer-scoped one passing a participant ratio that
5285
- * was deliberately never read. Placeholders like that are a standing invitation to read them as if
5286
- * they meant something.
5287
- *
5288
- * So there are two entry points, each taking only the facts its scope actually has:
5289
- *
5290
- * - {@link concludeCallIssue} — within one call. The axis is *how much of the meeting*, and whether
5291
- * the affected clients all subscribe to one published track.
5292
- * - {@link concludeObserverIssue} — across calls. The axis is *how many independent calls*, which is
5293
- * the only thing that separates "one bad room" from "our infrastructure".
5294
- *
5295
- * Neither the issue family nor the spread concludes anything alone: congestion in one call is a
5296
- * meeting problem, congestion in six calls is an infrastructure problem, and the issue type is
5297
- * identical in both.
5298
- */
5299
- /** Where the fault most likely sits, given who is affected. */
5300
- type IssueFaultDomain =
5301
- /** Independent calls affected at once — they share only the servers and the network. */
5302
- 'infrastructure'
5303
- /** One call, broadly affected — something that call shares (its SFU worker, room, or host). */
5304
- | 'call'
5305
- /** The subscribers of one published track — the publisher's path or the forwarding of it. */
5306
- | 'published-track'
5307
- /** A single endpoint — its own device or last mile. */
5308
- | 'endpoint'
5309
- /** Independent calls affected, but by something endpoints own — a client build, not a server. */
5310
- | 'client-population'
5311
- /** Not enough signal to attribute. */
5312
- | 'unknown';
5313
- /** A stated verdict, attached to the raised issue payload. */
5314
- type IssueConclusion = {
5315
- /** Where to look. */
5316
- faultDomain: IssueFaultDomain;
5317
- /** One line, written to be readable in an alert without opening a dashboard. */
5318
- summary: string;
5319
- /** What to check first. Omitted when the issue family is unknown to this module. */
5320
- recommendation?: string;
5321
- /**
5322
- * How much the spread alone justifies the verdict, `0..1`. Not a probability — a coarse ranking
5323
- * so alerting can threshold on it. More independent calls, or a tighter onset, means higher.
5324
- */
5325
- confidence: number;
5326
- };
5327
- /** The facts a **call-scoped** conclusion is drawn from. */
5328
- type CallIssueSpread = {
5329
- issueType: string;
5330
- /** Distinct clients of this call with the issue open. */
5331
- affectedClients: number;
5332
- /** Participants in the call — the denominator. */
5333
- totalClients: number;
5334
- /** True when the onsets clustered — a shared trigger rather than drift. */
5335
- onsetBurst: boolean;
5336
- /**
5337
- * Set when the affected clients are the subscriber set of **one published track**.
5338
- *
5339
- * The strongest call-scoped statement available: those clients share a publisher and nothing
5340
- * else, so the receivers are exonerated and the source's path is implicated.
5341
- */
5342
- publishedTrackId?: string;
5343
- };
5344
- /** The facts an **observer-scoped** conclusion is drawn from. */
5345
- type ObserverIssueSpread = {
5346
- issueType: string;
5347
- /** Distinct clients across the fleet with the issue open. */
5348
- affectedClients: number;
5349
- /** Clients in the fleet. Reported for context; it does not gate anything at this scope. */
5350
- totalClients: number;
5351
- /** Distinct calls containing at least one affected client. The dimension that matters here. */
5352
- affectedCalls: number;
5353
- /** Calls in flight. */
5354
- totalCalls: number;
5355
- /** True when the onsets clustered. */
5356
- onsetBurst: boolean;
5357
- };
5358
- /**
5359
- * Draw the conclusion for a group of clients **within one call**.
5360
- *
5361
- * Ordered most-to-least specific: a track-scoped group is a stronger statement than a call-wide one,
5362
- * and a single affected endpoint is not a statement about the call at all.
5363
- */
5364
- declare function concludeCallIssue(spread: CallIssueSpread): IssueConclusion;
5365
- /**
5366
- * Draw the conclusion for a group of clients spanning **several calls**.
5367
- *
5368
- * One affected call is not an observer-scoped finding — it has an obvious local explanation and the
5369
- * call-scoped detector has already reported it — so that case returns `call` and says so rather than
5370
- * dressing it up as a fleet event.
5371
- *
5372
- * Which domain breadth implicates depends on the family, and this is the whole reason the module
5373
- * exists: `congestion` across unrelated calls points at the servers, `cpulimitation` across unrelated
5374
- * calls points at what those *endpoints* share — a client release, a browser version, shared
5375
- * virtualised hardware — and pointing an SFU team at the second one wastes a night.
5376
- */
5377
- declare function concludeObserverIssue(spread: ObserverIssueSpread): IssueConclusion;
5378
-
5379
5793
  /** An entry retained by {@link SlidingWindow}. */
5380
5794
  type SlidingWindowEntry<T> = {
5381
5795
  timestamp: number;
@@ -5622,4 +6036,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
5622
6036
  declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
5623
6037
  declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
5624
6038
 
5625
- export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssueSpread, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CorroboratedPublisherFault, type Detector, Detectors, InMemorySink, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type PageHinkleyResult, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, RESOLVED_ISSUE_SUFFIX, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RunningValidator, type SampleRejectedReason, type ScoreCalculator, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultClientHealthThresholds, isClientIssueResolutionEntry, issuePayloadAsString, issuePayloadOf, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfSorted, robustZScore, setObserverLogger, summarize };
6039
+ export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CorroboratedPublisherFault, type Detector, Detectors, InMemorySink, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type PageHinkleyResult, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, RESOLVED_ISSUE_SUFFIX, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RunningValidator, type SampleRejectedReason, type ScoreCalculator, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, isClientIssueResolutionEntry, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfSorted, robustZScore, setObserverLogger, summarize };