Documentation

Release gates: failing a build when AI agents stall

An Ottom release gate fails a CI build when a release makes your store harder for AI agents to buy from. Your pipeline starts a scan of your store or its staging origin through the Ottom API, waits for it, and reads one field: gate.result. This page gives the rules, the two API calls and a recipe for GitHub Actions, GitLab CI and CircleCI.

How the gate decides

The noise floor is the same one alerts use. How it is measured is on how a score is calculated.

What gate.result can be

Setting it up

  1. 1. Mint a CI token

    On your workspace page, an owner or admin mints a CI token for the store. It is shown once. A token reaches one store and that store’s staging origin, nothing else.

  2. 2. Store it as a secret

    Save the token in your CI system as a secret named OTTOM_TOKEN, and the address to scan as OTTOM_STORE_URL: your store, or its staging origin.

  3. 3. Add the script and the job

    Commit the script below as ci/ottom-gate.sh and add the job for your CI system after the step that deploys.

Who can do what in a workspace is on the workspaces page.

The two API calls

Both take the token as Authorization: Bearer. The base address is https://ottom.io/api/ci/runs.

The daily limit and what happens at it are on the limits page.

The script

Every recipe below runs this. It starts a scan, polls every 30 seconds for up to 30 minutes, prints the result and the receipt link, and exits 1 when the gate fails.

#!/bin/sh
# Ottom release gate. Needs curl, jq, OTTOM_TOKEN and OTTOM_STORE_URL.
set -eu
API="https://ottom.io/api/ci/runs"
AUTH="Authorization: Bearer $OTTOM_TOKEN"

START=$(curl -sS --fail-with-body -X POST "$API" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg url "$OTTOM_STORE_URL" '{url: $url}')") || { echo "$START"; exit 1; }
RUN_ID=$(echo "$START" | jq -r '.runId')
echo "Ottom scan $RUN_ID started"

RESULT=pending
i=0
while [ "$RESULT" = pending ] && [ "$i" -lt 60 ]; do
  sleep 30
  i=$((i + 1))
  REPORT=$(curl -sS --fail-with-body "$API/$RUN_ID" -H "$AUTH") || { echo "$REPORT"; exit 1; }
  RESULT=$(echo "$REPORT" | jq -r '.gate.result // "pending"')
done

echo "$REPORT" | jq -r '"Gate: \(.gate.result). \(.gate.reason)", "Receipt: \(.gate.receiptUrl)"'
case "$RESULT" in
  fail) exit 1 ;;
  pending) echo "No gate result after 30 minutes."; exit 1 ;;
  *) exit 0 ;;
esac

GitHub Actions

In .github/workflows/ottom-gate.yml. Runs when your staging deploy workflow finishes. Add OTTOM_TOKEN under Settings, Secrets and variables, Actions. Change the workflow name to yours.

name: Ottom release gate
on:
  workflow_run:
    workflows: ["Deploy staging"]
    types: [completed]
jobs:
  gate:
    if: github.event.workflow_run.conclusion == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Ottom release gate
        env:
          OTTOM_TOKEN: ${{ secrets.OTTOM_TOKEN }}
          OTTOM_STORE_URL: https://staging.example.com
        run: sh ci/ottom-gate.sh

GitLab CI

In .gitlab-ci.yml. A job after your deploy job. Add OTTOM_TOKEN as a masked variable under Settings, CI/CD, Variables.

ottom_gate:
  stage: test
  needs: [deploy_staging]
  image: alpine:3.20
  variables:
    OTTOM_STORE_URL: https://staging.example.com
  before_script:
    - apk add --no-cache curl jq
  script:
    - sh ci/ottom-gate.sh

CircleCI

In .circleci/config.yml. A job that requires your deploy job. Add OTTOM_TOKEN as a project environment variable or in a context. The cimg/base image already has curl and jq.

version: 2.1
jobs:
  ottom-gate:
    docker:
      - image: cimg/base:stable
    environment:
      OTTOM_STORE_URL: https://staging.example.com
    steps:
      - checkout
      - run:
          name: Ottom release gate
          command: sh ci/ottom-gate.sh
workflows:
  release:
    jobs:
      - deploy-staging
      - ottom-gate:
          requires: [deploy-staging]

Questions

Does a release gate cost extra scans?

Each gate run is one scan of the store or its staging origin, and it counts against that store’s daily limit like any other. A store at its limit answers 429 and the script fails the step, so a busy pipeline should gate once per release rather than once per commit.

Why did a drop of a few points not fail the gate?

AI agents do not score a store identically every time. A drop has to be larger than the noise floor, at least 5 points, before the gate treats it as a change your release made. How the floor is measured is on the scoring page.

What is the difference between gate and verdict in the response?

verdict compares the run with the previous run of the same store and fails on new findings at a severity you pick. gate compares it with your production store and fails only on the thresholds you set. The recipes read gate. Both are in every response.

Can the gate scan a store that is not public yet?

Yes, if it is a staging origin set up for the store. A token for the store can scan that origin, and the run is judged against the production store.

Your stores and their gates are on your dashboard.