Skip to content

Latest commit

 

History

History
422 lines (298 loc) · 12.1 KB

File metadata and controls

422 lines (298 loc) · 12.1 KB

Contributing to PredictIQ

Thank you for your interest in contributing! This guide covers everything you need to get started.

Table of Contents


Development Setup

Prerequisites

Getting Started

# 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 dev

Branch Naming

Use 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


Commit Conventions

This project uses Conventional Commits.
The CHANGELOG is auto-generated from commit messages via git-cliff — do not edit CHANGELOG.md manually.

Format

<type>(<scope>): <short description>

[optional body]

[optional footer(s)]

Types

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

Examples

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

Pull Request Process

  1. Fork the repository and create your branch from main.
  2. Ensure all tests pass locally (see Running Tests).
  3. Keep commits focused — one logical change per commit.
  4. Open a PR against main with a clear title following the commit convention.
  5. Fill in the PR description:
    • What changed and why
    • How to test the change
    • Any breaking changes or migration steps
  6. Link related issues using Closes #<issue> in the PR description.
  7. At least one approval is required before merging.
  8. Squash-merge is preferred to keep the history clean.

PR Checklist

  • 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.md not manually edited

Running Tests

API (Rust)

cd services/api
cargo test

Integration Tests (API — requires backing services)

Integration 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-integration

You 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 -v

If a previous run left the stack running, clean it up first:

make test-integration-down

Database fixture — transaction rollback

Each 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.

Frontend (Next.js)

cd frontend
npm test              # unit tests (Jest)
npm run test:e2e      # end-to-end tests (Playwright)

Visual Regression Tests

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 Storage

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 install

Updating Baselines Locally

When UI changes are intentional and tests fail due to new screenshots, update baselines:

cd frontend
npx playwright test --update-snapshots

This 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"

Visual Diff Threshold

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:e2e

In CI, the threshold is enforced automatically. Tests fail if the pixel diff exceeds the configured threshold.

TTS Service

cd services/tts
npm test

Smart Contracts

cd contracts/predict-iq
make test

Property-Based Tests

Validation 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)

Minimum Supported Rust Version (MSRV)

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.

Current MSRV

Crate MSRV
predictiq-api (services/api) 1.75.0

Policy

  • The MSRV is set to the version required by the most-restrictive direct dependency (currently axum 0.7, sqlx 0.8, and tower-http 0.6, all of which require ≥ 1.75).

  • Bumping the MSRV is a semver-minor change and must be documented in CHANGELOG.md via a chore(api): bump MSRV to X.Y.Z commit.

  • A dedicated CI job (.github/workflows/msrv.yml) installs the declared MSRV toolchain using rustup and runs cargo check + cargo build --release against it on every PR that touches services/api/.

  • To verify the MSRV locally:

    rustup toolchain install 1.75
    rustup run 1.75 cargo check --manifest-path services/api/Cargo.toml

Code Style

Rust

  • Follow rustfmt defaults — run cargo fmt before committing.
  • Lint with cargo clippy -- -D warnings.

TypeScript / JavaScript

  • ESLint and Prettier are configured in the frontend/ directory.
  • Run npm run lint and npm run format before committing.

YAML / Markdown

  • Keep line length reasonable (80–100 characters).
  • Use 2-space indentation for YAML.

Branch Protection Rules

The main branch is protected. The following rules are enforced:

  • Pull request required — direct pushes to main are 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 pushesgit push --force to main is disabled.
  • No branch deletionmain cannot be deleted.

These rules are configured in the repository settings under Settings → Branches → Branch protection rules.

Code Ownership

Sensitive paths have designated reviewers defined in .github/CODEOWNERS. GitHub automatically requests a review from the relevant owner when a PR touches those paths.

Secrets and Environment Variables

  • Never commit real secrets or credentials.
  • Copy services/api/.env.example to services/api/.env and fill in real values locally. The .env file is gitignored.
  • All placeholder values in .env.example are intentionally empty or clearly fake.
  • Gitleaks runs on every push to detect accidental secret commits (see .gitleaks.toml).

SAST and Security Tooling

This project enforces security scanning at every stage of the development lifecycle.

Tools and thresholds

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

Local setup

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 .githooks

The 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 push

Custom rules for Stellar keys and API tokens are defined in .gitleaks.toml.


Questions?

Open an issue or start a discussion on GitHub.