diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 5d80b40a9..189b70d6a 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -256,8 +256,9 @@ That freshest grouped observation is now canonically exposed as the structured `latest_included_signal` object. Its `at`, `source`, `name`, and `type` fields identify exactly which included top-level surface reported most recently. `latest_included_signal_at`, `latest_included_signal_source`, and `last_seen` -remain rollout compatibility fields only; consumers should treat the object as -the primary contract and preserve its meaning in presentation. +remain rollout compatibility fields only on the raw wire shape; normalized +frontend clients should treat the object as the primary contract and must not +re-export those flat aliases in their public response model. That client contract must also fail closed when older or partial payloads omit the nested explanation object: the frontend may normalize missing explanation fields to empty reasons/surfaces plus a safe default summary, but it must not diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 1529273d2..8777b990c 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -225,17 +225,19 @@ That same settings surface must also label the monitored-system signal by its real meaning. The canonical API shape is now the structured `latest_included_signal` object, while `latest_included_signal_at`, `latest_included_signal_source`, and `last_seen` are rollout compatibility -fields only. It represents the freshest included grouped observation, not a -guarantee that every grouped source is healthy, so the UI must not present it -with single-source `Last Seen` wording. When the canonical object is present, -the surface should use its source/name/type attribution instead of showing an -unqualified aggregate timestamp, and that attribution should stay -customer-facing rather than exposing raw monitored-system type/source slugs in -the settings table or included-surface details. When the row expands to show -status reasoning, it should also restate the freshest included surface and -timestamp there so a degraded reason and a fresher grouped signal remain -readable together. When mixed-version payloads omit that canonical freshest -signal entirely, the settings surface should degrade to a safe customer-facing +fields only on the raw response payload. The normalized frontend client +contract should expose only the canonical object. It represents the freshest +included grouped observation, not a guarantee that every grouped source is +healthy, so the UI must not present it with single-source `Last Seen` wording. +When the canonical object is present, the surface should use its +source/name/type attribution instead of showing an unqualified aggregate +timestamp, and that attribution should stay customer-facing rather than +exposing raw monitored-system type/source slugs in the settings table or +included-surface details. When the row expands to show status reasoning, it +should also restate the freshest included surface and timestamp there so a +degraded reason and a fresher grouped signal remain readable together. When +mixed-version payloads omit that canonical freshest signal entirely, the +settings surface should degrade to a safe customer-facing fallback instead of an unexplained placeholder glyph. That same billing support boundary now also owns the shared monitored-system presentation helper. `frontend-modern/src/utils/monitoredSystemPresentation.ts` diff --git a/frontend-modern/src/api/__tests__/monitoredSystemLedger.test.ts b/frontend-modern/src/api/__tests__/monitoredSystemLedger.test.ts index 7f41e3d7e..94245b325 100644 --- a/frontend-modern/src/api/__tests__/monitoredSystemLedger.test.ts +++ b/frontend-modern/src/api/__tests__/monitoredSystemLedger.test.ts @@ -196,14 +196,15 @@ describe('MonitoredSystemLedgerAPI', () => { const result = await MonitoredSystemLedgerAPI.getLedger(); - expect(result.systems[0]?.latest_included_signal_at).toBe('2026-03-23T11:59:50Z'); - expect(result.systems[0]?.latest_included_signal_source).toBe('docker'); expect(result.systems[0]?.latest_included_signal).toEqual({ name: 'tower.local', type: 'docker-host', source: 'docker', at: '2026-03-23T11:59:50Z', }); + expect(result.systems[0]).not.toHaveProperty('latest_included_signal_at'); + expect(result.systems[0]).not.toHaveProperty('latest_included_signal_source'); + expect(result.systems[0]).not.toHaveProperty('last_seen'); }); it('falls back to legacy latest signal fields for older payloads', async () => { @@ -223,14 +224,15 @@ describe('MonitoredSystemLedgerAPI', () => { const result = await MonitoredSystemLedgerAPI.getLedger(); - expect(result.systems[0]?.latest_included_signal_at).toBe('2026-03-23T11:59:50Z'); - expect(result.systems[0]?.latest_included_signal_source).toBeUndefined(); expect(result.systems[0]?.latest_included_signal).toEqual({ name: 'Tower', type: 'host', source: undefined, at: '2026-03-23T11:59:50Z', }); + expect(result.systems[0]).not.toHaveProperty('latest_included_signal_at'); + expect(result.systems[0]).not.toHaveProperty('latest_included_signal_source'); + expect(result.systems[0]).not.toHaveProperty('last_seen'); }); it('preserves canonical status explanation reasons from the API contract', async () => { diff --git a/frontend-modern/src/api/monitoredSystemLedger.ts b/frontend-modern/src/api/monitoredSystemLedger.ts index 1e82f3a03..587823310 100644 --- a/frontend-modern/src/api/monitoredSystemLedger.ts +++ b/frontend-modern/src/api/monitoredSystemLedger.ts @@ -51,9 +51,6 @@ export interface MonitoredSystemLedgerEntry { status: MonitoredSystemLedgerStatus; status_explanation: MonitoredSystemLedgerStatusExplanation; latest_included_signal: MonitoredSystemLedgerLatestSignal; - latest_included_signal_at: string; // freshest included observation, RFC3339 or empty - latest_included_signal_source?: string; - last_seen?: string; // deprecated compatibility alias source: string; explanation: MonitoredSystemLedgerExplanation; } @@ -75,6 +72,9 @@ type MonitoredSystemLedgerRawEntry = Omit< MonitoredSystemLedgerEntry, 'status_explanation' | 'latest_included_signal' | 'explanation' > & { + latest_included_signal_at?: string; // freshest included observation, RFC3339 or empty + latest_included_signal_source?: string; + last_seen?: string; // deprecated compatibility alias status_explanation?: MonitoredSystemLedgerStatusExplanation; latest_included_signal?: MonitoredSystemLedgerLatestSignal; explanation?: MonitoredSystemLedgerExplanation; @@ -106,13 +106,11 @@ function normalizeMonitoredSystemLedgerEntry( entry, ); return { - ...entry, + name: entry.name, + type: entry.type, status, + source: entry.source, latest_included_signal: latestIncludedSignal, - latest_included_signal_at: latestIncludedSignal.at, - latest_included_signal_source: normalizeMonitoredSystemLedgerSource( - latestIncludedSignal.source, - ), status_explanation: { summary: entry.status_explanation?.summary ?? getMonitoredSystemStatusFallbackSummary(status), reasons: (entry.status_explanation?.reasons ?? []).map(normalizeMonitoredSystemLedgerStatusReason), diff --git a/frontend-modern/src/components/Settings/__tests__/MonitoredSystemLedgerPanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/MonitoredSystemLedgerPanel.test.tsx index 8104f8b33..355536745 100644 --- a/frontend-modern/src/components/Settings/__tests__/MonitoredSystemLedgerPanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/MonitoredSystemLedgerPanel.test.tsx @@ -85,9 +85,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'agent', at: '2026-01-01T00:00:00Z', }, - latest_included_signal_at: '2026-01-01T00:00:00Z', - latest_included_signal_source: 'agent', - last_seen: '2026-01-01T00:00:00Z', source: 'agent', explanation: { summary: @@ -156,9 +153,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'agent', at: '2026-01-01T00:00:00Z', }, - latest_included_signal_at: '2026-01-01T00:00:00Z', - latest_included_signal_source: 'agent', - last_seen: '2026-01-01T00:00:00Z', source: 'agent', explanation: { summary: @@ -199,9 +193,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'pbs', at: '2026-01-02T00:00:00Z', }, - latest_included_signal_at: '2026-01-02T00:00:00Z', - latest_included_signal_source: 'pbs', - last_seen: '2026-01-02T00:00:00Z', source: 'pbs', explanation: { summary: @@ -303,9 +294,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'agent', at: '2026-01-01T00:00:00Z', }, - latest_included_signal_at: '2026-01-01T00:00:00Z', - latest_included_signal_source: 'agent', - last_seen: '2026-01-01T00:00:00Z', source: 'agent', }, ], @@ -352,8 +340,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'agent', at: '', }, - latest_included_signal_at: '', - latest_included_signal_source: 'agent', source: 'agent', }, ], @@ -393,9 +379,6 @@ describe('MonitoredSystemLedgerPanel', () => { source: 'agent', at: '2026-01-01T00:00:00Z', }, - latest_included_signal_at: '2026-01-01T00:00:00Z', - latest_included_signal_source: 'agent', - last_seen: '2026-01-01T00:00:00Z', source: 'agent', }, ], diff --git a/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts b/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts index 0a6b9c0dc..9f7d4022e 100644 --- a/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts @@ -252,6 +252,11 @@ describe('monitored-system model guardrails', () => { expect(monitoredSystemLedgerApiSource).toContain('getMonitoredSystemExplanationFallbackSummary'); expect(monitoredSystemLedgerApiSource).toContain('type MonitoredSystemLedgerRawEntry ='); expect(monitoredSystemLedgerApiSource).toContain('systems?: MonitoredSystemLedgerRawEntry[];'); + expect(monitoredSystemLedgerApiSource).toContain('latest_included_signal_at?: string;'); + expect(monitoredSystemLedgerApiSource).toContain('last_seen?: string;'); + expect(monitoredSystemLedgerApiSource).not.toContain( + 'latest_included_signal_at: string; // freshest included observation, RFC3339 or empty', + ); expect(monitoredSystemLedgerApiSource).not.toContain( 'All included top-level collection paths currently report online status.', );