Thank you for your interest in contributing! TRUSCA is an Apache-2.0 licensed, self-hosted SCA portal, and we welcome contributions from the community — code, documentation, translations, bug reports, and design feedback.
This document describes how to set up the project locally, the conventions we follow, and what we expect in a pull request.
AI-assisted development. This project is developed with AI-assisted tooling (Claude Code) for scaffolding, refactoring, and review. Design decisions, code review, and accountability for every merged change remain human-owned by the maintainers listed in
MAINTAINERS.md. Pull requests from contributors using similar tooling are welcome — please disclose in the PR description and treat the AI as a collaborator, not the author.
- Code of Conduct
- Getting Started
- Development Workflow
- Coding Standards
- Testing & Coverage Gates
- Harness-First Principle
- Pull Request Process
- Commit Messages
- Internationalization (i18n)
- Documentation
- Security Issues
- License & DCO
This project adheres to the Contributor Covenant 2.1. By participating, you agree to uphold its terms. See Reporting for how to raise unacceptable behavior privately.
- Docker + Docker Compose V1 (the hyphenated
docker-composecommand — V2 /docker composeis not supported in our development environment) - Python 3.12 (backend)
- Node.js 20 (frontend)
- Git
git clone https://github.com/trustedoss/trusca.git
cd trusca
cp .env.example .env # adjust as needed
docker-compose -f docker-compose.dev.yml up -dAfter ~30 seconds, all five containers (postgres, redis, backend, celery-worker, frontend) should be healthy. The frontend is served on http://localhost:5173, the backend API on http://localhost:8000.
# Backend — CI runs these as two jobs against separate databases. Locally,
# run them in one go or one at a time; a re-run against a database the other
# suite already wrote to can fail on leftover rows.
cd apps/backend
pytest tests/unit tests/integration --cov
# Frontend
cd apps/frontend
npm run test -- --coveragemain— protected, deployable. Direct pushes are disabled; everything goes through pull requests.feature/<short-topic>— your working branch. Keep it small and focused.
- Browse open issues labeled
good first issueorhelp wantedin the issue tracker. - For larger features, open a discussion or feature-request issue first so we can align on scope before you write code.
git fetch origin
git rebase origin/mainRebase, don't merge — we keep main linear.
We treat the codebase as a global commercial product, not a personal project. Be tasteful.
- Style:
ruff(lint + format). Runruff check . && ruff format .before committing. - Types:
mypystrict mode. Public functions must be fully annotated. - Async first: prefer
async deffor I/O-bound endpoints, services, and integrations. SQLAlchemy 2.0 async sessions are the default. - Errors: all 4xx / 5xx responses use RFC 7807 Problem Details (
application/problem+json). Required fields:type,title,status,detail,instance. Domain extensions aresnake_case. - Logging:
structlogJSON lines, one event per line.request_id,user_id,team_id, andtask_idare propagated automatically. Never log secrets, tokens, or full email addresses — use themask_piihelper. - Configuration: call
os.getenv()at runtime, not module load. Never cache env vars in module-level constants. - Database: PostgreSQL only — no SQLite, no in-memory. Schema changes require a new Alembic migration.
- Migrations: forward-only.
downgrade()ispassorraise NotImplementedError. Schema and data migrations are separate revisions. Breaking changes follow expand → migrate-data → contract.
- Style:
eslintflat config +prettier(runnpm run lint && npm run format). - Types: strict TypeScript.
anyrequires a justification comment. - Components: prefer
shadcn/uiprimitives. Custom UI must use Tailwind design tokens (seesrc/index.css) — never hardcode colors or sizes. - State: server state lives in TanStack Query; client UI state lives in Zustand. Don't mix.
- i18n: every user-visible string goes through
t(). No hardcoded English in JSX.
- Image tags: never
:latest. Pin to a minor + patch version (e.g.node:20.18.1-alpine,postgres:17.2-alpine). - Compose: use
docker-compose(V1, hyphenated).docker compose(V2) is not supported. - Third-party GitHub Actions: pin by commit SHA with the release in a trailing comment (
uses: owner/action@<sha> # v1.2.3). A tag is mutable —@v1runs whatever the owner moved it to, inside a job that can hold our registry credentials. Actions underactions/,github/anddocker/stay on major tags. - Secrets: never commit. Use
.env.examplefor the schema; real values go in.env(git-ignored) or GitHub Actions secrets.
We block PRs that lower test coverage. The thresholds are enforced in CI:
| Scope | Tool | Threshold |
|---|---|---|
| Backend lines, whole tree | coverage-gate (backend) |
≥ 80% (fail_under=80 in pyproject.toml) |
| Backend lines, changed by the PR | diff-cover in the same job |
≥ 80% |
| Frontend lines | vitest --coverage |
≥ 80% lines / 70% branches (vite.config.ts) |
| E2E core scenarios | Playwright (harness pattern) | not run on pull requests |
A change that lowers coverage below the floor will fail CI. Add tests for the lines you write.
The E2E row is the exception: the Playwright suite runs on the nightly schedule and on a manual workflow_dispatch, not on pull requests (.github/workflows/ci.yml). Opening a PR does not exercise it, so do not rely on it to catch a regression in a user-visible flow. If your change touches one, run the suite yourself or ask a maintainer to dispatch it.
- Unit: pure functions, schemas, parsers, RBAC predicates.
- Integration: anything that touches PostgreSQL, Redis, Celery, or an external integration. Use real services (via
docker-compose), not mocks. - E2E: user-visible flows. Login, scan execution, report download, admin actions.
Mocks for external paid APIs (e.g., GitHub App, GCP) are acceptable. Mocks for our own database / queue are not.
Write the test harness before the feature, not after.
Every new screen or domain area must ship with its harness — a class or module that exposes the domain in test-friendly verbs (auth.login(), scan.expectInProgress(), project.openVulnerabilitiesTab()). The feature implementation comes second.
Why:
- Refactors stay cheap. UI restyles don't break tests because tests speak domain language, not selectors.
- Tests document behavior. Reading the harness tells you what the feature is supposed to do.
- Reviewers can read tests first to understand the change.
If you add a feature with no harness, the PR is incomplete. See apps/frontend/tests/_harness/PortalPage.ts for the UI pattern, and the shared fixtures in apps/backend/tests/conftest.py for the API side.
- Fork & branch —
git checkout -b feature/my-change. - Implement — follow the coding standards above. Keep PRs small (< 500 lines diff is the sweet spot; > 1000 lines should usually be split).
- Self-review — run lint, typecheck, tests locally. Fix all warnings, not just errors.
- Open the PR — fill out the pull request template completely. Empty checklists block review.
- CI must pass — all three jobs (lint, typecheck, test) on both backend and frontend matrices. We do not merge red.
- Review — at least one maintainer approval. Security-sensitive changes (auth, API keys, Trivy / external scanner integrations, OAuth, build gate) require additional review by a maintainer with the
securityrole. - Merge — maintainers merge via "Squash and merge" to keep
mainlinear. The squash message uses the PR title — write good titles.
- Coverage drop below 80%
- New strings without i18n keys (or KO translations missing)
- New endpoint without OpenAPI documentation
- New feature without an updated Docusaurus page
docker compose(V2) usage,:latesttags, or module-levelos.getenv()caching- Mocking the database in tests
- Backwards-compat shims that have no current consumer
We do not require a Contributor License Agreement. Your contribution is licensed under Apache-2.0 by the act of submitting it (see License & DCO).
We follow a relaxed Conventional Commits style:
<type>(<scope>): <short summary>
<body — what and why, not how>
<footer — refs, breaking changes>
Types: feat, fix, refactor, docs, test, chore, ci, build, perf, style.
Examples:
feat(auth): add refresh token rotation with reuse detection
fix(dt): retry on 502 with exponential backoff
docs(install): document upgrade path for v2.0.1
Squash-merged PRs inherit the PR title — make it conventional.
Every user-visible string must exist in both English (apps/frontend/src/locales/en/*.json) and Korean (apps/frontend/src/locales/ko/*.json). PRs that add only English will be asked to add Korean before merge.
Translation conventions:
- Keys are flat and dot-namespaced:
auth.login.submit. - Korean translations follow the domain glossary in the docs site
(
docs-site/docs/reference/glossary.md). - Use ICU plural / select syntax for variable counts.
CI runs i18next-parser --fail-on-update to catch missing keys.
Every user-facing feature ships with a Docusaurus page in docs-site/docs/. Backend API changes update the OpenAPI schema (FastAPI auto-generates this) and are reflected in the hosted API Reference at /reference/api.
The public roadmap and release history live in ROADMAP.md and CHANGELOG.md. Larger proposals go through a GitHub issue / discussion before a PR — see GOVERNANCE.md.
Do not open public issues for security vulnerabilities. See SECURITY.md for the responsible disclosure process and our response SLA.
By contributing to this project, you certify that:
- The contribution is your original work, or you have the right to submit it.
- You license your contribution under the Apache License 2.0.
- You understand that the project and your contribution are public.
This is the Developer Certificate of Origin (DCO) 1.1 in spirit. We do not require sign-offs in commits, but the same understanding applies.
GOVERNANCE.md— how decisions are made and how to become a maintainerMAINTAINERS.md— who maintains which areaSUPPORT.md— where to ask questions and report problemsCODE_OF_CONDUCT.md— community standardsSECURITY.md— vulnerability disclosure
Thanks again for contributing — every PR, issue, translation, and design suggestion makes the project better.
— The TRUSCA maintainers