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#

PartWhat it says
languageThe 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#

CommandThe lock
torb add, torb remove, torb updateResolve project.trb and write graph
torb lockWrites 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 publishWrites the settings block of the package it publishes
torb installReads it, fetches what is not in the cache yet, verifies every tree hash; never writes it
torb check, build, run, testRead 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 install
Esc