Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
148 changes: 146 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ name: pr
# `frontend`, `smoke-test`, `docker` (api buildx), `docker-ui` — each carry
# if: ${{ vars.SKIP_HEAVY_CI != 'true' }}
# so they can be skipped en masse to conserve GitHub Actions minutes during a
# budget crunch. The 4 sub-minute jobs (backend fast-lane unit tests, DCO,
# secrets guard, gitleaks) always run, retaining baseline signal.
# budget crunch. The sub-minute jobs (backend fast-lane unit tests, DCO,
# secrets guard, gitleaks) always run, retaining baseline signal. The two
# legal-compliance gates — `license-headers` (SPDX) and `license-inventory`
# (Apache-2.0 dependency check) — also ALWAYS run regardless of this switch:
# they're cheap (no service containers, no test suite) and must not let a
# missing header or a new GPL/AGPL runtime dep slip onto main while heavy jobs
# are off.
#
# Activate (skip heavy jobs): gh variable set SKIP_HEAVY_CI --body true
# Restore (run everything): gh variable delete SKIP_HEAVY_CI
Expand Down Expand Up @@ -94,6 +99,145 @@ jobs:
- name: pytest backend/tests/unit/
run: uv run pytest backend/tests/unit/ --no-cov -q

# -------------------------------------------------------------------------
# License headers — REUSE (https://reuse.software/) SPDX compliance.
# ALWAYS runs (not gated by SKIP_HEAVY_CI): a legal-compliance gate must hold
# even during a CI-budget crunch. Cheap — no service containers, just resolve
# the `reuse` dev pin and lint every tracked file for an SPDX header (inline
# or via REUSE.toml). Fix locally with `uv run reuse annotate`
# (see CONTRIBUTING.md "License headers (SPDX)").
# -------------------------------------------------------------------------
license-headers:
name: license-headers
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
version: "0.5.7"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install project deps (frozen)
run: uv sync --frozen
- name: Lint SPDX headers (reuse)
run: uv run reuse lint

# -------------------------------------------------------------------------
# Dependency license inventory — Apache-2.0 compatibility gate. ALWAYS runs
# (not gated by SKIP_HEAVY_CI). Regenerates docs/04_security/license-inventory.md
# from the locked closure (uv + pnpm) and fails if it's stale OR if any shipped
# dependency carries a forbidden (GPL/AGPL) or unclassified license. Needs both
# toolchains because the script spans Python (uv tree + pip-licenses) and
# frontend (pnpm licenses). Fix locally:
# `make license-inventory && git add docs/04_security/license-inventory.md`.
# -------------------------------------------------------------------------
license-inventory:
name: license-inventory
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
version: "0.5.7"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install Python deps (frozen)
run: uv sync --frozen
- uses: pnpm/action-setup@v6
with:
version: 9
- uses: actions/setup-node@v6
with:
node-version: 22
cache: "pnpm"
cache-dependency-path: ui/pnpm-lock.yaml
- name: Install pnpm deps (frozen)
run: pnpm --dir ui install --frozen-lockfile
- name: Check license inventory
run: uv run python scripts/gen_license_inventory.py --check

# -------------------------------------------------------------------------
# Static checks — ALWAYS run (not gated by SKIP_HEAVY_CI). Bundles the cheap,
# service-free quality gates that would otherwise be skipped during a CI-budget
# crunch: backend ruff + mypy + the bash drift-guards, and frontend prettier +
# eslint + tsc + vitest. No Postgres/Redis/ES/OpenSearch, no `next build`, no
# pytest — those stay in the heavy `backend` / `frontend` jobs. When the
# kill-switch is OFF these checks also run inside those jobs (minor, accepted
# duplication); when it's ON this job keeps static analysis + the frontend unit
# suite enforced on every PR.
# -------------------------------------------------------------------------
static-checks:
name: static-checks (lint + types + fe unit, always-run)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
version: "0.5.7"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install project deps (frozen)
run: uv sync --frozen
- name: Restore mypy + ruff caches
uses: actions/cache@v5
with:
path: |
.mypy_cache
.ruff_cache
key: mypy-ruff-${{ runner.os }}-${{ hashFiles('uv.lock', 'pyproject.toml') }}
restore-keys: |
mypy-ruff-${{ runner.os }}-

- uses: pnpm/action-setup@v6
with:
version: 9
- uses: actions/setup-node@v6
with:
node-version: 22
cache: "pnpm"
cache-dependency-path: ui/pnpm-lock.yaml
- name: pnpm install
run: pnpm --dir ui install --frozen-lockfile

- name: ruff check
run: uv run ruff check .
- name: Format check (ruff)
run: uv run ruff format --check .
- name: mypy --strict
run: uv run mypy backend/

- name: Verify source-of-truth enum comments
run: bash scripts/ci/verify_enum_source_of_truth.sh
- name: Verify demo-cluster slug parity
run: bash scripts/ci/verify_demo_slug_parity.sh
- name: Verify install.sh builds every buildable Compose service
run: bash scripts/ci/verify_install_builds_all_services.sh

- name: Format check (prettier)
run: pnpm --dir ui exec prettier --check src package.json tsconfig.json eslint.config.mjs .prettierrc.json
- name: ESLint
run: pnpm --dir ui lint
- name: tsc --noEmit
run: pnpm --dir ui typecheck
- name: vitest
run: pnpm --dir ui test

backend:
name: backend (lint + typecheck + tests + coverage)
# TEMP CI-budget kill-switch — see the SKIP_HEAVY_CI note at the top of
Expand Down
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,27 @@ and fail if any tracked file is uncovered — fix a new file by annotating it or
adding a glob to `REUSE.toml`. Full compliance is reported as
"Congratulations! Your project is compliant…".

### Dependency licenses

RelyLoop ships under Apache-2.0, which is incompatible with **strong copyleft
(GPL / AGPL)** in a *shipped* dependency. Every dependency is inventoried in
[`docs/04_security/license-inventory.md`](docs/04_security/license-inventory.md),
generated from the locked closure (`uv tree` + `pnpm licenses`):

```bash
make license-inventory # or: uv run python scripts/gen_license_inventory.py
```

The `license-inventory` CI job runs the same script with `--check`; it fails if
the committed inventory is stale **or** if any shipped dependency carries a
forbidden (GPL/AGPL) or unclassified license. If you add a dependency:

1. Run `make license-inventory` and commit the updated file.
2. If it introduces a non-permissive license, add an adjudication to the
`ADJUDICATIONS` map in [`scripts/gen_license_inventory.py`](scripts/gen_license_inventory.py).
Dev-only copyleft (e.g. the `reuse` linter, GPL-3.0) is fine — it never
ships. Strong copyleft in a runtime dependency is not; replace the dep.

### Verifying the gitleaks hook

To confirm secret scanning is wired correctly on your machine:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ui-fmt ui-lint ui-typecheck ui-test ui-build ui-dev \
pre-commit pre-commit-install \
up down restart logs reset migrate migrate-create seed-clusters seed-es \
dev dashboard
dev dashboard license-inventory

help: ## Show this help message
@echo ""
Expand Down Expand Up @@ -185,3 +185,6 @@ migrate-create: ## Create new migration: make migrate-create name=<slug> (runs

dashboard: ## Regenerate docs/00_overview/<release>_dashboard.{html,md} from feature folders
@python3 scripts/build_mvp1_dashboard.py

license-inventory: ## Regenerate the dependency license inventory (docs/04_security/license-inventory.md)
uv run python scripts/gen_license_inventory.py
Loading
Loading