From 28e277a88e8d00df807de2f514362bfb86b9ea6d Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Wed, 29 Apr 2026 16:35:40 +0000 Subject: [PATCH] fix(scep-intune): use useTrackedMutation for trust-anchor reload (M-009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 9 follow-up — the M-009 hard-zero regression guard in .github/workflows/ci.yml flagged the SCEPAdminPage's reload mutation as a bare useMutation() call. The repo's invalidation contract requires every mutation to go through useTrackedMutation with explicit invalidates: QueryKey[] | 'noop' so cached data never goes stale after a write. Swap the bare useMutation for useTrackedMutation with invalidates: [['admin', 'scep', 'intune', 'stats']] — the trust-anchor reload changes the per-profile trust pool reflected in IntuneStats, so the stats query MUST refetch on success. The audit-log queries stay on their own 60s timer (a SIGHUP-equivalent reload doesn't backfill new audit rows; nothing to invalidate there). Verification: * tsc --noEmit clean * vitest SCEPAdminPage.test.tsx: 13/13 still pass (the wrapper's onSuccess fires AFTER invalidation, so the modal-close + state reset assertions hold) * M-009 grep guard reproduced locally — bare useMutation sites = 0 --- web/src/pages/SCEPAdminPage.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/web/src/pages/SCEPAdminPage.tsx b/web/src/pages/SCEPAdminPage.tsx index 0895348..0e4b863 100644 --- a/web/src/pages/SCEPAdminPage.tsx +++ b/web/src/pages/SCEPAdminPage.tsx @@ -1,9 +1,10 @@ import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { getAdminSCEPIntuneStats, reloadAdminSCEPIntuneTrust, getAuditEvents } from '../api/client'; import PageHeader from '../components/PageHeader'; import ErrorState from '../components/ErrorState'; import { useAuth } from '../components/AuthProvider'; +import { useTrackedMutation } from '../hooks/useTrackedMutation'; import { formatDateTime } from '../api/utils'; import type { IntuneStatsSnapshot, IntuneTrustAnchorInfo, AuditEvent } from '../api/types'; @@ -299,7 +300,6 @@ function RecentFailuresTable({ events }: { events: AuditEvent[] }) { export default function SCEPAdminPage() { const auth = useAuth(); - const queryClient = useQueryClient(); const [reloadTarget, setReloadTarget] = useState(null); const [reloadError, setReloadError] = useState(undefined); @@ -328,12 +328,23 @@ export default function SCEPAdminPage() { refetchInterval: 60_000, }); - const reloadMutation = useMutation({ + // Bundle-8 / M-009 invalidation contract: trust-anchor reload changes + // both the per-profile trust pool (reflected in IntuneStats) AND every + // recently-failed Intune enrollment counter that might now succeed on + // retry. We invalidate the stats key so the per-profile trust-anchor + // panel reflects the new pool immediately; the audit log queries + // remain on their 60s timer (a SIGHUP-equivalent reload doesn't + // backfill new audit rows). + const reloadMutation = useTrackedMutation< + Awaited>, + Error, + string + >({ mutationFn: (pathID: string) => reloadAdminSCEPIntuneTrust(pathID), + invalidates: [['admin', 'scep', 'intune', 'stats']], onSuccess: () => { setReloadTarget(null); setReloadError(undefined); - void queryClient.invalidateQueries({ queryKey: ['admin', 'scep', 'intune', 'stats'] }); }, onError: (err: Error) => { setReloadError(err.message);