Skip to content

feat(ka): consolidate canonical runtime #861

feat(ka): consolidate canonical runtime

feat(ka): consolidate canonical runtime #861

Workflow file for this run

name: CI/CD Pipeline
on:
push:
branches: [ "main", "dev", "develop" ]
pull_request:
branches: [ "main", "dev", "develop" ]
jobs:
# Coding Standards
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Install Ruff
run: pip install ruff==0.15.0
- name: Run Ruff
run: ruff check . --select E9,F63,F7
# Backend Tests
backend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --require-hashes -r requirements.lock
- name: Security Audit
# CVE-2025-3000 (torch.jit.script memory corruption, local-host only, no
# patched release) is suppressed: torch is transitive and the app never
# calls torch.jit.script. Keep in sync with security.yml. See TODO.md.
# CVE-2026-12243 (nltk path traversal, no patch available for 3.9.x yet)
# PYSEC-2026-311 / GHSA-f4j7-r4q5-qw2c has no patched ChromaDB release;
# constrained deployment is documented and production remains blocked.
run: |
pip-audit -r requirements.lock --desc \
--ignore-vuln CVE-2025-3000 \
--ignore-vuln CVE-2026-12243 \
--ignore-vuln PYSEC-2026-311
- name: Run smoke bootstrap check
run: |
python scripts/test_smoke.py
- name: Set up Node.js for deterministic startup precheck
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
- name: Install frontend dependencies for startup precheck
run: |
cd frontend
npm ci
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
- name: Run deterministic startup precheck gate
run: |
python scripts/runtime_precheck.py --strict --skip-ports --allow-env-from-process --json-report reports/runtime_precheck_report_ci.json
env:
FLASK_ENV: "testing"
SESSION_SECRET: "ci-session-secret"
DATABASE_URL: "sqlite:///:memory:"
OPENAI_API_KEY: "mock-key-for-ci"
- name: Validate docs references
run: |
python scripts/verify_docs_references.py
- name: Validate SQLite/Postgres schema parity
run: |
python scripts/validate_schema_parity.py --report reports/schema_parity_report_ci.json
- name: Run Tests
run: |
python -m pytest tests/ --no-cov
env:
PYTHONPATH: "."
DATABASE_URL: "sqlite:///:memory:"
OPENAI_API_KEY: "mock-key-for-ci"
FLASK_ENV: "testing"
SESSION_SECRET: "ci-session-secret"
SECRET_KEY: "ci-test-secret-key"
JWT_SECRET_KEY: "ci-test-jwt-secret"
- name: Run API contract tests
run: |
python -m pytest -q --no-cov tests/contract/
env:
PYTHONPATH: "."
DATABASE_URL: "sqlite:///:memory:"
OPENAI_API_KEY: "mock-key-for-ci"
FLASK_ENV: "testing"
SESSION_SECRET: "ci-session-secret"
- name: Run local mode parity tests
run: |
python -m pytest -q --no-cov tests/parity/test_local_mode_parity.py
env:
PYTHONPATH: "."
DATABASE_URL: "sqlite:///:memory:"
FLASK_ENV: "testing"
SESSION_SECRET: "ci-session-secret"
- name: Run security regression smoke suite
run: |
python -m pytest -q --no-cov tests/security/test_security_headers.py tests/security/test_request_limits.py
env:
PYTHONPATH: "."
DATABASE_URL: "sqlite:///:memory:"
OPENAI_API_KEY: "mock-key-for-ci"
FLASK_ENV: "testing"
SESSION_SECRET: "ci-session-secret"
# Frontend Build
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install Dependencies
run: |
cd frontend
npm ci
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
- name: Generate Design Tokens
run: |
cd frontend
npm run tokens:build
- name: Lint Frontend
run: |
cd frontend
npm run lint
- name: Typecheck Frontend
run: |
cd frontend
npm run typecheck
- name: Run Frontend Unit Tests
run: |
cd frontend
npm run test
- name: Build Next.js
run: |
cd frontend
npm run build
env:
NEXT_TELEMETRY_DISABLED: 1
- name: Install Playwright system dependencies
timeout-minutes: 10
run: |
cd frontend
npx playwright install-deps chromium
- name: Set up ChromeDriver for a11y sweep
run: |
SYSTEM_CD=$(which chromedriver 2>/dev/null || echo "")
if [ -z "$SYSTEM_CD" ]; then
echo "System ChromeDriver not found; installing via npm postinstall"
cd frontend && node node_modules/chromedriver/install.js
else
echo "Using system ChromeDriver: $SYSTEM_CD ($(chromedriver --version))"
mkdir -p frontend/node_modules/chromedriver/lib/chromedriver
ln -sf "$SYSTEM_CD" frontend/node_modules/chromedriver/lib/chromedriver/chromedriver
fi
CHROME_BIN=$(which google-chrome google-chrome-stable chromium-browser chromium 2>/dev/null | head -1)
if [ -n "$CHROME_BIN" ]; then
echo "Found Chrome at $CHROME_BIN"
echo "CHROME_TEST_PATH=$CHROME_BIN" >> $GITHUB_ENV
else
echo "::warning::No Chrome binary found; a11y sweep may fail"
fi
- name: Run Frontend Accessibility Sweep
continue-on-error: true
run: |
cd frontend
npm run test:a11y:ci
- name: Run Frontend Route E2E Smoke
run: |
cd frontend
npm run test:e2e -- tests/e2e/route-sidebar-smoke.spec.ts
- name: Run Frontend Visual Regression Smoke (create missing baselines)
continue-on-error: true
run: |
cd frontend
npm run test:e2e:visual -- --update-snapshots=missing
- name: Run Frontend Visual Regression Smoke (validate)
run: |
cd frontend
npm run test:e2e:visual -- --update-snapshots=missing
windows-packaging-smoke:
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Build backend executable
run: |
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
python -m pip install --require-hashes -r requirements.lock
python scripts/build_backend.py
- name: Cache electron-builder binaries
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ env.LOCALAPPDATA }}\electron-builder\Cache
key: electron-builder-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
electron-builder-${{ runner.os }}-
- name: Install frontend dependencies
run: |
cd frontend
npm ci
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
CHROMEDRIVER_SKIP_DOWNLOAD: true
- name: Verify NSIS governance policy
run: |
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/windows/verify_nsis_governance.ps1 -RepoRoot (Get-Location).Path
- name: Build Windows installer
run: |
npm --prefix frontend run electron:dist
- name: Run packaging smoke (portable launch)
run: |
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/windows/run_packaging_smoke.ps1 -RepoRoot (Get-Location).Path
- name: Upload packaging smoke report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: packaging-smoke-report
path: |
reports/packaging_smoke_report.json
reports/nsis_governance_report.json
if-no-files-found: warn
governance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install governance dependencies
run: |
pip install ruff==0.15.0
npm --prefix frontend ci
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
- name: Run pre-commit governance checks
run: |
python scripts/dev/run_precommit_checks.py
- name: Verify environment parity
run: |
python scripts/verify_environment_parity.py --strict --json-report reports/environment_parity_report_ci.json
- name: Verify lockfile governance
run: |
python scripts/verify_lockfiles.py --json-report reports/lockfile_governance_report_ci.json
- name: Verify immutable workflow action pins
run: |
python scripts/verify_workflow_pins.py --json-report reports/workflow_pin_report_ci.json
- name: Verify retired release paths stay unreachable
run: |
python scripts/verify_legacy_retirement.py --report reports/legacy_retirement_report_ci.json
- name: Upload governance reports
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: governance-reports
path: |
reports/environment_parity_report_ci.json
reports/lockfile_governance_report_ci.json
reports/workflow_pin_report_ci.json
reports/legacy_retirement_report_ci.json
if-no-files-found: warn
# Docker Build (Verification)
docker-build:
needs: [backend-test, frontend-build, windows-packaging-smoke, governance]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build Backend Image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: backend/Dockerfile
push: false
tags: ukg-backend:latest
- name: Build Frontend Image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: frontend/Dockerfile
push: false
tags: ukg-frontend:latest