docs: correct POST /api/programs response shape and add restart-to-in… #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Hard-gate jobs (required on every PR to main). | |
| # --------------------------------------------------------------------------- | |
| lint: | |
| name: lint (fmt + clippy) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install toolchain (pinned MSRV via rust-toolchain.toml) | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci" | |
| - name: cargo fmt --check | |
| run: cargo fmt -- --check | |
| - name: cargo clippy (all-targets, -D warnings) | |
| run: cargo clippy --release --all-targets -- -D warnings | |
| unit: | |
| name: unit tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 12 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install toolchain | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci" | |
| - name: cargo test --lib | |
| run: cargo test --release --lib | |
| integration: | |
| name: integration tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| services: | |
| postgres: | |
| image: postgres:16 | |
| env: | |
| POSTGRES_DB: solarix | |
| POSTGRES_USER: solarix | |
| POSTGRES_PASSWORD: solarix | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U solarix -d solarix" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| DATABASE_URL: postgres://solarix:solarix@localhost:5432/solarix | |
| RUST_LOG: warn | |
| # NOTE: SOLANA_RPC_URL is NOT set at this level. The integration tests | |
| # that run today (bootstrap_test.rs, registration_test.rs) do not hit | |
| # the Solana RPC — they only exercise the DB bootstrap and registration | |
| # paths. Any future test that needs RPC should use a LiteSVM harness or | |
| # a local RPC fake; devnet is too rate-limited and too flaky for the | |
| # per-PR critical path (see ADR-0002 D2 — this is the same reason the | |
| # mainnet smoke test is isolated into `nightly.yml`). | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install toolchain | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci" | |
| - name: Run integration tests (branches on 6.5 feature flag) | |
| run: | | |
| # Story 6.5 adds `[features] integration = []` to Cargo.toml and the | |
| # `tests/common/postgres.rs` harness. Until the harness lands, run the | |
| # existing bootstrap_test.rs / registration_test.rs against the | |
| # postgres service container above. The feature guard check below | |
| # automatically flips to `--features integration` once Cargo.toml | |
| # declares it. | |
| if grep -q '^integration\b' Cargo.toml; then | |
| cargo test --release --tests --features integration | |
| else | |
| cargo test --release --tests | |
| fi | |
| coverage: | |
| name: coverage (lcov artifact) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 12 | |
| # NOTE: intentionally no `needs: unit`. Both jobs build the same code; | |
| # running them in parallel means a broken PR surfaces both failures at | |
| # once and the 12-minute end-to-end target is easier to hit. The previous | |
| # `needs: unit` gave no correctness benefit. | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Coverage does not currently need git history; the delta gate is | |
| # descoped (ADR-0002 D3). Restore to `fetch-depth: 0` when the | |
| # delta-gate follow-up story lands. | |
| fetch-depth: 1 | |
| - name: Install toolchain | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| with: | |
| components: llvm-tools-preview | |
| - name: Cache cargo registry + target | |
| # Separate cache key from the rest of `solarix-ci` because | |
| # `cargo-llvm-cov` sets `RUSTFLAGS=-Cinstrument-coverage`, which | |
| # produces `.rlib` artifacts that are incompatible with the other | |
| # jobs' builds. Sharing the cache would cause thrashing (either | |
| # job's target/ would force the other to fully rebuild). | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci-coverage" | |
| - name: Install cargo-llvm-cov (cached binary) | |
| # `cargo install --locked` re-compiles from source every run (~3–5 min | |
| # cold tax). `taiki-e/install-action` fetches a prebuilt binary and | |
| # caches it per version, bringing the install down to a few seconds. | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-llvm-cov | |
| - name: Run coverage (lib only — see ADR-0002 D6) | |
| run: cargo llvm-cov --release --lib --lcov --output-path lcov.info | |
| - name: Print coverage summary | |
| run: cargo llvm-cov report --release --summary-only | |
| - name: Upload lcov artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: lcov-info | |
| path: lcov.info | |
| retention-days: 14 | |
| # Fail the step (not just warn) if `cargo llvm-cov` silently | |
| # produced no output. The default `warn` hides this regression. | |
| if-no-files-found: error | |
| fuzz-smoke: | |
| name: fuzz smoke (60s) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 8 | |
| # NOTE: this job is now a HARD gate on every run. The original "soft gate" | |
| # guard `if: hashFiles('fuzz/Cargo.toml') != ''` was permanently true | |
| # because Story 6.4 already landed `fuzz/Cargo.toml` and | |
| # `fuzz/fuzz_targets/decode_instruction.rs` before this story merged | |
| # (see ADR-0002 § D1). Removing the guard matches what actually runs. | |
| continue-on-error: false | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install nightly toolchain | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci-nightly" | |
| - name: Install cargo-fuzz (cached binary) | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-fuzz | |
| - name: Run fuzz smoke | |
| # cargo-fuzz defaults to x86_64-unknown-linux-musl on some runners, | |
| # which is incompatible with ASAN (-Zsanitizer=address requires dynamic | |
| # libc). Explicitly target gnu to avoid the "sanitizer is incompatible | |
| # with statically linked libc" build error. | |
| run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu decode_instruction -- -max_total_time=60 | |
| security: | |
| name: security (audit + deny + gitleaks) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 8 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # gitleaks scans against the diff base, and a future audit step may | |
| # want history too. | |
| fetch-depth: 0 | |
| - name: Install toolchain | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci" | |
| - name: Install cargo-audit and cargo-deny (cached binaries) | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-audit,cargo-deny | |
| - name: cargo audit | |
| # --deny warnings is intentionally omitted: unmaintained/yanked warnings | |
| # from Solana transitive deps (litesvm, sqlx-mysql) have no upgrade path. | |
| # Unfixable CVEs are explicitly acknowledged here; cargo deny check | |
| # advisories (below) enforces the full advisory policy via deny.toml. | |
| run: | | |
| cargo audit \ | |
| --ignore RUSTSEC-2024-0344 \ | |
| --ignore RUSTSEC-2022-0093 \ | |
| --ignore RUSTSEC-2023-0071 | |
| - name: cargo deny check (advisories + bans + sources — licenses is fail-soft per ADR-0002 D5) | |
| run: cargo deny check advisories bans sources | |
| - name: gitleaks | |
| uses: gitleaks/gitleaks-action@v2 | |
| with: | |
| config: .gitleaks.toml | |
| docker-smoke: | |
| name: docker smoke (compose up → /health → logs) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| # Inherited by `docker-compose.yml`'s `${SOLANA_RPC_URL:-...}` expansion. | |
| # `docker-compose.yml` wires DATABASE_URL itself via the postgres service; | |
| # the workflow must NOT set DATABASE_URL at this level. | |
| SOLANA_RPC_URL: https://api.devnet.solana.com | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Reset any stale compose state | |
| run: docker compose down -v || true | |
| - name: docker compose up --build -d | |
| run: docker compose up --build -d | |
| - name: Wait for /health | |
| # 30 retries × 10 s = 300 s ceiling on runtime startup. The in-container | |
| # Solarix build already completed during `docker compose up --build`, | |
| # so this budget is purely for the binary cold-start + postgres | |
| # healthcheck + axum route mounting window. `--retry-all-errors` | |
| # covers a race where `/health` briefly 404s during route mounting | |
| # (default `--retry` only covers 5xx + connection-refused). | |
| run: | | |
| curl --retry 30 --retry-delay 10 --retry-connrefused --retry-all-errors --fail \ | |
| --connect-timeout 5 \ | |
| http://localhost:3000/health | |
| - name: Soft-check /ready (warning only until Story 6.3) | |
| run: | | |
| if curl -fsS http://localhost:3000/ready; then | |
| echo "/ready ok" | |
| else | |
| echo "::warning::/ready not yet implemented (Story 6.3)" | |
| fi | |
| - name: Log format check (every captured line must be valid JSON) | |
| run: | | |
| set -euo pipefail | |
| docker compose logs solarix --no-log-prefix > /tmp/solarix.log | |
| if [ ! -s /tmp/solarix.log ]; then | |
| echo "::error::No log lines captured from solarix container" | |
| exit 1 | |
| fi | |
| # Scan the full log (not just the first 5 lines). The previous | |
| # `head -5` version would false-negative on any `pretty` regression | |
| # that landed after the startup banner, and false-positive on any | |
| # clap/dotenv warning that landed in the first 5 lines. | |
| python3 - <<'PY' | |
| import json, sys | |
| path = "/tmp/solarix.log" | |
| count = 0 | |
| with open(path) as f: | |
| for i, raw in enumerate(f, 1): | |
| line = raw.strip() | |
| if not line: | |
| continue | |
| try: | |
| json.loads(line) | |
| except json.JSONDecodeError: | |
| print(f"::error::Non-JSON log line {i}: {line}") | |
| sys.exit(1) | |
| count += 1 | |
| if count == 0: | |
| print("::error::No non-empty log lines found — container may have crashed silently") | |
| sys.exit(1) | |
| print(f"All {count} captured log lines are valid JSON") | |
| PY | |
| - name: Soft-check /metrics (warning only until Story 6.2) | |
| run: | | |
| if curl -fsS http://localhost:3000/metrics | grep -q solarix_build_info; then | |
| echo "metrics ok" | |
| else | |
| echo "::warning::/metrics not yet implemented (Story 6.2)" | |
| fi | |
| - name: Capture logs on failure | |
| if: failure() | |
| run: docker compose logs solarix > solarix.log 2>&1 || true | |
| - name: Upload logs artifact on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docker-smoke-logs | |
| path: solarix.log | |
| retention-days: 7 | |
| if-no-files-found: warn | |
| - name: Cleanup | |
| # Intentionally NOT using `|| true` here. A cleanup failure means | |
| # `docker compose down -v` could not tear down the containers, which | |
| # is a real problem we want surfaced — not silently masked. | |
| # `if: always()` still guarantees the step runs on both success and | |
| # failure branches. | |
| if: always() | |
| run: docker compose down -v | |
| msrv: | |
| name: msrv build (1.88) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install pinned MSRV toolchain | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci" | |
| - name: cargo build --release | |
| run: cargo build --release | |
| # The matrix for stable/beta is split into two explicit jobs rather than a | |
| # `strategy.matrix` block. GitHub Actions does not allow expressions in | |
| # `uses:` fields, so a matrix would force the action ref back to | |
| # `dtolnay/rust-toolchain@master` (a moving target that the action's own | |
| # README discourages). Two explicit jobs trade a few YAML lines for an | |
| # elimination of the `@master` supply-chain hole. | |
| toolchain-stable: | |
| name: toolchain build (stable) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install stable toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci-stable" | |
| - name: cargo build --release | |
| # `RUSTUP_TOOLCHAIN` env overrides `rust-toolchain.toml` per rustup | |
| # docs, so the job actually exercises stable instead of the pinned | |
| # MSRV channel. | |
| env: | |
| RUSTUP_TOOLCHAIN: stable | |
| run: cargo build --release | |
| toolchain-beta: | |
| name: toolchain build (beta) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| # Beta is allowed to flake — surfacing upcoming breakages as a warning | |
| # annotation, not a hard fail. | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Install beta toolchain | |
| uses: dtolnay/rust-toolchain@beta | |
| - name: Cache cargo registry + target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "solarix-ci-beta" | |
| - name: cargo build --release | |
| env: | |
| RUSTUP_TOOLCHAIN: beta | |
| run: cargo build --release |