npx autonomous-qa is a first-class CI citizen: a pinned reusable suite gates a
pipeline exactly the way Newman gates one — a non-zero exit on any failure,
plus machine-readable reports (JUnit XML + JSON) a CI system can parse and
render.
A gate that exits 0 on a failure is a silent green — the worst possible
defect, because the pipeline goes green while the contract is broken. So:
| mode | command | exits non-zero when |
|---|---|---|
| pinned spec | autonomous-qa verify <target> --spec <file> |
any requirement fails, the digest pin mismatches, or the target is unreachable/refused |
| reusable suite | autonomous-qa suite <file> --env <name> |
any probe fails (same digest / reachability rules) |
| data-driven | autonomous-qa suite <file> --env <name> --iteration-data <dataset> |
any iteration fails (including an SSRF-refused row) |
Exit 0 happens only when everything passed. A digest-pin mismatch or an
unreachable target throws before a report exists; the CLI turns that into a
non-zero exit too. Reporting never masks a failure — the exit code is computed
from the run result independently of which reporters ran.
ONLY
verify/suite/suite --iteration-dataare safe to gate CI on. The bareautonomous-qa <target>command is advisory grade mode — it exits0for any grade A through D, even with failing checks, and only exits non-zero on grade F. It exists to show how close a target is to agent-first, not to pass/fail a pipeline. Gating CI on the bare grade command is a silent-green footgun: a target can regress from A to D — real checks failing along the way — and the job stays green. The CLI prints a stderr warning every time the bare grade command runs, for exactly this reason. If you want a CI gate, pin a spec or suite and useverify/suiteinstead.
Pick one or more reporters, Newman-style. --reporter is repeatable and
comma-splittable.
autonomous-qa suite examples/golden-scenario.suite.json --env prod \
--target https://your.api \
--expect-digest <pin> \
--reporter cli \
--reporter junit --reporter-junit-out reports/api-qa.junit.xml \
--reporter json --reporter-json-out reports/api-qa.jsoncli— the human/markdown output (the default when no--reporteris given), written to stdout.junit— a valid JUnit XML (testsuites/testsuite/testcasewith<failure>/<error>/<skipped>). Each requirement/probe — and each data-driven iteration × probe — is one<testcase>;failures/errorscounts are exact; names and details are XML-escaped. GitHub and GitLab parse this natively.json— a stable structured report ($report: "api.qa.ci-run",schemaVersion: 1): target, verdict, per-probe pass/fail + detail + timing, and the pinned digest.
Output paths:
--reporter-junit-out <path>/--reporter-json-out <path>— per-reporter.--reporter-out <path>— a shared fallback (only valid when a single file reporter is selected; using it with two file reporters is refused so one file can't clobber the other).- A file reporter with no path writes to stdout.
A working, copy-pasteable workflow lives at
.github/workflows/api-qa-example.yml.
It:
- checks out the repo and installs Node,
- runs the pinned suite against
${{ vars.API_QA_TARGET }}with--expect-digest ${{ vars.API_QA_SUITE_DIGEST }}— so a regression makes the step exit non-zero and the check goes red, - uploads the JUnit + JSON reports as artifacts on every run (
if: always()), so failure detail survives a red gate, - publishes the JUnit XML as a test report.
Set two repository/environment variables:
-
API_QA_TARGET— the deployed URL to verify. -
API_QA_SUITE_DIGEST— the ratified suite pin. Mint it once and re-mint only when you intentionally change what passing means:npx autonomous-qa spec-digest examples/golden-scenario.suite.json
autonomous-qa gate --estate <config.json> runs the pinned gate across a whole
estate of surfaces and turns the set into one pass/fail plus a
scoreboard (surface | target | grade | score | pinned spec | required). It
reuses gradePinned() per entry — the same verdict the deployed api.qa returns
for a pinned contract — and exits non-zero iff any REQUIRED surface fails its
pinned spec or errors. Non-required entries are advisory-only and never change
the exit code; an entry with no spec reports its AX letter grade but cannot
fail the gate.
node dist/cli/index.js gate --estate examples/ax-estate.json # exits 1 if any required surface failsexpectDigest pins the spec text (anti-Goodhart): a silently-edited spec can
never make the gate pass. A shipped GitHub Actions workflow that runs exactly
this lives at
.github/workflows/ax-estate-gate.yml.
A surface that is not publicly gradable (page.ax is 522, apps.ax is 403) is
graded before deploy — never faked. Two equivalent plug-ins:
-
In-process, no server at all — grade the Worker's
{ fetch }handler in memory with thetoConformvitest matcher orrunEstateGate()programmatically (each probe is dispatched to the handler in memory;allowPrivatestays false):import worker from '../src/worker.js' import spec from '../../examples/ax/ax-hi2-page-ax.spec.json?raw' it('page.ax conforms pre-deploy', async () => { await expect(worker).toConform(spec) })
-
Local dev server — stand up
wrangler dev, then gate the localhost URL with the per-entryallowPrivateopt-in (local-only; a remote target can never flip it on — the SSRF invariant):# page.ax: wrangler dev --port 8787 --var STRIPE_STUB:1 (then, from api.qa:) node dist/cli/index.js gate --estate examples/ax-estate.local.json
The same shape works on GitLab — the exit code fails the job, and the JUnit XML feeds the merge-request test widget:
api-qa:
image: node:20
script:
- npx --yes autonomous-qa suite examples/golden-scenario.suite.json
--env prod --target "$API_QA_TARGET" --expect-digest "$API_QA_SUITE_DIGEST"
--reporter junit --reporter-junit-out reports/api-qa.junit.xml
artifacts:
when: always
paths: [reports/]
reports:
junit: reports/api-qa.junit.xml