Files
odysseus/.github/workflows/ci.yml
T
dependabot[bot] cde0b58b93 chore(deps): bump the actions group across 1 directory with 11 updates
Bumps the actions group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-node](https://github.com/actions/setup-node) | `7.0.0` | `7.1.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `10.2.0` | `10.3.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.7` | `4.38.3` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.7` | `4.38.3` |
| [hadolint/hadolint-action](https://github.com/hadolint/hadolint-action) | `3.4.0` | `3.5.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.3.0` | `4.4.1` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.3.0` | `7.4.0` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.37.7` | `4.38.3` |
| [actions/deploy-pages](https://github.com/actions/deploy-pages) | `5.0.0` | `5.0.1` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |



Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `astral-sh/setup-uv` from 10.2.0 to 10.3.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/c18668ad3cf93ea998bef934396af7bb5c839dc7...1c37ad07a6a961277cf70c0d37d6f313000f5884)

Updates `github/codeql-action/init` from 4.37.7 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...24c54180a607b1449ed407dd24f251e4e9147c8d)

Updates `github/codeql-action/analyze` from 4.37.7 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...24c54180a607b1449ed407dd24f251e4e9147c8d)

Updates `hadolint/hadolint-action` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/hadolint/hadolint-action/releases)
- [Commits](https://github.com/hadolint/hadolint-action/compare/2a66e89f53d0771bb131a7fa31f3136336094aa6...06be81baf89a55ffd0e24b8f04a4185738dd3387)

Updates `docker/setup-buildx-action` from 4.3.0 to 4.4.1
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069)

Updates `docker/build-push-action` from 7.3.0 to 7.4.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

Updates `github/codeql-action/upload-sarif` from 4.37.7 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...24c54180a607b1449ed407dd24f251e4e9147c8d)

Updates `actions/deploy-pages` from 5.0.0 to 5.0.1
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/cd2ce8fcbc39b97be8ca5fce6e763baed58fa128...368f82528645a54fb793d4d04e342629a3f51346)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: hadolint/hadolint-action
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/build-push-action
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/deploy-pages
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 17:39:20 +00:00

250 lines
10 KiB
YAML

name: CI
on:
push:
branches: [main, dev]
pull_request:
# Least privilege: none of the jobs write to the repo.
permissions:
contents: read
# Cancel superseded runs on the same ref to save Actions minutes.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
focused-test-guidance:
name: Focused test guidance (report-only)
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Report changed test paths
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
report_file="$RUNNER_TEMP/focused-test-guidance.md"
publish_report() {
cat "$report_file"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
cat "$report_file" >> "$GITHUB_STEP_SUMMARY" || true
fi
return 0
}
report_unavailable() {
{
printf '%s\n\n' '## Focused test guidance unavailable (report-only)'
printf '%s\n\n' "$1"
printf '%s\n' 'Existing blocking CI remains the source of truth.'
} > "$report_file"
publish_report
exit 0
}
if [ -z "$BASE_SHA" ] || [ -z "$HEAD_SHA" ]; then
report_unavailable "Pull request base/head metadata is missing."
fi
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
report_unavailable "The pull request base commit is unavailable locally."
fi
if ! git cat-file -e "${HEAD_SHA}^{commit}" 2>/dev/null; then
report_unavailable "The pull request head commit is unavailable locally."
fi
if ! python3 .github/scripts/focused_test_guidance.py \
--base-sha "$BASE_SHA" \
--head-sha "$HEAD_SHA" > "$report_file"; then
report_unavailable "The focused test guidance helper could not produce a report."
fi
publish_report
python-syntax:
name: Python syntax (compileall)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
# Byte-compile sources — catches syntax errors without installing deps.
- run: python -m compileall -q app.py core routes src services scripts tests
- name: Check committed frontend deployment modules
run: python scripts/build_frontend.py --check
node-syntax:
name: JS syntax (node --check)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "20"
- run: npm ci --ignore-scripts --no-audit --no-fund
- name: Check frontend architecture
run: node scripts/check_frontend.mjs
# Syntax-check our own JS (skip vendored libs in static/lib).
- name: node --check
run: |
shopt -s globstar nullglob
for f in static/app.js static/js/**/*.js frontend/src/**/*.js; do
node --check "$f"
done
node --check scripts/check_frontend.mjs
- name: Test frontend architecture gates and stateless proof
run: node --test tests/frontendArchitecture.test.mjs
python-tests:
name: Python tests (pytest ${{ matrix.shard }})
# Keep the namespace/AppArmor setup tied to the audited Ubuntu release.
runs-on: ubuntu-24.04
# Make Python test validation authoritative for the configured scope.
strategy:
# Report every failing section in one run instead of cancelling the rest
# the moment one shard goes red.
fail-fast: false
matrix:
# Shards partition the suite by test file, so the four together run
# every test exactly once. tests/_shards.py owns the partition and
# tests/test_shards.py pins this list to its DEFAULT_SHARD_COUNT.
shard: ["1/4", "2/4", "3/4", "4/4"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
# Detect whether this PR only touches repository prose outside the Pages site.
# If so, skip the expensive pytest run while still reporting a passing check.
- name: Check for docs-only changes
id: docs-check
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.sha }}"
fi
# Keep website/ and assets/branding/ out of this bypass: pytest owns
# regression guards for their published-file and orphan-asset contracts.
changed=$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || git diff --name-only HEAD~1 HEAD)
non_docs=$(echo "$changed" | grep -Ev '^(docs/|[^/]+\.md$|\.github/[^/]+\.md$)' || true)
if [ -z "$non_docs" ]; then
echo "docs_only=true" >> "$GITHUB_OUTPUT"
echo "Docs-only change detected — skipping pytest."
else
echo "docs_only=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
python-version: "3.11"
# uv resolves the same versions as pip but installs ~5x faster, and its
# cache keeps each shard from re-downloading the whole requirement set.
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
enable-cache: true
cache-dependency-glob: requirements.txt
prune-cache: false
- run: uv pip install --system -r requirements.txt
if: steps.docs-check.outputs.docs_only != 'true'
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
node-version: "20"
cache: npm
- run: npm ci
if: steps.docs-check.outputs.docs_only != 'true'
# Browser binaries are keyed on the lockfile's Playwright version. On a
# hit, --with-deps still installs the system libraries but skips the
# browser downloads.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
path: ~/.cache/ms-playwright
key: ms-playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx playwright install --with-deps chromium
if: steps.docs-check.outputs.docs_only != 'true'
- run: mkdir -p data # sqlite DB lives at ./data/app.db
if: steps.docs-check.outputs.docs_only != 'true'
# One apt pass for FFmpeg (media integration tests) and bubblewrap
# (containment, verified in the next step).
- name: Install FFmpeg and bubblewrap
if: steps.docs-check.outputs.docs_only != 'true'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ffmpeg bubblewrap
command -v ffmpeg
ffmpeg -version | head -n 1
- name: Establish functional bubblewrap containment
if: steps.docs-check.outputs.docs_only != 'true'
shell: bash
run: |
set -euo pipefail
bwrap --version
sysctl kernel.unprivileged_userns_clone user.max_user_namespaces \
kernel.apparmor_restrict_unprivileged_userns
if [ "$(sysctl -n kernel.unprivileged_userns_clone)" != 1 ] || \
[ "$(sysctl -n user.max_user_namespaces)" -eq 0 ]; then
echo '::error::The pytest runner must allow unprivileged user namespaces; kernel namespace support is disabled.'
exit 1
fi
# Match containment._bwrap_available(): PID and mount namespaces,
# including fresh proc/dev mounts, as the unprivileged runner user.
bwrap_probe() {
timeout 3s bwrap --die-with-parent --unshare-pid --ro-bind / / \
--proc /proc --dev /dev /bin/true
}
if ! bwrap_probe && [ "$(sysctl -n kernel.apparmor_restrict_unprivileged_userns)" = 1 ]; then
# Ubuntu 24.04 restricts userns for unconfined applications. Allow
# only the distro bwrap entry point on this ephemeral pytest VM;
# retain the global restriction and all unrelated AppArmor policy.
sudo tee /etc/apparmor.d/odysseus-ci-bwrap > /dev/null <<'PROFILE'
abi <abi/4.0>,
include <tunables/global>
profile odysseus-ci-bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
}
PROFILE
sudo apparmor_parser -r /etc/apparmor.d/odysseus-ci-bwrap
fi
if ! bwrap_probe; then
echo '::error::Functional bubblewrap PID/mount namespaces are required for pytest; containment setup failed.'
exit 1
fi
# Also gate on the runtime probe so a future requirements change
# cannot silently leave this job without real containment coverage.
python - <<'PY'
from src import containment
if not containment._bwrap_available():
raise SystemExit("::error::Runtime bubblewrap functionality probe failed; pytest must not start.")
print("Runtime bubblewrap PID/mount namespace probe passed.")
PY
- name: pytest (shard ${{ matrix.shard }})
if: steps.docs-check.outputs.docs_only != 'true'
env:
PYTEST_SHARD: ${{ matrix.shard }}
run: python -m pytest -q -rs --durations=25 --shard "$PYTEST_SHARD"