# How to build an inventory of undocumented dependencies

> A method for finding what a system calls, reads, writes and waits on, when nobody wrote it down, and for recording how sure you are about each item.

Published 2026-10-24 by Maxint. Written for: Engineers, Engineering leaders.
Canonical: https://foundry.maxint.com/resources/inventory-of-undocumented-dependencies

A dependency is anything whose absence or change would alter what your system does. Most teams can name the obvious ones: the primary database, the payment provider, the message broker. The ones that cause incidents are the others. A cron job that reads a table nobody mentions. A shared S3 bucket two services write to. A DNS name that still resolves to a server someone meant to decommission.

An inventory is the list of all of them, with enough detail to answer three questions for each: what happens if it is slow, what happens if it is down, and what happens if it changes shape.

## Decide what counts

Before collecting anything, agree on the categories. A workable set:

- **Outbound calls.** HTTP, gRPC, database connections, cache lookups, queue publishes, SMTP, DNS.
- **Inbound triggers.** Who calls this system, what queues it consumes, which schedules fire it.
- **Shared storage.** Databases, tables, buckets, file shares and caches that more than one system reads or writes.
- **Configuration and secrets.** Environment variables, feature flags, config services, certificate stores.
- **Build and runtime.** Base images, package registries, language runtimes, licensed binaries.
- **People and processes.** The manual step someone runs on the first of the month. The spreadsheet that feeds an import.

The last category is the one most inventories skip. It is also the one that breaks when a person leaves.

## Collect from more than one source

No single source is complete, and each one lies in its own way. Use at least three and reconcile.

**Source code and configuration.** Grep for client constructors, connection strings, hostnames, ports and SDK imports. This finds what the code can call. It does not tell you what it does call in production, and it misses anything reached through reflection, plugins or config loaded at runtime.

**Network and runtime observation.** Flow logs, service mesh telemetry, `netstat` on a running host, egress firewall logs, database connection tables. This finds what actually talks to what over a window. It misses anything that did not happen during the window, such as a quarterly job.

**Infrastructure definitions.** IAM policies, security group rules, Kubernetes manifests, Terraform state. These show what is permitted. Permitted is not the same as used. A security group that allows port 1433 to a host decommissioned two years ago is a stale permission, not a dependency.

**Operational history.** Incident reports, on-call runbooks, change tickets. If an outage in a system you never considered caused an incident here, that is a dependency, however indirect.

**Interviews.** Ask the people who operate the system what they check first when it misbehaves. Their answer is usually a dependency the diagrams forgot.

## Record the evidence state for each item

The inventory is only useful if a reader can tell how much to trust each row. Give every dependency one of four states.

- **Observed.** Seen in a running environment. Connection to `pricing-svc:8443` seen in mesh telemetry on 14 consecutive days.
- **Inferred.** Reasoned from code or config, not seen at runtime. `SFTP_HOST` is read in `export.py`; no transfer was observed in the sampled window.
- **Verified.** Confirmed by a repeatable test. Blocking `pricing-svc` in staging caused `POST /quotes` to return `503` within 2 seconds; test `deps/pricing-unavailable.spec.ts`.
- **Open.** Unknown and recorded as such. `legacy_sync` table is written nightly; no reader identified.

A minimal row looks like this:

```yaml
- name: pricing-svc
  kind: outbound-http
  used_by: [POST /quotes, GET /catalog]
  state: verified
  evidence: mesh telemetry 2026-10-01..14; deps/pricing-unavailable.spec.ts
  failure_mode: 503 on quotes, catalog served from cache for 10 min
  owner: pricing-team
  open_questions: []
```

Resist the urge to tidy inferred items into observed ones to make the table look finished. An inventory with honest gaps is worth more than a complete-looking one that is wrong in places you cannot see.

## Work from the edges inward

Start with the system's boundary, not its internals.

1. **List every process that runs.** Services, workers, cron entries, scheduled lambdas, database triggers, stored procedures with schedules.
2. **For each process, list what it opens.** Sockets, files, database sessions, queue subscriptions.
3. **For each thing opened, find the other end.** Who owns it, what else uses it, whether it is shared.
4. **For each shared thing, list the other writers.** Two writers to one table is a dependency between the two writers, even if neither calls the other.
5. **Repeat for the time dimension.** Anything that runs weekly, monthly or at quarter end needs a window long enough to catch it, or a code-based inference labeled as such.

Shared storage deserves particular care. Two services that never call each other but both write `orders.status` are coupled more tightly than many services that do call each other, because a schema change breaks both and neither has a contract to point at.

## Characterize each dependency, not just name it

A name tells you something exists. A decision needs more.

- **Direction and protocol.** Who initiates, over what.
- **Criticality.** What the system does when the dependency is slow, returns errors or disappears. Does it fail the request, degrade, queue, or silently proceed with stale data?
- **Timeouts and retries.** Observed values, not configured ones. A 30-second configured timeout behind a 10-second load balancer timeout is effectively 10 seconds.
- **Data flowing across it.** Especially personal data, credentials and anything with retention rules.
- **Change coupling.** Whether a change on the far side has broken this system before.

Most of these start as open. That is fine. The inventory tells you where the tests are needed.

## Keep it alive

An inventory built once is a snapshot. Dependencies appear with every deployment. Three habits keep it current.

- **Re-observe on a schedule.** Re-run the runtime collection monthly and diff against the inventory. New hostnames and new tables are the signal.
- **Attach verification to the ones you rely on.** A failing dependency test tells you something moved before an incident does.
- **Make the inventory the artifact a change review reads.** If a change touches a shared table, the review should see every other reader and writer without anyone having to remember them.

## Where Foundry fits

Foundry builds this kind of inventory as part of mapping an undocumented system. The dependency map shows what calls what and what depends on it, with each edge labeled observed, inferred, verified or open, and the conformance tests behind verified edges are delivered with it. For systems that keep changing, Foundry re-analyzes and reports what moved. The [Understand use case](/use-cases/understand-software) describes the full deliverable.
