---
title: Gate your pipeline
description: Make a policy regression or a posture finding fail the build, with no interactive prompts.
label: Gate your pipeline
---

Two commands are designed for CI. Both exit non-zero on failure and neither
needs a terminal.

| Command | Fails when |
| --- | --- |
| `veefore test` | A policy test case fails, or a dangerous tool is permitted only by a default decision |
| `veefore doctor --strict` | Any posture finding, including warnings |

## Non-interactive authentication

Prompts fall back to defaults when stdin is not a TTY, and the secret prompt
refuses outright rather than hanging. Supply credentials as environment
variables:

```bash
VEEFORE_EMAIL=ci@example.com
VEEFORE_PASSWORD=…
VEEFORE_MFA_CODE=…        # only where MFA is enforced
VEEFORE_CONTROL_PLANE_URL=https://veefore.your-company.com   # self-hosted only
```

On hosted Veefore, leave `VEEFORE_CONTROL_PLANE_URL` unset. A self-hosted
address is usually not needed either, because the CLI reads it from the
project's committed `veefore.config.json`; set it when the job runs outside a
set-up project.

An API key is better than a password where the commands support it, because it
is scoped to a role and revocable on its own. See
[API keys](/build/api-keys).

:::security Never use an `OWNER` key or password in CI
A CI secret lives in your CI provider's storage, is readable by every build
step, and ends up in logs if anyone echoes the environment.

Create a custom role with exactly what the gate needs — `policy:read`,
`policy:simulate`, `agent:read`, `tool:read` — and nothing else.
:::

## GitHub Actions

```yaml
name: Veefore policy gate

on:
  pull_request:
  push:
    branches: [main]

jobs:
  policy:
    runs-on: ubuntu-latest
    env:
      VEEFORE_CONTROL_PLANE_URL: ${{ vars.VEEFORE_CONTROL_PLANE_URL }} # self-hosted only
      VEEFORE_EMAIL: ${{ secrets.VEEFORE_EMAIL }}
      VEEFORE_PASSWORD: ${{ secrets.VEEFORE_PASSWORD }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci
      - run: npm install -g veefore

      # Policy test cases must pass, and no dangerous tool may be reachable
      # only because no rule covers it.
      - run: veefore test

      # Posture audit. --strict makes warnings fail too.
      - run: veefore doctor --strict
```

## GitLab CI

```yaml
veefore-policy-gate:
  image: node:20
  variables:
    VEEFORE_CONTROL_PLANE_URL: $VEEFORE_CONTROL_PLANE_URL # self-hosted only
  script:
    - npm ci
    - npm install -g veefore
    - veefore test
    - veefore doctor --strict
```

Set `VEEFORE_EMAIL` and `VEEFORE_PASSWORD` as masked, protected CI variables.

## What `veefore test` actually checks

1. **A dry-run probe across every tool this agent is authorized for.** Catches an agent that is authorized for a tool nobody intended, and a tool whose schema has drifted.
2. **Every policy test case.** The Given/Then cases recorded on the policy versions in scope.
3. **Dangerous tool coverage.** Fails if a tool marked `dangerous: true` is permitted only by a `defaultDecision` with no rule covering it. This is the check most likely to catch a real problem — a destructive tool that is allowed because nobody wrote a rule, not because anyone decided it should be.

```bash
veefore test --tool refund_customer    # probe one tool
veefore test --json                    # machine-readable
```

## Gate on activation, not just on tests

Activation already requires passing tests, step-up authentication and a reason,
and step-up permissions are unavailable to API keys — so **CI cannot activate a
policy on its own**. That is deliberate: activation moves the authorization
boundary for every agent in scope, and it is a decision rather than a build
step.

The workable shape is:

```yaml
  # CI proves the policy is correct and nothing regressed.
  - run: veefore test
  - run: veefore doctor --strict

  # A human activates, with step-up, from their own session.
  #   veefore policies deploy pol_01ABC --reason "CHG-4411"
```

## Fail the build on a bad decision shape

Simulation is side-effect free, so you can assert on specific request shapes
from a test:

```ts
import { describe, expect, it } from 'vitest';

const BASE = process.env.VEEFORE_CONTROL_PLANE_URL ?? 'https://veefore.example.com';
const KEY = process.env.VEEFORE_API_KEY;

async function simulate(body: Record<string, unknown>) {
  const response = await fetch(`${BASE}/v1/policies/simulate`, {
    method: 'POST',
    headers: { authorization: `Bearer ${KEY}`, 'content-type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`simulate failed: ${response.status}`);
  return (await response.json()).data;
}

describe('refund policy', () => {
  it('allows small refunds', async () => {
    const result = await simulate({ agentId: AGENT, tool: 'refund_customer', amount: 50_00 });
    expect(result.decision).toBe('ALLOW');
  });

  it('requires approval above the ceiling', async () => {
    const result = await simulate({ agentId: AGENT, tool: 'refund_customer', amount: 1_500_00 });
    expect(result.decision).toBe('REQUIRE_APPROVAL');
  });

  it('refuses destructive database operations', async () => {
    const result = await simulate({
      agentId: AGENT,
      tool: 'run_query',
      parameters: { operation: 'DELETE' },
    });
    expect(result.decision).toBe('BLOCK');
  });
});
```

Prefer recorded policy test cases where you can — they also run at activation
time, which is the moment that matters. Use this pattern for assertions that
span several policies, or that depend on live state the recorded cases cannot
express.

## Verifying Veefore itself

If you run Veefore from source, the repository has its own gate:

```bash
npm run verify
```

Twenty-three stages: typecheck, lint, format, the scope audit, the quality
audit, the migration check, seven test suites, five smoke tests against real
processes, infrastructure validation, the console build and the docs check.

Optional stages are **skipped with a named reason** rather than silently
omitted, so a green run cannot hide a missing dependency.

```bash
npm run verify -- --bail           # stop at the first failure
npm run verify -- --only=policy    # one stage
VERIFY_QUIET=1 npm run verify      # capture output, print only on failure
```

## Related

:::cards

:::card Write your first policy | /build/first-policy
Where the test cases come from.
:::

:::card API keys | /build/api-keys
Scoping a key for CI.
:::

:::card CLI reference | /reference/cli
Every command and flag. Generated.
:::

:::card Testing strategy | /architecture/testing-strategy
What each suite in the repository proves.
:::

:::
