Guides

Build triage

When a pipeline fails, the first error is usually the cause and the rest follow from it. ananke triage jenkins finds that first error, separates the cascade from it, points at the changes near it and says where to rerun from. It reads files you already have, so it needs no Jenkins access, and it sends nothing anywhere.

ananke triage jenkins console.log                                  # from the log alone
ananke triage jenkins console.log --jenkinsfile Jenkinsfile --base origin/main
ananke triage jenkins console.log --changed api/orders.py --json -o triage.json

Download the console log from the build page (Console Output → View as plain text). Exit 1 means a failure was found, 0 that none was, and 2 that the file isn’t a Jenkins console log.

What it reports

PartHow it’s found
Stagesthe pipeline markers ([Pipeline] { (Test)), with their lines; times too when the Timestamper plugin is on
First failurethe earliest stage that failed on its own: a non-zero exit, a traceback, failing tests, a compile error, out of memory, or an error line
Cascadeevery later failure, and every stage skipped due to earlier failure. Fix the first failure; these should clear
Related changeschanged files the failing stage’s commands point at: a path they name (pytest tests/api) or the tool they run (pytest and .py files or pyproject.toml; npm and package.json; docker and Dockerfile)
Planfix the first failure, check the related changes, rerun from that stage

A related change is evidence to look at, not proof that it caused the failure.

In a workflow

The same triage is the jenkins-triage connector (build-log/v1 → triage-report/v1), so a workflow can use it and its output is checked before it’s kept.

What to fix first

ananke triage findings puts every finding the repository’s reports hold in one list, ranked against what changed:

ananke triage findings --base origin/main
ananke triage findings --changed api/orders.py --check-vulnerabilities --json
KindFrom
codeSARIF reports (CodeQL, Semgrep, Bandit, Trivy and others), with the rule’s own remediation text
testfailing tests in JUnit XML
coveragea changed file whose line coverage is under 60%
dependencyvulnerable packages from the dependency check, each with its package, version and advisory, and the project files that import it

Each finding has a stable id and is marked changed (in a changed file), impacted (in a file that imports a changed file, directly or not) or neither; a dependency is reachable when project code imports it. They’re ranked into fix now, fix soon and later, each with a suggestion. A fix version is given only when the advisory names one; otherwise it says there isn’t one. The report ends with the gates to rerun: the tools whose reports these came from. Nothing is run, and with no reports the findings are unknown, not zero.

API contract gaps

ananke triage contracts checks an API’s routes against everything that should agree with them:

ananke triage contracts
ananke triage contracts --json -o contract-gaps.json
  • Tested by: HTTP calls in test files (client.get("/orders/1"), requests.post(f"{BASE}/orders"), fetch("/x", { method: "DELETE" })), matched to a route by method and path, with parameters matching any value.
  • Called by: the frontend and service calls the repository analysis matched to the route.
  • Mismatches: calls and test calls to routes that don’t exist, and operations an OpenAPI file promises that the code doesn’t serve.
  • Recommended contract tests for each untested route: the success case, a missing resource for each path parameter, an invalid body for POST, PUT and PATCH, and a repeated DELETE. They’re suggestions named after the route: nothing is run or written.

A route nothing in the project calls may still be used by another service; it’s listed so someone can check.

What each operation takes and returns

ananke triage api-contract reads each route’s handler and every OpenAPI file, without importing or running anything:

ananke triage api-contract                      # a table and the findings
ananke triage api-contract --out contract       # api-contract.json, model-contract.yaml, api-contract.md
ananke triage api-contract --spec api/openapi.yaml --fail-on high
  • Auth: Depends(get_current_user), Security(...) and a route’s dependencies= in FastAPI; decorators such as @login_required and @jwt_required; Express middleware such as requireAuth; NestJS @UseGuards; security in OpenAPI, per operation or for the whole document (security: [] turns it off).
  • Request: path parameters; for FastAPI the handler’s typed parameters (a Pydantic model or dataclass is the body, a plain type the query); the fields a handler reads (request.args.get("q"), payload["items"], req.body.sku, const { sku } = req.body); OpenAPI parameters and request bodies with local $ref resolved.
  • Responses: response_model= and status_code=, and every status a handler raises or returns (HTTPException(status_code=404), abort(403), return ..., 409, res.status(400)).

Only the project’s own files are read: virtualenvs (any folder holding pyvenv.cfg), site-packages, node_modules and dot-folders are skipped. An Enum subclass becomes a schema listing its values.

Every fact names the file and line it came from and says whether it was observed (written down: an OpenAPI operation, a type, a response_model) or inferred (read from how the handler uses its input). Where an OpenAPI file describes an operation, it’s the declared contract, and the code fills in only what it leaves out.

The same facts become a BMAD model contract (model-contract.yaml): the schemas, an api_error schema with every error status seen, and each operation pointing at its request and response schemas by name. ananke spec compile --api-contract uses it (see BMAD).

Findings: operations with no 4xx response documented or raised; secured operations that document no 401 or 403; secured operations no test calls, or whose tests never check the failed-auth response; auth the code and the OpenAPI file disagree on; and frameworks whose routes aren’t read (Go, Spring, ASP.NET, Rails, Laravel, Rust), with the remedy of describing them in an OpenAPI file. --fail-on makes findings that severe or worse exit 1.

A missing file, a malformed or unsupported specification, or a project with no routes and no OpenAPI file is an error that says what to do (exit 2). The digest leaves out the time, so the same sources give the same digest.

Infrastructure

ananke triage infra reads what the repository declares and says how it fits together:

ananke triage infra
ananke triage infra --project ../shop --project ../payments      # several repositories, linked
ananke triage infra --log compose.log                            # from `docker compose logs > compose.log`
  • Resources, each with its file and line: Compose services (image, build folder, ports, health check), Dockerfiles (base image, ports, health check, whether it runs as root), Kubernetes objects (images, probes) and Terraform resources.
  • Links: depends_on, a service to the Dockerfile it builds, a Kubernetes Service to the workloads its selector matches, Terraform references, and across repositories, a URL that names another project’s service.
  • Warnings: containers running as root, unpinned base images, workloads without probes, and services that start after another without waiting for it to be healthy.
  • A container log, split by service and triaged, each service with its code folder, health check and what it depends on, and the first service to fail.
  • A safe order to check it in: dry runs first (docker compose config, terraform validate, kubectl apply --dry-run=client), then builds, then each service after what it depends on, checking it at each step. These are commands to run yourself: nothing is started, applied or contacted.

Contract tests from an OpenAPI document

ananke triage api-tests openapi.yaml                                   # tests/api_contract/test_api_contract.py
ananke triage api-tests openapi.yaml --run http://127.0.0.1:8000       # run them against your local service

For every operation it writes test cases: the positive case, an unknown resource for each path parameter, a request without credentials when the operation is secured, a body with a required field left out and one with a field of the wrong type, a five-second response limit, and repeating a PUT or DELETE. Request bodies come from each schema’s example, or its required fields filled by type: edit them to real values. It also lists operations that document no 404, 400/422 or 401/403 for a failure they can have.

The tests only run when you ask, and only against a service on this machine (127.0.0.1, localhost, ::1). The results are kept as JUnit XML, and the report marks each case executed (passed or failed) or suggested. A generated test never counts as a passing one. Exit 1 if an executed case failed.

Several repositories as one system

ananke triage topology --project ../orders --project ../web --pin   # remember them, pinned at their HEAD
ananke triage topology                                              # later: what moved, and what it reaches

--pin records the repositories in .ananke/workspace.json with the Git revision each is at. The report then shows, per repository, its package names, the services it declares and the routes it serves, and whether it has moved since its pin. It matches each HTTP call in one repository to a route another serves (method and path), marks the routes whose files changed since the pin so you see which callers they reach, lists calls that no repository serves, and names external systems (api.stripe.com) as boundary nodes. Only files and Git are read.

Edit this page on GitHub ↗