Pular para o conteúdo

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)

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]:

  1. Take all sections except [sig.ed25519].
  2. Sort sections by name lexicographically.
  3. Within each section, sort keys lexicographically.
  4. Serialize to canonical TOML (no extra whitespace, = separator, newline-terminated).
  5. UTF-8 encode → bytes.
  6. 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.