From 7957d6cf1432e58812d12d6a5174683721c6143b Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:28:00 -0700 Subject: [PATCH 1/3] fix(SEL-616): degrade Compliance when readiness/summary is unavailable Root cause of the prod 503 is outside this PR (regengine lacks SELECT on tenants.status for the bridge-key auth path). Soften the Compliance Dashboard so a score or workbench readiness failure stays sectional instead of blanking the page, and document the DevOps grant in the PR. --- .../pages/dashboard-load-errors.test.tsx | 16 +- .../src/app/dashboard/compliance/page.tsx | 164 +++++++++++++----- frontend/src/lib/api-hooks.ts | 7 + 3 files changed, 145 insertions(+), 42 deletions(-) diff --git a/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx b/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx index e7e3d297a..a477c383b 100644 --- a/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx +++ b/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx @@ -223,11 +223,25 @@ describe('shared dashboard load errors', () => { const user = userEvent.setup(); renderWithQuery(); - expect(await screen.findByRole('alert')).toHaveTextContent('We could not load this page.'); + expect(await screen.findByRole('alert')).toHaveTextContent('We could not load the compliance score.'); + // SEL-616: score failure must not blank Import readiness / setup paths + expect(await screen.findByText('Import readiness')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Retry' })); await waitFor(() => expect(fetchComplianceScoreMock).toHaveBeenCalledTimes(2)); }); + it('keeps Import readiness visible when score and readiness both fail (SEL-616)', async () => { + fetchComplianceScoreMock.mockRejectedValue(apiError(401, 'Unauthorized', crypto.randomUUID())); + fetchWorkbenchSummaryMock.mockRejectedValue(apiError(503, 'Service Unavailable', crypto.randomUUID())); + renderWithQuery(); + + expect(await screen.findByRole('alert')).toHaveTextContent('We could not load the compliance score.'); + expect(await screen.findByText('Import readiness')).toBeInTheDocument(); + expect(screen.getByText(/Import readiness is unavailable right now/i)).toBeInTheDocument(); + expect(screen.queryByText('We could not load this page.')).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Retry import readiness' })).toBeInTheDocument(); + }); + it('lets recall report retry a failed request in place', async () => { fetchRecallReportMock.mockRejectedValue(new Error('server error')); const user = userEvent.setup(); diff --git a/frontend/src/app/dashboard/compliance/page.tsx b/frontend/src/app/dashboard/compliance/page.tsx index fe90da7a7..550d81430 100644 --- a/frontend/src/app/dashboard/compliance/page.tsx +++ b/frontend/src/app/dashboard/compliance/page.tsx @@ -321,6 +321,7 @@ export default function ComplianceDashboardPage() { const { data: workbenchSummary = null, + isError: readinessFailed, refetch: fetchWorkbenchSummary, } = useQuery({ queryKey: ['inflow-workbench-readiness', tenantId], @@ -330,7 +331,8 @@ export default function ComplianceDashboardPage() { retry: false, }); - const error = scoreError; + // Score and workbench failures stay sectional so one outage cannot blank the page + // (SEL-616: readiness/summary 503 must not look like a full Compliance crash). const lastFetched = dataUpdatedAt ? new Date(dataUpdatedAt) : null; // Ordered breakdown keys by weight (highest first) @@ -381,12 +383,12 @@ export default function ComplianceDashboardPage() {

- {lastFetched && !error && ( + {lastFetched && !scoreError && ( Updated {lastFetched.toLocaleTimeString()} )} - {score && !error && ( + {score && !scoreError && ( + + ) : ( + <> +
+
+
+ {workbenchSummary?.score ?? NO_DATA_YET} +
+

+ {workbenchSummary?.label ?? 'No saved preflight run yet'} +

+
+ 0 + ? 'bg-re-warning-muted0/10 text-re-warning' + : 'bg-re-brand-muted text-re-brand' + }`} + > + {workbenchSummary?.unresolved_fix_count ?? 0} open fix{(workbenchSummary?.unresolved_fix_count ?? 0) === 1 ? '' : 'es'} + +
+
+
+ Export eligible + {workbenchSummary?.export_eligible ? 'Yes' : 'Not yet'} +
+
+ + )} + + )} {/* Score Content */} - {score && !loading && !error && ( + {score && !loading && !scoreError && ( {/* Status Banner */} @@ -512,40 +575,59 @@ export default function ComplianceDashboardPage() { Import readiness -
-
-
+

Couldn't load

+

+ Import readiness is unavailable right now. The rest of this dashboard can still be used. +

+ + + ) : ( + <> +
+
+
+ {workbenchSummary?.score ?? NO_DATA_YET} +
+

+ {workbenchSummary?.label ?? 'No saved preflight run yet'} +

+
+ 0 + ? 'bg-re-warning-muted0/10 text-re-warning' + : 'bg-re-brand-muted text-re-brand' + }`} + > + {workbenchSummary?.unresolved_fix_count ?? 0} open fix{(workbenchSummary?.unresolved_fix_count ?? 0) === 1 ? '' : 'es'} +
-

- {workbenchSummary?.label ?? 'No saved preflight run yet'} -

-
- 0 - ? 'bg-re-warning-muted0/10 text-re-warning' - : 'bg-re-brand-muted text-re-brand' - }`} - > - {workbenchSummary?.unresolved_fix_count ?? 0} open fix{(workbenchSummary?.unresolved_fix_count ?? 0) === 1 ? '' : 'es'} - -
-
-
- Export eligible - {workbenchSummary?.export_eligible ? 'Yes' : 'Not yet'} -
-
- Latest run - - {workbenchSummary?.run_id ? workbenchSummary.run_id.slice(0, 16) : 'none'} - -
-
+
+
+ Export eligible + {workbenchSummary?.export_eligible ? 'Yes' : 'Not yet'} +
+
+ Latest run + + {workbenchSummary?.run_id ? workbenchSummary.run_id.slice(0, 16) : 'none'} + +
+
+ + )} @@ -756,8 +838,8 @@ export default function ComplianceDashboardPage() { )} - {/* Empty state - no score and not loading */} - {!score && !loading && !error && isLoggedIn && ( + {/* Empty / unavailable score — still offer setup paths (SEL-616) */} + {!score && !loading && isLoggedIn && ( ( From c2dd8d29da1362d4fa4aa0152ad98fd7aadd95c1 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 21:26:05 -0700 Subject: [PATCH 2/3] fix(copy): Trust Preview detail avoids GA live-claim next to EPCIS Match #3707 wording: Preview stays evaluation-only without saying GA. Refresh copy-lint baseline so the gate matches the scan. Signed-off-by: Christopher Sellers <1@christophersellers.com> --- frontend/src/app/trust/page.tsx | 2 +- scripts/copy-lint/baseline.json | 26 -------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/frontend/src/app/trust/page.tsx b/frontend/src/app/trust/page.tsx index ad7269059..2637f9a13 100644 --- a/frontend/src/app/trust/page.tsx +++ b/frontend/src/app/trust/page.tsx @@ -50,7 +50,7 @@ const STATUS_SUMMARY = [ }, { label: 'Preview', - detail: 'Capability is usable for evaluation in supported workspaces, but is not GA. GS1 EPCIS 2.0 export and ingest stay Preview until separately promoted.', + detail: 'Capability is usable for evaluation in supported workspaces, but has not reached general release. GS1 EPCIS 2.0 export and ingest stay Preview until separately promoted.', }, { label: 'Pilot', diff --git a/scripts/copy-lint/baseline.json b/scripts/copy-lint/baseline.json index a9272faea..6d091f71f 100644 --- a/scripts/copy-lint/baseline.json +++ b/scripts/copy-lint/baseline.json @@ -2,22 +2,12 @@ "frontend/src/app/auth/verify/page.tsx": { "empty-render": 2 }, - "frontend/src/app/blog/fda-food-traceability-requirements/page.tsx": { - "epcis-unlabeled": 1 - }, "frontend/src/app/blog/food-traceability-software/page.tsx": { - "epcis-unlabeled": 1, "sample-demo": 2 }, "frontend/src/app/blog/fsma-204-compliance-checklist-2026/page.tsx": { "invented-entity": 2 }, - "frontend/src/app/blog/fsma-204-compliance-guide/page.tsx": { - "epcis-unlabeled": 2 - }, - "frontend/src/app/blog/fsma-204-cte-kde-guide/page.tsx": { - "epcis-unlabeled": 5 - }, "frontend/src/app/contact/layout.tsx": { "sample-demo": 2 }, @@ -38,7 +28,6 @@ "empty-render": 1 }, "frontend/src/app/design-system/page.tsx": { - "invented-entity": 1, "sample-demo": 1, "sandbox": 1 }, @@ -58,9 +47,6 @@ "dash-placeholder": 3, "empty-render": 2 }, - "frontend/src/app/fsma-204/[cte]/page.tsx": { - "invented-entity": 1 - }, "frontend/src/app/identity/page.tsx": { "dash-placeholder": 2, "empty-render": 1 @@ -78,9 +64,6 @@ "frontend/src/app/legal/vulnerability-disclosure/page.tsx": { "sample-demo": 1 }, - "frontend/src/app/product/page.tsx": { - "sandbox": 2 - }, "frontend/src/app/records/page.tsx": { "dash-placeholder": 3, "empty-render": 1 @@ -100,9 +83,6 @@ "frontend/src/app/tools/tlc-validator/components/TLCValidatorClient.tsx": { "sample-demo": 1 }, - "frontend/src/app/why-regengine/page.tsx": { - "epcis-unlabeled": 1 - }, "frontend/src/components/dashboard/compliance-status-widget.tsx": { "empty-render": 1 }, @@ -133,9 +113,6 @@ "frontend/src/lib/dev-compliance-snapshots.ts": { "dash-placeholder": 2 }, - "frontend/src/lib/epcis-upload.ts": { - "epcis-unlabeled": 4 - }, "frontend/src/lib/mock-tenants.ts": { "sample-demo": 1 }, @@ -144,8 +121,5 @@ }, "frontend/src/lib/trust-subprocessors.ts": { "sample-demo": 1 - }, - "frontend/src/lib/utils.ts": { - "dash-placeholder": 1 } } From 3508ff8fa4be02301a7b5f5ee4099892ab63c67d Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:03:08 -0700 Subject: [PATCH 3/3] test(frontend): cover 401 on workbench readiness (SEL-616) Readiness 401 rejects once with a machine-readable auth status, and the Compliance page shows the unavailable state instead of placeholder readiness data when only readiness is unauthorized. Co-Authored-By: Claude Opus 5.5 --- frontend/src/__tests__/lib/api-hooks.test.ts | 19 +++++++++++++ .../pages/dashboard-load-errors.test.tsx | 28 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/frontend/src/__tests__/lib/api-hooks.test.ts b/frontend/src/__tests__/lib/api-hooks.test.ts index 26017d541..3fd63c52c 100644 --- a/frontend/src/__tests__/lib/api-hooks.test.ts +++ b/frontend/src/__tests__/lib/api-hooks.test.ts @@ -75,6 +75,25 @@ describe('internal api hooks', () => { expect(new Headers(scoreInit.headers).get('X-RegEngine-API-Key')).toBeNull(); }); + it('rejects a 401 on workbench readiness once, with a machine-readable auth status (SEL-616)', async () => { + fetchWithCsrfMock.mockResolvedValue({ + ok: false, + status: 401, + statusText: 'Unauthorized', + headers: new Headers(), + }); + + const { fetchWorkbenchReadinessSummary } = await import('@/lib/api-hooks'); + const { getErrorStatus, isAuthError } = await import('@/lib/api-error'); + const error = await fetchWorkbenchReadinessSummary('tenant-123').catch((err: unknown) => err); + + expect(error).toBeInstanceOf(Error); + expect(getErrorStatus(error)).toBe(401); + expect(isAuthError(error)).toBe(true); + // 401 is final: no retry/backoff on the readiness path. + expect(fetchWithCsrfMock).toHaveBeenCalledTimes(1); + }); + it('throws client errors immediately without retrying them', async () => { fetchWithCsrfMock.mockResolvedValue({ ok: false, diff --git a/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx b/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx index a477c383b..f8dc86ae3 100644 --- a/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx +++ b/frontend/src/__tests__/pages/dashboard-load-errors.test.tsx @@ -53,7 +53,11 @@ vi.mock('@/hooks/use-agent-reviews', () => ({ vi.mock('@/hooks/use-dashboard-refresh', () => ({ useDashboardRefresh: vi.fn() })); vi.mock('next/link', () => ({ default: ({ children, href }: { children: React.ReactNode; href: string }) => {children} })); vi.mock('framer-motion', () => ({ - motion: { div: ({ children, ...props }: React.PropsWithChildren>) =>
{children}
}, + motion: { + div: ({ children }: React.PropsWithChildren>) =>
{children}
, + li: ({ children }: React.PropsWithChildren>) =>
  • {children}
  • , + circle: () => , + }, AnimatePresence: ({ children }: React.PropsWithChildren) => <>{children}, })); @@ -242,6 +246,28 @@ describe('shared dashboard load errors', () => { expect(screen.getByRole('button', { name: 'Retry import readiness' })).toBeInTheDocument(); }); + it('rejects a 401 on readiness without showing placeholder readiness data (SEL-616)', async () => { + fetchComplianceScoreMock.mockResolvedValue({ + tenant_id: 't', + overall_score: 82, + grade: 'B', + breakdown: {}, + next_actions: [], + events_analyzed: 10, + last_chain_hash: null, + }); + fetchWorkbenchSummaryMock.mockRejectedValue(apiError(401, 'Unauthorized', crypto.randomUUID())); + renderWithQuery(); + + expect(await screen.findByText(/Import readiness is unavailable right now/i)).toBeInTheDocument(); + // An unauthorized readiness request must not render as an empty-but-healthy workspace. + expect(screen.queryByText('No saved preflight run yet')).not.toBeInTheDocument(); + expect(screen.queryByText(/open fix/)).not.toBeInTheDocument(); + expect(screen.queryByText('We could not load this page.')).not.toBeInTheDocument(); + expect(screen.queryByText('We could not load the compliance score.')).not.toBeInTheDocument(); + expect(fetchWorkbenchSummaryMock).toHaveBeenCalledTimes(1); + }); + it('lets recall report retry a failed request in place', async () => { fetchRecallReportMock.mockRejectedValue(new Error('server error')); const user = userEvent.setup();