fix(observability,settings): the lag event names its real fields and ladders shed duplicates

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.
This commit is contained in:
Marc-André Bombeck committed 2026-06-12 22:38:03 +02:00
1 parent e122b50477
commit 1f45cf4da4
4 files changed
+27 -3

No files matched your search

+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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
@@ -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);
});
});
+5 -1
View File
@@ -361,7 +361,11 @@ function buildResolved(
// the old behaviour ranked them (after everything ranked).
const merged = {} as Required<MetricPriority>;
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),
);