project.lock.trb
The locked manifest - the exact version, tree hash and registry of every package a workspace depends on, and the evaluated settings of each of its packages - written deterministically and read without running anything.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
A workspace has one project.lock.trb, at its root, next to the root
project.trb. It exists so that two checkouts of the same commit build every dependency from the
exact same files, without asking a registry again. It is a receiver script of literals in the vocabulary of
project.trb, and the toolchain reads it statically and never evaluates it: installing never runs code.
Synopsis#
// project.lock.trb - written by `torb add`, `torb remove`, `torb update` and `torb publish`. Do not edit.
language = "0.1.0"
settings "acme/app" {
version = "0.1.0"
license = "MIT"
from {
file "VERSION", hash: "sha256:3a7f..."
variable "CI_COMMIT_TAG", hash: "sha256:bd5c..."
variable "RELEASE_CHANNEL", unset: true
}
}
graph {
package "acme/json", version: "1.2.0", registry: "https://packages.torb.dev/", hash: "sha256:9f2c..." {
capabilities "files"
}
package "acme/local", path: "../local"
package "acme/text", archive: "https://files.acme.test/text-1.0.0.tar.gz", hash: "sha256:b3ce..."
package "acme/yaml", git: "https://git.acme.test/yaml.git", ref: "v2", revision: "4f0c...", hash: "sha256:51d7..."
}What it does#
What each part pins#
| Part | What it says |
|---|---|
language | The language of the root project, where it names one |
settings "<package>" { } | The evaluated settings of one package of the workspace, printed back as literals: what a consumer of the published package reads instead of its project.trb. Written by torb lock, for every member, and by torb publish, for the package it publishes |
from { } | The last part of a settings block: what the evaluation of the package's project.trb read. First every file, sorted by path, relative to the package and without the project.trb itself - file "<path>", hash: "sha256:...", the SHA-256 of its bytes; hash: "directory:sha256:..." for a directory, the SHA-256 of its names, sorted and one per line; hash: "unreadable" for one that could not be read; missing: true for a path that was not there. Then every variable of the environment, sorted by name - variable "<name>", hash: "sha256:..." for one that was set, the SHA-256 of the name, a line break and the value, and variable "<name>", unset: true for one that was not. Never a value of a variable, and never a file's text. Left out where the evaluation read neither, so a literal project.trb locks as before; a lock written before 2026-10-12 has no variables in it, one written before 2026-10-21 no files, and both are read unchanged |
graph { } | One package line per package the workspace depends on, direct or transitive |
package "<name>", version:, registry:, hash: | A registry package: the exact version, the registry that answered (the URL the owner is bound to, as the canonical text of its URI: https://packages.torb.dev/ for an unbound owner. A lock written before 2026-10-08 has it without the /; it is read as the same registry, and torb lock rewrites it once), and the tree hash of its archive |
package "<name>", archive:, hash: | A source ... archive: package: its URL as the source line has it - a file: one as written, any other one as the canonical text of its URI - and the tree hash its hash: names. A lock written before 2026-10-12 has no such row and is read unchanged |
package "<name>", git:, ref:, revision:, hash: | A source ... git: package: the repository's URL as the source line has it, the line's revision: as ref: where it is a tag or a branch (none where it is the full commit id), the full id of the commit it stood at as revision:, and the tree hash of the package's files there. A lock written before 2026-10-12 has no such row and is read unchanged |
package "<name>", path: | A source ... path: package, listed rather than pinned: it is source the project owns, and a hash of it would change on every edit |
capabilities "..." | What the package touches by its own imports (files, network, processes, environment, ...), as its release in the index says |
The tree hash is sha256: over a listing of every file of the package - the SHA-256 of its bytes and its path,
one line each, sorted by path. It is not the hash of the archive's bytes, so a mirror may recompress an archive without
changing a lock. It is checked every time a package is installed, before anything is written into the cache.
Who writes it, who reads it#
| Command | The lock |
|---|---|
torb add, torb remove, torb update | Resolve project.trb and write graph |
torb lock | Writes the settings block of every member from its evaluated manifest and keeps graph; with --check, writes nothing and fails where the file is not what it would write |
torb publish | Writes the settings block of the package it publishes |
torb install | Reads it, fetches what is not in the cache yet, verifies every tree hash; never writes it |
torb check, build, run, test | Read graph and never write it; refuse where it does not match project.trb; ignore settings |
The build takes a locked registry, archive: or git: package from the package manager's cache, where
torb install unpacked it, and a path: package from its directory - each is then a package of the
workspace like a member, so use X from "acme/json" resolves to it. It refuses, with the command that fixes it,
where a member depends on a package the lock does not pin, pins at a version the requirement does not allow, or pins
from another registry than the one the owner is bound to, where an archive: row is not the URL and hash the source
line names, a git: row not the URL and revision, or no source line names it any more, and where a locked package is not installed:
$ torb check app
error: `acme/app` depends on `acme/json:^2.0.0`, and project.lock.trb pins acme/json 1.2.0: run `torb update acme/json`Written the same way everywhere#
The same lock is the same bytes on every machine, so it can be reviewed in a diff and compared byte for byte: the parts
in a fixed order, the settings blocks sorted by package and the lines inside one in the order of the vocabulary, the
packages of graph sorted by name, the files of from sorted by path and its variables by name, nothing about the machine - no time, no absolute path, no tool build, a path:
relative to the lock - hashes as sha256: and lowercase hexadecimal, UTF-8, LF, and exactly one trailing newline.
torb lock --check holds a checked-in lock to that: it writes the file in memory and fails where one byte differs,
which makes it a check for continuous integration.
Pitfalls#
Do not edit it by hand. A hand-edited version or hash is refused by the next install; torb update writes the
graph again from project.trb, and torb lock the settings blocks.
A project.trb that reads a variable or a file makes torb lock --check depend on it. The from { } block holds
the hash of what the variable or the file was when the lock was written, and the evaluated version may hang on it as
well, so a checkout with another value - or without the variable, or with another VERSION - writes another lock and
--check fails, naming the line. Run the check with the variables the lock was written with, or keep the manifest
literal where the lock is checked in.
An archive: package is pinned by its tree hash, not by its URL. The URL may serve other bytes tomorrow; the
install refuses them, naming both hashes, before anything is written into the cache. An archive that is already
unpacked under its hash is never fetched again, so --offline builds from the cache. Its project.trb must name the
package of the source line, and a package with an archive: dependency cannot be published - whoever installs it
has no such source line.
A git: package is pinned by its commit and tree hash, not by its tag or branch. The install fetches the locked
commit, never what the tag or branch names now, and refuses a tree of another hash; a tag or branch that moved changes
nothing until torb update moves the row. The files are the package's - the .trb files and the
documents beside project.trb, as an archive of them would hold - so other files of the repository change no hash,
and a symbolic link anywhere in it is refused. A tree that is already unpacked under its hash runs no git at all,
and --offline never does. Its project.trb must name the package of the source line, and a package with a git:
dependency cannot be published.
A path: package is not reproducible. The lock names its directory and nothing about its content, which is the
honest half of what a lock can promise about a directory somebody is editing - and why a package with a path:
dependency cannot be published.
Examples#
The whole cycle of a project with one dependency: add writes project.trb and the lock, lock --check holds the
lock to the manifest, and install fetches what it pins on another checkout:
$ torb add acme/http
$ torb lock --check
$ torb installRelated#
- project.trb - the manifest whose dependencies this file resolves.
- torb add, torb update, torb lock, torb install - the commands that write and read it.
- Workspaces - the one lock file several members share.
- The torb command - every subcommand.