Snapshot manifest format (.kvg)
snapshots specs/snapshots/manifest.kmd
Normative format for the Koder Snapshot manifest (`.kvg` — Koder Value Graph): file encoding (UTF-8/NFC), TOML-like section shape, required keys, and the trailing `[sig.ed25519]` block signed over canonical bytes. Tracking: snapshots-RFC-001. No automated audit script yet (`audit: false`); conformance is reviewed against the RFC.
Corpo da especificação
Spec: Koder Snapshot manifest format (.kvg)
- Version: 0.1 (Draft)
- Tracking: snapshots-RFC-001
- Status: Draft
R1 — File format
- Extension:
.kvg(Koder Value Graph). - Encoding: UTF-8, normalized to NFC.
- Top-level shape: TOML-like sections, square-bracket-delimited.
- Signing: trailing
[sig.ed25519]block over canonical bytes (everything before[sig.ed25519]line, sorted keys per section). - Max size: 10 MB (manifest only; blobs stored separately).
R2 — Required top-level sections
[snapshot]
version = <int> # currently 1
hostname = <string>
created_at = <RFC3339>
koder_user_id = <UUID-string>
parent = <UUID-string|null>
arch = "x86_64"|"aarch64"
encrypted_fields = <string[]>
encryption.recipient = <"user@koder-id">
[sig.ed25519] is appended at the bottom — its value field is the
base64 Ed25519 signature over the canonical-bytes hash.
R3 — Optional sections
[system], [apps], [repos], [secrets], [files], [skips],
[hooks] — all optional. Missing section means "no-op" for restore.
R4 — [system]
[system]
timezone = "America/Sao_Paulo"
locale = "pt_BR.UTF-8"
keyboard = "br"
gpu_driver = "nvidia-595" # null if no proprietary driver
Restore applies only if [system] is present AND user opt-in
(default OFF — too invasive).
R5 — [apps]
Each manager has a list of explicit installs (NOT deps).
[apps]
apt = ["vim", "git", "curl"]
snap = [
{name="android-studio", channel="stable"},
{name="scrcpy"}
]
flatpak = [{ref="org.gimp.GIMP", origin="flathub"}]
kpkg = ["koder-tools", "koder-jet"]
Restore diffs current vs manifest, installs missing, never removes (non-destructive).
R6 — [[repos.entry]]
Each repo is an entry in the array:
[[repos.entry]]
slug = "koder"
url = "https://flow.koder.dev/Koder/koder.git"
path = "~/dev/koder"
branch = "master"
dirty = false # if true: warn at restore
config = "submodules: recursive"
For repos without public origin (e.g. local-only branches): URL points
to kdrive://users/<u>/snapshots/<sid>/repos/<slug>.bundle.
R7 — [secrets]
Entire block is selectively-encrypted: each value is a base64
crypto_box_seal (libsodium) ciphertext against the user's published
X25519 pubkey.
[secrets]
ssh_id_ed25519 = "<base64-ciphertext>"
netrc = "<base64-ciphertext>"
gpg_secring = "<base64-ciphertext>"
rclone_config = "<base64-ciphertext>"
gnome_keyring_export = "<base64-ciphertext>"
Decryption requires user's local X25519 privkey, stored in
gnome-keyring after first snapshot create. Recovery via BIP-39 12
words if keyring lost.
R8 — [[files.entry]]
Files are stored as tar.zst blobs in the Drive next to the manifest; manifest references by path + sha256.
[[files.entry]]
local_path = "~/Documentos"
blob = "kdrive://users/.../snapshots/<id>/data/Documentos.tar.zst"
sha256 = "<hex>"
size_bytes = 227482624
Restore extracts blob at local_path (overwrites with prompt; or
--force-overwrite flag).
R9 — [skips]
[skips]
patterns = [
"~/.cache/**",
"~/temp/**",
"~/.var/cache/**",
"~/snap/**/common/.cache/**"
]
Capture skips these. Restore is no-op (skips already excluded from blobs).
R10 — [hooks]
[hooks]
pre_restore = []
post_restore = [
"/k-setup local",
"kpkg install koder-tools",
"ln -sf ~/dev/koder/meta/context ~/.claude"
]
Hooks run sequentially with bash -c. Failure logs but continues
(warn-not-fail, per policies/regression-tests.kmd resilience model).
R11 — Canonical bytes for signing
To produce the bytes signed by [sig.ed25519]:
- Take all sections except
[sig.ed25519]. - Sort sections by name lexicographically.
- Within each section, sort keys lexicographically.
- Serialize to canonical TOML (no extra whitespace,
=separator, newline-terminated). - UTF-8 encode → bytes.
- SHA-256 → 32 bytes → Ed25519 sign with user's identity privkey.
Test templates
- T1 Canonical serialization round-trip: parse → serialize → parse → byte-identical
- T2 Sig validation: valid sig accepts; tampered byte rejects
- T3 Cross-arch refuse: manifest x86_64 + host aarch64 → restore
refuses without
--force-cross-arch - T4 Encryption round-trip: encrypt with pubkey, decrypt with matching privkey, get original
- T5 Wrong privkey: decrypt fails loud
- T6 Section ordering insensitive: same manifest with sections reordered produces same canonical bytes
- T7 Repos array empty: restore is no-op
- T8 Hook failure: pre_restore[0] returns rc=1 → restore logs warn but continues with file/secret/repo blocks
- T9 Multi-tenant isolation: User A's manifest references
kdrive://users/<A>/...— restore by User B refuses (404 from Drive)
Status
Draft v0.1. Tied to snapshots-RFC-001 ratification.