Initial import of the codespaces skill: query .belief_map.sexp for module boundaries, dependencies, blast radius, and the minimal files to read before non-trivial code changes. Includes the belief-map and infra-topology builders, the belief-search query engine and its tests, and reference docs for the S-expression notation and query patterns.
12 KiB
Infrastructure Topology Builders for Belief Map
Context and motivation
The belief map currently models source-code relationships: imports, entity references, data flows, and interface implementations. It has no visibility into infrastructure topology -- which services exist, how they connect at the network level, what databases/queues/caches they depend on, and how they are deployed.
This creates a blind spot: when an agent investigates a cross-service issue or plans a deployment change, the belief map shows code dependencies but not the runtime service graph. The data exists in three IaC formats already present in the codebase:
- Kustomize (
.k8s/base/kustomization.yaml): 20+ services in the mono workspace, each with deployment manifests declaring env vars, service endpoints, and resource dependencies (postgres, rabbitmq, minio, vault) - Helm charts (
charts/*/Chart.yaml): avionica streaming services (sara-mock, transformer, ergoss-push, nats, prometheus, dashboard) - Terraform (
infra/terraform/*.tf): infrastructure prerequisites (namespaces, secrets, PVCs, configmaps) with module dependencies
Goals:
- Extract service topology from all three IaC formats and emit sexp edges into the belief map
- Detect service-to-service dependencies: env var references, service DNS names, port mappings
- Detect service-to-infrastructure dependencies: databases, message queues, object stores, vaults
- Output is the same
.belief_map.sexpformat, appended or merged by the main builder
Non-goals for first implementation (v1):
- No runtime service discovery (no kubectl, no cluster queries)
- No Tilt/docker-compose parsing (separate effort)
- No ingress/route topology (just service-to-service and service-to-infra)
- No multi-cluster or multi-namespace topology
- No cost or resource quota analysis
Implementation considerations
Pure static analysis. All three parsers read YAML/HCL files from disk. No cluster access needed.
Additive to existing graph. Infrastructure edges are a new layer on top of the source-code graph. They use new edge types (k8s-service, k8s-depends, infra-resource) that don't conflict with existing imports/calls-api/data-flow edges.
Node identity. Infrastructure nodes use the service name from k8s metadata (e.g., app-service, drive-postgres, nats) prefixed with infra/ to distinguish from source-code module IDs. Example: infra/app-service, infra/drive-postgres.
Minimal dependencies. Kustomize and Helm parsers use only PyYAML (already available). Terraform parser uses hcl2 Python library or falls back to regex if unavailable.
Invocation. Each script is standalone but designed to be called from build_belief_map.py as an optional post-pass:
python3 scripts/build_infra_topology.py [--kustomize PATH] [--helm PATH] [--terraform PATH]
Or integrated into the main builder via --infra flag.
High-level behavior
Discovery
-
Scanner walks the workspace looking for:
kustomization.yaml/kustomization.ymlfiles (kustomize)Chart.yamlfiles (helm)*.tffiles in directories containingmain.tf(terraform)
-
For each discovered IaC root, the appropriate parser runs.
-
All parsers emit the same intermediate format: a list of
InfraNodeandInfraEdgedataclasses. -
A merge step deduplicates nodes by service name and writes sexp output.
Kustomize parser
Reads each kustomization.yaml and its referenced resources:
- Parse
kustomization.yaml-> extractresourceslist - For each resource directory, parse
deployment.yaml/statefulset.yaml:- Extract service name from
metadata.nameorapp.kubernetes.io/namelabel - Extract container image -> maps service to codebase repo
- Extract
envandenvFrom-> detect service endpoint references:*_HOST,*_URL,*_SERVICE_HOST-> depends-on target serviceDATABASE_URL,POSTGRES_*-> depends-on databaseRABBITMQ_*,AMQP_*,NATS_*-> depends-on message queueREDIS_*-> depends-on cacheS3_*,MINIO_*-> depends-on object store
- Extract service name from
- Parse
service.yaml-> extract ports and service type
Output edges:
(infra-node infra/app-service :kind deployment :image upgraide/app-service :port 3001 :component api)
(infra-node infra/app-postgres :kind deployment :image postgres:16 :port 5432 :component database)
(k8s-depends infra/app-service infra/app-postgres :via env :transport tcp)
(k8s-depends infra/app-service infra/drive-service :via env :transport http)
(k8s-service infra/app-service app-service :namespace upgraide :port 3001)
Helm parser
Reads Chart.yaml and values.yaml:
- Parse
Chart.yaml-> extract chart name, version, dependencies - Parse
values.yaml-> extract:image.repository/image.tag-> container imageenv.*-> service endpoint references (same heuristics as kustomize)service.port-> exposed port
- Parse
Chart.yamldependencies-> direct chart-to-chart edges
Output edges:
(infra-node infra/sara-mock :kind helm-chart :image sara-mock :port 8080 :chart sara-mock)
(helm-depends infra/sara-mock infra/nats :via chart-dependency)
Terraform parser
Reads *.tf files:
- Find all
moduleblocks -> extract module name and source - Find all
resourceblocks -> extract resource type and name - Build dependency graph from:
module.X.output_namereferences in other modulesdepends_onexplicit declarations- Variable references across modules
Output edges:
(infra-node infra/tf-namespace :kind terraform :resource kubernetes_namespace)
(infra-node infra/tf-secrets :kind terraform :resource module)
(tf-depends infra/tf-secrets infra/tf-namespace :via module-reference)
S-expression output format
New fact types (appended to .belief_map.sexp):
; Infrastructure nodes
(infra-node <id> :kind <deployment|statefulset|helm-chart|terraform> :image <image> :port <port> :component <label>)
; Kustomize service dependencies
(k8s-depends <src-service> <tgt-service> :via <env|dns|volume> :transport <http|tcp|amqp|grpc>)
; Kustomize service declarations
(k8s-service <service-id> <dns-name> :namespace <ns> :port <port>)
; Helm chart dependencies
(helm-depends <src-chart> <tgt-chart> :via <chart-dependency|values-ref>)
; Terraform module dependencies
(tf-depends <src-module> <tgt-module> :via <module-reference|depends-on|variable>)
; Cross-layer: maps infra service to source-code repo
(infra-maps infra/app-service app-service :via image-name)
The infra-maps edge connects infrastructure nodes to source-code nodes, enabling queries like:
(deps "infra/app-service" 2) ; what infra does app-service depend on?
(rdeps "infra/drive-postgres" 1) ; which services use this database?
Error handling and UX
- Missing YAML files: skip with
; warning: skipped <path>: file not foundcomment in output - Malformed YAML: skip resource, emit
; error: parse failed <path>: <reason> - Missing PyYAML: exit with clear error message and install instructions
- Missing hcl2 (for terraform): fall back to regex-based parsing, emit
; warning: terraform parsed with regex fallback - No IaC files found: emit
; info: no infrastructure files foundand exit cleanly
Update cadence / Lifecycle
Infrastructure topology changes rarely compared to source code. The scripts should:
- Run only when
--infraflag is passed tobuild_belief_map.py - OR when the user explicitly runs
build_infra_topology.py - Cache results alongside
.belief_map_cache.jsonusing mtime-based invalidation on IaC files
Future-proofing
- Tilt / docker-compose: same intermediate format, different parser. Add
--tiltand--composeflags. - Ingress topology: parse
ingress.yaml/Ingressresources for external routing - Multi-namespace: extend
infra-nodewith:namespacefield - Service mesh: parse Istio/Linkerd VirtualService/DestinationRule for traffic routing edges
- GitOps: parse ArgoCD Application manifests for deployment pipeline edges
- Runtime enrichment: optional
--liveflag to query kubectl for actual pod status
Implementation outline
Phase 1: Core infrastructure (shared)
- Define
InfraNodeandInfraEdgedataclasses inbuild_infra_topology.py - Define env-var heuristic patterns for service/infra detection
- Implement sexp writer for new fact types
- Add
--infraflag tobuild_belief_map.pythat calls the topology builder post-pass
Phase 2: Kustomize parser
- Walk directories for
kustomization.yaml - Parse resource references -> find deployment/service YAML
- Extract service names, images, ports, env vars
- Apply env-var heuristics to detect dependencies
- Emit
infra-node,k8s-depends,k8s-servicefacts
Phase 3: Helm parser
- Walk directories for
Chart.yaml - Parse Chart.yaml dependencies
- Parse values.yaml for image/port/env configuration
- Parse templates/*.yaml for env var references (handle Go template syntax)
- Emit
infra-node,helm-dependsfacts
Phase 4: Terraform parser
- Walk directories for
main.tf - Parse module blocks and resource blocks (via hcl2 or regex)
- Build dependency graph from cross-module references
- Emit
infra-node,tf-dependsfacts
Phase 5: Cross-layer mapping
- For each
infra-nodewith:image, match image name against known repo names - Emit
infra-mapsedges connecting infrastructure to source-code graph - Update
belief_search.pyto parse new edge types
Phase 6: Integration
- Add new edge types to
belief_search.pyedge parser - Update
sexp-notation.mdreference with new fact types - Update SKILL.md with infrastructure query examples
Testing approach
Unit tests:
- Kustomize parser: fixture kustomization.yaml + deployment.yaml -> expected InfraNode/InfraEdge list
- Helm parser: fixture Chart.yaml + values.yaml -> expected output
- Terraform parser: fixture main.tf with modules -> expected dependency graph
- Env-var heuristics: input env var name/value -> expected dependency type
Integration tests:
- Run kustomize parser against
mono/app-service/.k8s/base/-> verify app-service depends on postgres - Run helm parser against
nextnet-avsync-streaming/charts/-> verify chart dependencies - Run terraform parser against
avsync-stream-lab/infra/terraform/-> verify module dependencies
Manual verification:
- Run
build_infra_topology.pyon mono workspace - Query
belief_search.py rdeps infra/drive-postgres 1-> verify all drive consumers listed - Query
belief_search.py deps infra/app-service 2-> verify database + queue dependencies shown
Acceptance criteria
-
Given a workspace with
.k8s/base/directories, when runningbuild_infra_topology.py --kustomize ., theninfra-nodefacts are emitted for each deployment with correct service name, image, and port. -
Given a deployment.yaml with
DATABASE_URLenv var, when parsing, then ak8s-dependsedge is emitted to the postgres service with:transport tcp. -
Given a kustomization.yaml with
resources: [app-service, app-web, postgres], when parsing, then all three services appear asinfra-nodefacts. -
Given a Helm Chart.yaml with
dependencies, when parsing, thenhelm-dependsedges are emitted for each dependency. -
Given Terraform modules with cross-references (
module.namespace.name), when parsing, thentf-dependsedges reflect the dependency order. -
Given an
infra-nodewith:image upgraide/app-service, when cross-layer mapping runs, then aninfra-mapsedge connectsinfra/app-serviceto the source-codeapp-servicenode. -
Given
--infraflag passed tobuild_belief_map.py, when building completes, then infrastructure facts appear in.belief_map.sexpalongside source-code facts. -
Given no IaC files in the workspace, when running the topology builder, then it exits cleanly with an informational message and no errors.
-
belief_search.pycorrectly loads and queriesinfra-node,k8s-depends,helm-depends,tf-depends, andinfra-mapsedges.
Estimated scope
| Component | Lines | Effort |
|---|---|---|
| Shared infra types + sexp writer | ~80 | Small |
| Kustomize parser | ~200 | Medium |
| Helm parser | ~150 | Medium |
| Terraform parser | ~120 | Medium |
| Cross-layer mapping | ~40 | Small |
| belief_search.py integration | ~10 | Trivial |
| Tests | ~150 | Medium |
| Total | ~750 | ~2 days |