Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
56fb1d9
fix(epcis-export): return honest empty state instead of sample data
PetrefiedThunder Oct 23, 2026
7b58a3a
fix(frontend): restore main build after SEL-410/SEL-414 ErrorState co…
PetrefiedThunder Oct 23, 2026
b51349a
Merge remote-tracking branch 'origin/main' into fix/epcis-export-hone…
PetrefiedThunder Oct 23, 2026
7026cb1
test(epcis): drop SAMPLE_EPCIS_EVENTS patches; data-free empty-state …
PetrefiedThunder Oct 23, 2026
51fa80a
ci: fix required gates on #3524 (pin copy-lint actions, inventory new…
PetrefiedThunder Oct 23, 2026
9b4d11a
chore(secrets): refresh .secrets.baseline (detect-secrets 1.5.0)
PetrefiedThunder Oct 23, 2026
66733bc
test(ingestion): make #2868 error-logging tests data-free to fix coll…
PetrefiedThunder Oct 23, 2026
278e85a
fix(ci): scope suppliers no-data test; match uses: as YAML key in sup…
PetrefiedThunder Oct 23, 2026
d7c1505
test(ingestion): restore data-free ingest_batch_failed route coverage…
PetrefiedThunder Oct 23, 2026
9cd1357
fix(frontend): show QA-signal loading row on dashboard; align qa-heal…
PetrefiedThunder Oct 23, 2026
9466f30
fix(tenant): update route manifest expectations for two new tenant-sc…
PetrefiedThunder Oct 23, 2026
e0605c4
ci: list 9 missing workflows in inventory; stop action-pin matching '…
PetrefiedThunder Oct 23, 2026
547ed16
ci(frontend): allowlist visual regression files in Knip unused file g…
PetrefiedThunder Oct 23, 2026
987124f
test(ingestion): fix-forward email_ingest principal stub and sel409 a…
PetrefiedThunder Oct 23, 2026
7818f30
fix(deps): bump pypdf to 6.21.2 for CVE-2026-115572/115585/115580
PetrefiedThunder Oct 23, 2026
16cff9d
Merge remote-tracking branch 'origin/fix/errorstate-props-main' into …
PetrefiedThunder Oct 23, 2026
fa310ca
Merge remote-tracking branch 'origin/fix/epcis-export-honest-empty' i…
PetrefiedThunder Oct 23, 2026
3a1e236
Merge remote-tracking branch 'origin/fix/test-collect-2868' into code…
PetrefiedThunder Oct 23, 2026
e24873c
Merge remote-tracking branch 'origin/fix/tenant-route-manifest-2-rout…
PetrefiedThunder Oct 23, 2026
1384bc9
Merge remote-tracking branch 'origin/codex/fix-email-ingest-sel409-te…
PetrefiedThunder Oct 23, 2026
3091304
Merge remote-tracking branch 'origin/gemini/pypdf-cve-bump' into code…
PetrefiedThunder Oct 23, 2026
510ee72
Merge remote-tracking branch 'origin/muse/unused-guard-visual-allowli…
PetrefiedThunder Oct 23, 2026
e1c82ea
test(ingestion): fix suite A collection (2868 persistence import) and…
PetrefiedThunder Oct 23, 2026
30f39e6
Merge remote-tracking branch 'origin/devops/workflow-contract-fix' in…
PetrefiedThunder Oct 23, 2026
21288ef
fix(ci): stop Head SHA Check Guard failing main on check_suite/workfl…
PetrefiedThunder Oct 23, 2026
a476eac
Merge remote-tracking branch 'origin/claude/ingestion-suite-a' into c…
PetrefiedThunder Oct 23, 2026
48a6df6
Merge remote-tracking branch 'origin/antigravity/head-sha-guard' into…
PetrefiedThunder Oct 23, 2026
30cc483
ci: clear detect-secrets false positive in vercel-preview-guard
PetrefiedThunder Oct 23, 2026
b8d9015
test(ingestion): make 944/945 envelope test data-free after #3500 fix…
PetrefiedThunder Oct 23, 2026
1d3e137
Merge commit 'b8d901531a4a60bd96b8882199ba9d8515bae1cd' into codex/ma…
PetrefiedThunder Oct 23, 2026
182a4e4
ci: refresh .secrets.baseline line numbers (detect-secrets 1.5.0, no …
PetrefiedThunder Oct 23, 2026
e65bea9
test(ingestion): capture ingest_batch_failed via dedicated logger; bu…
PetrefiedThunder Oct 23, 2026
28693c2
Merge remote-tracking branch 'origin/devops/workflow-contract-fix' in…
PetrefiedThunder Oct 23, 2026
866471d
ci: refresh .secrets.baseline after test change (drop stale entry)
PetrefiedThunder Oct 23, 2026
8ed7c06
fix: address CodeRabbit review on #3552 (checkout creds, retry a11y n…
PetrefiedThunder Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/copy-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: '20'
- run: node --test scripts/copy-lint/check-copy.test.mjs
Expand Down
20 changes: 16 additions & 4 deletions .github/workflows/head-sha-check-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ jobs:

const targetUrl = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

// check_suite and workflow_run events run in the default-branch context, so a
// failed job there is attributed to main even though it evaluated a PR head.
// For those events the PR verdict is carried by the commit status posted on the
// PR head SHA; only fail the job itself for pull_request events.
const failGuard = (msg) => {
if (context.eventName === 'pull_request') {
core.setFailed(msg);
} else {
core.warning(`${msg} (reported via commit status; not failing ${context.eventName} run on default branch)`);
}
};

// Prevent self-trigger loops
if (context.eventName === 'workflow_run' && context.payload.workflow_run?.name === 'Head SHA Check Guard') {
core.info('Skipping run triggered by own workflow_run completion.');
Expand Down Expand Up @@ -143,7 +155,7 @@ jobs:
const prHeadSha = matchedPr.head?.sha;

if (!prHeadSha) {
core.setFailed(`PR #${prNumber} has no head SHA.`);
failGuard(`PR #${prNumber} has no head SHA.`);
return;
}

Expand All @@ -160,7 +172,7 @@ jobs:
description: 'Head moved',
target_url: targetUrl,
});
core.setFailed(`Head moved: PR #${prNumber} head is ${prHeadSha}, evaluated commit was ${evaluatedSha}`);
failGuard(`Head moved: PR #${prNumber} head is ${prHeadSha}, evaluated commit was ${evaluatedSha}`);
return;
}

Expand Down Expand Up @@ -314,7 +326,7 @@ jobs:
description: 'Head moved',
target_url: targetUrl,
});
core.setFailed(`Head moved: PR #${prNumber} head is ${latestPrAfterWait.head?.sha}, evaluated commit was ${evaluatedSha}`);
failGuard(`Head moved: PR #${prNumber} head is ${latestPrAfterWait.head?.sha}, evaluated commit was ${evaluatedSha}`);
return;
}

Expand Down Expand Up @@ -394,7 +406,7 @@ jobs:
description: statusDesc,
target_url: targetUrl,
});
core.setFailed(`Head SHA Check Guard failed on head SHA ${evaluatedSha}. Failed checks: ${failedList}`);
failGuard(`Head SHA Check Guard failed on head SHA ${evaluatedSha}. Failed checks: ${failedList}`);
return;
}

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/vercel-preview-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,22 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
PREVIEW_URL: ${{ needs.preview.outputs.preview_url }}
BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} # pragma: allowlist secret
with:
github-token: ${{ github.token }}
script: |
script: | # pragma: allowlist secret
const marker = '<!-- vercel-preview-access -->';
const secret = process.env.BYPASS_SECRET;
if (secret) core.setSecret(secret); // Emits ::add-mask:: before using it.
const bypassValue = process.env.BYPASS_SECRET;
if (bypassValue) core.setSecret(bypassValue); // Emits ::add-mask:: before using it.
const preview = process.env.PREVIEW_URL;
if (!preview) throw new Error('The preview job did not return an access URL');
const url = new URL(preview);
if (secret) {
url.searchParams.set('x-vercel-protection-bypass', secret);
if (bypassValue) {
url.searchParams.set('x-vercel-protection-bypass', bypassValue);
url.searchParams.set('x-vercel-set-bypass-cookie', 'true');
}
const body = `${marker}\nVercel preview: ${url.toString()}` +
(secret ? '' : '\n\nThe bypass secret is not configured.');
(bypassValue ? '' : '\n\nThe bypass secret is not configured.');
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const comments = await github.paginate(github.rest.issues.listComments, {
Expand Down
50 changes: 7 additions & 43 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
"knip": {
"entry": [
"scripts/design-lint.mjs",
"tests/e2e/qa-2026-10-02/fe-regressions.config.ts"
"tests/e2e/qa-2026-10-02/fe-regressions.config.ts",
"playwright.visual.config.ts",
"tests/visual/**/*.ts",
"src/lib/capabilities.ts"
]
},
"//": "Pinning policy (#569): next, eslint-config-next, @sentry/nextjs, and @supabase/supabase-js are pinned to exact versions (no ^ or ~ prefix) because breaking changes in these packages have caused production incidents. Bump them deliberately after testing. All other deps use ^ for minor/patch auto-updates.",
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/__tests__/components/error-state.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ErrorState } from '@/components/ui/error-state';

describe('ErrorState', () => {
it('keeps the callout style and shows retry progress with a caller class', () => {
render(<ErrorState message="Could not load" onRetry={vi.fn()} retrying className="m-4" />);

expect(screen.getByRole('alert')).toHaveClass('bg-re-danger-bg', 'm-4');
expect(screen.getByRole('button', { name: 'Retrying…' })).toBeDisabled();
expect(screen.getByText('Retrying…')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ vi.mock('@/lib/tenant-context', () => ({
}));

vi.mock('@/hooks/use-api', () => ({
useQAHealth: () => ({ data: null, isLoading: false, error: null }),
useSystemStatus: () => ({ data: { overall_status: 'healthy', services: [] }, isLoading: false }),
useSystemMetrics: () => ({ data: { open_alerts: 0, events_ingested: 0 }, isLoading: false }),
useQAHealth: () => ({ data: undefined, isLoading: false, error: null, refetch: vi.fn() }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('shared dashboard load errors', () => {
expect(onRetry).toHaveBeenCalledOnce();

rerender(<ErrorState message="We could not load this page." onRetry={onRetry} requestId={requestId} retrying />);
expect(screen.getByRole('button', { name: 'Retry' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Retrying…' })).toBeDisabled();
});

it('omits the request ID when none is available', () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/__tests__/pages/qa-health.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('QAHealthPage', () => {

expect(screen.getByText('QA health request failed')).toBeInTheDocument();
expect(screen.getByRole('alert')).toHaveTextContent(
'The sysadmin QA endpoint did not return a dashboard summary.',
'We could not load this page.',
);
expect(screen.queryByRole('heading', { name: 'QA Health' })).not.toBeInTheDocument();

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/__tests__/pages/suppliers-metrics.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ it('shows "No data yet" for lot codes when no facility has a count', async () =>
mocks.tlcs.mockRejectedValue(new Error('boom'));
render(<SupplierDashboardPage />);
await waitFor(() => expect(screen.getAllByText('No lot code data yet')).toHaveLength(2));
expect(screen.getByText('No data yet')).toBeInTheDocument();
const tile = screen.getByText('Lot Codes Tracked').parentElement as HTMLElement;
expect(within(tile).getByText('No data yet')).toBeInTheDocument();
expect(document.body.textContent).not.toMatch(/NaN/);
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export default function DashboardPage() {
rowHref={(row) => row.href}
density="medium"
minWidth="480px"
status={qa.status === "loading" ? "loading" : attentionRows.length === 0 ? "empty" : "idle"}
status={attentionRows.length === 0 ? "empty" : "idle"}
empty={{
icon: CheckCircle2,
title: "Nothing needs attention",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/dashboard/suppliers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from 'lucide-react';

import { apiClient } from '@/lib/api-client';
import { NO_DATA_YET } from '@/lib/empty-values';
import { useAuth } from '@/lib/auth-context';
import { useTenant } from '@/lib/tenant-context';
import { useDashboardRefresh } from '@/hooks/use-dashboard-refresh';
Expand Down
18 changes: 13 additions & 5 deletions frontend/src/components/ui/error-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface ErrorStateProps {
message: string
onRetry?: () => void
retryLabel?: string
requestId?: string
retrying?: boolean
className?: string
}

Expand All @@ -20,6 +22,8 @@ export function ErrorState({
message,
onRetry,
retryLabel = "Retry",
requestId,
retrying = false,
className,
}: ErrorStateProps) {
return (
Expand All @@ -29,14 +33,18 @@ export function ErrorState({
title={title}
className={className}
action={
onRetry ? (
<Button variant="secondary" size="sm" onClick={onRetry}>
{retryLabel}
</Button>
) : undefined
<>
{onRetry && (
<Button variant="secondary" size="sm" onClick={onRetry} disabled={retrying}>
{retrying ? "Retrying…" : retryLabel}
</Button>
)}
<a href="/settings/support" className="text-small underline">Contact support</a>
</>
}
>
{message}
{requestId && <p className="mt-2 text-small">Request ID: {requestId}</p>}
</Callout>
)
}
6 changes: 3 additions & 3 deletions packages/factory-local/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1235,9 +1235,9 @@ pygments==2.21.0 \
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
# via pytest
pypdf==6.21.1 \
--hash=sha256:2d62c383acd6e0dff0a96322ba8b1135229343b084d73c6f6cfd947fd291afcb \
--hash=sha256:553e2d42f5bb3bb2a785a7d3de8ef90bb7df74b5413eff5a7b4388007ea9254b
pypdf==6.21.2 \
--hash=sha256:0a3e1c5c886a1be03646c16a04827cc166c2a0575c971b26a4bf89686551c2d4 \
--hash=sha256:1d44d1a3a200f25062adff0085b02040e4646431ac2ccac22e42679e91466084
# via -r requirements.txt
pytest==9.1.1 \
--hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \
Expand Down
2 changes: 1 addition & 1 deletion packages/factory-local/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ neo4j>=5.23,<6
streamlit>=1.38,<2
pytest>=9.0.3,<10
beautifulsoup4>=4.12,<5
pypdf>=5,<7
pypdf>=6.21.2,<7
openai>=1.40,<3
python-dotenv>=1.0,<2
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ beautifulsoup4>=4.15.0
lxml>=6.1.3
pdfminer.six>=20260107
pdfplumber>=0.11.10
pypdf>=6.21.1,<7 # CVE-2026-71852, CVE-2026-71870; earlier: CVE-2026-59935/59936 (inline-image DoS), CVE-2026-48155/48156, GHSA-jm82-fx9c-mx94
pypdf>=6.21.2,<7 # CVE-2026-115572/115580/115585; CVE-2026-71852, CVE-2026-71870; earlier: CVE-2026-59935/59936 (inline-image DoS), CVE-2026-48155/48156, GHSA-jm82-fx9c-mx94
fpdf2>=2.8.9,<3
python-docx>=1.2.0
docx2txt>=0.9
Expand Down
6 changes: 3 additions & 3 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3198,9 +3198,9 @@ pyotp==2.10.0 \
--hash=sha256:1df2f6a1bcc3bb0716172a5215ddc2f8c7c7fd26a13df9927d52e1746934836c \
--hash=sha256:d01e9703443616b03c57c700b5cbffd56a1f929c1b0f8f03131bc78c1fca9d3f
# via -r requirements.in
pypdf==6.21.1 \
--hash=sha256:2d62c383acd6e0dff0a96322ba8b1135229343b084d73c6f6cfd947fd291afcb \
--hash=sha256:553e2d42f5bb3bb2a785a7d3de8ef90bb7df74b5413eff5a7b4388007ea9254b
pypdf==6.21.2 \
--hash=sha256:0a3e1c5c886a1be03646c16a04827cc166c2a0575c971b26a4bf89686551c2d4 \
--hash=sha256:1d44d1a3a200f25062adff0085b02040e4646431ac2ccac22e42679e91466084
# via -r requirements.in
pypdfium2==5.13.0 \
--hash=sha256:07f58e91b8c45ca144a1ff3008faf3c73ef8a5e9fb32988831788363288228cd \
Expand Down
4 changes: 3 additions & 1 deletion scripts/ci/check_supply_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
from pathlib import Path

SHA40 = re.compile(r"^[0-9a-f]{40}$")
USES_RE = re.compile(r"[\"']?uses[\"']?:\s*[\"']?([A-Za-z0-9_.\-/]+)(?:@([^\"'#\s]+))?[\"']?")
USES_RE = re.compile(
r"(?:^\s*(?:-\s*)?|[\[{,]\s*)[\"']?uses[\"']?\s*:\s*[\"']?([A-Za-z0-9_.\-/]+)(?:@([^\"'#\s]+))?[\"']?"
)
USES_KEY_RE = re.compile(r"^\s*(?:-\s*)?[\"']?uses[\"']?\s*:(.*)$")
PIPE_SHELL_RE = re.compile(r"\|\s*(sudo(\s+-\S+)*\s+)?(sh|bash)\b")
LATEST_URL_RE = re.compile(r"https?://\S*latest\S*|@latest\b", re.IGNORECASE)
Expand Down
Loading
Loading