Example
facets.lock
name is still viper-planning and its files still live under skills/viper-planning/. Only the destination on disk changes: the skill materializes into skills/team-planning/, whose primary file is still SKILL.md. The same holds for the filesystem server: its authored name and fingerprint stay put, and only the configuration key becomes team-filesystem.
Fields
number
required
The schema version. A normal install writes
0.4. Versions 0.2 and 0.3 remain readable.Versions are dispatched by exact match, never by ordering. A version number names a schema, not a position in a sequence, so a loader recognizes exactly what it supports and fails on anything else. There is no cross-version fallback: a malformed 0.4 document fails as 0.4.A normal install migrates a verified 0.2 or 0.3 lockfile to 0.4, deriving every server inventory from the verified facet content. A frozen install never rewrites it.map<name, entry>
required
One entry per resolved facet, keyed by name. The name lives only in the key. Keys are written sorted, so add, remove, and add again produces byte-identical output.
Entry fields
tagged union
required
Provenance, discriminated by
kind, so an illegal cross-kind combination cannot be expressed:registry:{ kind, registry }, the origin the artifact was resolved from. It carries no version specifier, becauseversionis the resolved identity.git:{ kind, url, commit }. The commit is required, since it is the immutable identity that makes a git install reproducible. The symbolic ref is deliberately not recorded: a ref is what the user requested, and it is mutable.local:{ kind, path }.
string
required
An exact
M.N.P version. No ranges, wildcards, or prerelease. Always written from resolution, never copied from a specifier.string
required
The canonical fingerprint of the verified content, derived from the freshly verified or built archive rather than trusted from input.
array
required
The text assets this facet contributed, each
{ scope, type, name, materialization, files }. scope is system, user, or project; type is skill, agent, or command. Names MUST pass asset-name validation, so a crafted lockfile cannot smuggle path traversal into adapter I/O.This field holds text assets only. MCP servers are a separate identity space recorded in servers, and a server-only facet has an empty assets array.name and files are authored, unchanged by aliasing, because they anchor integrity.materialization is the resolved disposition, and all three arms are legal here. This array is the resolved set, not the on-disk set, so an omitted text asset stays listed with its complete records.files records every file the text asset owns as { path, integrity }, sorted strictly ascending by path, with at least one record. Each hash is the recomputed per-entry hash of the verified file. Archive-only supplementary files never appear here; they stay covered by facet-level integrity.Ownership is derived, not merely asserted. An agent or command entry MUST contain exactly one record at its own canonical primary path, and every record in a skill entry MUST live under that skill’s authored root with SKILL.md present. A path no derivation from the text asset’s authored identity could produce is rejected, so integrity and deletion can never be pointed at an unrelated file.array
required
Every MCP server the facet declares, each
{ name, fingerprint, materialization }, sorted strictly ascending by name with no duplicates. Required in 0.4: an empty array states that the facet declares no servers, and a missing array is a schema violation, never an empty inventory.name is the authored server name and MUST satisfy the server name grammar. fingerprint is the declaration’s canonical fingerprint, spelled sha256: followed by 64 lowercase hexadecimal digits. materialization is the resolved disposition, and all three arms are legal: an omitted server stays listed with its fingerprint.An alias appears only in materialization. It changes neither name nor fingerprint. The enclosing entry supplies the facet’s source, version, and integrity, so a server record never repeats them.A record carries no command, argument, environment value, URL, or tool-specific representation, and it is not approval or ownership evidence. See Server inventory.A
0.2 text-asset entry has no materialization field. It reads as { "kind": "authored" }, the only disposition that schema could express. Neither 0.2 nor 0.3 records servers.Server inventory
Each0.4 facet entry records its complete authored server inventory, so a consumer can derive the selected server set and every contributing facet from committed files alone. The lockfile inventory API performs that derivation.
Records come only from verified facet content. Adapters, native configuration, and the machine-local ownership record never supply them. When an install reproduces a facet at its locked integrity, the complete recorded inventory MUST agree with the verified declarations, omitted servers included, or the install fails before any configuration work. See Verification.
A record never replaces the declaration. Native configuration is always rendered from the integrity-pinned facet.json, and facet integrity still detects any change to a declaration’s bytes.
Fingerprint encoding
Every0.4 fingerprint uses the facets:mcp-server:v1 canonical encoding and SHA-256. That binding is fixed for 0.4: a future encoding requires a new lockfile version rather than a new meaning for stored fingerprints.
Two preimages pin the encoding:
Disclosure
A fingerprint is a hash over the complete portable declaration, including literal environment values. It is not encryption. Anyone holding the lockfile and knowing the rest of a declaration can test guesses for a low-entropy value offline, which matters most for a local-source facet that was never published.Unrecognized fields
Fields the schema does not define are accepted and survive a rewrite. When an install rebuilds the lockfile it carries forward unrecognized fields on the document, on each facet entry, on a retained entry’ssource when the kind is unchanged, on each text asset matched by authored identity, on each file record matched by path, and on each 0.4 server record matched by authored name within its facet. Migrating 0.2 or 0.3 to 0.4 preserves them exactly as a same-version write does.
A schema-defined field always wins over an unrecognized field of the same name, so a future version claiming a name cannot be shadowed by stale data. Unrecognized fields attached to something that no longer exists are dropped with it.
servers is schema-defined only in 0.4. A servers field on a 0.2 or 0.3 entry is an unrecognized field: it is never read as an inventory, and migration replaces it whole with the verified inventory without importing anything from its contents.
Preserving an unrecognized field is not a secrecy filter. Whatever such a field contains is carried forward as written.