# CI/CD Pipeline Validation

Automate Mandrel specification validation across GitHub Actions, GitLab CI/CD, Azure DevOps, and Google Cloud Build.

Source: https://mandrel-web-363efb.gitlab.io/docs/guides/ci-cd-validation/

---


Manual validation with the CLI catches errors locally, but team development requires automated gates at the pull request and merge request stage. Continuous integration ensures invalid risk profiles, malformed capabilities, and unverified extension blocks never merge into default branches.

The Mandrel project provides turnkey validation templates for major CI platforms. All templates execute the official zero-dependency CLI container:

```text
registry.gitlab.com/the-mandrel-project/mandrel-containers/cli:latest
```

The container runs unprivileged under `nonroot` (`uid 65532`) on Google Distroless Debian 12. It contains static binaries for `mandrel-cli` and official plugins, requiring no host Go runtime or external package managers.

---

## GitHub Actions

Add the validation action to your workflow file (for example, `.github/workflows/validate-spec.yml`):

```yaml
name: Validate Specifications

on:
  push:
    branches: [main]
  pull_request:

jobs:
  validate-mandrel-spec:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Validate Spec
        uses: the-mandrel-project/mandrel-containers/integrations/github@v0.0.1
        with:
          spec-path: "agent-spec.yaml"
          strict: "true"
```

### Configuration Options

| Option | Type | Default | Purpose |
| :--- | :--- | :--- | :--- |
| `spec-path` | string | `mandrel-spec.yaml` | Relative path to the specification file |
| `image-tag` | string | `latest` | Tag of the Mandrel CLI container image |
| `strict` | boolean | `false` | Escalate validation warnings to fatal pipeline errors |
| `allow-unclaimed-extensions` | boolean | `false` | Permit undeclared `spec.extensions` blocks |

---

## GitLab CI/CD

Use the official component from the Mandrel CI/CD Catalog in your `.gitlab-ci.yml`:

```yaml
stages:
  - lint
  - test

include:
  - component: $CI_SERVER_FQDN/the-mandrel-project/mandrel-ci/validate-spec@v2.4.0
    inputs:
      stage: lint
      spec_path: "agent-spec.yaml"
      strict: true
```

If including the template directly from a repository clone:

```yaml
stages:
  - lint

include:
  - local: integrations/gitlab/validate-mandrel-spec.yml
    inputs:
      stage: lint
      spec_path: "agent-spec.yaml"
```

### Configuration Inputs

| Input | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `stage` | string | `lint` | Pipeline stage in which to execute validation |
| `spec_path` | string | `mandrel-spec.yaml` | Path to the specification file |
| `image_tag` | string | `latest` | Image tag of the Mandrel CLI container |
| `strict` | boolean | `false` | Treat warnings as fatal errors |
| `allow_unclaimed_extensions` | boolean | `false` | Permit undeclared extension fields |

---

## Azure DevOps

Reference the step template in your `azure-pipelines.yml`:

```yaml
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - checkout: self

  - task: Bash@3
    displayName: 'Validate Mandrel Specification'
    inputs:
      targetType: 'inline'
      script: |
        docker run --rm \
          -v "$(System.DefaultWorkingDirectory):/workspace:ro" \
          -w /workspace \
          registry.gitlab.com/the-mandrel-project/mandrel-containers/cli:latest \
          validate --strict agent-spec.yaml
```

When importing the reusable template file directly:

```yaml
steps:
  - template: integrations/azure-devops/validate-mandrel-spec.yml
    parameters:
      specPath: 'agent-spec.yaml'
      strict: true
```

### Parameters

| Name | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `specPath` | string | `mandrel-spec.yaml` | Path to the specification relative to repository root |
| `imageTag` | string | `latest` | CLI image tag to execute |
| `strict` | boolean | `false` | Escalate warnings to fatal errors |
| `allowUnclaimedExtensions` | boolean | `false` | Permit undeclared extension fields |

---

## Google Cloud Build

Include a validation step in your `cloudbuild.yaml`. Cloud Build executes containers natively and automatically mounts the workspace at `/workspace`:

```yaml
steps:
  - id: 'validate-mandrel-spec'
    name: 'registry.gitlab.com/the-mandrel-project/mandrel-containers/cli:latest'
    args:
      - 'validate'
      - '--strict'
      - 'agent-spec.yaml'
```

Alternatively, invoke the standalone configuration via the Google Cloud CLI:

```bash
gcloud builds submit \
  --config=integrations/cloudbuild/validate-mandrel-spec.yaml \
  --substitutions=_MANDREL_SPEC_PATH=agent-spec.yaml,_MANDREL_CLI_TAG=latest
```

### Substitutions

| Name | Default | Description |
| :--- | :--- | :--- |
| `_MANDREL_SPEC_PATH` | `mandrel-spec.yaml` | Path to the specification file |
| `_MANDREL_CLI_TAG` | `latest` | CLI container image tag |

---

## Local Testing with Podman

To reproduce CI validation locally prior to opening a pull request, run the container with Podman:

```bash
podman run --rm \
  -v "$(pwd):/workspace:ro" \
  -w /workspace \
  registry.gitlab.com/the-mandrel-project/mandrel-containers/cli:latest \
  validate --strict agent-spec.yaml
```

Successful validation returns exit code `0`:

```text
✓ Spec 'my-specialist-agent' is valid.
```

Failed validation returns exit code `2` (`ExitValidation`) and diagnostic violation messages:

```text
[ERROR] spec.runtime.timeout-seconds: timeout-seconds must be >= 0, got -5 [mandrel.io/core E002]
Error: validation failed with 1 error(s)
```

---

## Exit Codes and Pipeline Handling

The Mandrel CLI returns deterministic exit codes to allow CI pipelines to distinguish between specification errors and operational or infrastructure failures:

| Exit Code | Constant | Meaning | Pipeline Behavior |
| :--- | :--- | :--- | :--- |
| `0` | `ExitSuccess` | Specification passed validation cleanly | Job passes |
| `1` | `ExitGeneral` | Operational failure (missing file, invalid flag syntax) | Job fails (infrastructure error) |
| `2` | `ExitValidation` | Specification violated schema constraints or governance policies | Job fails (spec invalid) |
| `3` | `ExitAuth` | Token missing or authentication failure | Job fails (auth error) |
| `4` | `ExitPlugin` | Plugin execution failed or crashed | Job fails (plugin error) |

### Advisory Linting in GitLab CI

Pipelines running in advisory mode can soft-fail on specification errors while continuing to fail hard on operational problems:

```yaml
validate-mandrel-spec:
  allow_failure:
    exit_codes: [2]
```

### GitHub Actions Exit Code Handling

GitHub workflows can evaluate exit codes or step outcomes to branch behavior:

```yaml
      - name: Validate Mandrel Spec
        id: validate
        continue-on-error: true
        run: |
          docker run --rm -v "$GITHUB_WORKSPACE:/workspace:ro" -w /workspace \
            registry.gitlab.com/the-mandrel-project/mandrel-containers/cli:latest \
            validate --strict agent-spec.yaml
          echo "exit_code=$?" >> $GITHUB_OUTPUT

      - name: Report Specification Errors
        if: steps.validate.outputs.exit_code == '2'
        run: echo "Specification violated governance policies."
```


