Qualify is heavier than verify. Use it where the depth is worth the runtime cost:
| Workflow | Recommended | Why |
|---|---|---|
| **Pull request** | No — use `verify` | `verify` is fast (<5s for typical services) and catches behavioral regressions per-route. Qualify adds multi-minute scenario/stateful/chaos runs that are too slow for PR feedback loops. |
| **Nightly** | Yes | Full scenario, stateful, and chaos execution against staging. Catch protocol-level regressions that single-route verification cannot see. |
| **Pre-release** | Yes | Run qualify against the exact artifact that will be promoted to production. Treat a passing qualify run as a release gate for critical flows. |
| **Specialist workflows** | Yes | Auth flows, billing sequences, idempotency guarantees, and pagination consistency need multi-step qualification that verify cannot express. |
| **Chaos engineering** | Nightly or ad-hoc | Chaos injection increases latency. Run it in dedicated CI slots, not on every commit. |
### Quick workflow setup
```javascript
// apophis.config.js — two profiles for different cadences
exportdefault{
mode:'qualify',
profiles:{
'nightly':{
name:'nightly',
mode:'qualify',
preset:'deep',
features:['scenario','stateful','chaos'],
routes:[],
},
'pre-release':{
name:'pre-release',
mode:'qualify',
preset:'deep',
features:['scenario','stateful'],
routes:[],
},
},
presets:{
deep:{timeout:15000,chaos:false},
},
}
```
Run nightly: `apophis qualify --profile nightly`
Run pre-release: `apophis qualify --profile pre-release --format json-summary`
Artifacts include `executedRoutes` and `skippedRoutes` arrays. `skippedRoutes` contains reasons such as mode mismatch, environment policy, or route filter exclusion.
Qualify gates are not individually gated per environment. The `blockQualify` flag controls all qualify execution, and `allowChaosOnProtected` controls chaos on protected routes.
Each qualify run produces an artifact JSON document. Key sections:
### executionSummary
```json
{
"executionSummary":{
"totalPlanned":15,
"totalExecuted":12,
"totalPassed":10,
"totalFailed":2,
"scenariosRun":3,
"statefulTestsRun":5,
"chaosRunsRun":4,
"chaosRoutesPlanned":2,
"chaosRoutesExecuted":2,
"totalSteps":12
}
}
```
Use `totalExecuted` vs `totalPlanned` to see how many checks actually ran (gate gating, route filtering, chaos selection). A non-zero `totalPlanned` with zero `totalExecuted` means all gates were disabled or no routes matched.
{"route":"DELETE /items/:id","reason":"No scenario covers this route"},
{"route":"GET /health","reason":"Not selected by chaos strategy: one"}
]
}
```
`executedRoutes` lists every route that had at least one scenario step, stateful command, or chaos injection. `skippedRoutes` explains why every other discovered route was excluded.
### profileGates
```json
{
"profileGates":{
"scenario":true,
"stateful":true,
"chaos":false
}
}
```
Shows which gates were active. Combine with `executionSummary` per-gate counts to verify each active gate produced results.
### stepTraces
Each entry records an individual step execution:
```json
{
"stepTraces":[
{
"step":0,
"name":"create-order",
"route":"POST /orders",
"durationMs":12,
"status":"passed"
}
]
}
```
Filter by `status` to isolate failures. Look at `durationMs` for performance regressions.
Qualify exits with code 1 if zero checks executed. This prevents silent passes when all routes are filtered out or gates are disabled.
## Test Budget
The `runs` field in your preset controls how many property-based tests execute per route. Default is 50. Lower for faster CI feedback, higher for deeper exploration: