Skip to content

fix(frontend): keep the attachments that uploaded when one in the bat… #546

fix(frontend): keep the attachments that uploaded when one in the bat…

fix(frontend): keep the attachments that uploaded when one in the bat… #546

Workflow file for this run

name: Quality
# Split into two jobs so a pull request gets fast feedback:
#
# fast Lint, tests, contracts, and the frontend. Runs on every PR and on
# main. The full local tier covers the same fast gates; the quick tier
# omits browser and bundle checks for iteration (see CONTRIBUTING.md).
#
# heavy Windows packaging -- the launcher bundle, the one-folder build, and
# a smoke test of the packaged executable. Runs after the fast and
# supported-Python jobs on PRs, pushes, and manual dispatch.
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
fast:
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.11"
cache: pip
cache-dependency-path: requirements-dev.lock.txt
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm
cache-dependency-path: frontend/package-lock.json
# requirements*.lock.txt pin exact versions and hashes for every
# dependency (direct and transitive) resolved for this job's Python
# 3.11 target. A stale lock -- committed after editing pyproject.toml
# without regenerating it -- would silently let this job install
# something different from what it claims to verify, so check first.
#
# `uv pip compile` reads its own prior output file as a starting point
# and only re-resolves what pyproject.toml's constraints actually force
# -- regenerating in place (the same path already committed) and then
# diffing against git reuses every already-locked version for an
# unrelated transitive dependency, rather than re-resolving it to
# whatever happens to be newest on the index right now. Compiling to a
# fresh path instead would make this check flaky: an unconstrained
# transitive dependency (e.g. numpy) can pick up a new upstream release
# between two independent resolves with no repository change at all.
- name: Verify dependency lockfiles are up to date
shell: pwsh
run: |
python -m pip install uv
uv pip compile pyproject.toml --python-version 3.11 --generate-hashes -o requirements.lock.txt
uv pip compile pyproject.toml --extra dev --python-version 3.11 --generate-hashes -o requirements-dev.lock.txt
# `git diff --name-only` rather than `git diff --exit-code`: a
# non-zero native exit is a terminating error under this shell's
# $ErrorActionPreference, so the throw below never ran and the
# regeneration instructions never reached the log. Without
# --exit-code, diff exits 0 either way and reports drift as output.
# Content comparison also matters: uv writes LF, so `git status`
# would flag a line-ending-only difference that is not real drift.
$drift = git --no-pager diff --name-only -- requirements.lock.txt requirements-dev.lock.txt
if ($drift) {
git --no-pager diff -- requirements.lock.txt requirements-dev.lock.txt
throw "requirements*.lock.txt does not match pyproject.toml. Regenerate with 'uv pip compile pyproject.toml --python-version 3.11 --generate-hashes -o requirements.lock.txt' (add --extra dev for the dev lock) and commit the result."
}
- name: Install dependencies
run: python -m pip install -r requirements-dev.lock.txt
- name: Lint Python
run: python -m ruff check backend tests tools main.py app_factory.py
- name: Type-check Python
run: python -m mypy
- name: Run headless tests
run: python -m pytest
- name: Run the artifact boundary review
run: python tools/artifact_boundary_review.py --json --strict
- name: Compile application modules
run: python -m compileall -q main.py app_factory.py backend
- name: Verify generated API contracts
run: |
python tools/generate_contracts.py --check
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install Playwright Chromium
working-directory: frontend
run: npx playwright install chromium
- name: Check frontend types
working-directory: frontend
run: npm run typecheck
- name: Lint frontend
working-directory: frontend
run: npm run lint
- name: Run frontend unit tests
working-directory: frontend
run: npm test -- --run
- name: Run frontend browser tests
working-directory: frontend
timeout-minutes: 10
run: npm run e2e -- --workers=1
- name: Build frontend bundle
working-directory: frontend
run: npm run build
python-compatibility:
name: Python ${{ matrix.python-version }} compatibility
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: requirements-dev.txt
# Deliberately loose, unlocked pins here: this job's purpose is to
# catch a dependency that only breaks on one specific Python version,
# which a lock resolved for a single interpreter (see the `fast` and
# `heavy` jobs) cannot exercise. Do not switch this to
# requirements-dev.lock.txt.
- name: Install dependencies
run: python -m pip install -r requirements-dev.txt
- name: Lint Python
run: python -m ruff check backend tests tools main.py app_factory.py
- name: Run Python tests
run: python -m pytest -q
- name: Compile application modules
run: python -m compileall -q main.py app_factory.py backend
heavy:
# Packaging is slow, but a green PR must prove that launcher and runtime
# changes still produce a build that starts.
needs: [fast, python-compatibility]
runs-on: windows-latest
timeout-minutes: 90
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.11"
cache: pip
cache-dependency-path: requirements.lock.txt
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: python -m pip install -r requirements.lock.txt
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Verify launcher-managed bundle
shell: pwsh
run: python main.py --build-frontend --data-dir "$env:RUNNER_TEMP\cortex-quality-data"
- name: Install package builder
run: python -m pip install "pyinstaller==6.14.2"
- name: Prepare signed WebView2 bootstrapper
shell: pwsh
run: ./packaging/prepare_webview2.ps1
- name: Build one-folder Windows package
run: python -m PyInstaller --noconfirm --clean packaging/Cortex.spec
- name: Verify packaged WebView2 bootstrapper
shell: pwsh
run: |
$bootstrapper = Get-ChildItem dist/Cortex -Recurse -Filter MicrosoftEdgeWebview2Setup.exe | Select-Object -First 1
if ($null -eq $bootstrapper) { throw "Packaged WebView2 bootstrapper is missing." }
$signature = Get-AuthenticodeSignature -LiteralPath $bootstrapper.FullName
if ($signature.Status -ne "Valid") { throw "Packaged WebView2 signature is invalid." }
- name: Smoke-test packaged headless launcher
shell: pwsh
timeout-minutes: 5
run: |
$data = Join-Path $env:RUNNER_TEMP "cortex-package-smoke"
$executable = Join-Path (Get-Location) "dist\Cortex\Cortex.exe"
$process = $null
$ready = $false
try {
$process = Start-Process -FilePath $executable -ArgumentList @("--headless", "--data-dir", $data) -PassThru
$deadline = (Get-Date).AddSeconds(30)
while ((Get-Date) -lt $deadline) {
$process.Refresh()
if ($process.HasExited) {
throw "Packaged Cortex exited before its health endpoint became ready (code $($process.ExitCode))."
}
$recordPath = Join-Path $data "cortex.instance.json"
if (Test-Path -LiteralPath $recordPath -PathType Leaf) {
try {
$record = Get-Content -LiteralPath $recordPath -Raw | ConvertFrom-Json
$response = Invoke-WebRequest -UseBasicParsing -TimeoutSec 2 -Uri "http://127.0.0.1:$($record.port)/api/v1/health/ready"
if ($response.StatusCode -eq 200) {
$ready = $true
break
}
} catch {
# The backend can be between record creation and bind.
}
}
Start-Sleep -Seconds 1
}
if (-not $ready) {
throw "Packaged Cortex did not expose its health endpoint within 30 seconds."
}
} finally {
if ($null -ne $process) {
$process.Refresh()
if (-not $process.HasExited) {
Stop-Process -Id $process.Id -Force
}
}
}