Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/No-Country-simulation/G9-LATAM-Team-58/llms.txt

Use this file to discover all available pages before exploring further.

Mindloom uses two GitHub Actions workflows to move code from a feature branch into production. ci.yml validates changes on pull requests and pushes to dev — running only the jobs whose source files actually changed. deploy.yml fires on every push to main, SSH-ing into the OCI Ampere A1 VM to rebuild and restart the containers in place. The build never happens on a GitHub runner: the VM is linux/arm64 and runners are x86, so every image is built natively on the VM.

ci.yml — Validate

Triggers

on:
  pull_request:
    branches: [dev, main]
  push:
    branches: [dev]
ci.yml runs on pull requests targeting dev or main, and on direct pushes to dev. It does not trigger on pushes to main — that is deploy.yml’s responsibility.

Path-filtered jobs

The first job, changes, uses dorny/paths-filter@v4 to detect which parts of the codebase were modified. Downstream jobs read its outputs and skip themselves when their paths were not touched:
filters: |
  inference:
    - 'inference/app/**'
    - 'inference/tests/**'
    - 'inference/requirements.txt'
  api:
    - 'api/pom.xml'
    - 'api/src/**'
  web:
    - 'web/package.json'
    - 'web/src/**'
    - 'web/vite.config.*'
The filters target the manifest and source files, not the whole folder. A change to inference/README.md does not trigger the inference job. A change to docs/ does not trigger any job.
JobTrigger pathsWhat it runs
inferenceinference/app/**, inference/tests/**, inference/requirements.txtpip install torch (CPU wheel), ruff check ., pytest -q
apiapi/pom.xml, api/src/**./mvnw -q -B verify (includes contract tests)
webweb/package.json, web/src/**, web/vite.config.*pnpm install --frozen-lockfile, pnpm run lint, pnpm run build

Job details

Runs on ubuntu-latest. Installs torch from the CPU wheel index first (to avoid pulling 2 GB of CUDA packages), then installs requirements.txt and requirements-dev.txt, runs ruff check . for linting, and pytest -q for tests.
- run: pip install torch --index-url https://download.pytorch.org/whl/cpu
- run: pip install -r requirements.txt
- run: pip install -r requirements-dev.txt
- run: ruff check .
- run: pytest -q
Python version: 3.12 (matches the python:3.12-slim base image in the Dockerfile, required because numba dispatchers pickled inside model.joblib are sensitive to the exact CPython bytecode version).
Runs on ubuntu-latest with Java 25 (Temurin distribution, matching <java.version> in pom.xml). Executes the full Maven verify lifecycle, which includes unit tests and the contract test.
- uses: actions/setup-java@v5
  with:
    distribution: temurin
    java-version: "25"
    cache: maven
- run: ./mvnw -q -B verify
The contract test mocks the Python inference service and asserts the exact JSON shape of PredictResponse and EmbedResponse. If someone changes the inference response contract without updating the API client, this test fails in CI before the change reaches production.
Runs on ubuntu-latest. Uses pnpm 11.3.0 with Node 22 (node 22 is required — pnpm 11.x uses node:sqlite built-in, which is unavailable in Node 20). Installs with --frozen-lockfile to mirror the exact dependency tree checked into pnpm-lock.yaml, lints, and builds the static bundle.
- uses: pnpm/setup@v2
  with:
    version: "11.3.0"
    runtime: node@22
    cache: true
    cache-dependency-path: web/pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run build

deploy.yml — Publish

Triggers and concurrency

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: deploy-vm
  cancel-in-progress: false
deploy.yml fires on every push to main and can be triggered manually via workflow_dispatch. The concurrency configuration is critical: cancel-in-progress: false means two pushes to main in quick succession are serialized, not race-conditioned. The second deploy waits for the first to finish, leaving the VM in a consistent state rather than colliding mid-build.

The deploy job

The entire deploy job is a single appleboy/ssh-action@v1 step. It SSH-es into the VM using three repository secrets and runs the following script:
set -euo pipefail
cd ~/techmind
git fetch origin main
git reset --hard origin/main
docker compose --profile web build
docker compose --profile web up -d --remove-orphans
docker image prune -f
docker compose ps
set -euo pipefail ensures the workflow step fails if any command in the script fails — ssh-action@v1 has no script_stop input, so this is the mechanism that surfaces errors. The deploy script steps in order:
1

Fetch and reset

git fetch origin main and git reset --hard origin/main bring the VM’s working tree to the exact state of the main branch tip. Any local modifications inside the ~/techmind directory are discarded. Files outside the git tree (.env, wallet/) are untouched.
2

Build images

docker compose --profile web build rebuilds all three images natively on the ARM64 VM. The --profile web flag includes the web service, which is otherwise hidden behind the compose profile.
3

Restart containers

docker compose --profile web up -d --remove-orphans starts or restarts containers using the freshly built images. --remove-orphans cleans up containers from services that were removed from docker-compose.yml.
4

Prune old images

docker image prune -f removes dangling images (the previous build’s untagged layers) to reclaim disk space on the VM.
5

Verify

docker compose ps prints the current container states. The output is visible in the GitHub Actions log for post-deploy verification.

Why the build runs on the VM

Build on the VM (current approach)

The VM is linux/arm64 (OCI Ampere A1). Building natively takes minutes, requires no container registry, and produces images that run without any platform translation.

Cross-build on the runner (alternative)

Using docker buildx with QEMU emulation on an x86 runner can produce linux/arm64 images, but takes 20–40 minutes once the transformer weights are baked in. It also requires an intermediate container registry (Docker Hub, GHCR, or OCI Registry) to transfer the image to the VM.
If the build is ever moved to a GitHub runner, platform: linux/arm64 is not optional. An x86 image deployed to the Ampere A1 VM will fail immediately with exec format error when Docker tries to start the container.

Required repository secrets

Configure these three secrets in Settings → Secrets and variables → Actions:
SecretDescription
VM_HOSTPublic IP or hostname of the OCI Ampere A1 VM
VM_USERSSH username on the VM (e.g. ubuntu or opc)
VM_SSH_KEYPrivate SSH key (PEM format) authorized on the VM
No OCI CLI secrets (OCI_CLI_USER, OCI_CLI_KEY_CONTENT, etc.) are needed. The VM authenticates with OCI Object Storage using Instance Principal — the IAM role is attached to the VM’s compute instance, and the oci Python SDK picks it up automatically.

Branch Rules

feature/tm-NN-short-description  ──PR──►  dev  ──PR──►  main
  • Never push directly to main. A direct push triggers an immediate production deployment.
  • All pull requests must pass CI (green ci.yml checks) before merging.
  • Merges to main additionally require 1 approval.
  • Rebase your branch on dev before opening a PR to keep the history linear.

Troubleshooting

Cause: An image built on x86 (e.g. built locally on a Mac or Linux x86 machine, or pulled from a registry without the correct platform tag) was deployed to the ARM64 VM.Fix: Ensure the build runs on the VM itself via the deploy workflow, or use docker buildx build --platform linux/arm64 with an intermediate registry. Never build on a GitHub runner and copy the image binary to the VM without specifying the target platform.
Cause: The db Spring profile is not active, or the Oracle wallet is not mounted correctly.Check:
  1. Verify SPRING_PROFILES_ACTIVE=db is set in the container — docker compose exec api env | grep SPRING_PROFILES.
  2. Verify the wallet is mounted — docker compose exec api ls /app/wallet should list tnsnames.ora and other wallet files.
  3. Check API startup logs — docker compose logs api — for DriverManager or TNS errors.
Cause: The load() function failed during startup. This happens when models/latest.txt does not exist in the OCI bucket, or when the path it contains does not point to a valid model.joblib.Check:
  1. docker compose logs inference — look for the exception from load().
  2. Verify models/latest.txt exists in the MODEL_BUCKET bucket and contains a valid path prefix.
  3. If the healthcheck window is too short for your model size, increase --start-period in the inference Dockerfile.
Cause: pnpm-lock.yaml is out of date or missing. The CI job uses --frozen-lockfile, which aborts if the lockfile does not match package.json.Fix: Run pnpm install locally to regenerate the lockfile, then commit and push the updated pnpm-lock.yaml.
Cause: The deploy script runs git reset --hard origin/main, which overwrites any modifications inside the ~/techmind git working tree with the contents of main.Fix: Never edit files inside ~/techmind that are tracked by git. Configuration that must persist across deploys (.env, wallet/) lives outside the git tree and is untouched by git reset --hard.
Cause: Two merges to main happened in close succession. The concurrency: cancel-in-progress: false setting serializes deploys rather than cancelling the first one.Action: Check the GitHub Actions tab — the second run will be queued with status Waiting. It will start automatically once the first completes. Do not cancel or re-trigger manually.

Build docs developers (and LLMs) love