Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ assert_fs = "1.0"
axum = { version = "0.8", default-features = false, features = [
"tokio",
"http1",
"query",
] }
hex = "0.4.3"
pretty_assertions = "1.4"
Expand Down
13 changes: 6 additions & 7 deletions docs/src/commands/buffrs-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ it will default to the latest version of this artifact in the registry.

The repository name should adhere to lower-kebab case (e.g. `my-buffrs-repo`).
The package name has its own set of constraints as detailed in [Package Name
Specification](../reference/pkgid-spec.md). When specified, the version must
adhere to the [Semantic Version convention](https://semver.org/) (e.g. `1.2.3`)
-- see [SemVer compatibility](../reference/semver.md) for more information.

Currently there is no support for resolving version operators but the specific
version has to be provided. This means `^1.0.0`, `<2.3.0`, `~2.0.0`, etc. can't
be installed, but `=1.2.3` has to be provided.
Specification](../reference/pkgid-spec.md). When specified, the version must be a valid
[SemVer requirement](../reference/semver.md). Both exact pins (`=1.2.3`) and
range operators (`^1.0.0`, `~2.1.0`, `>=1.5.0`, etc.) are supported. During
`buffrs install`, the resolver queries the registry and selects the highest
available version that satisfies the requirement, then records the concrete
version in the lockfile for reproducibility.

#### Lockfile interaction

Expand Down
4 changes: 2 additions & 2 deletions docs/src/guide/consuming-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type = "lib"
version = "1.0.0"

[dependencies]
google = { version = "=1.0.0", registry = "<your-registry>", repository = "<your-repository> }
google = { version = "^1.0.0", registry = "<your-registry>", repository = "<your-repository>" }
```

Running `buffrs install` yields you with the following filesystem:
Expand Down Expand Up @@ -65,7 +65,7 @@ major difference is the lack of the `[package]` section in your manifest.

```
[dependencies]
logging = { version = "=1.0.0", registry = "<your-registry>", repository = "<your-repository> }
logging = { version = "^1.0.0", registry = "<your-registry>", repository = "<your-repository>" }
```

Running a `buffrs install` yields you the very same as above, except for the
Expand Down
70 changes: 70 additions & 0 deletions docs/src/reference/resolver.md
Original file line number Diff line number Diff line change
@@ -1 +1,71 @@
# Dependency Resolution

When you run `buffrs install`, the resolver builds a complete dependency graph
for your project — including all transitive dependencies — and determines the
concrete version to install for each package.

## Resolution algorithm

For each dependency (direct or transitive), the resolver follows this priority
order:

1. **Lockfile hit** — if `Proto.lock` already records a version of the package
that satisfies the requirement, that version is used immediately without
contacting the registry. This makes repeated installs fast and reproducible.

2. **Registry resolution** — if no matching locked version exists, the resolver
queries the registry for all available versions of the package, then selects
the **highest** version that satisfies the requirement.

3. **Download and cache** — the resolved version is downloaded, stored in the
local cache, and its digest is recorded in the lockfile for future installs.

Transitive dependencies are discovered by reading the `Proto.toml` bundled
inside each downloaded package archive, then resolved recursively using the
same steps above.

## Version conflict detection

If the same package is required by more than one path in the dependency tree,
the resolver checks that the already-resolved version satisfies all
requirements. If it does not, the install fails with a version conflict error:

```
version conflict for leaf-lib: requirement ^2.0.0 is not satisfied by
resolved version 1.5.0 (chosen to satisfy ^1.0.0)
```

To fix a conflict, update the requiring packages so their version requirements
overlap, or introduce a package that bridges the incompatible requirements.

Note that this conflict detection operates **within a single package's
dependency graph**. In a workspace, different members may independently resolve
different versions of the same package — the workspace lockfile records them
separately using a `(name, version)` composite key.

## Workspace resolution

In a workspace, each member package's dependency graph is resolved
independently. The workspace lockfile (`Proto.lock` at the workspace root)
accumulates all resolved packages across all members. Because the workspace
lockfile allows multiple versions of the same package, two members that require
incompatible versions of a shared library can co-exist.

If a subsequent install finds a workspace lockfile, it reuses those locked
versions (subject to satisfying each member's requirements) to avoid redundant
registry queries.

## Topological ordering

After the full graph is built, packages are sorted topologically so that each
dependency is installed before its dependants. This guarantees that vendored
proto sources are available in the correct order during compilation.

## Determinism and the lockfile

The resolver always picks the **highest** satisfying version when multiple
candidates exist. This is deterministic given the same set of available
registry versions. Once a version is recorded in `Proto.lock`, it is used
as-is on all subsequent installs, regardless of newer versions that may have
been published since. Run `buffrs install` after deleting or modifying
`Proto.lock` to re-resolve against the current registry state.
87 changes: 87 additions & 0 deletions docs/src/reference/semver.md
Original file line number Diff line number Diff line change
@@ -1 +1,88 @@
# SemVer Compatibility

buffrs uses [Semantic Versioning](https://semver.org/) for all packages.
Version requirements in `Proto.toml` follow the same syntax as
[Cargo](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html).

## Version requirement syntax

A version requirement is placed in the `version` field of a dependency:

```toml
[dependencies.my-lib]
version = "^1.2.0"
registry = "https://my-registry.example.com"
repository = "my-repo"
```

The following operators are supported:

### Caret (`^`) — default for ranges

Allows minor and patch updates within the same major version.
This is the recommended operator for most dependencies.

| Requirement | Resolves versions |
|-------------|-------------------|
| `^1.2.3` | `>=1.2.3, <2.0.0` |
| `^1.2` | `>=1.2.0, <2.0.0` |
| `^1` | `>=1.0.0, <2.0.0` |
| `^0.2.3` | `>=0.2.3, <0.3.0` |
| `^0.0.3` | `>=0.0.3, <0.0.4` |

Note that `0.x` versions are treated as unstable: `^0.2` only allows `0.2.x`,
not `0.3.x`, since breaking changes are expected in pre-1.0 packages.

### Tilde (`~`) — patch-level updates only

Allows patch updates within the same minor version.

| Requirement | Resolves versions |
|-------------|-------------------|
| `~1.2.3` | `>=1.2.3, <1.3.0` |
| `~1.2` | `>=1.2.0, <1.3.0` |
| `~1` | `>=1.0.0, <2.0.0` |

### Exact (`=`) — pin to a specific version

Resolves to exactly the stated version, with no flexibility.

```toml
version = "=1.2.3"
```

Use exact pins when you need bit-for-bit reproducibility in the manifest
itself, or when you are distributing a library whose consumers should be
in full control of the version.

### Comparison operators

For more control, the standard comparison operators are available:

| Requirement | Meaning |
|-----------------|----------------------------------|
| `>=1.2.0` | Any version at or above 1.2.0 |
| `>1.2.0` | Any version strictly above 1.2.0 |
| `<2.0.0` | Any version strictly below 2.0.0 |
| `<=2.0.0` | Any version at or below 2.0.0 |
| `>=1.0.0, <2.0.0` | Intersection (multiple constraints) |

## How the resolver picks a version

When a requirement matches more than one available version, buffrs always
selects the **highest** satisfying version. The resolved concrete version is
written to `Proto.lock` to ensure reproducible installs — re-running
`buffrs install` will use the locked version rather than querying the registry
again.

See [Dependency Resolution](./resolver.md) for a full description of the
resolution algorithm.

## Choosing between pinning and ranges

| Situation | Recommended style |
|-----------|-------------------|
| Public library — let consumers decide | `^1.0.0` |
| Internal service — stable dependency set | `^1.0.0` or `~1.2.0` |
| Security patch must be applied exactly | `=1.2.5` |
| Compatibility ceiling known | `>=1.0.0, <3.0.0` |
88 changes: 61 additions & 27 deletions docs/src/reference/specifying-dependencies.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,86 @@
# Specifying Dependencies

Dependencies are declared in the `[dependencies]` section of the `Proto.toml`
manifest. Each entry maps a dependency package name to a dependency
specification object.
Dependencies are declared in the `[dependencies]` section of `Proto.toml`.
Each entry names the package and provides a version requirement, registry URL,
and repository name.

## Remote Dependencies

Remote dependencies are downloaded from an Artifactory registry during
[`buffrs install`](../commands/buffrs-install.md).
## Inline table syntax

```toml
[dependencies]
my-package = { registry = "https://your.registry/artifactory", repository = "my-repo", version = "1.2.3" }
[dependencies.my-lib]
version = "^1.0.0"
registry = "https://my-registry.example.com"
repository = "my-repo"
```

The `version` field must be an exact semantic version (e.g. `"1.2.3"`).
Version ranges or operators (`^`, `~`, `<`, `>`) are not currently supported.
The three required fields for a remote dependency are:

| Field | Description |
|-------|-------------|
| `version` | A SemVer requirement — see [SemVer Compatibility](./semver.md) |
| `registry` | Base URL of the Artifactory registry |
| `repository` | Repository name within that registry |

## Adding dependencies via the CLI

Use [`buffrs add`](../commands/buffrs-add.md) to add a remote dependency from
the command line:
The `buffrs add` command writes the manifest entry for you:

```bash
# Caret range (recommended): resolves to the highest 1.x.y
buffrs add --registry https://my-registry.example.com my-repo/my-lib@^1.0.0

# Exact pin: resolves to exactly 1.2.3
buffrs add --registry https://my-registry.example.com my-repo/my-lib@=1.2.3

# Latest: omitting the version resolves to the latest available
buffrs add --registry https://my-registry.example.com my-repo/my-lib
```
buffrs add --registry https://your.registry/artifactory my-repo/my-package@1.2.3

After adding a dependency, run `buffrs install` to resolve and download it.

## Version requirements

buffrs supports the full range of SemVer requirement operators:

```toml
version = "^1.0.0" # >=1.0.0, <2.0.0 (recommended for most deps)
version = "~1.2.0" # >=1.2.0, <1.3.0 (patch updates only)
version = ">=1.5.0" # any version at or above 1.5.0
version = "=1.2.3" # exactly 1.2.3
version = ">=1.0, <2.0" # explicit intersection
```

## Local Dependencies
The resolver queries the registry and selects the **highest** available version
satisfying the requirement. See [SemVer Compatibility](./semver.md) for the
full operator reference.

## Local dependencies

Local dependencies are resolved from the local filesystem relative to the
manifest. They are useful for multi-package repositories where packages depend
on each other without going through a remote registry.
You can depend on a package in a local directory (useful in monorepos or during
development):

```toml
[dependencies]
my-lib = { path = "../my-lib" }
[dependencies.my-lib]
path = "../my-lib"
```

The `path` field is a relative path from the manifest file to the dependency's
root directory (the directory containing the dependency's `Proto.toml`).

Local dependencies do not have a version requirement — the package at that
path is used as-is. They cannot be mixed with a remote entry for the same
package name.
Comment on lines +70 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the removed lines:

Suggested change
Local dependencies do not have a version requirement — the package at that
path is used as-is. They cannot be mixed with a remote entry for the same
package name.
The `path` field is a relative path from the manifest file to the dependency's
root directory (the directory containing the dependency's `Proto.toml`).
Local dependencies do not have a version requirement — the package at that
path is used as-is. They cannot be mixed with a remote entry for the same
package name.
See [Local Dependencies](../guide/local-dependencies.md) for more information.

@heatonmatthew heatonmatthew Apr 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed.

Please check you're happy.


See [Local Dependencies](../guide/local-dependencies.md) for more information.

## Lockfile
## The lockfile

After adding or modifying dependencies in the manifest, run
[`buffrs install`](../commands/buffrs-install.md) to resolve and lock them.
The lockfile (`Proto.lock`) records the exact resolved versions and checksums
and should be committed to version control.
Once resolved, the concrete version is recorded in `Proto.lock`. Subsequent
installs use the locked version without re-querying the registry, ensuring
reproducible builds across machines and CI environments.

See [Manifest vs Lockfile](../guide/manifest-vs-lockfile.md) for more
information.
Commit `Proto.lock` to version control for applications and services. For
libraries intended to be consumed by others, committing the lockfile is
optional — consumers will resolve their own versions.

See [Manifest vs Lockfile](../guide/manifest-vs-lockfile.md) for more detail.
Loading