Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub Actions Integration

This page provides a GitHub Actions workflow for validating the AMP project structure and optionally running a model smoke test in CI.

Validation Workflow

name: Validate AMP

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.9"

      - name: Install validation dependencies
        run: pip install pyyaml

      - name: Run structural validation
        run: python scripts/validate_amp.py

  smoke-test:
    runs-on: ubuntu-latest
    if: hashFiles('model/best-xgboost-model') != ''
    needs: validate
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.9"

      - name: Install dependencies
        run: |
          pip install numpy xgboost

      - name: Run model smoke test
        run: |
          python -c "
          import numpy as np
          import xgboost as xgb

          booster = xgb.Booster(model_file='model/best-xgboost-model')
          sample = np.array([[-1.35980713, -0.0727811733, 2.53634674, 1.37815522,
              -0.33832077, 0.462387778, 0.239598554, 0.0986979013,
              0.36378697, 0.090794172, -0.551599533, -0.617800856,
              -0.991389847, -0.311169354, 1.46817697, -0.470400525,
              0.207971242, 0.0257905802, 0.40399296, 0.251412098,
              -0.0183067779, 0.277837576, -0.11047391, 0.0669280749,
              0.128539358, -0.189114844, 0.133558377, -0.0210530535,
              149.62]])

          prediction = booster.inplace_predict(sample)
          assert 0.0 <= prediction[0] <= 1.0, f'Out of range: {prediction[0]}'
          binary = 0 if prediction[0] <= 0.35 else 1
          assert binary in (0, 1), f'Unexpected output: {binary}'
          print(f'Prediction: {prediction[0]:.4f} -> {binary}')
          print('Smoke test passed.')
          "

Workflow Details

validate job

Runs on every push and pull request to master. Requires only pyyaml (no heavy ML dependencies). Executes a validation script that checks:

  • Repository structure (S-rules)
  • AMP configuration (A-rules)
  • Dependency manifest (D-rules)
  • Endpoint contract via AST parsing (E-rules)
  • Cluster utility structure (C-rules)

smoke-test job

Runs only when the trained model file exists in the repository (model/best-xgboost-model). Installs numpy and xgboost, loads the model, runs a sample prediction, and asserts the output is valid.

This job is conditional — it will be skipped if the model has not been committed (e.g., during initial development before training).

Setting Up the Validation Script

The workflow assumes scripts/validate_amp.py exists. Create this file using the SDK code from Building a Validation SDK:

# scripts/validate_amp.py
# Paste the ValidationResult, ValidationIssue, Severity classes
# and all validate_* functions from the SDK guide,
# then add the __main__ block:

if __name__ == "__main__":
    result = validate(".")
    for issue in result.issues:
        print(f"[{issue.severity.value.upper()}] {issue.rule}: {issue.message}")
    if not result.passed:
        raise SystemExit(1)
    print("All validation checks passed.")

Extending the Workflow

To add custom validation rules:

  1. Define a new rule ID (e.g., X-001) and severity in the Validation Rules Reference.
  2. Add a validate_custom() function to the SDK.
  3. Call it from the validate() entry point.

To validate dependency versions (not just presence):

import re

def validate_pinned_versions(root, result):
    content = (root / "requirements.txt").read_text()
    for line in content.strip().splitlines():
        line = line.strip()
        if line and not line.startswith("#") and not line.startswith("-"):
            if "==" not in line:
                result.issues.append(ValidationIssue(
                    rule="D-W01", severity=Severity.WARNING,
                    message=f"Package not pinned: {line}",
                    path="requirements.txt",
                ))