Skip to main content
A lock-v1 document maps repository-relative file paths to BLAKE3 content IDs. This specification defines the logical model, serialization, sharded representation, and three-way merge semantics. File content and local metadata are outside the format.
Format identifier: https://getgat.dev/spec/lock-v1.

Conformance language

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are normative when written in uppercase. This specification separates the from its . Readers MAY accept the compatibility forms listed in Line endings and canonical output. Writers SHOULD emit the canonical form.

Semantic model

A lock-v1 document represents a finite map:
Each map entry identifies one tracked file.
  • A path MUST occur at most once in the logical lock.
  • Two or more paths MAY reference the same content ID.
  • Directories are not entries. A tracked directory is represented by the file entries beneath that path prefix.
  • Entry order is not semantically significant.
  • Flat and sharded representations of the same path-to-OID map are semantically equivalent.
An entry has exactly two fields: path and oid. File size, timestamps, permissions, and storage locations are not part of lock-v1.

Canonical flat format

The default representation is a UTF-8 file named gat.lock at the repository root. Example, with illustrative digest values:
A document containing only the version line represents an empty map. An absent gat.lock is not a lock-v1 document.

Grammar

Canonical record grammar:
quoted-path uses the encoding and decoded-path constraints in Paths. and denote single characters.

Version line

The first line MUST be exactly:
The comparison is case-sensitive and byte-for-byte. A lock-v1 writer MUST NOT emit a UTF-8 byte-order mark before the version line. An implementation MUST NOT guess the format when the version line is missing or different. An incompatible future format must use a different version identifier.

Entry rows

Every non-empty entry row has exactly two fields separated by one horizontal TAB (U+0009):
The path field MUST use the canonical quoting and escaping. There are no optional fields. A conforming reader MUST reject an entry row that:
  • has no TAB-separated OID field,
  • has more than two fields,
  • has an invalid path,
  • has an invalid OID, or
  • duplicates a path already present in the logical lock.
Comments are not part of lock-v1.

Line endings and canonical output

Canonical lock-v1 output:
  • is UTF-8,
  • uses LF (U+000A) line endings,
  • terminates the version line and every entry row with LF,
  • contains no blank lines, and
  • sorts entry rows by path in ascending lexical order.
Readers MAY accept CRLF line endings, a missing final LF, or empty lines after the version line as a compatibility leniency. Re-serializing such input SHOULD produce the canonical form above.

Paths

The path field encodes the canonical repository-relative path of the tracked file. Every path MUST be encoded as a canonical JSON string following RFC 8785 §3.2.2.2, including the enclosing double quotes ("):
  • U+0008, U+0009, U+000A, U+000C, and U+000D MUST use \b, \t, \n, \f, and \r, respectively.
  • All other U+0000–U+001F controls MUST use \u00hh with lowercase hexadecimal digits.
  • Double quote and backslash MUST use \" and \\, respectively.
  • All other Unicode scalar values MUST be written literally as UTF-8, including /, U+007F, U+2028, and U+2029, without Unicode normalization.
Readers MUST reject noncanonical encodings: unknown or incomplete escapes, uppercase hexadecimal digits, Unicode escapes for characters requiring short escapes or literal UTF-8, escaped /, unescaped quotes, raw U+0000–U+001F controls, and unquoted paths. Invalid Unicode, including lone surrogates, MUST be rejected. The decoded path MUST also satisfy the path rules below; in particular, backslash remains invalid in a decoded canonical path. For example, "data/fi\tle.bin" encodes a path with an actual TAB inside its filename; "data/fi\nle.bin" encodes a path with an actual LF. Each remains one physical row. The following rules apply to the decoded path. A valid lock-v1 path MUST:
  • be valid UTF-8,
  • be non-empty,
  • be relative to the repository root,
  • use / as its separator,
  • contain no empty path segments,
  • contain no . path segments,
  • contain no .. path segments,
  • not begin with ./,
  • not begin with /,
  • not end with /,
  • contain no backslash (\).
Path identity is the same on every operating system. Colons are ordinary path characters, even in a leading segment such as C:foo.
Format validity is independent of filesystem support. For example, C:foo and dir/a:b.txt are valid lock paths even on systems that prohibit these filenames.
Examples: ASCII controls are valid decoded path characters and MUST be escaped in the quoted field. The stored UTF-8 path is the logical map key. No or applies.
Implementations mapping lock entries to filesystem paths MUST preserve and MUST NOT allow a lock path to escape the repository root.

Content IDs

The second field is a BLAKE3 content identifier:
<digest> MUST be exactly 64 lowercase hexadecimal characters (0-9, a-f), representing the 32-byte BLAKE3 digest of the file’s raw content bytes. The blake3: algorithm prefix is mandatory in the lock document. For the semantic map, the content ID is the digest value. The prefix is part of the serialized field, not a separate lock property. The digest:
  • is content-addressed,
  • does not include the file path,
  • does not include the file size,
  • does not include timestamps or filesystem metadata, and
  • may therefore be shared by multiple paths containing identical bytes.
Uppercase hexadecimal is non-canonical and invalid in lock-v1.

Canonical ordering

For a flat gat.lock, writers MUST sort rows by decoded path UTF-8 bytes. Quotes and escape sequences do not participate in ordering. Ordering does not change lock semantics. Consumers SHOULD treat the document as a map keyed by path; row position has no semantic meaning. For a sharded lock, rows MUST be sorted by path within each shard. The order in which shard files are traversed has no semantic meaning.

Sharded representation

A sharded representation stores the lock-v1 map in a gat.lock/ directory. changes only the container layout. Every shard file is independently a lock-v1 document and MUST use the same version line and two-column TSV row format defined above. Flat and sharded representations use the same format identifier.

Shard assignment

For a canonical sharded lock at depth N, where N >= 1:
  1. Encode the decoded canonical path as its exact UTF-8 byte sequence, without quotes or escaping.
  2. Compute BLAKE3(path_bytes).
  3. Take the first N digest bytes.
  4. Encode each selected byte as exactly two lowercase hexadecimal characters.
  5. Use the first N - 1 values as nested directories.
  6. Use the final value as the shard filename with a .tsv suffix.
Therefore:
For example, if the BLAKE3 digest of a path’s UTF-8 bytes begins with bytes 12 34, that path belongs to:
at shard depth N = 2. The shard hash is a hash of the path, not the file content OID. Because BLAKE3 digests are 32 bytes, this mapping is defined for depths up to 32. Implementations MAY expose a smaller configurable maximum.

Shard invariants

A canonical sharded lock MUST satisfy all of the following:
  • every shard is a valid lock-v1 document,
  • all entry paths remain globally unique across all shards,
  • each entry is placed in the shard determined by the algorithm above,
  • every shard uses the same shard depth for a given lock tree,
  • shard directory/file names use lowercase two-digit hexadecimal components,
  • rows within each shard are sorted by path, and
  • empty/stale shard files SHOULD NOT be emitted.
A consumer MUST enforce global path uniqueness across shard boundaries. A consumer SHOULD detect entries stored in the wrong shard rather than assigning semantic meaning based only on the shard filename.

Shape detection

Consumers MUST determine whether a revision contains:
  • a flat gat.lock file, or
  • a sharded gat.lock/ tree
from the revision itself, not from external configuration. Different revisions may use different representations.

Semantic equivalence

Two lock-v1 representations are semantically equivalent when they define the same path-to-OID map, even if:
  • their rows appear in a different order,
  • one is flat and the other is sharded, or
  • they use a different valid shard depth.
Tools that compare revisions SHOULD compare the logical map rather than raw textual layout when they need semantic results.

Three-way merge

A semantic three-way merge operates independently for each path in the union of the maps. Let the value for a path be its OID, or None when the path is absent. For each path:
A successful merged result SHOULD be serialized canonically. Row adjacency and shard placement do not affect the result.

Invalid examples

Invalid: lock-v1 has no size field and entry rows have exactly two columns.

Versioning

The version URI is part of the format. An incompatible change to any property required to parse or interpret lock-v1 — for example changing row fields, pathname escaping, or content-ID syntax — MUST use a new version identifier. Readers MUST reject unknown version identifiers instead of silently interpreting them as lock-v1. Changes that preserve the lock-v1 document grammar and semantics, such as choosing a different valid shard depth, do not require a new lock version.