Wrapped DEKs and salts stored inline as base64 in the manifest's
encryption section. No separate files (encryption.yaml, salt files,
dek.enc.*) — the manifest is fully self-contained.
Pulling to a new machine gives you everything needed to decrypt.
Server never has the DEK. FIDO2 cross-machine note: device-bound
hmac-secret requires add-fido2 on each machine; passphrase fallback
enables cross-machine decryption.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automatic unlock resolution: try FIDO2 first (no typing, just touch),
fall back to passphrase if device not present. User never specifies
which method — sgard reads encryption.yaml and walks sources in order.
encrypt init --fido2 creates both sources (FIDO2 primary + passphrase
fallback) to prevent lockout on FIDO2 key loss. Separate salt files
per KEK source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encryption is per-file (--encrypt flag on add), not per-repo. A repo
can have a mix of encrypted and plaintext blobs. Commands that only
touch plaintext entries never prompt for the DEK.
Manifest signing deferred — the trust model (which key signs, how do
pulling clients verify across multiple machines) needs proper design.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-layer key hierarchy: DEK (random, encrypts blobs) wrapped by
KEK (derived from passphrase via Argon2id or FIDO2 hmac-secret).
XChaCha20-Poly1305 for both blob encryption and DEK wrapping.
Post-encryption hashing (manifest hash = SHA-256 of ciphertext).
Plaintext hash stored separately for efficient status checks.
Multiple KEK sources per repo. Server never sees the DEK.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace per-call SSH signing with a two-layer auth system:
Server: AuthInterceptor verifies JWT tokens (HMAC-SHA256 signed with
repo-local jwt.key). Authenticate RPC accepts SSH-signed challenges
and issues 30-day JWTs. Expired-but-valid tokens return a
ReauthChallenge in error details (server-provided nonce for fast
re-auth). Authenticate RPC is exempt from token requirement.
Client: TokenCredentials replaces SSHCredentials as the primary
PerRPCCredentials. NewWithAuth creates clients with auto-renewal —
EnsureAuth obtains initial token, retryOnAuth catches Unauthenticated
errors and re-authenticates transparently. Token cached at
$XDG_STATE_HOME/sgard/token (0600).
CLI: dialRemote() helper handles token loading, connection setup,
and initial auth. Push/pull/prune commands simplified to use it.
Proto: Added Authenticate RPC, AuthenticateRequest/Response,
ReauthChallenge messages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two rejection paths: expired-but-valid tokens get a ReauthChallenge
with a server-generated nonce (fast path, saves a round trip).
Invalid/corrupted tokens get plain Unauthenticated (client falls back
to full self-generated auth flow).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-layer auth: SSH key signing to obtain a 30-day JWT, then
token-based auth for all subsequent requests. Auto-renewal is
transparent — client interceptor catches Unauthenticated, re-signs,
caches new token, retries. Server is stateless (JWT signed with
repo-local secret key). Token cached at $XDG_STATE_HOME/sgard/token.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ARCHITECTURE.md: move mirror/prune to local command table, fix
remove description (prune cleans blobs, not checkpoint), fix
Phase 2 section to only list remote commands
- README.md: add --force to mirror down, fix prune --remote
description, build instructions include both binaries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client orchestrates the two-step push/pull protocol: manifest exchange
followed by chunked blob streaming. Push detects server-newer (returns
ErrServerNewer) and up-to-date states. Pull computes missing blobs
locally and streams only what's needed. Prune delegates to server RPC.
6 integration tests via in-process bufconn server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add now recursively walks directories instead of creating a single
"directory" type entry. Extract addEntry helper for reuse. Implement
MirrorUp (sync filesystem state into manifest) and MirrorDown (sync
manifest state to filesystem with untracked file cleanup). Add CLI
mirror command with up/down subcommands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 10: GetManifest, BlobExists, ReadBlob, WriteBlob, ReplaceManifest
accessor methods on Garden. 5 tests.
Step 11: ManifestToProto/ProtoToManifest conversion functions in
server package with time.Time <-> timestamppb handling. Round-trip
test covering all 3 entry types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose GetManifest, BlobExists, ReadBlob, WriteBlob, and
ReplaceManifest on *Garden to support future gRPC and higher-level
operations without breaking encapsulation. Includes 5 unit tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GoReleaser builds static binaries for linux/darwin on amd64/arm64,
injecting version via ldflags. Version command prints the embedded
version (defaults to "dev" for local builds).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update formula references from cert to sgard, add needs: goreleaser
dependency, and pass tag-name to the bump action.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove: untrack files, remove manifest entries, save. 2 tests.
Verify: check blobs against manifest hashes, report ok/mismatch/missing. 3 tests.
List: return all tracked entries, CLI formats by type. 2 tests.
Diff: compare stored blob vs current file, simple line diff. 3 tests.
Each command in its own file (garden/<cmd>.go) for parallel development.
Remove, verify, list implemented by parallel worktree agents; diff manual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Garden.List() method that returns manifest entries, unit tests
for empty and populated repos, and a CLI command that formats output
by entry type (file with hash prefix, link with target, directory).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify iterates manifest file entries, confirms each blob exists in the
store, and re-hashes the content to detect corruption. Includes unit
tests for the ok, hash-mismatch, and blob-missing cases, plus a thin
CLI wrapper that exits non-zero on any failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Garden.Remove() which unregisters paths from the manifest,
plus unit tests and the CLI wiring via cobra.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restore writes tracked files back to their original locations.
Supports selective path restoration, force mode, and a confirm
callback for files where the on-disk mtime >= manifest timestamp
(truncated to seconds for cross-platform reliability). Creates
parent directories, recreates symlinks, and sets file permissions.
CLI: sgard restore [path...] [--force].
6 new tests (file, permissions, symlink, parent dirs, selective, confirm skip).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checkpoint re-hashes all tracked files, stores changed blobs, and
updates per-file timestamps only when content changes. Missing files
are skipped gracefully. Status compares each tracked entry against
the filesystem and reports ok/modified/missing.
CLI: sgard checkpoint [-m message], sgard status.
4 new tests (changed file, unchanged file, missing file, status).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Garden package ties manifest and store together. Supports adding
files (hashed and stored as blobs), directories (manifest-only),
and symlinks (target recorded). Paths under $HOME are stored as
~/... in the manifest for portability. CLI init and add commands
wired up via cobra.
8 tests covering init, open, add for all three entry types,
duplicate rejection, HashFile, and tilde path expansion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manifest package (5 tests): Manifest/Entry structs with YAML tags,
New(), Load(), Save() with atomic write.
Store package (11 tests): content-addressable blob store keyed by
SHA-256, Write/Read/Exists/Delete with atomic writes, two-level
directory layout, hash validation.
Both implemented in parallel worktrees and merged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement the store package with SHA-256 keyed blob storage using a
two-level directory layout (blobs/XX/YY/hash). Supports atomic writes
via temp file + rename, deduplication, and hash validation. Includes
comprehensive tests for round-trip, deduplication, existence checks,
deletion, and subdirectory creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Manifest and Entry structs with YAML tags, New() constructor,
Load(path) for reading, and Save(path) with atomic write (temp file +
rename). Tests cover round-trip serialization, atomic save cleanup,
entry type invariants, nonexistent file error, and empty manifest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all old C++ source files, proto definitions, CMake build,
clang configs, IDE files, and Trunk linter config. Initialize Go
module (github.com/kisom/sgard) with cobra and yaml.v3 deps.
Set up cobra root command with --repo persistent flag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>