Content hashing

The hash covers only the captured configuration — not the name, not the
tags, not the timestamp, not the description:

content_hash = "sha256:" + sha256(canonical_json(captured))

Two consequences follow, both intended. Renaming or re-tagging a snapshot does
not change its hash. And two snapshots of identical configuration are visibly
duplicates by matching hash
, regardless of when they were taken or what they
were called. The UI renders the trailing hex as a fingerprint chip for exactly
this comparison.

The hash is verified at three points, with escalating strictness: on read, as
a non-fatal warning appended to the snapshot's warnings; on restore, as an
additional confirmation gate; and on import, as a hard rejection.

Layered above the hash is an HMAC provenance signature, applied only on export
and computed under a box-local secret. On import the signature yields one of
three outcomes: a match means the snapshot originated on this box; a mismatch
means it came from another box and is accepted with a warning; and an absent
signature means a legacy export and is accepted silently. This is a provenance
hint, not an authorisation gate
— cross-box import is a supported workflow, not
an attack to be blocked.