Skip to content

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.

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.

{
"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.
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.

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.

"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.

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 discovery pattern 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.
  • bundles is non-empty but discovery is absent.
  • version is 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 discovery pattern.
  • A bundle key is a directory-segment ancestor of another bundle key.
  • A required pin field is missing or has the wrong type.
  • revision is 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 contentHash does not mathematically match the aggregate hash computed from its recorded files map.
  • A bundle’s files map 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.

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:

  1. Encoding: UTF‑8 without a Byte Order Mark (BOM).
  2. Line endings: Single LF (\n). No CR (\r).
  3. Trailing newline: Exactly one \n at the end of file.
  4. Indentation: Two spaces per nested level. No tabs. Empty {} and [] serialized inline.
  5. Top‑level key order: "version", "discovery", "policy", "bundles". Omit "discovery" if not present.
  6. Bundle entry key order: "revision", "contentHash", "files". Omit "revision" if 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 normalization).
  8. 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.

An informative JSON Schema is provided in seal.schema.json in the spec repository. It exists for tooling, editor integration, and structural validation.

When the CLI updates seal.json, it acquires an exclusive file lock and uses atomic filesystem renaming:

acquire flock on sibling seal.json.lock
write encoded bytes to seal.json.tmp.<pid>.<rand>
fsync parent directory
rename seal.json.tmp.<pid>.<rand> → seal.json
release flock

Locking 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.