headwater
headwater tells a repository which releases of what it depends on are newer than what it runs. It reads what the repository declares, asks where each dependency is published, and reports every newer release with the words upstream wrote for it. It looks often for the dependencies the repository’s authors care most about, and less often for the rest. It never changes the repository, never judges what a release means, and never acts on one: a person or an agent decides, and the change is theirs (5, 36).
Why it exists
A repository runs versions of things published elsewhere, and upstream keeps publishing. Dependency updaters make the change as well: they open a branch per bump and wait for a review. Where agents work in worktrees, a change already has its own path: a worktree, the repository’s gate, a landing through sluice. What the agents lack is the knowledge to start from: which releases exist that the repository does not run, what upstream said of each, and which advisories name a version it runs.
headwater supplies that knowledge and stops there. swancloud, the first fleet to run it, watches the tools it pins with a release adapter of its own, which finds releases and reads their notes. headwater does that work for any repository, for every dependency a profile can read (Part C).
What it promises
- The pin alone decides what is new (9). headwater keeps no record of what it reported, so a release stays in every report until the pin reaches it or an acknowledgement leaves it out (21). A fixer clears a release by landing the bump.
- Upstream’s words arrive as upstream wrote them (11). headwater gives each release’s notes whole, or the part asked for with a way to read the rest (12), and no model reads them inside headwater.
- A release has one name for ever: its dependency’s identity and its version (10). A recorder that keys on it keeps each release once.
- Asking what headwater holds costs no source anything (17). The answer comes from what headwater keeps, and writes nothing.
- Only acknowledgements cannot be rebuilt (29). The next scan rebuilds everything else headwater keeps.
- Every asker gets the same answer (32). A person at the command line, an agent at a session’s start and a pane in herdr’s sidebar ask one core and read one form (33, 35).
Where it stops
headwater moves no pin, builds nothing, tests nothing and lands nothing. A fixer does that, and the repository’s own gate decides whether it lands. headwater reports what the repository’s authors chose, never what those dependencies depend on in turn; a vulnerability scanner reads the whole graph. It never summarises notes or judges a release. It tells nobody on its own: a surface tells, when its asker asks. Licences and bills of materials are outside it (section 8).
Where it sits
headwater stands alone, with plugins for Claude Code and herdr. Its
Flywheel plugin holds an adapter part that prints each release as a
capture for millpond, the inbox, which lands each
release once and lets curation turn it into work. Whatever bump comes
of it lands through sluice. headwater never knows which
inbox records its releases (design/capture/design.md).
A release that needs judgment, such as a major version or notes that announce breaking changes, can go from headwater’s herdr plugin to a sounding. A sounding is one question, worked by one session in one place, ending on a conclusion the operator decides.
How to read this book
The chapters follow a release from the declaration that names its dependency, through the scan that finds it and the brief a fixer reads, to the capture that records it. The glossary defines each term once. The open questions page lists what the operator has yet to rule, grouped by the chapter each ruling would change.
The appendix is the statement of invariants, included from
design/headwater/requirements.md. Part A is what must hold of
headwater, Part B its contracts across five seams, and Part C the
profiles that satisfy them. A number in parentheses cites a clause of
it.
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.
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.
Releases and what is new
A release is one version of a dependency that its source publishes, with its date and a link to it. For each dependency, headwater asks every bound release source that knows its identity for the releases it publishes (43), and sets each one against the pin.
Newer, by the source’s own order
A release is newer than a pin only by the dependency’s own version order: how its source orders its versions (6). headwater never orders two versions by their dates, by their names’ spelling, or by the order a source lists them. A backport published today is older than last month’s major release, and headwater reports the same newer releases whichever order a source lists them in.
Each ecosystem brings its own order (C.3). Semantic Versioning 2.0.0 orders npm, Cargo and GitHub tags that parse, PEP 440 orders PyPI, and Go’s module rules order Go. A calendar version compares field by field where a watch list says so. The same two versions always order the same way (45).
headwater reports a release whose order against the pin cannot be decided as undecided, beside the newer ones (7). It never counts such a release as newer and never leaves it out, so the asker sees it and decides.
Every newer release, and only those
A report holds every release newer than the pin that the source publishes, and none that is not newer (8). A fixer moving a dependency three releases forward reads what upstream wrote of each of the three.
Inside or outside the declared range
The capture design settles one more mark on each release, and the
statement takes it at plan step H1 (design/capture/design.md).
headwater marks each release inside or outside the range its
declaration gives, such as ^1.4 or >=1.4,<2, which the dependency
description carries. The package manager takes a release inside the
range by changing only the lockfile. A release outside it changes the
manifest, as a major release does. headwater reports both, so a major
release is never hidden (8).
Each declaration’s range marks the release on its own. A release of a dependency declared in two places can be inside one range and outside the other, and the brief names both declarations (4, 26).
The pin alone decides
The pin alone decides what is new (9). headwater reads no record of what it reported before to decide it, so every scan reports a release until the pin reaches or passes it, or an acknowledgement leaves it out (21). A fixer clears a release by moving the pin: the bump lands, the next reading has the new pin, and the releases it reaches leave the report with nothing else written (S4).
A tool that remembered what it had shown would need that memory kept, shared between checkouts and repaired when it went wrong. headwater keeps none, so two checkouts at one pin always agree.
One identity for ever
A release’s identity is its dependency’s identity and its version,
written as the Package URL with its version:
pkg:github/jdx/mise@2026.10.10 (10, C.1). Every scan, every surface
and every host names the same release the same way, so an asker can key
on it. A recorder that hears of a release every day keeps it once (S6).
What upstream wrote
A release reaches the asker with what its source published of it: its version, its date, its link and its notes (11). Notes are what upstream wrote of a release, as upstream wrote it. headwater never summarises, rewords, translates, ranks or judges a release, and no model reads notes inside headwater. The asker judges, from upstream’s own words.
A notes source gives them (46). The first is the body of the GitHub
release at the version’s tag in the dependency’s source repository.
Where no release body exists, the second reads the version’s section
of that repository’s CHANGELOG.md at the tag (C.4).
Every report and every brief gives each release’s notes whole, or the part an asker asked for together with a way to read the whole again (12). A release whose notes run to forty kibibytes reaches a session as the part the session asked for, with the way to the rest, and not one word of what arrives is changed (S7). headwater keeps no part of notes it cannot give whole.
Where headwater asks
A source’s answer is reported as that source gave it. Where two sources disagree about one release, the report names both and chooses neither (39). The first release sources are deps.dev, queried in batches by Package URL with no key, and GitHub releases, read through the asker’s own sign-in (C.3, section 9). What a source learns of the repository is in The core and its seams.
The capture design settles where headwater asks first, and the
statement takes it at plan step H1 (design/capture/design.md).
headwater asks each ecosystem’s own package manager first, read-only in
the repository’s own shell, so it sees the registries the repository is
configured for, private ones among them. It asks npm view and
npm outdated, go list -m -versions, mise ls-remote, and the
configured registry’s index for Python and Cargo. deps.dev, GitHub and
ecosyste.ms answer where no package manager runs or none exists, and
OSV answers for advisories.
Still to rule: questions 9, 12, 13, 22 and 23, and the statement’s own questions on prereleases and withdrawn releases; the range and the package managers enter the statement at plan step H1. See Open questions.
Tiers and scans
A repository’s authors watch a few of its dependencies closely and the rest less often. headwater looks often for the first and less often for the rest, and the authors say which is which.
Tiers
A tier is a named set of a repository’s dependencies, with a cadence. Every dependency of a repository state belongs to exactly one tier (13), and the default tier holds every dependency no other tier names. headwater reads the tiers and their cadences from the repository state (14), through each dependency description’s answer of the tiers, each with its cadence and the identities it names (41). headwater never moves a dependency between tiers and never changes a cadence. The authors decide both, in the repository, where a change to them lands like any other change.
The first profile reads the tiers from .config/headwater.toml: each
tier’s name, its cadence and the identities it names. A repository with
no such file has the default tier alone, with a cadence of seven days
(C.2).
Cadence and due
A tier’s cadence is the longest time it may go without a complete scan while scans are asked for. headwater runs no schedule of its own: an asker asks, at a session’s start, on a timer or by hand, and the cadence decides how much of each scan reaches a source.
A scan is one reading of a repository state’s dependencies and of the sources of those in the tiers it covers. It is complete for a tier when every dependency of the tier was read and every source reached. A tier is due when its cadence has passed since its last complete scan, or when it has never had one.
A scan of what is due reaches the sources of the due tiers’ dependencies and of no others (15). A scan of a named tier reaches its sources whether or not it is due. A session that starts every hour and asks for what is due reaches a source only when a tier’s cadence has passed.
An incomplete scan
A scan that leaves a tier incomplete reports every dependency it could read, reports the rest as unread with the reason, and leaves the tier due (16). A source that cannot be reached costs its own dependencies and nothing else: the scan reports the tier’s other dependencies, and the next scan of what is due tries again (S3).
Asking what headwater holds
Asking what headwater holds reaches no source and writes nothing (17). The answer is the newest report for each tier, and says when each was made and whether its tier is due. A scan of what is due, asked when no tier is due, reaches no source either. A session that starts again an hour after its first is told the same releases and when they were found (S2).
Still to rule: questions 2, 14, 15, 16, 18, 19 and 20, and the statement’s own question on when a cadence starts, in Open questions.
Advisories
An advisory is a published warning that names versions of a dependency as unsafe. headwater asks every bound advisory source for the advisories that name a version (47). A dependency whose pin an advisory names is reported as affected, naming the advisory, in every report that holds the dependency (18).
Apart from notes
headwater reports an advisory apart from notes (19). It never folds an advisory into a release’s notes and never infers one from them. A release whose notes mention a security fix is reported with those words, as upstream wrote them, and only an advisory source makes a dependency affected.
Each advisory comes with its identity, its severity as the advisory gives it, and a link (47). headwater ranks none of them and translates no severity into a scale of its own.
Nothing hides an affected pin
An acknowledgement never changes an advisory. A dependency with an advisory naming its pin is reported as affected whatever its acknowledgements say (22). A person who dismissed the release that would fix it still sees the advisory in every report that holds the dependency, until the pin moves to a version the advisory does not name (S10). Acknowledgements shows the two side by side over time.
The advisory source
The first advisory source is OSV, asked in batches by Package URL and version, with each advisory’s severity as the advisory gives it (C.5). OSV learns what every source learns: the identities and versions headwater asks about, and nothing else of the repository (37).
What it leaves to others
headwater reports advisories for the dependencies the repository’s authors chose. What those depend on in turn is outside it; scanning the whole graph for advisories belongs to a vulnerability scanner (section 8).
Still to rule: questions 6 and 21, and the statement’s own question on whether advisories wait on a tier’s cadence, in Open questions.
Acknowledgements
The pin alone decides what is new (9), so a release a repository will never take stays in every report until something leaves it out. An acknowledgement does that: a person’s word, or an acting session’s, on one release of one dependency.
Dismissed and skipped
An acknowledgement is one of two kinds.
- A dismissed release is left out of reports (21).
- A skipped release is left out until a release newer than it is published. headwater then reports that newer release with the skipped one beside it (21). A person who skipped a release sees it again when its successor arrives, next to what replaced it.
Who gives one
An acknowledgement is only ever given by a person or an acting session, for one release of one dependency (20). headwater never infers one, and never gives one when a pin moves. A recorder never writes headwater’s state (section 2), so a program that keeps what it hears gives no acknowledgement either.
Every acknowledgement names who gave it and when, and stays readable after the release it acknowledges leaves every report (24). Acknowledgements are the one part of the held state that no scan can rebuild (29), because only a person or an acting session can give them.
What it never touches
An acknowledgement never changes a pin, a version order or an advisory (22). It decides which releases a report shows, and nothing else. An asker may always ask for a report that disregards every acknowledgement, and gets the report the pins alone decide (23).
Over time
One dependency, pkg:pypi/acme-client, pinned at 3.1.0, with an
advisory ADV-1 that names 3.1.0:
| step | what happens | reported as newer | ADV-1 |
|---|---|---|---|
| 1 | a scan | 3.2.0, 3.3.1 | affected |
| 2 | a person dismisses 3.2.0 | 3.3.1 | affected |
| 3 | a person skips 3.3.1 | none | affected (22) |
| 4 | upstream publishes 3.4.0; a scan | 3.4.0, with 3.3.1 beside it as skipped | affected |
| 5 | a fixer moves the pin to 3.4.0, the change lands; a scan | none | not affected: it does not name 3.4.0 |
At every step a report that disregards acknowledgements shows every release newer than the pin (23). After step 5 both acknowledgements stay readable (24), and the pin’s move neither gave nor removed one (20, S4). The advisory stood through steps 2 to 4, though a person had dismissed the release that would fix it (S10).
Still to rule: questions 24 and 25, the statement’s own question on whose acknowledgement it is, and whether a curation drop acknowledges, in Open questions.
Briefs and fixers
headwater never acts on a release (36). A fixer does: a coding agent or a person who makes changes, and who decides whether and how to move a pin. headwater hands the fixer a brief, everything needed to move one dependency to one release, and the change the fixer makes is the fixer’s.
What a brief holds
headwater gives a brief for any release a report holds (25). The brief holds:
- the release, with its version, its date and its link;
- its notes, whole, or the part asked for with a way to read the whole again (12);
- its advisories;
- every declaration of the dependency in the repository state, with the pin each gives (26).
The declarations let a fixer move every one. A dependency declared in two projects at two pins needs both moved, and the brief names both, with the older pin the report measured from (4, 26).
What a brief reaches
Asking for a brief reaches no source but the notes and advisory sources, and writes nothing but notes it keeps whole (25). The release itself comes from what headwater already holds.
What a brief leaves out
A brief prescribes nothing (27). It names no tool that acts on it and no step a fixer must take. The fixer chooses: a lockfile update, a code change the notes call for, or no change at all. headwater judges none of these, and a brief reads the same whichever fixer will act on it (34).
The command and the diff
The capture design settles two more things a brief carries, and the
statement takes them at plan step H1, where they amend (27)
(design/capture/design.md). A brief names the ecosystem’s own
command for the move: npm update or npm install <name>@<version>,
uv lock --upgrade-package, cargo update -p, go get, mise use.
Whoever makes the change runs it, and headwater never does. Beside the
release’s notes, a brief can carry the published package’s own diff
between the pin and the release (npm diff), so a change to a README
or a CHANGELOG shows as upstream shipped it.
After the brief
The fixer makes the change in a place of its own, and the repository’s own gate decides whether it lands (section 8). In a repository that lands through sluice, the fixer submits and sluice lands what passes. Once the change lands, the next reading has the new pin, and the releases it reaches leave every report with no acknowledgement written (9, S4).
A surface hands a brief on. The Claude Code plugin hands it to a
background agent in a worktree of its own (section 9). The herdr
plugin’s key hands it to flume, which starts a new agent in
a worktree with it (section 9, design/capture/design.md). In a repository
crew runs, a bump comes only through crew’s plan, and the herdr key
hands the release to millpond instead; Releases as
captures follows that path.
Still to rule: questions 26 and 27; the command and the diff enter the statement at plan step H1. See Open questions.
The held state
headwater keeps a little of its own for each repository: the held state. It holds what headwater needs to answer without asking a source again, and the acknowledgements people give.
Outside the repository, shared by every checkout
headwater keeps its held state outside the repository’s content (28). A checkout’s status never shows it, and no commit carries it. Every checkout of one repository shares one held state, so an agent in one worktree and a pane in another read the same report and see the same acknowledgements.
The first store keeps it in the files under <git common dir>/headwater/,
which every worktree of the repository shares, and keeps notes whole in
the same directory (C.6).
Disposable, except acknowledgements
Everything in the held state but acknowledgements can be discarded, and the next scan rebuilds it with no report changed but the times it gives (29). A person who deletes the held state apart from its acknowledgements loses nothing a scan cannot fetch again: the next scan reports exactly what the last one did, at a new time (S9). Acknowledgements are the one thing no source can give back.
Two scans at once
Agents in many checkouts of one repository scan at once (section 9). Two scans of one repository at once leave the held state as one or the other would have left it. Neither leaves it half written, and no acknowledgement is lost (30, S8).
The store has two operations (B.5). A read returns a repository’s held state and the version it was read at (48). A write takes the version read and the next held state; it is all or nothing, and succeeds only if the held state is still at the version read (49). The first store writes a new file beside the old and renames it over the old, under a lock held for the write (C.6).
One reading per report
Every report is made from one reading of the repository state (31). A report never mixes the declarations of two repository states, so a scan that runs while a checkout moves reports one state whole.
No credentials
headwater reaches a source with the asker’s own credentials or with none, and never keeps a credential in its held state (38). Every checkout of the repository shares the held state, and nothing in it signs in anywhere.
Still to rule: questions 1, 28 and 31, and the statement’s own question on where the held state lives when several hosts share one repository, in Open questions.
The core and its seams
headwater is one core that knows git and reaches everything else through five seams. Each seam is a contract of Part B, and each is bound by naming one or more profiles of Part C (51). Parts A and B do not change to admit a profile.
The five seams
The dependency description (B.1) reads a repository state. It answers every dependency the state declares, each with its identity, every declaration of it and the pin each gives, and every declaration it could not read, with the reason (40). It answers the tiers, each with its cadence and the identities it names (41). Both answers are the same for the same repository state, and reading either changes nothing (42).
The release source (B.2) lists every release it publishes for an identity, each with its version, its date, its link and whether the source has withdrawn it. Where the source cannot be reached, it says so (43). It orders two versions of one identity: one is newer, they are the same, or their order cannot be decided (44). The order is the source’s own, and the same two versions always order the same way (45).
The notes source (B.3) gives what upstream wrote of one release, whole and unchanged, or says upstream wrote nothing (46).
The advisory source (B.4) gives every advisory that names one version of an identity, each with its identity, its severity as the advisory gives it, and a link (47).
The store (B.5) reads a repository’s held state with the version it was read at (48). It writes the next held state all or nothing, and only if the held state is still at that version (49). It holds nothing in the repository’s content, and every checkout of one repository reaches the same store (50).
Binding
Any number of dependency descriptions, release sources, notes sources and advisory sources are bound at once, each answering for the identities it knows, and exactly one store is written (51). A repository that holds npm packages, Python requirements and a watch list binds all three descriptions, and deps.dev and GitHub releases each answer for the identities they know.
The capture design puts each ecosystem’s own package manager first among the release sources, run read-only in the repository’s own shell, with deps.dev, GitHub and ecosyste.ms answering where none runs. The statement takes it at plan step H1 (Where headwater asks).
What a source learns
headwater sends a source nothing of the repository but the identities
and versions it asks about (37): no file, no path, no commit. It reaches
a source with the asker’s own credentials or with none (38). GitHub
releases are read through the asker’s own gh sign-in, fifty
repositories a query (C.3). Where two sources disagree about one
release, the report names both and chooses neither (39).
One suite
One conformance suite holds every profile to Parts A and B, and a profile that fails it is not one (52). Each of its scenarios cites the clauses it exercises, and the suite fails when no scenario cites a clause (section 12). A new profile implements its seam’s contract and passes the suite, and nothing else in headwater changes.
One binary
headwater is one static binary, written in Rust as sluice is, and gives every answer as JSON on request (section 9).
Still to rule: questions 4, 11, 32 and 33, and how a package manager in the repository’s shell keeps (5), in Open questions.
Askers and surfaces
An asker is whoever asks headwater about a repository: a person, a coding agent, a surface, a recorder or a scheduler. It decides which repository and which tiers to ask about, and when. It never changes an answer by who it is, and never supplies a release or a version order (section 2). A surface is one way an asker reaches headwater.
One core, one answer
The same question about the same repository state and the same held state gets the same answer, whoever asks and through whichever surface (32). Every surface answers from one core, and no surface computes a report, a brief, a due tier or an acknowledgement of its own (33). Nothing headwater reports depends on which surface asked, which recorder will keep it, or which fixer will act on it, and headwater needs none of them to answer (34).
Every answer has one form a program reads, the same through every surface, and that form only ever gains fields (35). A program written against an early answer keeps reading every later one. The form is JSON, given on request (section 9).
The four surfaces
The Claude Code, herdr and Flywheel surfaces are plugins kept outside
the core, as sluice keeps its plugins (section 9), one per host under
plugins/ beside the core, plugins/claude, plugins/herdr and
plugins/flywheel (design/capture/design.md).
- The command line answers any question a person or a script asks, rendered for reading or as JSON.
- The Claude Code plugin notes new releases at a session’s start, and hands a release to a background agent in a worktree of its own. A colleague who opens a session in a repository is told of the newer releases in its favorites tier, with their notes, and nothing in the repository changes (S1).
- The herdr plugin gives a sidebar row, a popup, and a key that
hands a release to a new agent in a worktree. The key hands the
release’s brief to flume as a fix, and flume starts the
agent that makes the bump and submits it to sluice
(
design/capture/design.md). A sidebar that asks what headwater holds reaches no source however often it redraws (17). Where sounding is installed, another key opens a sounding on a release that needs judgment (design/capture/design.md). - The Flywheel plugin holds one part, an adapter that turns each release into a capture (Releases as captures).
Telling
headwater tells nobody on its own (section 8). A surface tells, when its asker asks: the Claude Code plugin at a session’s start, the herdr plugin when its pane is drawn, the Flywheel plugin’s adapter when millpond runs it. A scheduler is an asker like any other.
Handing a release on
A surface may hand a brief to a fixer, and the change the fixer makes is the fixer’s (36). headwater itself never acts on a release.
In a repository crew runs, the herdr plugin’s key hands the release to millpond as a capture and starts no agent outside crew’s plan (Releases as captures).
Where sounding is installed, a release that needs judgment can go from
the herdr plugin’s key to a sounding instead,
such as a major version or notes that announce breaking changes. The
release’s brief is the sounding’s source. The plugin hands a brief on,
as any surface may (36). The sounding works the release as one
question in a worktree of its own and ends on a conclusion the operator
decides (Deciding a conclusion).
The person at the key decides which release needs judgment: headwater
judges no release (11) and acts on none (36)
(design/capture/design.md).
Still to rule: question 3 and the names of the questions a surface asks, in Open questions.
Releases as captures
Some repositories keep what they hear as work to curate. headwater feeds them through its Flywheel plugin, whose one part is an adapter that turns each release into a capture (section 9). millpond keeps captures in an inbox, and millpond’s book defines the capture contract, the inbox and curation. What follows is what headwater puts into each capture.
A recorder like any other
The adapter is a recorder: a program that keeps what it hears as its own
records. It asks headwater as any asker does, decides how it records
each release, and never writes headwater’s state (section 2). headwater
never knows which inbox records its releases (design/capture/design.md)
and needs no recorder to answer (34).
One capture per release
headwater capture prints one capture line for each release headwater
reports (design/capture/design.md):
sourceisrelease, andevent_atis when the release was published, never when headwater found it;rawholds the notes whole, and what the source answered;- the excerpt is the notes, asserted by the upstream, of kind
ask. It asserts “<name><version>is released; the repository pins<pin>”, and argues with the declarations a bump moves.
A release is its own excerpt: upstream’s notes are the quote, so no reader session reads it into signals.
The key
A capture’s key is a source event’s identity, the same from every host and every run. headwater keys a release in one of two ways:
- an entry of a watch list keys as
release/<name>/<version>, under the name the list gives it, the key swancloud’s release adapter has used since its first landing; - every other release keys by its identity,
release/<type>/<namespace>/<name>/<version>, from its Package URL.
Because the pin alone decides what is new (9), the adapter prints a release at every run until the pin moves, and millpond’s dedupe lands it once (S6).
In a repository crew runs
In a repository crew runs, a bump comes only as a chore crew’s planner
makes from an item. There the herdr plugin’s fix key hands the release
to millpond as a capture, and no agent starts outside crew’s plan
(design/capture/design.md). The release then reaches a landed bump by
the path every capture takes. Curation routes its signal into an item,
and the planner proposes the chore. A team bumps the
dependency with headwater’s brief in hand, and sluice lands the
change. The next scan no longer reports the release, because the pin
moved, and nobody wrote an acknowledgement
(millpond’s account).
Where the adapter lives
The adapter is headwater’s because it wraps headwater’s domain, and it
lives in plugins/flywheel beside headwater’s core. millpond runs it
like any other adapter part, on the schedule its inbox’s settings give
it (design/capture/design.md).
Not yet in the statement: the key, the files a bump of a watch-list entry moves, and the capture lines, which plan step H1 writes in. See Open questions.
The watch list
Some dependencies have no file in the repository that names their version in a form a program reads: a tool a fleet builds from a pinned release, whose pin only an evaluation computes. The watch list declares them.
What it is
The watch list is a dependency description (C.2): a file naming each
dependency’s upstream repository, its tag pattern, its pin and its
tier. headwater reads it as it reads any other declaration, and the pin
is what the file gives. Each entry also carries a name and the files a
bump moves (design/capture/design.md).
| an entry holds | for example |
|---|---|
| its name | mise |
| its upstream repository | jdx/mise |
| its tag pattern | the shape of the tool’s release tags |
| its pin | 2026.9.18 |
| its tier | the tier it belongs to |
| the files a bump moves | packages/mise/default.nix |
A watch-list entry’s identity is the Package URL of its upstream
repository, pkg:github/jdx/mise (C.1). GitHub releases lists its
releases, and orders them by Semantic Versioning where the tags parse
and by the watch list’s tag pattern otherwise. Where the list says the
version is a calendar version, they compare field by field (C.3).
When a watch list names fourteen tools by their upstream repositories and tag patterns, headwater reports each release under the same identity a declaration of that tool would give it (S11). A tool named both in a watch list and in another declaration is one dependency (2), and each of its releases is one release.
The key it gives
A watch-list entry’s release keys as release/<name>/<version>, under
the name the list gives it, when the Flywheel plugin turns it into a
capture (Releases as captures).
The first fleet
swancloud, the first fleet that runs headwater, generates its watch
list from lib/release-sources.nix, the list of tools it pins (C.2). The
fleet hands headwater the pins that only Nix evaluates. It commits the
generated file, and a flake check refuses the file when it drifts from
the evaluation, as swancloud already checks its generated task list
(design/capture/design.md).
Once headwater reports swancloud’s releases, the fleet’s own release adapter retires. Its captures’ keys carry on unchanged, because a watch-list entry keys as that adapter always has.
Still to rule: question 10, with 7, 8 and 14 from other chapters; the files a bump moves and the key enter the statement in plan step H1. See Open questions.
Glossary
The words of the design, each defined once. The statement’s vocabulary (section 3) is the binding copy of the words it defines, and its actors table (section 2) of the actors. The words of the capture design and of crew are millpond’s book’s, word for word, and the words for a sounding and for flume are sounding’s book’s. A number after “Flywheel’s” in them cites Flywheel’s statement.
millpond’s book uses three of the statement’s words for other things.
A capture’s source names where its event came from, such as
release. An item’s brief is the text its work starts from. An
unread capture waits for a reader. In this book each keeps the
statement’s meaning.
The statement’s words
| word | meaning |
|---|---|
| repository state | the content of a repository at one commit, together with any uncommitted change the asker names as part of it |
| dependency | something published apart from the repository that its authors chose to use: a package, a tool, an image, or another repository’s releases |
| declaration | one place in a repository state that names a dependency, and what it says of the version |
| identity | the one name of a dependency that every declaration, source and asker uses for it; a Package URL without its version |
| pin | the version a repository state runs of a dependency, or unknown when the state names no single version |
| source | where upstream publishes a dependency’s releases, or an index of those places; a release source, notes source or advisory source by what it gives |
| release | one version of a dependency that its source publishes, with its date and a link to it |
| version order | how a dependency’s source orders its versions |
| newer | of a release against a pin, later in the dependency’s version order |
| notes | what upstream wrote of a release, as upstream wrote it |
| advisory | a published warning that names versions of a dependency as unsafe |
| tier | a named set of a repository’s dependencies, with a cadence; the default tier holds every dependency no other tier names |
| cadence | the longest time a tier may go without a complete scan while scans are asked for |
| due | of a tier, that its cadence has passed since its last complete scan, or that it has never had one |
| scan | one reading of a repository state’s dependencies and of the sources of those in the tiers it covers |
| complete | of a tier’s scan, that every dependency of the tier was read and every source reached |
| unread | of a dependency in a scan, that headwater could not read its declarations or reach its source, with the reason |
| report | what a scan found: for each dependency of the tiers it covers, its pin, the releases newer than it, the advisories that name it, or that it is unread |
| acknowledgement | a person’s word, or an acting session’s, on one release of one dependency: dismissed, or skipped until a newer release |
| brief | everything a fixer needs to move one dependency to one release: the release, its notes, its advisories, and every declaration of the dependency |
| held state | everything headwater keeps of its own for one repository |
| surface | one way an asker reaches headwater |
| profile | one way a contract of Part B is satisfied, as Part C describes |
The actors
| word | meaning |
|---|---|
| asker | a person, a coding agent, a surface, a recorder or a scheduler that asks headwater about a repository |
| fixer | a coding agent or a person who makes changes, and decides whether and how to move a pin, given a brief |
| recorder | a program that keeps what it hears as its own records, and never writes headwater’s state |
| upstream | whoever publishes a dependency, deciding what is released, when, under which version, and what its notes say |
Words the design adds
| word | meaning |
|---|---|
| undecided | of a release, that its order against the pin cannot be decided; reported beside the newer releases (7) |
| affected | of a dependency, that an advisory names its pin (18) |
| withdrawn | of a release, that its source has withdrawn it, as the source says (43) |
| seam | one of the five contracts of Part B through which the core reaches everything that is not git |
| binding | the profiles named for each seam: any number of descriptions and sources, and exactly one store (51) |
| conformance suite | the one set of scenarios that holds every profile to Parts A and B, each citing the clauses it exercises (52) |
| Package URL | the standard name of a package across ecosystems (ECMA-427), such as pkg:npm/react; with a version, a release’s identity (C.1) |
| range | what a declaration says of the versions it takes, such as ^1.4 or >=1.4,<2; a release inside it needs only the lockfile changed, one outside it a change to the manifest (design/capture/design.md) |
| watch list | a dependency description that names each dependency’s upstream repository, tag pattern, pin, tier, name and the files a bump moves (C.2) |
| millpond | the inbox: it runs adapters, keeps captures and signals, serves the tools curation works through, and sends an item on (its book) |
Words from millpond’s book
| word | meaning |
|---|---|
| adapter | a program that knows one source: it lists the source’s events, keys each one, fetches the raw material, marks a capture that is its own excerpt, and prints contract lines (Flywheel’s 115) |
| adapter part | a plugin’s part of the adapter kind: a command, the shape of its configuration, and the secrets it needs (Flywheel’s 228) |
| bolt | in crew, a team’s branch of work: its units and chores land on it, and it lands on the shared branch |
| capture | the record of one source event: its key, its source, its event date and a pointer to its raw material; the same event captured twice is one capture (Flywheel’s 111) |
| chore | in crew, a piece of work that needs no design of its own, such as a tool’s bump; a route move offers one |
| curation | a Claude session that proposes one move per signal, in batches, through millpond’s tools; the operator accepts or changes each batch (Flywheel’s 116) |
| dedupe | millpond’s lookup of each capture’s key in every capture the inbox holds, so a key lands once |
| excerpt | the verbatim text a signal quotes from its source, with its position (Flywheel’s 113) |
| inbox | a git repository, or a branch in one, holding captures, signals, moves and items in Flywheel’s record formats |
| item | the unit of work: the signals curation joined, one or many, with its brief and its sends; Flywheel’s proposed intent (Flywheel’s 109) |
| key | a source event’s identity, the same from every host and every run (Flywheel’s 111) |
| move | the one standing decision on a signal: drop, answered, challenge, join, attach or route, each with a stated consequence (Flywheel’s 107, 116) |
| own excerpt | a capture is its own excerpt when its line carries the signal’s fields, so millpond writes its one signal as it lands; a release is one |
| planner | crew’s agent that orders items across teams and turns each into a chore or a unit |
| plugin | the installable unit in every host, holding parts; Flywheel’s statement calls it a package, and a named set of parts a bundle (Flywheel’s 228, 284) |
| reader | a Claude session that turns an unread capture into signals with millpond’s read tool |
| route | the move that offers a chore, which becomes an item of one, or an ask, which millpond records |
| signal | one assertion from a capture: its kind, who asserted it, its subjects, the assertion in a sentence, the verbatim excerpt with its position, and the claims it argues with; never changed once written (Flywheel’s 113) |
| unit | in crew, one designed piece of a team’s work, landed on its bolt |
Words from sounding’s book
| word | meaning |
|---|---|
| conclusion | conclusion.md: the answer, its evidence, what it would change, the work it proposes, and what stays open; the operator decides it |
| flume | the tool that launches and tracks one agent in one worktree through a kind’s stages, on the host it runs on, and tidies the place when the work lands or is dropped (its book) |
| sounding | one question, worked by one session in one place, ending on one conclusion; also the tool that runs them (its book) |
Open questions
What the design does not settle, and where each question is held. The chapters rest only on what the statement and the capture design settle. A question leaves this page when the operator rules: the ruling lands in the statement, and the chapter that rests on it is rewritten whole.
How the questions are ruled
A fresh model reconstructed headwater from the statement alone,
design/headwater/models/reconstruction-1.md, and asked 32 questions
of it. A 33rd asks whether a source should be called a lookup. Plan
step D1 has the operator rule all 33, into
design/headwater/models/reconstruction-1-answers.md, and step H1
writes the rulings into the statement as clauses numbered from 53.
Step H1 also writes in what the capture design settles for headwater beyond the statement. For releases, that is the range a release falls inside or outside and each ecosystem’s package manager as a source; for a brief, its command and diff. For captures and the watch list, it is the key, the files a bump moves and the capture lines. Each appears below under the chapter that describes it.
Each question below names the clauses it touches and, where it has one, the model’s lean. The statement’s own questions (section 10) sit beside the questions they belong with; the model answers each in its section 11, and those answers wait on the same ruling.
What a repository depends on
- 5. A pin, or several. The vocabulary makes a pin one version or unknown, yet a dependency declared at several pins is measured from the oldest (3, 4, 7). The ruling decides which holds, and what a dependency reports when its declarations mix known and unknown pins, or give two pins whose order is undecided. The model: the oldest pin, and unknown in both other cases.
- 7. One identity across Package URL types. One tool can be
pkg:cargo/xthrough one declaration andpkg:github/o/xthrough a watch list, and a GitHub Action may be one type to GitHub releases and another to ecosyste.ms (2, C.1, S11). Who maps between them, and which identity wins? - 8. From a declaration to a Package URL. How do the mise and GitHub
Actions profiles turn a declaration such as
node = "20", or a tool with a backend prefix, into a Package URL (C.1, C.2, S11)? - 17. An unreadable declaration with no identity. It belongs to no tier (1, 13, 16). The model reports it in every answer, apart from the tiers, and lets it make no tier due; the ruling may instead keep some tier incomplete.
- 29. Uncommitted changes. How does an asker name an uncommitted change as part of a repository state, and may a scan of such a state write the held state every checkout shares (28, 31)? The model: it may, since what is held is kept per identity and the pins stay with each reading.
- 30. What the declarations cover. Do they include npm’s dev, peer and optional dependencies, Go’s indirect requirements, and Cargo’s dev and build dependencies and workspace members (C.2)? Does a broken lockfile make a dependency unread, or its pin unknown (1, 3)? The model: unread.
- Several projects (section 10). How does a repository holding several projects declare one tier across them, and is a dependency in two projects one dependency or two (2, 13)? The model: a tier names identities, and one identity is one dependency.
Releases and what is new
- 9. How a release’s version is spelled. GitHub tags (
v1.2.3,cli-v1.2.3) and registry versions (1.2.3) spell one release differently, and PEP 440 orders1.0and1.0.0as the same while their spellings differ (10, 44, C.3). Whose spelling does a release’s identity carry? - 12. Sources that disagree. Is a release that one source lists and another has not seen yet a disagreement, and are different dates or withdrawn marks? Whose order decides newness when two sources’ orders differ (8, 39, 44, 51)? The model: the union, with each source’s answer shown, and a release undecided where the orders conflict.
- 13. The changelog’s precedence. The changelog profile answers only where no release body exists (C.4). When both exist and differ, does reading only the release body choose between sources, which (39) forbids?
- 22. Prereleases. No newer release may be left out (8), and a release source’s answer carries no prerelease mark (43). The model reports every newer prerelease, marked where the version’s grammar says so. The statement asks the same (section 10): are prereleases reported only where the pin is one, or where a tier asks for them?
- 23. Withdrawn releases. Is a withdrawn release one “the source publishes” (8, 43)? The model reports it with the source’s mark. The statement also asks what a report says of a pin whose own release was withdrawn (section 10); the model gives the same mark on the pin.
- Into the statement: the range. Plan step H1 writes in each release’s mark, inside or outside the range its declaration gives, with the range carried by the dependency description. Each declaration’s range marks the release on its own (4, 26).
- Into the statement: the package managers. Plan step H1 writes in each ecosystem’s package manager, run read-only in the repository’s shell, as a source, with deps.dev where none runs.
Tiers and scans
- 2. Which repository state a held answer is for. Read literally, asking what headwater holds returns the newest report for each tier, whichever checkout made it, with that checkout’s pins (17, 31, 32). The model keeps what the sources said in place of reports, derives each answer from the asker’s own reading, and adds a status for a dependency no scan has looked for yet.
- 14. Tiers from several descriptions. Every dependency description answers the tiers (41), the first profile reads them from one file, and the watch list carries a tier for each entry (C.2). How do the answers combine, and which wins?
- 15. One identity in two tiers. Every dependency then belongs to more than one tier, against (13), and headwater may not choose (14). The model makes the tiers unreadable: every dependency is unread for that reason, and every tier is due.
- 16. The default tier’s cadence, and patterns. Can the tiers file
set the default tier’s cadence, and can a tier name identities by a
pattern such as
pkg:npm/@acme/*(41, C.2)? - 18. A forced scan that fails within the cadence. By the vocabulary, a named-tier scan that comes out incomplete within the cadence of the tier’s last complete scan leaves the tier not due; by (16) it leaves the tier due. The model follows (16).
- 19. A dependency that is never read. One that no bound profile knows, or whose source always refuses, keeps its tier due for ever, and every scan of what is due reaches that tier’s sources again (15, 16, 51). Is that intended?
- 20. Whether notes and advisory sources count. Does an unreached notes or advisory source make a tier incomplete, and does the limit to due tiers bind advisory queries (15, 16)? The model answers yes to both.
- Cadence from start or end (section 10). Is a cadence measured from a complete scan’s start or its end (15)? The model: from its start, since a report knows only as much as its earliest reading.
Advisories
- 6. Which pin an advisory names. With several declared pins, does an advisory naming any one of them make the dependency affected? Does a report also give the advisories that name each newer release, or only a brief does (4, 18)? The model: affected if any declared pin is named.
- 21. An unreached advisory or notes source. A release source can say it could not be reached (43); a notes source and an advisory source cannot (46, 47), so an unreached advisory source reads as no advisories. The model adds the outcome, so headwater never reports a dependency as unaffected when the truth is unknown.
- Advisories and cadence (section 10). Do advisories wait on a tier’s cadence, or is every pin’s advisory looked for at every scan (15, 18)? The model: they wait, and authors who want advisories sooner put the dependency in a faster tier.
Acknowledgements
- 24. Who may acknowledge, and how headwater knows. The actors table gives no actor the decision, an acting session is undefined, and at its interface headwater cannot tell a person from a recorder that asks to acknowledge (20, 24). What identifies who gave one?
- 25. Undoing and conflict. Can an acknowledgement be withdrawn or replaced? What holds when one person dismisses a release and another skips it? The model takes the latest. Does a skipped release return when the release that would bring it back is itself withdrawn (20, 21, 24)?
- Whose acknowledgement (section 10). Is an acknowledgement each person’s or the repository’s, and does it travel to another host that shares the repository (20, 28)? The model: the repository’s, as far as the store reaches.
- A curation drop (
design/capture/design.md). A curation drop of a release signal could acknowledge the release in headwater, so its plugins stop showing it, though a recorder never writes headwater’s state.
Briefs and fixers
- 26. What a brief covers. Only the target release’s notes, or every release between the pin and the target, which a fixer moving several releases needs? What does asking for a brief of a release no report holds return (25)? The model refuses.
- 27. A part of the notes, and the way to the rest. What part may an asker ask for, and what is the way to read the whole once the held notes are discarded, when asking what headwater holds may reach no source (12, 17, 25)? The model: the way is a brief.
- Into the statement: the command and the diff. Plan step H1 writes in a brief that names the ecosystem’s own command for the move, amending (27), and that can carry the published package’s diff between the pin and the release.
The held state
- 1. What a repository is. The vocabulary defines a repository state and no repository (28, 48, 50, C.6). Read as one clone, one git common directory, the first store satisfies (28) and (50); read as every clone of one origin across hosts, no profile does. The model: one git common directory.
- 28. Whether a rebuild assumes the sources stood still. If upstream edited notes, withdrew a release or published one between scans, the rebuilt report differs in more than its times, and after a discard every tier is due (11, 29, S9). Are notes read again at every scan, as the model does, or kept once?
- 31. What “one or the other” means. Do two scans at once end as if run one after the other, the second written onto the first, as the model does (30, 49)? Or does one scan’s result stand whole, with the other’s discarded?
- Several hosts (section 10). Where does the held state live when several hosts share one repository (28, 50)? Under the first store each clone has its own, and one held state across hosts needs a store every host reaches.
The core and its seams
- 4. Who binds the profiles. If the binding sits in the repository state, the authors choose sources, which the actors table does not give them; if it is the operator’s, the answer depends on it (32, 51). The model: the operator’s configuration, outside the repository, named in every answer.
- 11. A dependency’s source repository. GitHub releases and both notes profiles need the dependency’s source repository, which another profile or a declaration supplies (C.3, C.4). Part B’s operations take only an identity and a version (43, 46).
- 32. Whether (37) can be checked. Only against recorded requests. A batched query shows a source many of a repository’s dependencies at once, and the asker’s credentials identify the asker. Is either “something of the repository”?
- 33. Source or lookup. The capture design calls what the statement calls a source a lookup. Until the ruling lands in the statement, the book keeps the statement’s word.
- A package manager in the repository’s shell (5). The commands headwater runs only read, but entering a repository’s shell can write, as devenv writes its lock. The capture design holds this open too. Plan step H5’s suite replays recorded package-manager output, which covers what a registry learns (37).
Askers and surfaces
- 3. What else an answer depends on. An answer depends on more than the repository state and the held state (32). It also depends on the binding, the clock, headwater’s own version and the asker’s credentials. A private source that one asker reaches and another does not changes what the second asker’s scan writes. Which of these may change an answer (38, 45, 51)?
- The questions a surface asks. The core’s questions and their
names come with the rulings. The model names
held,scan,brief,acknowledge,acknowledgementsandforget; plan step H4 namesscan,notes,brief,ackandoverview.
Releases as captures
- Into the statement. Plan step H1 writes three things in after D1
and D2. They are the key a watch-list entry gets, the files a bump of
it moves, and the Flywheel plugin printing the capture contract. Until
then the chapter describes
design/capture/design.md. - The capture design’s decisions. Plan step D2 has the operator accept or change the design’s decisions. Two of them place headwater: it is for dependencies and eddy for meetings, and an adapter that wraps another tool’s domain ships with that tool.
The watch list
- 10. An order that depends on the watch list. GitHub releases orders tags by the watch list’s tag pattern, and compares calendar versions where a watch list says so (C.3). Yet the order takes only an identity and two versions, and the listing only an identity (6, 43, 44). A change to the watch list at another commit would change the order, against (45), and the authors never decide that a release is older than it is.
- The files a bump moves. Plan step H1 writes them into the statement with a watch-list entry’s key; the plan calls them claims, a word Flywheel uses for something else. Questions 7, 8 and 14 bear on the watch list too.
Preamble, purpose, actors and vocabulary
A statement of what headwater must always do and never do, written to admit any model that satisfies it.
The statement is in three parts. Part A is what must hold of headwater on its own: what a repository depends on, which releases of those dependencies are new to it, when each is looked for, what headwater keeps, and what it gives the people and programs that ask. Part B is its contracts: the operations it needs across its five seams, a dependency description, a release source, a notes source, an advisory source and a store, and the guarantee each must give. Part C is the profiles: the ways each contract is satisfied.
Parts A and B name no mechanism: no command, file, format, field, registry, service or tool. One thing of the world they do name freely, because section 9 fixes it: git. Every other real tool is named only in section 9 and in Part C. A reader who has never seen a dependency updater should be able to design from Parts A and B and git alone.
1. Purpose
headwater tells a repository which releases of what it depends on are newer than what it runs. It reads what the repository declares, asks where each dependency is published, and reports every newer release with the words upstream wrote for it. It looks often for the dependencies a repository’s authors care most about, and less often for the rest. It answers the same way to a person, a coding agent, a pane beside the agents, and a program that records what it hears, and none of them needs another to use it. headwater never changes a repository, never judges what a release means, and never acts on one: a person or an agent decides, and the change is theirs.
2. Actors
| actor | may decide | may not |
|---|---|---|
| the repository’s authors | what the repository depends on, which dependencies belong to which tier, and each tier’s cadence | they never decide what a source publishes, or that a release is older than it is |
| an asker (a person, a coding agent, a surface, a recorder, a scheduler) | which repository and which tiers to ask about, and when | it never changes an answer by who it is, and never supplies a release or a version order |
| upstream (whoever publishes a dependency) | what is released, when, under which version, and what its notes say | it never learns anything of the repository beyond the identities and versions headwater asks about |
| headwater | which releases are newer than each pin, as this statement fixes them, and what it keeps to answer again | it never changes a repository, never rewords or judges what upstream wrote, never acts on a release, and never keeps a credential |
| a fixer (a coding agent or a person who makes changes) | whether and how to move a pin, given a brief | it never changes what headwater reports about upstream |
| a recorder (a program that keeps what it hears as its own records) | how it records each release, and what its own judgment of one is | it never writes headwater’s state |
| the operator | everything the repository’s authors and an asker do not settle |
3. Vocabulary
Terms are defined by meaning. A model may add terms; it may not redefine these.
- repository state: the content of a repository at one commit, together with any uncommitted change the asker names as part of it.
- dependency: something published apart from the repository that the repository’s authors chose to use: a package, a tool, an image, or another repository’s releases.
- declaration: one place in a repository state that names a dependency, and what it says of the version.
- identity: the one name of a dependency that every declaration, source and asker uses for it.
- pin: of a dependency at a repository state, the version the repository runs, or unknown when the repository state names no single version.
- source: where upstream publishes a dependency’s releases, or an index of those places.
- release: one version of a dependency that its source publishes, with its date and a link to it.
- version order: how a dependency’s source orders its versions.
- newer: of a release against a pin, later in the dependency’s version order.
- notes: what upstream wrote of a release, as upstream wrote it.
- advisory: a published warning that names versions of a dependency as unsafe.
- tier: a named set of a repository’s dependencies, with a cadence. The default tier holds every dependency no other tier names.
- cadence: the longest time a tier may go without a complete scan while scans are asked for.
- due: of a tier, that its cadence has passed since its last complete scan, or that it has never had one.
- scan: one reading of a repository state’s dependencies and of the sources of those in the tiers it covers.
- complete: of a tier’s scan, that every dependency of the tier was read and every source reached.
- unread: of a dependency in a scan, that headwater could not read its declarations or reach its source, with the reason.
- report: what a scan found: for each dependency of the tiers it covers, its pin, the releases newer than it, the advisories that name it, or that it is unread.
- acknowledgement: a person’s word, or an acting session’s, on one release of one dependency: dismissed, or skipped until a newer release.
- brief: everything a fixer needs to move one dependency to one release: the release, its notes, its advisories, and every declaration of the dependency.
- held state: everything headwater keeps of its own for one repository.
- surface: one way an asker reaches headwater.
- profile: one way a contract of Part B is satisfied, as Part C describes.
Part A
Part A. What headwater decides
A.1 What a repository depends on
- headwater reads only the dependencies a repository state declares, and invents none. A declaration it cannot read is reported unread, naming the declaration, and is never dropped from a report.
- Every dependency has exactly one identity, whichever declaration names it and whichever source publishes it. Two declarations of one identity are one dependency.
- A pin is exactly one version or unknown. A dependency whose pin is unknown is reported as unknown and never as having nothing newer.
- 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 that dependency when it is newer than the oldest of them.
- Reading a repository state changes nothing in it. headwater never writes a repository’s content, its history or its configuration.
A.2 Releases and what is new
- A release is newer than a pin only by the dependency’s own version order. headwater never orders two versions by their dates, by their names’ spelling, or by the order a source lists them.
- A release whose order against the pin cannot be decided is reported as undecided, beside the newer ones, never as newer and never left out.
- A report holds every release newer than the pin that the source publishes, and none that is not newer.
- The pin alone decides what is new. headwater reads no record of what it reported before to decide it, so every scan reports a release until the pin reaches or passes it, or an acknowledgement leaves it out (21).
- A release has one identity for ever: its dependency’s identity and its version. Every scan, surface and host names the same release the same way, so an asker can key on it.
- A release is reported with what its source published of it: its version, its date, its link and its notes, as upstream wrote them. headwater never summarises, rewords, translates, ranks or judges a release, and no model reads notes inside headwater.
- Every report and every brief gives each release’s notes whole, or gives the part an asker asked for together with a way to read the whole again. headwater keeps no part of notes it cannot give whole.
A.3 Tiers and cadence
- Every dependency of a repository state belongs to exactly one tier.
- The tiers and their cadences are read from the repository state. headwater never moves a dependency between tiers, and never changes a cadence.
- A scan of what is due reaches the sources of the due tiers’ dependencies and of no others. A scan of a named tier reaches its sources whether or not it is due.
- A scan that leaves a tier incomplete reports every dependency it could read, reports the rest as unread with the reason, and leaves the tier due.
- Asking what headwater holds reaches no source and writes nothing. The answer is the newest report for each tier, and says when each was made and whether its tier is due.
A.4 Advisories
- A dependency whose pin an advisory names is reported as affected, naming the advisory, in every report that holds the dependency.
- An advisory is reported apart from notes. headwater never folds an advisory into a release’s notes, and never infers one from them.
A.5 Acknowledgements
- An acknowledgement is only ever given by a person or an acting session, for one release of one dependency. headwater never infers one, and never gives one when a pin moves.
- A dismissed release is left out of reports. A skipped release is left out until a release newer than it is published, which is then reported with the skipped one beside it.
- An acknowledgement never changes a pin, a version order or an advisory. A dependency with an advisory naming its pin is reported as affected whatever its acknowledgements say (18).
- An asker may always ask for a report that disregards every acknowledgement, and gets the report the pins alone decide (9).
- Every acknowledgement names who gave it and when, and stays readable after the release it acknowledges leaves every report.
A.6 Briefs
- For any release a report holds, headwater gives a brief. Asking for one reaches no source but the notes and advisory sources, and writes nothing but notes it keeps whole (12).
- A brief names every declaration of the dependency in the repository state and the pin each gives, so a fixer can move every one.
- A brief prescribes nothing. It names no tool that acts on it, and no step a fixer must take.
A.7 Held state
- headwater keeps its held state outside the repository’s content. Every checkout of one repository shares one held state.
- Everything in the held state but acknowledgements can be discarded, and the next scan rebuilds it with no report changed but the times it gives.
- Two scans of one repository at once leave the held state as one or the other would have left it. Neither leaves it half written, and no acknowledgement is lost.
- Every report is made from one reading of the repository state. A report never mixes the declarations of two repository states.
A.8 Askers and surfaces
- The same question about the same repository state and the same held state gets the same answer, whoever asks and through whichever surface.
- Every surface answers from one core. No surface computes a report, a brief, a due tier or an acknowledgement of its own.
- Nothing headwater reports depends on which surface asked, which recorder will keep it, or which fixer will act on it, and headwater needs none of them to answer.
- Every answer has one form a program reads, the same through every surface, and that form only ever gains fields.
- headwater never acts on a release. A surface may hand a brief to a fixer, and the change the fixer makes is the fixer’s.
A.9 Sources and what they learn
- headwater sends a source nothing of the repository but the identities and versions it asks about.
- headwater reaches a source with the asker’s own credentials or with none, and never keeps a credential in its held state.
- A source’s answer is reported as that source gave it. Where two sources disagree about one release, the report names both and chooses neither.
Part B
Part B. Contracts across the seams
B.1 The dependency description
- read-dependencies: given a repository state, every dependency it declares, each with its identity, every declaration of it and the pin each gives; and every declaration that could not be read, with the reason.
- read-tiers: given a repository state, its tiers, the cadence of each, and which identities each names.
- Both answers are the same for the same repository state, and reading either changes nothing (5).
B.2 The release source
- list-releases: given an identity, every release its source publishes, each with its version, date and link, and whether the source has withdrawn it; or that the source could not be reached.
- order: given an identity and two versions, which is newer, that they are the same, or that their order cannot be decided.
- The order is the source’s own (6), and the same two versions always order the same way.
B.3 The notes source
- read-notes: given an identity and a version, what upstream wrote of that release, whole and unchanged, or that it wrote nothing.
B.4 The advisory source
- read-advisories: given an identity and a version, every advisory that names that version, each with its identity, its severity as the advisory gives it, and a link.
B.5 The store
- read: given a repository, its held state and the version it was read at.
- write: given a repository, the version read and the next held state. The write is all or nothing, and succeeds only if the held state is still at the version read (30).
- The store holds nothing in the repository’s content (28), and every checkout of one repository reaches the same store.
B.6 Binding the contracts
- Each seam is bound by naming one or more profiles of Part C. Any number of dependency descriptions, release sources, notes sources and advisory sources are bound at once, each answering for the identities it knows, and exactly one store is written.
- One conformance suite holds every profile to Parts A and B; a profile that fails it is not one.
Part C
Part C. Profiles
Parts A and B do not change to admit a profile.
C.1 Identity: Package URL
An identity is a Package URL (ECMA-427) without its version:
pkg:npm/react, pkg:pypi/fastapi, pkg:github/jdx/mise. A release’s
identity is the same Package URL with its version (10):
pkg:github/jdx/mise@2026.10.10.
C.2 Dependency descriptions
| profile | declarations read | the pin |
|---|---|---|
| npm | package.json dependencies | the version the lockfile resolves; unknown without a lockfile |
| Python | pyproject.toml and requirements*.txt | the version uv.lock or poetry.lock resolves, or a == requirement; unknown otherwise |
| Cargo | Cargo.toml dependencies | the version Cargo.lock resolves |
| Go | go.mod requirements | the version go.mod names |
| mise | mise.toml and .tool-versions tools | the version each names; unknown for latest or a prefix |
| GitHub Actions | uses: in .github/workflows | the tag or version named; unknown for a branch |
| container images | FROM in a Dockerfile | the tag named; unknown for latest |
| watch list | a file naming each dependency’s upstream repository, its tag pattern, its pin and its tier | as the file gives it; swancloud’s, generated from lib/release-sources.nix |
Tiers are read from .config/headwater.toml: each tier’s name, its
cadence, and the identities it names. A repository with no such file has
the default tier alone, with a cadence of seven days.
C.3 Release sources
| profile | identities | list-releases / order |
|---|---|---|
| deps.dev | npm, PyPI, Cargo, Go, Maven, NuGet, RubyGems | the versions and dates of a batched Package URL lookup, with no key; order by the ecosystem’s own rules |
| a registry’s own API | the registry’s packages | the registry’s version list, for a release deps.dev has not seen yet; order as above |
| GitHub releases | pkg:github/*, and any dependency whose source repository is on GitHub | the repository’s releases through GraphQL, fifty repositories a query, with the asker’s own gh sign-in (38); order by Semantic Versioning where the tags parse, by the watch list’s tag pattern otherwise |
| ecosyste.ms | GitHub Actions, container images, Homebrew, nixpkgs and the other registries it indexes | its packages API by Package URL |
Order by ecosystem: Semantic Versioning 2.0.0 for npm, Cargo and GitHub tags that parse; PEP 440 for PyPI; Go’s module rules; and a calendar version compared field by field where a watch list says so.
C.4 Notes sources
| profile | read-notes |
|---|---|
| GitHub release | the body of the GitHub release at the version’s tag in the dependency’s source repository, which deps.dev links or the watch list names |
| changelog | the section for the version in the source repository’s CHANGELOG.md at that tag, where no release body exists |
C.5 Advisory source: OSV
| operation | how OSV satisfies it |
|---|---|
| read-advisories | OSV’s batched query by Package URL and version; severity as the advisory gives it |
C.6 Store: the git directory
| operation | how the git directory satisfies it |
|---|---|
| read | the files under <git common dir>/headwater/, which every worktree of the repository shares (28) |
| write | a new file written beside the old and renamed over it, under a lock held for the write (30, 49) |
Notes are kept whole under the same directory, never in the repository’s content.
Invariants, non-goals and givens
7. Global invariants
- I1. headwater never changes a repository, and never acts on a release (5, 36).
- I2. The pin alone decides what is new (9).
- I3. Upstream’s words reach the asker as upstream wrote them, with no model between (11, 19).
- I4. A release has one identity for ever, everywhere (10).
- I5. Asking what headwater holds reaches no source and writes nothing (17).
- I6. Nothing headwater keeps but acknowledgements is needed to answer again (29).
8. Non-goals
- Moving a pin, building, testing or landing a change. A fixer does that, and the repository’s own gate decides whether it lands.
- What a dependency depends on in turn. headwater reports what the repository’s authors chose; scanning the whole graph for advisories belongs to a vulnerability scanner.
- Judging what a release means for the repository, or summarising its notes.
- Recording releases as anyone’s own records. A recorder does that through its own plugin, from what headwater reports.
- Telling anyone by itself. A surface tells, when its asker asks.
- Licences and software bills of materials.
9. Environment givens
Constraints already fixed:
- Repositories are git repositories.
- An identity is a Package URL (ECMA-427).
- The askers include coding agents working in many checkouts of one repository at once.
- Sources are reached over the network, with the asker’s own credentials where a source needs any.
- headwater is free and open source.
The rest of the stack, which a model builds on unless it says why not:
- One static binary, written in Rust, as sluice is.
- Every answer as JSON on request, the form of (35).
- Four surfaces answering from one core, each a plugin kept outside the core as sluice keeps its plugins: a command line; a Claude Code plugin that notes new releases at a session’s start and hands a release to a background agent in a worktree of its own; a herdr plugin with a sidebar row, a popup and a key that hands a release to a new agent in a worktree; and a Flywheel plugin whose one part is an adapter that turns each release into a capture.
- The profiles of Part C: the watch list, npm, Python, GitHub releases, deps.dev, OSV and the git directory first.
Questions, scenarios and what to deliver
10. Questions the model must answer
These are not requirements.
- Are prereleases reported, and when: only where the pin is a prerelease, or where a tier asks for them (8)?
- What does a report say of a release its source has withdrawn, and of a pin whose own release has been withdrawn (43)?
- Do advisories wait on a tier’s cadence, or is every pin’s advisory looked for at every scan (15, 18)?
- Is an acknowledgement each person’s, or the repository’s, and does it travel to another host that shares the repository (20, 28)?
- Where does the held state live when several hosts share one repository (28, 50)?
- How does a repository holding several projects declare one tier across all of them, and is one dependency in two projects one dependency or two (2, 13)?
- Is a cadence measured from a complete scan’s start or its end (15)?
11. Scenarios the model must satisfy
- S1. A colleague opens a session in a repository that declares its packages and its Python requirements. Two dependencies of the favorites tier have newer releases, and the session is told of both with their notes. Nothing in the repository changes.
- S2. The same session starts again an hour later. No tier is due, no source is reached, and the session is told the same two releases and when they were found.
- S3. One favorite’s source cannot be reached. The report names it unread with the reason, reports the other favorites, and the tier is still due at the next start.
- S4. A fixer moves one favorite to its newest release, and the change lands. The next scan no longer reports that release, and no acknowledgement was given.
- S5. A person dismisses a release. No later report holds it. When a newer release is published, that one is reported.
- S6. A recorder asks for the favorites’ releases every day. A release is reported every day, under the same identity, until its pin moves, and the recorder keeps it once.
- S7. A release’s notes run to forty kibibytes. A report that holds it and a brief for it give the notes whole, or the part asked for and a way to read the rest, and not one word is changed.
- S8. Two checkouts of one repository scan at once. The held state afterwards is one of theirs, whole, and every acknowledgement stands.
- S9. The held state is deleted, apart from its acknowledgements. The next scan reports exactly what the last one did, at a new time.
- S10. An advisory names a pin. Every report that holds the dependency names the advisory, though a person dismissed the release that would fix it.
- S11. A watch list names fourteen tools by their upstream repositories and tag patterns. Each release is reported under the same identity a declaration of that tool would give it.
- S12. A person asks through the command line, an agent through its session and a pane through its sidebar, about one repository state. All three get the same report.
12. What to deliver
A model of headwater that satisfies Parts A and B: its components, the answers each surface gives, how a scan is made and a report derived, with fixed examples, and how a dependency description, a release source and a store are added. A conformance suite whose scenarios each cite the clauses they exercise, failing when a clause is cited by nothing. The profiles of Part C, each passing the suite.