Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What a repository depends on

headwater starts from a repository state: the content of a repository at one commit, together with any uncommitted change the asker names as part of it. Every bound dependency description reads that state and reports what it declares (40). headwater reads only the dependencies the state declares, and invents none (1).

Declarations and identities

A declaration is one place in a repository state that names a dependency, with what it says of the version. A dependency entry in package.json, a requirement in pyproject.toml, a FROM line in a Dockerfile and a uses: line in a workflow are each a declaration (C.2).

Every dependency has exactly one identity, whichever declaration names it and whichever source publishes it (2). The identity is a Package URL without its version: pkg:npm/react, pkg:pypi/fastapi, pkg:github/jdx/mise (C.1). Two declarations of one identity are one dependency, wherever in the repository they sit.

headwater reports a declaration it cannot read as unread, naming the declaration and the reason, and never drops it from a report (1). A dependency left out would read as one with nothing newer. An unread entry says what is true: headwater does not know.

The pin

A dependency’s pin is the version the repository runs. Each description reads it where its ecosystem records it: the version a lockfile resolves for npm, Cargo and Python, an exact == requirement for Python, the version go.mod names for Go (C.2). A pin is exactly one version or unknown (3). A range with no lockfile behind it, a latest tag, a branch or a version prefix names no single version, so its pin is unknown.

headwater reports a dependency whose pin is unknown as unknown, never as having nothing newer (3). An asker who reads that nothing is newer can rely on it.

A dependency declared at more than one pin in one repository state is reported with every declaration and its pin. A release is newer for it when it is newer than the oldest of them (4). The report measures from the declaration that lags, and a fixer sees which one that is.

DECLARATIONS DEPENDENCIES WHAT IS MEASURED pyproject.toml acme-client>=3.0, resolved by uv.lock 3.2.0 tools/pyproject.toml acme_client==3.1.0, no lockfile 3.1.0 web/package.json "acme-fmt": "~1.0.0", no lockfile unknown pkg:pypi/acme-client two declarations, one identity (2) pkg:npm/acme-fmt one declaration newer than 3.1.0 the older of the two (4) pin unknown never "nothing newer" (3)
Declarations join by identity. acme_client and acme-client are one Package URL, so the two declarations are one dependency, measured from its older pin (2, 4). A range with no lockfile names no single version, and the report says the pin is unknown (3).

Reading changes nothing

headwater never writes a repository’s content, its history or its configuration (5). A dependency description gives two answers, the dependencies and the tiers, and both are the same for the same repository state. Reading either changes nothing (42). Agents in many checkouts of one repository ask at once (section 9), and a read that changed anything would change what the others read.

The descriptions

Each ecosystem’s declarations have a profile of their own (C.2): npm, Python, Cargo, Go, mise, GitHub Actions, container images and the watch list. The first set is the watch list, npm and Python (section 9). Each profile says which files it reads and where it finds the pin, and each reports the pin as unknown where its files name no single version. The watch list declares what no other file can, and has a chapter of its own.

Still to rule: questions 5, 7, 8, 17, 29 and 30, in Open questions.