LEVIATHAN v962456e · 962456eee1

trident

Versions, the lock file, and integrity

How trident picks versions of repository dependencies, what trident.lock and the checksum record guarantee, and how to build without the network.

since 0.1.0-alpha.1linux

Description

This page is about repository dependencies: dependencies whose path is the name of a git repository such as github.com/acme/json and whose version is required (see trident.dependencies). Local dependencies are plain directories and have no versions to choose.

Versions

A package's versions are the git tags of its repository that have the form vMAJOR.MINOR.PATCH, for example v1.2.0. The version you write in a [[dep]] table is a minimum. When several projects in the dependency graph ask for different minimums of the same package, trident chooses the highest of the minimums. This is Minimal Version Selection: the result never moves to a newer version on its own, and it is the same for everyone who builds the project. A dependency graph that contains a cycle is rejected before anything is fetched, and the error lists the complete chain of selected versions.

The lock file

The result of the selection is written next to the manifest as trident.lock:

version = 1

[[module]]
path = "github.com/x/json"
selected = "1.1.0"
hash = "sha256:92a36e0acd02cb343ff8eab7247416aca4ae24f2aa19c119633079d42d667a45"

Each module records its path, the selected version and the SHA-256 hash of its source. A project that has a lock file that agrees with its manifest builds exactly what the lock says, with no resolution. Commit trident.lock with your project. If you edit the [[dep]] tables by hand and do not lock again, the next command stops with an error rather than silently choosing something else:

error: trident.lock is stale: lock is missing required module 'github.com/x/json' — run `trident lock`

Commands that change dependencies, add, remove, update, lock and fetch, write a fresh lock themselves. trident why <path> shows the version that was selected for a module and which projects require it:

$ trident why github.com/x/json
github.com/x/json@1.1.0
  required by root (>= 1.1.0)

Where sources are kept, and how they are checked

Fetched source is stored by content under $TRIDENT_HOME/store/<sha256>/; when TRIDENT_HOME is not set the directory is ~/.trident. The store holds exactly the files a package declares as its sources.

A record of every module's hash lives in $TRIDENT_HOME/checksum.db. It is an append-only log in which each record includes the hash of the one before it, so changing any entry afterwards is detectable. A tag that has been moved to different source, or source that has changed since it was first recorded, is rejected. trident audit runs that verification on demand, and it also compares the sources with the hashes in trident.lock:

$ trident audit
OK   github.com/x/json@1.1.0  sha256:92a36e0acd02cb343ff8eab7247416aca4ae24f2aa19c119633079d42d667a45
audit passed: 1 module(s) verified

When the content does not match the lock, the audit fails and says so, and tells you to run trident lock if the change was intended.

Optional services

The network is only needed to fetch a package for the first time. Two optional services change where trident looks, and both are selected with an environment variable:

  • TRIDENT_PROXY names a cache that serves packages without contacting git. It is a directory, a file:// location or an http(s):// address (which needs curl on the PATH) laid out as modules/<encoded-module>/@v/ containing a list file of versions, a vX.Y.Z.toml manifest and a vX.Y.Z.tar archive per version. The archive contains only the declared sources. Hashes and the lock are checked exactly as for git, so the proxy does not have to be trusted.
  • TRIDENT_INDEX names a registry of short names, in the same three forms, laid out as names/<encoded-name>. It is only consulted for a friendly name such as json; a full repository path always goes straight to git. The first registration of a name wins: publishing the same name from another repository fails with a message that it is already registered.

Building without the network

trident vendor copies every module in the lock into vendor/ beside the manifest, in a folder named after its path. --vendor on a build command then reads only trident.lock and vendor/: it does not run git, use a proxy, or touch the global store. A build with --vendor works with git unavailable and TRIDENT_HOME empty.

A package as it ends up in the build

namespace Json {
    public string encode(int n) => "{\"value\":" + n + "}";
    public string tag(string s) => "<" + s + ">";
}
uses Json;
console.writeln(encode(42));
console.writeln(tag("hi"));
{"value":42}
<hi>

Rules

  • Versions are git tags of the form vMAJOR.MINOR.PATCH.
  • A version in a [[dep]] table is a minimum, and the highest minimum wins.
  • A lock file that agrees with the manifest is used as it is. A lock that disagrees is an error, not something trident quietly repairs.
  • A module's content must match both the checksum record and the lock. A mismatch fails the build.
  • --vendor never uses git, a proxy or the global store.
  • The dependency graph must not contain a cycle.

Examples

Adding a dependency, locking it, checking it and making the project buildable offline:

trident add github.com/x/json@1.1.0 --as Json
trident audit
trident vendor
trident build --vendor

Moving to the newest tag of the same major version, and then seeing what was chosen:

trident update github.com/x/json
trident why github.com/x/json

Taking packages from a team cache rather than from git:

TRIDENT_PROXY=/srv/trident-cache trident fetch

Notes

  • trident update only moves repository dependencies, and only to a version of the same major version. It rewrites the manifest and the lock.
  • A yanked version cannot be chosen by a new resolution but still works from an existing lock; see trident.publish-and-audit.

See also

  • Dependencies — Declaring local and repository dependencies, renaming a dependency's namespace with as, development-only dependencies, and the rule that you may only use what you declared.
  • Publishing, yanking, and auditing packages — Releasing a version of a package, withdrawing it without breaking existing users, and requiring human review and signed provenance for the dependencies a project uses.
  • The trident commands — Every trident subcommand with its arguments, grouped into building, managing dependencies, and publishing and auditing.