Thank you for your interest in contributing! This guide covers everything you need to get started.
- Rust (stable toolchain)
- Node.js 18+
- Docker and Docker Compose
- PostgreSQL 15+ (or use the provided Docker Compose stack)
# Clone the repository
git clone https://github.com/solutions-plug/predictIQ.git
cd predictIQ
# Start backing services (Postgres, Redis, etc.)
docker compose up -d
# API service
cd services/api
cp .env.example .env # fill in required values
cargo build
# Frontend
cd frontend
cp .env.example .env.local # fill in required values
npm install
npm run dev
# TTS service
cd services/tts
npm install
npm run devUse the following prefixes:
| Prefix | Purpose |
|---|---|
feat/ |
New feature |
fix/ |
Bug fix |
chore/ |
Maintenance, dependency updates |
docs/ |
Documentation only |
refactor/ |
Code refactoring without behaviour change |
perf/ |
Performance improvement |
ci/ |
CI/CD changes |
Examples: feat/market-resolution, fix/rate-limit-header, docs/contributing
This project uses Conventional Commits.
The CHANGELOG is auto-generated from commit messages via git-cliff — do not edit CHANGELOG.md manually.
<type>(<scope>): <short description>
[optional body]
[optional footer(s)]
| Type | When to use |
|---|---|
feat |
A new feature (triggers a minor version bump) |
fix |
A bug fix (triggers a patch version bump) |
docs |
Documentation changes only |
chore |
Build process, dependency updates, tooling |
refactor |
Code change that neither fixes a bug nor adds a feature |
perf |
Performance improvement |
test |
Adding or updating tests |
ci |
CI/CD configuration changes |
Append ! after the type/scope for breaking changes (triggers a major version bump):
feat(api)!: remove deprecated /v0 endpoints
feat(markets): add oracle result caching
fix(newsletter): handle duplicate subscription gracefully
docs(api): document rate-limit response headers
chore(deps): bump axum to 0.7.5
- Fork the repository and create your branch from
main. - Ensure all tests pass locally (see Running Tests).
- Keep commits focused — one logical change per commit.
- Open a PR against
mainwith a clear title following the commit convention. - Fill in the PR description:
- What changed and why
- How to test the change
- Any breaking changes or migration steps
- Link related issues using
Closes #<issue>in the PR description. - At least one approval is required before merging.
- Squash-merge is preferred to keep the history clean.
- Branch is up to date with
main - Commit messages follow Conventional Commits
- Tests added or updated for the change
- Documentation updated if behaviour changed
- No secrets or credentials committed
-
CHANGELOG.mdnot manually edited
cd services/api
cargo testIntegration tests require PostgreSQL, Redis, and a Stellar RPC node. The easiest way to start them is via the provided Makefile target, which starts a Docker Compose stack, runs the tests, and tears the stack down:
make test-integrationYou can also manage the services manually:
# Start services
docker compose -f docker-compose.test.yml up -d --wait
# Run tests
cd services/api
TEST_DATABASE_URL=postgres://predictiq_test:predictiq_test@localhost:5433/predictiq_test \
TEST_REDIS_URL=redis://localhost:6380 \
STELLAR_RPC_URL=http://localhost:8080 \
cargo test --test '*' -- --test-threads=1
# Tear down (always run, even on failure)
docker compose -f docker-compose.test.yml down -vIf a previous run left the stack running, clean it up first:
make test-integration-downEach integration test that touches the database should use the
with_test_transaction helper from tests/common/db_fixture.rs.
It wraps the test body in a database transaction that is rolled back
at the end, so no test leaves rows that can affect subsequent tests:
use common::db_fixture::with_test_transaction;
#[tokio::test]
async fn my_test() {
let pool = common::db_fixture::test_pool().await;
with_test_transaction(&pool, |mut conn| async move {
// use conn for all DB operations in this test
sqlx::query("INSERT INTO ...").execute(&mut *conn).await.unwrap();
// transaction is automatically rolled back when this closure returns
}).await;
}Do not commit within the closure — the rollback guarantees a clean slate for the next test regardless of execution order.
cd frontend
npm test # unit tests (Jest)
npm run test:e2e # end-to-end tests (Playwright)Visual regression tests use Playwright snapshots to detect unintended UI changes. Baseline screenshots are stored in Git LFS to keep the repository size manageable.
Baseline screenshot files are configured in .gitattributes to be tracked by Git LFS:
frontend/e2e/**/__snapshots__/*.png filter=lfs diff=lfs merge=lfs -text
Before running visual regression tests locally, ensure Git LFS is installed:
# Install Git LFS (macOS)
brew install git-lfs
git lfs install
# Or on Linux
sudo apt-get install git-lfs
git lfs installWhen UI changes are intentional and tests fail due to new screenshots, update baselines:
cd frontend
npx playwright test --update-snapshotsThis command captures new baseline screenshots. Commit the updated baselines via Git LFS:
git add frontend/e2e/**/__snapshots__/
git commit -m "test: update visual regression baselines"The visual regression tests use a configurable diff threshold (default: 0.1%) to prevent flakiness from minor pixel differences. Configure the threshold via the VISUAL_DIFF_THRESHOLD environment variable:
# Run with custom threshold (e.g., 0.2%)
VISUAL_DIFF_THRESHOLD=0.2 npm run test:e2eIn CI, the threshold is enforced automatically. Tests fail if the pixel diff exceeds the configured threshold.
cd services/tts
npm testcd contracts/predict-iq
make testValidation logic in src/validation.rs is covered by property-based tests
using proptest. These run as
part of cargo test and are gated in CI with at least 1 000 cases per
property via PROPTEST_CASES=1000.
To run them locally with the same case count:
cd services/api
PROPTEST_CASES=1000 cargo test prop_When adding a new validation function, add a corresponding proptest! block
that at minimum covers:
- Zero-length input
- Input longer than
MAX_LEN + 1 - All-whitespace strings
- Strings containing null bytes (
\0) - Strings that must pass unchanged (valid inputs)
The services/api crate declares a rust-version field in its Cargo.toml.
This is the oldest Rust toolchain version the crate is guaranteed to compile on.
| Crate | MSRV |
|---|---|
predictiq-api (services/api) |
1.75.0 |
-
The MSRV is set to the version required by the most-restrictive direct dependency (currently
axum 0.7,sqlx 0.8, andtower-http 0.6, all of which require ≥ 1.75). -
Bumping the MSRV is a semver-minor change and must be documented in
CHANGELOG.mdvia achore(api): bump MSRV to X.Y.Zcommit. -
A dedicated CI job (
.github/workflows/msrv.yml) installs the declared MSRV toolchain usingrustupand runscargo check+cargo build --releaseagainst it on every PR that touchesservices/api/. -
To verify the MSRV locally:
rustup toolchain install 1.75 rustup run 1.75 cargo check --manifest-path services/api/Cargo.toml
- Follow
rustfmtdefaults — runcargo fmtbefore committing. - Lint with
cargo clippy -- -D warnings.
- ESLint and Prettier are configured in the
frontend/directory. - Run
npm run lintandnpm run formatbefore committing.
- Keep line length reasonable (80–100 characters).
- Use 2-space indentation for YAML.
The main branch is protected. The following rules are enforced:
- Pull request required — direct pushes to
mainare not allowed; all changes must go through a PR. - CI must pass — all status checks in
.github/workflows/must succeed before a PR can be merged. - At least 1 approval required — a PR must receive at least one approving review from a team member.
- No force pushes —
git push --forcetomainis disabled. - No branch deletion —
maincannot be deleted.
These rules are configured in the repository settings under Settings → Branches → Branch protection rules.
Sensitive paths have designated reviewers defined in .github/CODEOWNERS. GitHub automatically requests a review from the relevant owner when a PR touches those paths.
- Never commit real secrets or credentials.
- Copy
services/api/.env.exampletoservices/api/.envand fill in real values locally. The.envfile is gitignored. - All placeholder values in
.env.exampleare intentionally empty or clearly fake. - Gitleaks runs on every push to detect accidental secret commits (see
.gitleaks.toml).
This project enforces security scanning at every stage of the development lifecycle.
| Tool | Scope | Threshold |
|---|---|---|
| cargo-audit | Rust dependencies (contracts + API) | Fails CI on any known CVE |
| Semgrep | Rust, Node.js, TypeScript, JavaScript source | Fails CI on error-level findings; rulesets: p/security-audit, p/rust, p/nodejs, p/typescript, p/javascript |
| Gitleaks | Git history and staged changes | Fails CI and pre-push hook on any detected secret |
| TruffleHog | Git history (verified secrets only) | Fails CI on verified secrets |
| Trivy | Filesystem and container images | Fails CI on CRITICAL or HIGH findings |
| CodeQL | JavaScript, TypeScript, Rust | Fails CI on security-extended queries |
Install and activate the gitleaks pre-push hook to catch secrets before they reach CI:
# Install gitleaks (macOS)
brew install gitleaks
# Or on Linux
wget https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_amd64 -O gitleaks
sudo install gitleaks /usr/local/bin/
# Activate the project hook (run once after cloning)
git config core.hooksPath .githooksThe hook runs gitleaks protect --staged on every git push, scanning staged commits against .gitleaks.toml. To skip in an emergency (e.g. a false positive you've already triaged):
SKIP=gitleaks git pushCustom rules for Stellar keys and API tokens are defined in .gitleaks.toml.
Open an issue or start a discussion on GitHub.