Skip to content

Lockfile format reference

This page is the structural reference for seal.json. The normative source is SPEC.md §File Format and §Lockfile Serialization. For a higher‑level overview see the lockfile concept page.

{
"version": 1,
"discovery": [
".claude/skills/*",
".github/copilot-instructions.md"
],
"policy": "block",
"bundles": {
"./.claude/skills/code-review": {
"contentHash": "sha256:9fe7f798a33bac89fa4fc396647caa03a17b64372af0b000dcfe89f1e10d4442",
"files": {
"SKILL.md": "sha256:3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
"scripts/review.sh": "sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"
}
},
"./.github/copilot-instructions.md": {
"contentHash": "sha256:6f0a80207b49f4bddb82f4c0acf52bed52a9eed87f7d237c0cec2bd2ae0ce612",
"files": {
".": "sha256:2cf24dba5fb0a030e97e08cf8c7b7b2895a7e3262fc17f6a8a5c8d29364b9325"
}
}
}
}
Field Required Type Description
version yes integer Schema version. Currently 1.
discovery no array of string Glob patterns defining project‑local sealed roots. Used by standalone tools to find unpinned bundles and define bundle boundaries.
policy yes "block" | "warn" Enforcement policy for project‑local verification failures.
bundles yes object Map of bundle key → bundle entry.

No other top‑level fields are permitted. A lockfile containing an unrecognised top‑level field is invalid.

Must be the literal integer 1. A missing, non‑integer, or unsupported version makes the lockfile invalid.

Future versions will increment this; v1 implementations MUST reject lockfiles whose version they do not support, rather than silently best‑effort parsing.

Array of glob patterns expanded against the project root to find candidate sealed roots.

The field is structurally optional so an empty lockfile can omit it. If bundles is non-empty, discovery MUST be present and MUST statically produce every recorded bundle key.

Pattern rules (strict):

  • * matches zero or more characters within a single path segment. MUST NOT match /.
  • * MUST match hidden files and directories (names starting with .).
  • ?, [, ], {, } are literal filename characters. Only * is a wildcard.
  • ** (recursive matching) is NOT supported. A pattern containing ** makes the lockfile invalid.
  • Patterns MUST use forward slashes (/).
  • Patterns MUST NOT start with ./ or /.
  • Patterns MUST NOT contain backslashes (\).
  • Patterns MUST NOT contain a segment equal to . or ...
  • Patterns MUST NOT contain repeated separators (//) or end with /.
  • Empty patterns are invalid.

If discovery is absent or omitted entirely, no glob expansion occurs. That is valid only when bundles is empty.

The standalone CLI omits discovery from a freshly written lockfile if it would otherwise be an empty array, keeping initial diffs clean.

Every directory path or regular file path that directly results from expansion is one independent sealed root. Directories are hashed recursively. Regular files become single-file sealed roots with files: { ".": "sha256:..." }.

Discovery patterns are also boundary declarations. Patterns such as skills and skills/* are invalid together because they can produce nested sealed roots for the same subtree. So are wildcard-to-literal overlaps such as skills/* and skills/foo/*: the first pattern can produce ./skills/foo, while the second can produce roots beneath it.

Exactly one of:

  • "block" (default): unverified or mismatched project‑local bundles MUST NOT be loaded. CLI exits 1.
  • "warn": those bundles MAY still be loaded; the user is alerted.

Any other value (including casing variants) makes the lockfile invalid.

Object mapping bundle keys to bundle entries. May be empty ({}). If present, keys MUST be unique after NFC normalisation.

The map key for each entry in bundles. Identifies a project‑local sealed root.

Format: ./ + project‑root‑relative path, NFC‑normalised, forward slashes only.

Rules:

  • MUST start with ./.
  • The exact key . is invalid. Seal v1 does not support the project root itself as a sealed root.
  • MUST NOT contain .. segments, repeated separators (//), backslashes (\), CR (\r), or LF (\n).
  • MUST NOT end with a trailing slash.
  • MUST NOT be a subdirectory of another bundle key in the same lockfile (evaluated by discrete directory segments, not raw string prefix).
  • MUST be statically produced by at least one discovery pattern when bundles is non-empty.
  • Strictly case‑sensitive. ./Skills/X and ./skills/X are distinct keys.
  • Path strings are normalised to Unicode NFC before sorting, hashing, or writing.
Source Normalised key
plugins/foo ./plugins/foo
./plugins/foo/ ./plugins/foo
.agents/skills/review ./.agents/skills/review
Field Required Type Description
revision no string Informational metadata only. Source‑specific version identifier recorded at pin time (e.g. git commit SHA). Not part of integrity verification.
contentHash yes string Aggregate SHA‑256 of all tracked files. See content hash.
files yes object Map of sealed‑root‑relative file path → per‑file SHA‑256. MUST contain at least one entry.

No other fields are permitted in a bundle entry.

Advisory metadata. Tooling MAY record a source-specific version identifier, such as a git commit SHA, when it can derive one at pin time. The current CLI focuses integrity on file hashes and omits this field.

If the recorded value differs from the value an implementation would derive now, that difference MAY be reported as diagnostic metadata drift. It MUST NOT change the verification result or authorize content that would otherwise fail verification.

Format: sha256: followed by exactly 64 lowercase hexadecimal characters.

Computed deterministically. See content hash.

A bundle’s recorded contentHash MUST equal the aggregate computed from its files map. If it does not, the lockfile is invalid.

Object mapping sealed‑root‑relative file paths to per‑file SHA‑256 hashes.

File path rules:

  • Relative to the sealed root.
  • Forward slashes (/) on all platforms.
  • NFC‑normalised before sorting, hashing, or writing.
  • MUST NOT be empty.
  • MUST NOT start with / or ./.
  • MUST NOT contain .. segments or . segments used as intermediate directories (e.g. foo/./bar).
  • MUST NOT contain // (repeated separators).
  • MUST NOT contain backslashes (\).
  • MUST NOT end with /.
  • The exact path . is permitted only as the sole files entry for a regular-file sealed root.

Per‑file hash format: sha256: followed by exactly 64 lowercase hexadecimal characters.

The files map MUST contain at least one entry. An empty files map makes the lockfile invalid.

The aggregate contentHash is computed deterministically from the per‑file hashes:

  1. Normalize each tracked path to a sealed‑root‑relative path using forward slashes.
  2. NFC‑normalise the path.
  3. Sort the resulting paths lexicographically by their UTF‑8 byte sequence.
  4. For each path, construct the entry <path>:<hash> (e.g. SKILL.md:sha256:3a7b…).
  5. Join all entries with a single LF (\n, U+000A).
  6. UTF‑8 encode the joined string.
  7. Compute SHA‑256 of those bytes.
  8. Encode as sha256: + 64 lowercase hex characters.
contentHash = SHA-256( UTF8( sort(paths).map(p => p + ":" + files[p]).join("\n") ) )

The LF delimiter prevents concatenation collisions between entries. The aggregate changes if any file is added, removed, or modified.

  • File content hashing. Per‑file hashes are computed over the raw file bytes exactly as read from disk. Implementations MUST NOT normalise line endings, decode text, strip whitespace, parse structured files, or otherwise transform the content before hashing.
  • Path format. Paths in files are relative to the sealed root and MUST use forward slashes on all platforms.
  • Unicode and case. All path strings are NFC‑normalised before sorting, hashing, or writing. Paths are strictly case‑sensitive.
  • Path normalisation. Before recording or comparing a path, implementations MUST remove any leading ./, collapse repeated separators, and reject absolute paths. Paths containing .. segments or intermediate . segments are rejected. The exact bundle key . is invalid in Seal v1. The exact file path . is permitted only as the sole entry for a regular-file sealed root.
  • Internal consistency. A lockfile is invalid if any bundle’s files map contains keys that collapse to identical normalised paths.
  • Tracked entries only. Directories are not hashed directly. Only tracked file entries appear in files.
  • Unsupported file types. Symlinks and other non‑regular files are not supported in v1. During verify, an unsupported file classifies the bundle as Mismatch. During pin, encountering one aborts the operation.

For directory sealed roots, all regular files in the sealed root are hashed, with these exclusions:

  1. Any file or directory exactly named .git (at any depth within the sealed root), along with its contents.
  2. The seal.json lockfile, but ONLY if the bundle’s sealed root contains the project lockfile. This is a circular case that normal Seal v1 lockfiles avoid because the project root itself is not a valid sealed root.

For regular-file sealed roots, the file itself is hashed as the sole tracked entry named ..

A seal.json file inside an ordinary bundle directory is regular bundle content and is tracked.

Everything else is tracked, including dependency directories (node_modules, vendor, etc.). If a bundle ships dependencies, changes to those dependencies are detected. Bundle authors who want clean diffs should not ship dependency directories.

Runtime state (caches, logs, downloads, temp files, generated artifacts) MUST be stored outside the bundle root. If a bundle writes runtime state into its own root after pinning, those changes are treated like any other modification and cause verification to fail until the state is removed or the bundle is re‑pinned.

Identical lockfile states MUST produce byte‑identical files on disk. This is a normative requirement. Tools that produce different bytes for the same logical state are non‑conforming.

  1. Encoding. UTF‑8, no Byte Order Mark.
  2. Line endings. Single LF (\n, U+000A). No CR.
  3. Trailing newline. Exactly one \n at end of file.
  4. Indentation. Two spaces per nested level. No tabs. Empty {} and [] serialised inline with no internal whitespace.
  5. Top‑level key order. "version", "discovery", "policy", "bundles", in that order. Omit "discovery" when not present.
  6. Bundle entry key order. "revision", "contentHash", "files", in that order. Omit "revision" when not present.
  7. Sorted keys. Keys within bundles and within each files object MUST be sorted lexicographically by their UTF‑8 byte sequence after NFC normalisation.
  8. Spacing. A single space after :. No trailing commas.

A lockfile is invalid if any of these are true:

  • The file is not valid JSON.
  • The file contains unrecognised fields at any level.
  • A discovery pattern is malformed (per the rules above).
  • Two discovery patterns can produce sealed roots where one is a directory-segment ancestor of another.
  • bundles is non-empty but discovery is absent.
  • version is missing, not an integer, or not supported.
  • Required top‑level fields are missing or have the wrong type.
  • A bundle key is duplicated after NFC normalisation.
  • A bundle key is malformed.
  • A bundle key is not statically produced by any discovery pattern.
  • A bundle key is a directory-segment ancestor of another bundle key.
  • A required bundle entry field is missing or has the wrong type.
  • revision is present but empty.
  • A recorded hash is not exactly sha256: + 64 lowercase hex characters.
  • A recorded path is malformed.
  • A bundle’s files map is empty.
  • A bundle’s recorded contentHash does not equal the aggregate of its recorded files map.

If seal.json is invalid, the implementation MUST fail closed for the project, regardless of policy. The CLI uses exit code 2 for fatal lockfile validation errors.