# Contributing ## Sign the CLA (once) Case is dual-licensed (AGPL for the box, MIT for the clients) and is also offered under a commercial license. That second license only works if the project can sublicense every line it ships, so contributions come in under a [Contributor License Agreement](CLA.md). **You keep your copyright.** It is a license with the right to sublicense, not an assignment. To sign, add your GitHub username to [`CLA-SIGNERS`](CLA-SIGNERS) in the same pull request as your first contribution: ```bash echo "your-github-username" >> CLA-SIGNERS ``` That edit records your agreement and covers everything you contribute after it. No bot, external service, or separate paperwork. New files take the `SPDX-License-Identifier` of their directory (see [LICENSE.md](LICENSE.md)); CI fails a file without one. ## Tests (no Docker) Use Python 3.12 and Node 22, matching CI. ```bash python3 -m venv .venv .venv/bin/pip install -r requirements-dev.txt for f in tests/test_*.py; do case "$f" in tests/test_acceptance.py) continue ;; esac .venv/bin/python "$f" || exit 1 done .venv/bin/ruff check --select F,E9 control-plane mcp image tests npm --prefix web ci npm --prefix web test ``` Unit tests run one file per process, as CI does: each file sets up its own vault when it is imported, so a single pytest run across the files shares one and fails. ## Acceptance tests (Docker) ```bash docker build -t case-desk:acceptance image CASE_ACCEPTANCE_IMAGE=case-desk:acceptance .venv/bin/python -m pytest -q tests/test_acceptance.py ``` The suite starts its own cased on a random loopback port, with a temporary vault and token. It removes only computers created by that run. Failed runs retain logs and screenshots in the printed scratch directory. `CASE_KEEP=1` also keeps the primary test computer and its volume for inspection. A7 needs ntfy and a phone. A8 is skipped unless `CASE_A8=1` is set, because it restarts the Docker VM and interrupts every container using it. Run that check only on a dedicated test machine. ## Layout - `control-plane/`: REST API (composition root: `cased.py`) - `image/`: desktop container - `mcp/case_mcp.py`: MCP wrapper - `web/web-ui/`: Drive UI - `compose.yaml`: self-host stack Be decent to people: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). PRs that touch credential injection (`image/deskd.py` login/inject, `store.py`, `links.py`) need maintainer review. [SECURITY.md](SECURITY.md) is the bar.