Files
Chris TonjesandClaude Opus 5.5 5f2d885fdc Phase 0 milestone 0.8: CI pipeline on Gitea Actions
Workflow moved to .gitea/workflows/ci.yml (Gitea is the CI host). Every
gate now passes locally, step-for-step; green on main is pending an
act_runner on gitea.doesworks.net (runs #1-#5 are queued "Waiting").

Gate fixes found by actually running them:
- pip-audit: `--disable-pip` without `-r` was a usage error, so the old
  step could never pass. Now `pip-audit --strict .` audits the runtime
  dependency set from pyproject.
- pip-audit then flagged ecdsa (PYSEC-2026-1325, no fix), a hard dep of
  python-jose. Swapped to PyJWT (HS256 only; 3-line change in
  auth/security.py). Flag to OWC.
- PyJWT warned the test JWT secret was 11 bytes. JWT_SECRET now requires
  >= 32 bytes (RFC 7518 s3.2) at settings load; dev/compose/test defaults
  updated. Test-only value carries an inline gitleaks:allow.
- bandit B101: assert in retention._count_past replaced with a raise.
- gitleaks: gitleaks-action needs a GitHub token/license; replaced with
  the pinned 8.30.1 binary, sha256-verified, scanning full history.

Added:
- SBOM: CycloneDX 1.6 from a clean runtime-only venv (39 components),
  uploaded as a CI artifact. YPMS-POAM-042.
- cyclonedx-bom in dev extras; CI outputs gitignored.

158 tests passing, 95.83% coverage; ruff, mypy, bandit, pip-audit,
gitleaks clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:57:09 -04:00

82 lines
2.6 KiB
YAML

# YPMS CI — Gitea Actions (act_runner) on gitea.doesworks.net.
#
# Gates (PHASE0_PLAN 0.8; NIST SA-11, RA-5, CM-8, SR-4):
# lint (ruff) · types (mypy strict) · tests + 80% coverage floor (includes
# the Alembic migration-drift test) · SAST (bandit) · SCA (pip-audit on the
# runtime dependency set) · secret scan (gitleaks, full history) ·
# SBOM (CycloneDX 1.6, runtime-only, uploaded as an artifact).
#
# Runner: needs the `ubuntu-latest` label mapped to an act-compatible image
# (e.g. catthehacker/ubuntu:act-22.04) with outbound access to github.com for
# actions/* and the gitleaks release, and to PyPI.
#
# Suppressions are per-finding with a written reason, never blanket skips.
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
GITLEAKS_VERSION: "8.30.1"
# sha256 of gitleaks_8.30.1_linux_x64.tar.gz from the release checksums file
GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # gitleaks scans full history
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e '.[dev]'
- name: Lint (ruff)
run: ruff check src tests
- name: Type check (mypy)
run: mypy src
- name: Tests + coverage
run: pytest --cov=src/ypms --cov-report=term-missing --cov-fail-under=80
- name: SAST (bandit)
run: bandit -r src -c pyproject.toml
- name: SCA (pip-audit, runtime dependencies)
run: pip-audit --strict .
- name: Secret scan (gitleaks)
run: |
curl -sSfL -o gitleaks.tgz \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
echo "${GITLEAKS_SHA256} gitleaks.tgz" | sha256sum -c -
tar xzf gitleaks.tgz gitleaks
./gitleaks git . --redact --no-banner --exit-code 1
- name: SBOM (CycloneDX, runtime only)
run: |
python -m venv /tmp/runtime
/tmp/runtime/bin/pip install --quiet .
cyclonedx-py environment --of JSON -o sbom.cdx.json /tmp/runtime/bin/python
# v3 targets the GHES-style artifact API Gitea implements. Move to v4 only
# after confirming this Gitea server accepts it.
- uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.cdx.json