Skip to main content

YAML adapter

from callix import YamlAdapter

graph = YamlAdapter().analyze("path/to/repo")
PropertyValue
Language idyaml
Project markerany .yaml / .yml file
Extensions.yaml, .yml
Resolvernone — there are no symbols
External requirementsnone

Why YAML is here

YAML is not a programming language and this adapter does not pretend it is: there are no functions to declare and nothing to resolve. What it contributes is the half of a system that code never states.

An OpenAPI document lists the routes a service serves. A Kubernetes Ingress lists the paths that reach the cluster. A Compose file lists the services and which of them wait on which. None of that appears in any source file, and all of it is part of how the system fits together.

Because a boundary's ID comes from the mechanism and the normalized key alone, a route declared in a specification lands on the same node as the handler that implements it and the client that calls it:

from callix import PythonAdapter, YamlAdapter

graph = YamlAdapter().analyze("api") # openapi.yaml
graph.merge(PythonAdapter().analyze("api"), allow_shared=True)
graph.link_boundaries()
# client.fetch_one -> openapi.yaml through GET /engines/{}

That is what makes drift between a specification and its implementation a question the graph can answer: a route in the spec with nothing exposing it, or a handler with no spec entry, both show up as a boundary with only one side.

What it extracts

The flavour is read off the top-level keys, since there is no manifest to consult:

Recognized byProduces
openapi: or swagger:one EXPOSES per path × verb under paths:, confidence 1.0; external $ref targets as IMPORTS
apiVersion: + kind: Ingressone EXPOSES per spec.rules[].http.paths[].path, method ANY
services:a MODULE per service, DEPENDS_ON between them from depends_on
.gitlab-ci.ymla MODULE per job, DEPENDS_ON from needs, IMPORTS from include
.github/workflows/*.ymla MODULE per job, DEPENDS_ON from needs, a DEPENDENCY per external uses:, IMPORTS for a local one
Chart.yamla DEPENDENCY per subchart under dependencies:
kustomization.yamlIMPORTS per entry under resources:

Both spellings are accepted wherever YAML allows two — a dashed list and a flow sequence, a plain list and a mapping whose values carry conditions. That is not politeness: needs: [build] and a dashed needs: appear in the same file routinely.

Multiple files

A reference to another document becomes an IMPORTS edge, so multi-file YAML stops being a coincidence of two files sharing a directory:

compose.yaml --imports--> compose.db.yaml (file)
.gitlab-ci.yml --imports--> ci/build.yml (file)
.gitlab-ci.yml --imports--> Security/SAST.gitlab-ci.yml (external symbol)
workflow:verify --imports--> ./.github/workflows/reusable.yml

Paths are resolved relative to the including file, and a target that is not a local file — a GitLab template: or remote:, an http $ref — becomes an EXTERNAL_SYMBOL rather than being dropped, the same rule the other adapters follow for imports.

Job names are qualified by their file (.github/workflows/ci.yml:test): two pipelines may both have a test, and they are not the same job. Compose service names are not, because a service is global to its project.

Helm

Point the adapter at a chart directory and it will read Chart.yaml — the subcharts under dependencies: become DEPENDENCY nodes — but it will get nothing out of templates/.

That is not an omission. A Helm template is not YAML:

path: {{ .Values.ingress.path | quote }}

Tree-sitter parses it into something, and the something is meaningless — the key of that boundary would literally be {{ .Values.ingress.path | quote }}. So any document containing {{ … }} is marked helm-template and skipped, which is more useful than a graph full of unrendered placeholders.

For the real thing, render first:

helm template . --output-dir rendered
YamlAdapter().analyze("rendered") # http:ANY /engines

The rendered manifests are ordinary Kubernetes YAML, and everything above applies to them.

A specification states its contract outright, so nothing about it is inferred and the confidence is 1.0. An Ingress says nothing about the method, so the key records it as ANY — the same shape Go's own ServeMux produces.

What it does not do

No FUNCTION, CLASS or METHOD nodes: YAML has none. Boundaries attach to the FILE node, which is the innermost thing that contains them.

resolver_status is ok with zero queries. That is deliberate and different from unavailable: nothing failed and nothing is missing, because there was nothing to resolve. resolve=False is accepted and ignored, so the adapter interface stays uniform.

Every file carries its recognized flavour in metadata["flavour"], so a document callix could make nothing of is visible as unknown rather than silently absent.