code-quality.yml ran every pre-commit hook minus a skip list in each job, so newer hooks leaked in: check-docs ran four times per PR, the translation hooks ran in every job, and Biome ran on every PR whether the web app changed or not. The desktop backend had no security scan at all, the gates counted a cancelled or timed-out job as passing, and hooks pinned tool versions a second time. - code-quality.yml keeps only what belongs to no product, run by hook name on every PR: file checks, check-docs and detect-secrets - docker-tests.yml replaces backend-tests.yml and e2e-tests.yml for surfsense_backend and surfsense_web: ruff, bandit, Biome, backend unit and integration tests, and the e2e journey, each job run only when its files change - desktop-tests.yml filters per job instead of per workflow, adds bandit for the desktop backend, and checks translations, failing on a stale en.json that typecheck used to regenerate silently - each workflow ends in a gate (Docker Gate, Desktop Gate, Quality Gate) that always reports and fails on failed or cancelled jobs, so it can be made a required check without blocking PRs that don't touch that product - bandit is split into bandit-docker and bandit-desktop aliases, so each product scans only its own code - the Biome hook runs only when a commit touches surfsense_web, instead of blocking every commit on the app's existing formatting errors - Biome and formatjs run from each app's own install (pnpm exec, and a new translations:verify script), so their versions live only in package.json - hook versions updated: pre-commit-hooks v6.0.0, bandit 1.9.4, commitizen v4.19.0, and ruff v0.15.8 to match surfsense_backend's own ruff - surfsense_web's pnpm comes from its own package.json and the root package.json is removed; every action is on its latest version - CONTRIBUTING.md, docs/contracts/README.md, auth_mint.py, the translate skill and localization.md name the new workflows and commands
2.5 KiB
Contracts
A contract is a frozen file describing something that crosses from one tree to another. Each side builds and tests against the file and its fixture, not against the other side's code. These four were frozen for the 2.0 launch.
| # | Contract | Producer | Consumer | In use since |
|---|---|---|---|---|
| 1 | License file | surfsense_backend license routes |
surfsense_local license module |
2.0.0 |
| 2 | Scraper API auth | surfsense_backend capabilities routes |
the paid scraper plugin and surfsense_mcp |
not yet; neither side is built |
| 3 | Export bundle | surfsense_backend export |
surfsense_local import |
2.0.0 |
| 4 | Sunset flag | surfsense_backend /health |
legacy desktop v0.0.40, whose source is at tag archive/hosted-2026-09 |
v0.0.40, 12 Sep 2026; the flag reads true from T-0, 18 Sep 2026 |
Fixtures
export-sample/is an unzipped contract-3 bundle. Tests zip it in a setup step. Check it withpython3 check-export-sample.py.license-sample/holds contract-1 files signed by a test key, plus the generator that makes them. Regenerate and self-check withnode license-sample/generate.mjs.
Both checks are dependency-free and run from this folder.
Five tests read files in this folder by path. surfsense_local/backend/tests/integration/migration/test_import.py, surfsense_local/backend/tests/integration/license/test_license.py and surfsense_backend/tests/unit/services/test_account_export.py read the fixtures; surfsense_backend/tests/integration/test_account_export.py runs check-export-sample.py against a real export; and the release build's test-key guard, surfsense_local/backend/tests/packaging/test_license_key.py, reads the test public key. Moving this folder means updating all five in the same change, plus the skip entry in scripts/check_docs.py and the docs/contracts/** path filters in .github/workflows/docker-tests.yml and .github/workflows/desktop-tests.yml.
Changing a contract
A contract changes by a pull request that the owners of both sides approve. CODEOWNERS requests their review, but GitHub accepts one approval, so the second is by agreement. If the change is not backward compatible, bump the version field the contract names (format for the bundle, the alg/field list for the license file) and update the fixture in the same PR. Never change a fixture without changing the contract text that describes it.