From 1f45cf4da4436dbb7ad727237800fe8a9d6e4a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 12 Jun 2026 22:38:03 +0200 Subject: [PATCH] fix(observability,settings): the lag event names its real fields and ladders shed duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wide-event type declared loop_p99_ms while the monitor publishes loop_last_ms — a dashboard built on the declared field would find nothing; the type now states what is emitted. Ladder reconciliation collapses duplicate entries first-occurrence-wins, so a duplicate-laden ladder written through the API can no longer overflow the schema's eight-entry cap when the settings page echoes the resolved shape back. --- src/lib/logging/event-builder.ts | 2 +- src/lib/logging/types.ts | 2 +- .../__tests__/source-priority.test.ts | 20 +++++++++++++++++++ src/lib/validations/source-priority.ts | 6 +++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/lib/logging/event-builder.ts b/src/lib/logging/event-builder.ts index 68d9ec18f..1b6204d62 100644 --- a/src/lib/logging/event-builder.ts +++ b/src/lib/logging/event-builder.ts @@ -137,7 +137,7 @@ export class WideEventBuilder { // nicht existiert — der Slot ist dort einfach leer. const lag = ( globalThis as unknown as { - [key: symbol]: { loop_max_ms: number; loop_p99_ms: number }; + [key: symbol]: { loop_max_ms: number; loop_last_ms: number }; } )[Symbol.for("healthlog.eventLoopLag")]; if (lag) this.event.runtime = lag; diff --git a/src/lib/logging/types.ts b/src/lib/logging/types.ts index 6c42befbe..5d192e770 100644 --- a/src/lib/logging/types.ts +++ b/src/lib/logging/types.ts @@ -95,7 +95,7 @@ export interface WideEvent { // den Stall mitfuehrt) runtime?: { loop_max_ms: number; - loop_p99_ms: number; + loop_last_ms: number; }; // Fehler-Details diff --git a/src/lib/validations/__tests__/source-priority.test.ts b/src/lib/validations/__tests__/source-priority.test.ts index 2ec68be46..856822b0c 100644 --- a/src/lib/validations/__tests__/source-priority.test.ts +++ b/src/lib/validations/__tests__/source-priority.test.ts @@ -457,3 +457,23 @@ describe("parseSourcePriority — stored-ladder reconciliation (v1.16.11)", () = expect(resolved.metricPriority.sleep).toContain("WITHINGS"); }); }); + +describe("parseSourcePriority — duplicate tolerance (v1.16.11)", () => { + it("collapses duplicates so a resolved ladder can never overflow the schema cap", () => { + const resolved = parseSourcePriority({ + metricPriority: { + weight: [ + "MANUAL", + "MANUAL", + "WITHINGS", + "MANUAL", + "WITHINGS", + ], + }, + }); + const ladder = resolved.metricPriority.weight; + expect(new Set(ladder).size).toBe(ladder.length); + expect(ladder.slice(0, 2)).toEqual(["MANUAL", "WITHINGS"]); + expect(ladder.length).toBeLessThanOrEqual(8); + }); +}); diff --git a/src/lib/validations/source-priority.ts b/src/lib/validations/source-priority.ts index 9a9b14de5..87895c498 100644 --- a/src/lib/validations/source-priority.ts +++ b/src/lib/validations/source-priority.ts @@ -361,7 +361,11 @@ function buildResolved( // the old behaviour ranked them (after everything ranked). const merged = {} as Required; for (const key of Object.keys(replaced) as (keyof MetricPriority)[]) { - const stored = replaced[key]; + // First occurrence wins on duplicates — a duplicate-laden ladder + // written through the API directly would otherwise grow past the + // schema's 8-entry cap once the settings page echoes the resolved + // shape back through PUT. + const stored = [...new Set(replaced[key])]; const missing = DEFAULT_SOURCE_PRIORITY[key].filter( (source) => !stored.includes(source), );