The lockfile
seal.json is the trust anchor. It is the one file a sealed project commits to version control to declare exactly which bundle contents are authorized to load.
Location
Section titled “Location”my-project/├── seal.json└── ...One lockfile per project: seal.json at the project root. Tracked using version control, reviewable in code review, and verifiable in CI.
If no seal.json is present at the project root, Seal is inactive for that project. Implementations behave normally unless another policy outside this specification requires Seal.
The standalone CLI does not traverse parent directories to find seal.json. It runs from the project root or exits with code 2.
Top‑level fields
Section titled “Top‑level fields”{ "version": 1, "discovery": [ ".claude/skills/*", ".github/copilot-instructions.md" ], "policy": "block", "bundles": { "./.claude/skills/code-review": { "contentHash": "sha256:9fe7f798…", "files": { "SKILL.md": "sha256:3a7bd3e2…", "scripts/review.sh": "sha256:7d865e95…" } }, "./.github/copilot-instructions.md": { "contentHash": "sha256:6f0a8020…", "files": { ".": "sha256:2cf24dba…" } } }}| Field | Required | Description |
|---|---|---|
version |
yes | Schema version. Currently 1. |
discovery |
no | Array of glob patterns defining project‑local sealed roots. Used by standalone tools to find unpinned bundles and define bundle boundaries. |
policy |
yes | "block" or "warn". Controls behaviour on unverified or mismatched project‑local bundles. |
bundles |
yes | Map of bundle key to bundle entry. |
Bundle entry fields
Section titled “Bundle entry fields”| Field | Required | Description |
|---|---|---|
revision |
no | Informational metadata only. A source‑specific version identifier recorded at pin time (e.g. git commit SHA). Not part of integrity verification. |
contentHash |
yes | Aggregate SHA‑256 over all tracked files. See bundles & components. |
files |
yes | Map of relative file paths to their individual sha256:<hex> hashes. |
revision is advisory. If an implementation can derive it for an installed unit and the derived value differs from the recorded one, it MAY report diagnostic metadata drift. That difference MUST NOT by itself change the verification result, invalidate the lockfile, or authorize content that would otherwise fail verification.
Discovery patterns
Section titled “Discovery patterns”The optional discovery array contains glob patterns that standalone tools expand to find unpinned bundles in the project tree.
If bundles is non-empty, discovery MUST be present and every recorded bundle key MUST be statically produced by at least one discovery pattern. Static production is checked from the pattern text and bundle key; it does not depend on whether the path currently exists on disk.
The matching rules are strict and intentionally narrow:
*matches zero or more characters within a single path segment. It MUST NOT match/.*MUST match hidden files and directories (names starting with.).?,[,],{, and}are treated as literal filename characters. Only*functions as a wildcard.**(recursive matching) is NOT supported in v1. A lockfile containing**in a discovery pattern is invalid.- Patterns MUST use forward slashes (
/) and MUST NOT start with./or/. - Patterns MUST NOT contain backslashes (
\), repeated separators (//),.or..path segments, or a trailing slash. - Regular files matched by a discovery pattern become single-file sealed roots.
- Completely empty directories (or directories containing only excluded files) are silently ignored during discovery.
Every directory path or regular file path that results from deterministic glob expansion is treated as exactly one independent bundle sealed root. Tools MUST NOT recursively traverse inside matched directories looking for further nested bundles.
If a matched sealed root has no corresponding exact bundle key in seal.json, the entire sealed root is flagged as Unverified.
Patterns also establish the bundle-boundary model. skills produces ./skills as one recursive bundle; skills/* produces each direct child as a separate bundle. A lockfile cannot use both shapes for the same subtree, and it also cannot combine patterns like skills/* and skills/foo/* where one pattern can produce a parent root and the other can produce roots beneath it.
The policy field
Section titled “The policy field”"policy": "block"Two values are allowed: "block" or "warn".
| Policy | Effect on Unverified / Mismatch project‑local bundles |
|---|---|
"block" |
Bundle MUST NOT be loaded or executed. CLI exits with code 1. |
"warn" |
User is alerted; bundle MAY still load. CLI exits with code 0. |
Removed entries are reported as drift and do not by themselves block loading, under either policy. Project‑external extensions are unconditionally blocked regardless of policy.
If an implementation provides its own override (project or organization policy), the strictest value wins.
Lockfile validity
Section titled “Lockfile validity”If seal.json is present, it MUST be a valid Seal lockfile before any bundle verification begins. A lockfile is invalid if any of these are true:
- The file is not valid JSON.
- The file contains unrecognized fields or properties at any level.
- A
discoverypattern contains**, starts with./or/, uses backslashes (\), contains repeated separators, contains.or..path segments, or has a trailing slash. - Two discovery patterns can produce sealed roots where one is an ancestor of another.
bundlesis non-empty butdiscoveryis absent.versionis missing, not an integer, or not supported by the implementation.- Required top‑level fields are missing or have the wrong type.
- A bundle key is duplicated after parsing or normalization.
- A bundle key is invalid (e.g., contains
.., repeated separators, trailing slash, or is the subdirectory of another bundle key). - A bundle key is not statically produced by any
discoverypattern. - A bundle key is a directory-segment ancestor of another bundle key.
- A required pin field is missing or has the wrong type.
revisionis present but empty.- A recorded hash is not in the required
sha256:<lowercase-hex>format (exactly 64 hex chars). - A recorded path is invalid.
- A bundle’s recorded
contentHashdoes not mathematically match the aggregate hash computed from its recordedfilesmap. - A bundle’s
filesmap is empty.
If seal.json is invalid, the implementation MUST fail closed for the project: it MUST NOT load any project‑local bundle, project‑local loadable artifact, or project‑external extension that would otherwise load. This is independent of policy. If the lockfile is invalid, the implementation cannot trust policy either.
Deterministic serialization
Section titled “Deterministic serialization”Because seal.json is tracked in version control and reviewed by humans, identical lockfile states MUST produce byte‑identical files on disk. Every tool that writes or updates seal.json MUST serialize it deterministically:
- Encoding: UTF‑8 without a Byte Order Mark (BOM).
- Line endings: Single LF (
\n). No CR (\r). - Trailing newline: Exactly one
\nat the end of file. - Indentation: Two spaces per nested level. No tabs. Empty
{}and[]serialized inline. - Top‑level key order:
"version","discovery","policy","bundles". Omit"discovery"if not present. - Bundle entry key order:
"revision","contentHash","files". Omit"revision"if not present. - Sorted keys: Keys within
bundlesand within eachfilesobject MUST be sorted lexicographically by their UTF‑8 byte sequence (after NFC normalization). - Spacing: A single space after
:. No trailing commas.
The CLI satisfies this by building the structure in an ordered map, calling json.MarshalIndent(root, "", " "), and appending a trailing \n. Golden‑file tests against testdata/golden/*.json enforce byte‑exact reproducibility.
Companion JSON Schema
Section titled “Companion JSON Schema”An informative JSON Schema is provided in seal.schema.json in the spec repository. It exists for tooling, editor integration, and structural validation.
Atomic writes and locking
Section titled “Atomic writes and locking”When the CLI updates seal.json, it acquires an exclusive file lock and uses atomic filesystem renaming:
acquire flock on sibling seal.json.lockwrite encoded bytes to seal.json.tmp.<pid>.<rand>fsync parent directoryrename seal.json.tmp.<pid>.<rand> → seal.jsonrelease flockLocking plus atomic rename prevent torn or partially written lockfiles from concurrent writers. Verification is read‑only: it does a single read and operates on the snapshot, relying on the atomic‑rename property to ensure the bytes it sees are either fully old or fully new.