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.
Canonical example
Section titled “Canonical example”{ "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" } } }}Top‑level object
Section titled “Top‑level object”| 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.
version
Section titled “version”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.
discovery
Section titled “discovery”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.
policy
Section titled “policy”Exactly one of:
"block"(default): unverified or mismatched project‑local bundles MUST NOT be loaded. CLI exits1."warn": those bundles MAY still be loaded; the user is alerted.
Any other value (including casing variants) makes the lockfile invalid.
bundles
Section titled “bundles”Object mapping bundle keys to bundle entries. May be empty ({}). If present, keys MUST be unique after NFC normalisation.
Bundle key
Section titled “Bundle key”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
discoverypattern whenbundlesis non-empty. - Strictly case‑sensitive.
./Skills/Xand./skills/Xare 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 |
Bundle entry
Section titled “Bundle entry”| 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.
revision
Section titled “revision”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.
contentHash
Section titled “contentHash”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 solefilesentry 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.
Content hash
Section titled “Content hash”The aggregate contentHash is computed deterministically from the per‑file hashes:
- Normalize each tracked path to a sealed‑root‑relative path using forward slashes.
- NFC‑normalise the path.
- Sort the resulting paths lexicographically by their UTF‑8 byte sequence.
- For each path, construct the entry
<path>:<hash>(e.g.SKILL.md:sha256:3a7b…). - Join all entries with a single LF (
\n,U+000A). - UTF‑8 encode the joined string.
- Compute SHA‑256 of those bytes.
- 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.
Canonicalisation rules
Section titled “Canonicalisation rules”- 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
filesare 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
filesmap 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. Duringpin, encountering one aborts the operation.
What gets hashed
Section titled “What gets hashed”For directory sealed roots, all regular files in the sealed root are hashed, with these exclusions:
- Any file or directory exactly named
.git(at any depth within the sealed root), along with its contents. - The
seal.jsonlockfile, 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.
Serialization rules
Section titled “Serialization rules”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.
- Encoding. UTF‑8, no Byte Order Mark.
- Line endings. Single LF (
\n,U+000A). No CR. - Trailing newline. Exactly one
\nat end of file. - Indentation. Two spaces per nested level. No tabs. Empty
{}and[]serialised inline with no internal whitespace. - Top‑level key order.
"version","discovery","policy","bundles", in that order. Omit"discovery"when not present. - Bundle entry key order.
"revision","contentHash","files", in that order. Omit"revision"when not present. - Sorted keys. Keys within
bundlesand within eachfilesobject MUST be sorted lexicographically by their UTF‑8 byte sequence after NFC normalisation. - Spacing. A single space after
:. No trailing commas.
Lockfile validity
Section titled “Lockfile validity”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
discoverypattern is malformed (per the rules above). - Two discovery patterns can produce sealed roots where one is a directory-segment ancestor of another.
bundlesis non-empty butdiscoveryis absent.versionis 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
discoverypattern. - A bundle key is a directory-segment ancestor of another bundle key.
- A required bundle entry field is missing or has the wrong type.
revisionis present but empty.- A recorded hash is not exactly
sha256:+ 64 lowercase hex characters. - A recorded path is malformed.
- A bundle’s
filesmap is empty. - A bundle’s recorded
contentHashdoes not equal the aggregate of its recordedfilesmap.
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.
See also
Section titled “See also”- Verification outcomes: how the lockfile’s state translates to a pass/fail result.
- JSON Schema: the informative schema companion.
- Full SPEC.md: the normative source.