# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Current release-proof posture is tracked in `docs/release/evidence/` and `docs/ops/current.md`; older entries below may describe historical release intent rather than the current supported/proven surface. ## [Unreleased] ### Added - Device secret keys can be encrypted at rest (BL2-Q10). Set `crypto.device_key_wrap_identity_file` (or `TCFS_DEVICE_KEY_WRAP_IDENTITY_FILE`) to an age identity file kept outside the tcfs config directory. New `device-.age` files are then age-encrypted to its first identity. `tcfs device protect-keys [--dry-run]` and tcfsd at startup encrypt existing plaintext key files in place, each only after a read-back check. `tcfs device status` prints `key_at_rest`. Without the setting, keys stay plaintext at mode 0600 and a warning is logged. - `tcfs health` prints one verdict and exits `0` (`UP`), `2` (`DAEMON_DOWN`), `3` (`BACKEND_DOWN`) or `4` (`DAEMON_BUSY`, no answer within 10 s). `scripts/tcfsd-health-check.sh` is a reference supervisor check that restarts tcfsd only on `DAEMON_DOWN` or a stale mount, with backoff (#612). - Single-read ingest (`crates/tcfs-sync/src/ingest.rs`): each file is read once between stat-identity checks (twice above the 64 MiB streaming threshold); a file that changes mid-read is retried three times, then refused with a typed `SourceChanged`. Non-UTF-8 names and unreadable directories are typed refusals instead of silent drops or whole-root aborts, and reconcile never deletes or pulls under a refused path (#612). - Streaming hydration (TIN-1419): bounded parallel chunk fetch verified as bytes arrive, single-flight hydration with deadlines, and NFS READs served by range from a verified cache file with bounded memory (#612). - `docs/SPEC.md`: tcfs product spec (purpose, requirements, acceptance, boundary with bulkload, current status, single-source map). - `tcfs device revoke` gained an operator-grade UX (TIN-1417): `--dry-run` previews the resolved target, post-revoke roll-call impact, effective `crypto.wrap_mode`, and propagation target without mutating anything; `--sync-remote` publishes the revocation to the canonical signed fleet registry in the same step (previously a separate `device enroll --sync-remote` that was easy to forget, leaving a revocation no peer ever saw). A device may now be addressed by name, full device id, or the 8-character id prefix printed by `tcfs device list`, and an ambiguous selector is a hard error listing the candidates instead of a silent first match. Revocation is applied by device id when one exists, so a same-named ghost sibling is never collaterally revoked. - Operator runbook for migrating a shared-master fleet to per-device wrapping (`docs/ops/shared-master-fleet-migration-runbook-2026-07-28.md`): per-step commands, hard preconditions, verification, abort, and a status ledger for the ratified `master -> dual -> roll-call -> per_device` sequence. Documents the FileProvider backend-capability precondition the roll-call gate cannot see — the `direct`/`uniffi` (iOS) backends only unwrap under the master key and fail closed on a per-device v3 manifest, so they must be accounted for before any contract flip. Doc-only: no fleet state and no config default changes. - Daemon-trusted stable-root routing for isolated conflict caches: clients can inspect a named root and run the bounded Git keep-both dry-run/execute flow without supplying a state path or storage prefix. Registered roots inherit the global storage transport. An earlier mechanism build was enrolled and exercised on Honey before the TIN-2856 incident freeze; this exact hardening head remains source-only, and that earlier run is not evidence for it. The entire named-root precursor, including read-only `conflicts --root`, is Linux/macOS-only and fails closed on every other platform. - A separate strict V1 root registry now exposes authenticated, read-only `tcfs roots list` and `tcfs roots status` inventory. Fleet-stable specs and host-local bindings have independent BLAKE3 fingerprints; availability and persisted state counts are source-only B0a status, with reconcile support fixed to `none`. ### Changed - Periodic reconcile backs off (up to one hour) while a storage probe fails, and push/pull no longer hold the lock the status RPC needs (#612). - The old-stack revival paths are retired: the paused posture-gateway and smoke-tunnel manifests are removed, the pause record's Restore section is superseded, and the paused-replicas test refuses revival commands (#611). ### Fixed - Documentation truthing: the LaTeX security, architecture, protocol and FileProvider documents no longer claim zstd compression, encrypted manifests or filenames, platform keychain storage, or FileProvider uploads; the example config no longer claims a generic `TCFS_*` override, a RocksDB cache or a live `workers`/`max_retries`; private addresses and tailnet names were removed from public docs. ### Security - NATS token auth now reads the token from a file at connect time (OI-1003-Q177): `sync.nats_token_file`, overridden by `TCFS_NATS_TOKEN_FILE`. The daemon, the sync worker, and `tcfs mount` all resolve it through `tcfs_core::nats_token`. The file must be absolute (or `~/` with an absolute, non-`/` HOME), a regular file (symlinks are followed and the target is checked; the open is `O_NONBLOCK|O_NOFOLLOW` and re-verified by `fstat`, so a swapped-in FIFO cannot hang startup), outside `/nix/store`, owned by the service user or root, `mode & 0o077 == 0` (a direct child of systemd's `$CREDENTIALS_DIRECTORY` may carry the ACL-mask group bit, never world bits), non-empty, at most 4096 bytes, and free of whitespace/control characters after one trailing newline is trimmed. Every refusal is a typed `NatsTokenError` that never carries token bytes; a non-absolute path value is never echoed (in errors, logs or `config show`) because it may be a token pasted into the path field. **Breaking:** the inline `sync.nats_token` value is now refused at connect time (it still parses, so `config show` keeps reporting `nats_token_configured`), because an inline token rendered by Nix lands in the world-readable store. The NixOS module gains `services.tcfsd.natsTokenFile` (delivered via systemd `LoadCredential`) and the Home Manager module gains `programs.tcfs.natsTokenFile`; both take a runtime path string, never a value or a Nix path literal, and assert against `/nix/store` paths and inline `settings.sync.nats_token`. The Home Manager renderer keeps `syncRoot` when `natsTokenFile` is also set (`checks.tcfs-user-config-render` guards this). The live fleet e2e suite reads `TCFS_NATS_TOKEN_FILE` too. - `tcfs device revoke` now confirms before mutating, and refuses to proceed on a non-interactive stdin without `--yes`, because revocation is sticky fleet-wide: the registry merge only ever flips `revoked` false to true and no peer can reverse it. It also refuses by default to revoke the identity the running host is itself enrolled as (`--allow-self` to override), and its `--sync-remote` path reuses the same TIN-1417 B4 trust enforcement as enroll — a signature-present-but-invalid remote registry is hard-rejected and an unsigned legacy remote is refused unless `--accept-unsigned-remote` is passed, so an attacker-injected recipient cannot be laundered into a signed registry. The existing forward-secrecy warning is unchanged, and under `crypto.wrap_mode = master` the command now states plainly that a revoke has no cryptographic effect at all. - V1 inventory never reinterprets the legacy conflict-only root registry, creates a state-lock inode, repairs or recovers a cache, exposes an MCP tool, or grants reconcile/mutation authority. It filters unauthorized prefixes before host-path probes, makes unknown and unauthorized IDs indistinguishable, and validates every cache route before publishing deterministic counts. - Registered-root authorization now hides known-but-unauthorized IDs behind the same not-found response as unknown IDs, before path-bearing validation. - Registered-root RPCs now require real session enforcement and fail closed when `auth.require_session = false`; the development-only synthetic admin bypass is excluded from every named-root operation, while existing legacy auth-disabled daemon behavior is unchanged. - Registered-root Git keep-both dry-run requires pull permission; execute requires both pull and push. Inspect-only policy permits a pull-authorized dry-run but rejects execute even for a pull+push session. - Legacy primary-cache Git resolution now checks capabilities by data flow: dry-run requires pull and execute requires pull+push. The unrooted legacy per-file mutation strategies (`keep-local`, `keep-remote`, and `keep-both`) are retired fail-closed; `defer` remains a push-authorized no-op until ordinary-file resolution has daemon-owned root and manifest identity. - Registered-root conflict operations reject stale cache keys outside the authorized storage prefix. This precursor owns only repository-group Git keep-both mutation; ordinary per-file mutation is disabled on both primary and named roots. - The shipped CLI no longer advertises retired ordinary-file strategies, and the unrooted `resolve_conflict` MCP mutation tool is removed. Older direct gRPC clients receive one uniform fail-closed response before path or state lookup. - Git keep-both captures the authorized repository's filesystem identity, binds Git mutation subprocesses and lock acquisition to captured repository and `.git` directory handles, and revalidates the configured pathname across storage, state-lock, mutation, and state-flush boundaries so a same-path replacement is never mutated. This applies to every Git keep-both mutation, including the legacy primary-cache route; all such mutation is Linux/macOS-only and fails closed elsewhere. The seam accepts only the ordinary files-ref backend and rejects reftable, enabled `core.sharedRepository`, and shared-writable repositories. - Named-root routing requires the local root, critical Git metadata, state directory, and state cache to be owned by the exact tcfsd effective UID. Roots, metadata, and state directories must not be group/world writable; the cache must be mode 0600 or stricter. Canonical ancestors must be owned by that UID or root and not writable by another principal, except for a protected root-owned sticky boundary such as `/tmp`. - Git keep-both cleanliness checks use isolated metadata, disable lazy fetch and replacement refs, and reject executable filter/attribute and partial clone routing. The cooperative Git lock uses one persistent advisory-locked inode and serializes TCFS writers only. Native Git busy markers remain a fail-closed preflight, but same-euid native Git races are outside the resolver's concurrency guarantee. - CLI `push`, `pull`, `rm`, and executing `reconcile` serialize explicit `--state` cache mutations with daemon-side registered-root operations before constructing storage clients. These remain legacy mutation routes, not named-root authorization surfaces; only `conflicts --state` is diagnostic. - CLI and MCP configuration display now replaces the inline NATS token with a `nats_token_configured` boolean. Displayed endpoint URLs are origin-only and strip userinfo, path, query, and fragment components. CLI, MCP, and daemon parse failures no longer echo offending TOML source lines into operator output or system logs. - CLI-generated signed enrollment invites now carry only the URL-normalized HTTP(S) origin in `storage_endpoint`; userinfo, path, query, and fragment are omitted, malformed or unsupported schemes fail closed, and bucket and remote prefix remain dedicated fields. - Credential-bearing S3/SeaweedFS clients now require HTTPS by default across daemon, CLI, direct mount, and FileProvider operator construction. Plaintext compatibility requires an explicit development/test opt-in. - On Linux/macOS, JSON sync-state reads now fail closed on symlinks, hardlinks, permissive modes, unsafe parent chains, ACL grants, and non-content I/O failures. A missing or content-corrupt primary may recover only from a securely opened, valid backup; the first durable repair preserves that known-good backup. - tcfsd now keeps policy, auth, and pending-delete authority only in its persistent owner-only data directory, holds a process-lifetime singleton lock before opening caches or sockets, and refuses the former shared `/tmp` fallback. Automatic remote deletes use a durable ledger plus atomic rename-without-replacement so recovery never overwrites a recreated path. - Indexed publication now verifies live create-if-absent, same-ETag update, and stale-read semantics before remote mutation; configured client-side concurrency limits cannot serialize the verification race. - Portable remote paths now acquire monotonic per-component namespace reservations, atomically excluding Unicode/case aliases and concurrent file-versus-directory claims across publishers. - Remote file, directory-marker, rename-source, and trash removal now uses conditional version-4 tombstones instead of proof followed by unconditional delete. Consumers distinguish physical absence from a durable deletion: only a freshly rechecked v4 tombstone may authorize local byte removal. Trash restore/purge uses one exclusive immutable lifecycle claim per exact generation, makes interrupted restores retryable, validates current and staged manifest bindings before either tombstoning or republication, and retains its evidence. Trash-backed v4 tombstones bind the exact generation key and evidence digest at the deletion linearization point; the later completion marker is independently verifiable, so a lost response stays recoverable while a failed compare-and-swap remains indeterminate. Historical timestamp-only generations remain explicitly restorable. Retention begins at the storage timestamp of the delete-completion marker (or its exact bound tombstone), uses one cleaned fixed storage-clock guard, and retains legacy generations without a provable deletion time unless the operator requests `--all`. Trash scans validate legacy payload/manifest bindings, report and retain malformed objects without blocking valid listing rows or independently proved purge claims; purge returns a structured partial report and the CLI exits unsuccessfully after reporting any retained issue. Exact-key restore bypasses unrelated scan issues, and `trash purge --all` conflicts with `--older-than`. Executing prefix migration now requires an explicit quiesced-writer assertion and manifest-binding validation; exact double-prefixed sources are logically retired while orphan-prefix evidence remains for reachability-safe physical GC. - FileProvider reads now use a distinct exact-path RPC, so an older daemon returns `UNIMPLEMENTED` before mutation instead of silently falling back to a same-basename object. Manual hydrators bind index size and chunk count before local mutation, and deleted index tombstones are treated as logical absence. VFS, daemon listing, Apple providers, and Windows placeholder discovery now exclude physical tombstone objects while propagating corrupt index state. Apple write capabilities and callbacks plus the C, gRPC, and UniFFI mutation entry points are explicitly read-only and reject before I/O until an opaque exact-version conditional publication protocol is available. Incomplete or skipped daemon push streams now terminate with an error rather than success, and the gRPC-backed direct reader rejects malformed or missing required encryption key material before constructing storage or daemon clients. ## [0.12.17] - 2026-07-09 > **Version-truth note (TIN-2684):** the version strings `0.12.15` and > `0.12.16` were deployed to fleet hosts from untagged main builds but were > never released or tagged; their changes are folded into this release. This > is the first tagged release since `v0.12.14`. ### Added - **`.git`-as-files bidirectional conflict safety (G5-git-5, TIN-1620) is proven end-to-end**: worktree/gitfile fence (#526), foreign-lock respect + `remote_manifest_key` (#528), keep-both resolver parking losing refs under `refs/tcfs/theirs//**` (#529), and the loser-side no-loss guard with verified undo bundles (#534). Divergent convergence proven live on a two-host canary — all six G5-git-13 criteria (#542). - **Off-neo darwin closure CI** (#524): every push to main builds and publishes the full aarch64-darwin closure (including `tcfsd`) from hosted macOS CI (~40 min) for fleet substitution. - One-time **legacy state absorb**: a `.db`-only host migrates its state file to the canonical `state.json` atomically (temp + parse-validate + rename) at daemon boot (#545). ### Fixed - **Out-of-band `git commit` never ticked the vector clock**, making real divergence structurally unreachable as a conflict (TIN-2584, #540). - **Plan-path conflicts were recorded with `status="synced"`**, leaving the keep-both resolver blind to them (TIN-2652, #541). - **CLI `--state` overrides and the daemon now converge on one state file**: overrides are tilde-expanded and normalized to the `.json` sibling the daemon actually reads; boot-time `purge_stale` never drops entries with unresolved conflicts (TIN-2657, #545). - FileProvider backends read Dual/v2 manifests via master-wrap fallback (TIN-1898). - Zero-diff roam fidelity: mtime preservation and opt-in symlink convergence (#508 lineage). ### Changed - **Per-device file-key wrapping is now a tri-state `crypto.wrap_mode`** (TIN-1417, replaces the `crypto.per_device_wrapping` boolean). Values: `master` (DEFAULT — shared-master wrap, byte-identical to the previous default), `dual` (EXPAND/transitional — emits BOTH the master wrap and per-device wraps; manifest stays v2 and back-compatible), and `per_device` (CONTRACT — emits per-device wraps ONLY, drops the master wrap for true revocation, and bumps the manifest to v3 so pre-per-device binaries fail CLOSED). Back-compat: a legacy `crypto.per_device_wrapping = true` config deserializes to `dual` (keeps the master fallback); `false`/absent maps to `master`. A present `wrap_mode` is canonical and wins. The daemon REFUSES to drop the master wrap (`per_device`) until a roll-call probe confirms every active, non-revoked device carries a real age recipient; otherwise it falls back to `dual` and warns loudly. Default remains `master`, so existing fleets are unaffected until the migration is explicitly enabled. ## [0.12.14] - 2026-06-03 First finalized release of the 0.12.13→0.12.14 line (0.12.13 shipped only as rc1–rc4 prereleases). Captures the fail-closed secrets deny-set and the daemon-startup reliability fixes that landed on `main` after `v0.12.13-rc4` — previously present in deployed builds but in no published tag. ### Security - **Fail-closed secrets deny-set in the sync `Blacklist`** (TIN-1737, `crates/tcfs-sync/src/blacklist.rs`): secret-bearing paths are never synced — `.ssh/`, `.gnupg/`, `sops-nix/` directories; `auth.json`, `.netrc`, `.pgpass`, `.credentials.json` files; `.env*` files; and `.sqlite*`/`.db*` suffixes. Enforced fail-closed so a misconfiguration cannot leak credentials. - **Per-device file-key wrapping substrate** (TIN-1417, behind a flag, default off): age-recipient per-device key wrapping is now available via `crypto.per_device_wrapping`; the default remains shared-master so existing fleets are unaffected until the migration lands. - **Enrollment & auth hardening**: single-use enrollment invites; admin-gated enrollment control RPCs; device-invite enrollment bootstrap (TIN-1424); real local age identities generated on enrollment; auth sessions are now persisted and attached to protected RPCs. ### Added - **Device registry sync during enrollment** (#476): enrolling a device now publishes it into the shared device registry. - **FileProvider config rendering** (#466, TIN-1425): the daemon and CLI render the FileProvider config from the active config; `tcfs init` writes a config; FileProvider surface contract is asserted in CI (TIN-1547). - **`natsTls` config option** (default `false`, #473). - **Storage restore SLO budgets** in CI (TIN-1622). ### Fixed - **Daemon control-plane startup hangs** (TIN-1758): readiness no longer blocks on remote index discovery; the FileProvider socket bind is kept off the runtime workers and serves late socket binds — the daemon comes up reliably under load. - **`tcfs pull `** writing into a hash-named file in the cwd instead of the requested path (#473). - Hardened large-restore retry observability; avoided a macOS LaunchAgent config restart loop (#457). ## [0.12.13] - 2026-05-18 ### Added - **`tcfs index inspect `** (`crates/tcfs-cli`): read-only CLI subcommand that reports a single remote-index entry's state in human or JSON form. Statuses: `visible`, `missing_index`, `missing_manifest`, `preparing_only`, `no_visible_entry`, `parse_error`. Lets operators and the smoke harness distinguish "missing remote fixture" from "real FileProvider/FUSE read failure" — the diagnostic primitive that unblocked the production Dev ID FileProvider proof packet. - **Production Dev ID FileProvider acceptance** (M10): first green end-to-end macOS post-install smoke against a Developer ID notarized `.pkg` on the canonical PZM self-hosted runner, covering hydrate, evict/rehydrate, mutation, and conflict-status. Run `26062554542`. Archived under `docs/release/evidence/macos-postinstall-prod-devid-hydration-...`. - **`AGENTS.md`**: canonical agent context at the repo root for Claude Code, Codex CLI, and other agent tooling. `.claude/CLAUDE.md` remains as a secondary machine-local overlay. - **Linux post-install smoke scaffold** (TIN-1422): `scripts/linux-postinstall-smoke.sh` + `scripts/test-linux-postinstall-smoke.sh` + `.github/workflows/linux-postinstall-smoke.yml`. Structural analog of the macOS harness gated on the same `tcfs index inspect` `status=visible` check. Lands the shape; FUSE evict/rehydrate semantics, mutation write-back, conflict-status analog, and Fedora RPM matrix entry are tracked as TIN-1422 follow-ups. - **macOS pkg authenticated install mode** + remote pkg notarization proof workflow + required-notarized-release gate. ### Changed - **`scripts/macos-postinstall-smoke.sh`**: new `--seed-expected-file` and `--rebuild-domain` flags. Hydration check now gates on `tcfs index inspect` reporting `status=visible` before treating a FileProvider read timeout as a desktop bug. - **`.github/workflows/macos-postinstall-smoke.yml`**: derives `storage.enforce_tls` from the endpoint scheme (HTTPS→true, HTTP→false) so tailnet-internal smoke endpoints work without sacrificing public endpoint hardening. New `exercise_dev_id_layered_proof` input runs evict/rehydrate + mutation against production Developer ID without requiring `fileprovider_testing_mode=true` (whose entitlement only ships on Mac App Development profiles). `exercise_conflict_status` validation loosened to allow Dev ID layered proof. - **macOS FileProvider host app**: respects `TCFS_FILEPROVIDER_REBUILD_DOMAIN=1` to remove and re-add the FileProvider domain before smoke runs (stale-domain diagnostics). - **`docs/ops/macos-fileprovider-reality.md`**: lede now records the 2026-05-18 production Dev ID hydration milestone. Historical context preserved with dated inline annotations. ### Fixed - **Git repo canary chunking**: treat git pack indexes as large sequential chunks to avoid pathological FastCDC chunk explosion on `.pack`/`.idx` files. - **Restore semantics**: empty directory markers preserved across push/pull roundtrips and reconciliation. - **macOS pkg signing**: p12 passwords accept blank strings during signing proofs (matches some operator key-management flows). ## [0.12.3 - 0.12.12] - 2026-04-17 to 2026-05-08 ### Added - **Release evidence packets**: current proof after `0.12.2` is tracked in `docs/release/evidence/`, including Linux lifecycle, PZM testing-mode FileProvider lifecycle/conflict, Homebrew/Nix, container, and Linux package distribution packets. ### Changed - Public docs now separate packaged artifact smoke, host-proven lifecycle, non-production Apple testing-mode evidence, production Finder acceptance, and Kubernetes rollout proof. ## [0.12.2] - 2026-04-16 ### Added - **Distribution proof runbook**: added the canonical release smoke matrix and corrected Homebrew tap flow so post-cut install verification has one current operator path. ### Changed - Apple and iOS support surfaces now point to dated April 15, 2026 evidence instead of generic support claims, and the odrive parity backlog is refreshed against the current Linux-first product posture. ### Fixed - **macOS release packaging**: release builds now vendor OpenSSL for macOS arm64 artifacts, and release CI fails if `tcfsd` still links a dynamic Homebrew OpenSSL dylib. - **Sync state keying**: fixed state-key lookup after delete-through-symlink-parent cases so `remove()` and follow-up reads hit the same cached entry. ## [0.12.1] - 2026-04-15 ### Added - **Named live acceptance lane**: `neo-honey` is now the canonical live SeaweedFS + NATS + two-device smoke path, with a documented script and matching e2e naming. - **Failure-oriented validation**: added targeted coverage for manifest/index crash windows, retry backoff behavior, NATS durable replay semantics, live storage outage recovery, and CLI/gRPC/MCP/FUSE workflow paths. - **Orphan chunk reporting and cleanup**: reconcile can now surface orphaned remote chunks and clean them up conservatively after a grace period. ### Changed - Release workflow now signs GHCR images by immutable digest, honors explicit tags on manual proof runs, and no longer lets Apple notarization outages fail the entire release. - Release notes and platform/docs surfaces now describe Apple notarization as attempted rather than guaranteed, and keep Apple packaging positioned as experimental. ### Fixed - **Crash-safe rel-path publish**: manifest/index publication now uses recovery-aware staged, preparing, and committed index states with deterministic crash-window recovery. - **Upload and path correctness**: fixed upload TOCTOU races, Unicode rel-path normalization, gRPC push path traversal rejection, manifest-read retries, and resumable key rotation. - **State and lifecycle correctness**: fixed StateCache metadata persistence, PathLocks cleanup under contention, orphan cleanup wiring, and rename/delete sync lifecycle handling across CLI and FUSE flows. ## [0.12.0] - 2026-04-08 ### Added - **Finder status surfaces**: macOS FileProvider gained Finder Sync badge support, download progress reporting, and policy-aware excluded or pinned status badges. - **Conflict UX improvements**: conflict notifications, conflict-copy remote writes, and CLI policy controls for handling sync conflicts. - **Apple packaging updates**: release artifacts include Apple Silicon `.pkg` installers and bundled `TCFSProvider.app` payloads; current notarization posture is tracked in the Apple surface docs and later release-proof evidence. ### Changed - macOS release builds now vendor OpenSSL to avoid code-signing and runtime loader failures during packaging. - Canonical release assets now ship from `Jesssullivan/tummycrypt` with GitHub Releases, GHCR images, Homebrew tap updates, and Nix cache publication. ## [0.11.1] - 2026-04-08 ### Added - **`tcfs reconcile` CLI**: explicit reconcile command wired to the bidirectional plan-and-execute sync engine introduced in `0.10.0`. - **Scheduler safety wiring**: `PathLocks` and `FileSyncStatus` now drive daemon scheduling decisions instead of remaining library-only primitives. - **Watcher blacklist enforcement**: daemon watcher-to-scheduler flow now applies the shared blacklist logic consistently. - CI step for the sync integration path to exercise the new robustness and reconciliation lanes during automation. ### Changed - Release pipeline hardening for container build and publish flow, including lowercase GHCR naming fixes. - Re-enabled Attic cache use in the Nix fleet configuration after the earlier stale-cache disablement. - FileProvider build script now validates outputs and required configuration before packaging. ### Fixed - Removed plaintext credential handling from repo and deployment surfaces. - Documentation refreshed to match the active development state after the `0.10.x` sync-safety push. ## [0.11.0] - 2026-04-07 ### Added - **Read-write FUSE/VFS path**: write, create, unlink, mkdir, rename, and rmdir operations now flow through the SeaweedFS-backed mount path. - **FUSE write pipeline parity**: FastCDC chunking, transparent `.tc` handling, NATS publish on write, and index-first auto-pull wiring for mounted file edits. - **Encryption wiring across push paths**: `EncryptionContext` and encrypted manifest support now cover the remaining CLI and daemon upload paths. - **Daemon self-sufficiency**: daemon startup can provision sockets, local directories, credentials, and unlock flow without external bootstrapping. - **iOS/FileProvider onboarding work**: QR enrollment improvements, credential-broker support, build-info surfaces, and several FileProvider fixes landed in the Apple lane. ### Changed - FUSE3 returned as the default mount backend, with the NFS path retained as fallback. - Stale Attic cache configuration was disabled in Nix surfaces while binary-cache behavior was being corrected. ### Fixed - Cross-host sync lookup and pull behavior now normalize `rel_path`, repair absolute-path push fallout, and add fallback filename search where older index state exists. - FUSE hydration now understands JSON v2 manifests and decrypts encrypted chunks with the correct file identity. - Watcher, daemon, and NATS paths now avoid skipped-upload orphaned index entries and missing prefix propagation. ## [0.10.0] - 2026-04-05 ### Added - **Centralized blacklist** (`tcfs-sync::blacklist`): Consolidates 6 scattered exclusion sites (watcher, engine, VFS, git safety) into a single `Blacklist` type with `check()`, `check_name()`, and `check_path_components()` methods. Configurable glob patterns, hidden dirs, .git policy. - **Directory reconciliation pipeline** (`tcfs-sync::reconcile`): Plan-then-execute bidirectional sync. `reconcile()` diffs local tree vs remote index producing a `ReconcilePlan` (pure data). `execute_plan()` performs I/O via existing engine primitives. Supports push, pull, delete, conflict detection via vector clocks. - **Per-folder sync policies** (`tcfs-sync::policy`): `PolicyStore` with `FolderPolicy` (SyncMode, download_threshold, auto_unsync_exempt). Parent-chain walk inheritance — policy on `/project` applies to all descendants. - **Auto-unsync controller** (`tcfs-sync::auto_unsync`): Background sweep finds files where `last_synced` exceeds configurable max age. Respects PolicyStore exemptions, skips dirty files (unsynced local changes). Removes from state cache only — local files preserved. - **PathLocks**: Per-path async mutex preventing concurrent push/pull/unsync on the same file. RAII guard with automatic cleanup. - **FileSyncStatus enum**: 5-state runtime status (NotSynced, Synced, Active, Locked, Conflict) for gRPC/JSON. - **Unsync dirty-child check**: Before folder unsync, scans `children_with_prefix()` for unsynced modifications. Errors unless `force=true`. - **Diagnostics RPC**: New gRPC endpoint reporting state cache size, conflict count, NATS seq, auto-unsync eligible count, storage health, uptime. - **Scheduler observability**: `active()`, `completed()`, `failed()` counters on `SyncScheduler` for operational monitoring. - `children_with_prefix()` on `StateCacheBackend` for directory-level state queries. - `list_remote_index()` for fetching remote S3 index entries. - `parse_index_entry()` for the `manifest_hash=.../size=.../chunks=...` format. - Config fields: `auto_unsync_max_age_secs`, `auto_unsync_interval_secs`, `auto_unsync_dry_run`. - CI job for `cargo test -p tcfs-sync --features nats,crypto`. ### Changed - **BREAKING**: `auth.require_session` now defaults to `true` (was `false`). Add `auth.require_session = false` for dev environments. - **BREAKING**: `NatsClient::connect()` now accepts `require_tls: bool` parameter. If `nats_tls=true` and URL is `nats://`, auto-upgrades to `tls://`. - Storage `RetryLayer` now uses exponential backoff factor (2.0x) in addition to existing jitter. - Scheduler backoff adds ±25% jitter to prevent thundering herd on retries. - Auto-unsync interval task wired into daemon lifecycle (after session cleanup). ### Fixed - **Credential file permissions**: TOTP credentials, WebAuthn credentials, session tokens, device registry, and state cache now enforce `chmod 0o600` on write (was umask-dependent). - **NATS TLS enforcement**: `nats_tls` config option was defined but silently ignored. Now wired into `connect()` with URL scheme upgrade. - CLI uses config bucket as default push prefix instead of filename. - NATS rel_path normalization for cross-host state cache lookup. ### Security - 5 credential write paths now enforce restrictive file permissions (`0o600`). - NATS connections without TLS log explicit plaintext warning. - Session authentication required by default for all gRPC calls. --- ## [0.9.3] - 2026-03-20 ### Added - iOS FileProvider: build info section, TestFlight compliance, QR enrollment view. - Auth credential broker for zero-touch device enrollment. ### Fixed - FileProvider: EDEADLK hydration deadlock, cbindgen exports, ATS exceptions. - iOS: QR enrollment + encryption + deep links. --- ## [0.9.0 – 0.9.2] - 2026-03-10 – 2026-03-18 ### Added - Read-write FUSE support: write, create, unlink via SeaweedFS. - FastCDC chunking for FUSE writes. - NATS publish on FUSE write + index-first auto-pull. - Transparent `.tc` suffix for FUSE userspace. - FUSE directory ops: mkdir, rename, rmdir. - Vector clock conflict detection in FUSE writes. - FUSE3 as default mount backend (NFS as fallback). ### Fixed - Orphaned index entries on skipped uploads. - NFS: panic detection, timeout handling, in-process mount, sudo on Linux. - Watcher: skip directories in push path. - NATS: stream subject filter updates via `create_or_update_stream`. - VFS: JSON v2 manifest parsing in hydration path. - Flake: disable stale Attic cache. ### Changed - Retired legacy FUSE crates (removed 11,418 LOC). --- ## [0.6.0-dev] - Unreleased (pre-0.9.0 development) ### Added - **RocksDB state cache backend**: `StateCacheBackend` trait with JSON (default) and RocksDB (behind `full` feature) implementations - **E2E encryption in push/pull pipeline**: `EncryptionContext` wires `tcfs-crypto` into chunk upload/download when `config.crypto.enabled = true` - **SyncManifest `encrypted_file_key`**: Base64-encoded wrapped per-file key stored in manifest for encrypted files - **Windows CFAPI wiring**: `tcfs-cloudfilter` provider, hydration, and placeholder modules use `tcfs-sync` manifest parsing and chunk integrity verification - **macOS FileProvider FFI**: `tcfs-file-provider` exposes C-compatible `extern "C"` functions via cbindgen for Swift consumption - **Tailscale NATS exposure**: OpenTofu module `tailscale-nats` exposes NATS to tailnet via Tailscale operator (no public IP) - **Darwin launchd support**: Home Manager module generates `launchd.agents.tcfsd` on macOS, `systemd.user.services.tcfsd` on Linux - **`syncRoot` option**: Exposed in both NixOS and Home Manager modules for daemon auto-pull target directory - **`TCFS_S3_ACCESS`/`TCFS_S3_SECRET` env vars**: tcfs-native credential env var names (highest priority in fallback chain) - **Justfile**: IaC command surface for OpenTofu, Kubernetes, NATS, and build operations - Encryption round-trip integration tests (`encrypted_roundtrip_test.rs`) - RocksDB persistence tests (`rocksdb_state_test.rs`) ### Changed - `tcfs-sync` gains `crypto` feature flag (optional `tcfs-crypto` + `base64` deps) - `upload_file_with_device()` and `download_file_with_device()` accept optional `EncryptionContext` - `tcfs-file-provider` crate type changed from lib to `["lib", "staticlib"]` with cbindgen header generation - Lab fleet examples rewritten from `services.tcfsd` (NixOS) to `programs.tcfs` (Home Manager) - NATS URL in fleet configs changed to a Tailscale MagicDNS name - `dist/com.tummycrypt.tcfsd.plist` updated with `--mode daemon` flag and Nix usage guidance - Fleet deployment docs overhauled: Tailscale NATS, Home Manager startup, corrected env var names - `just` added to flake.nix devShell ## [0.5.0] - 2026-02-23 ### Added - **ResolveConflict RPC**: Fully wired with keep_local (re-upload manifest with ticked vclock), keep_remote (download remote version), keep_both (rename local + download remote), and defer strategies - **NATS auto-pull**: State sync loop now downloads remote files automatically in `auto` conflict mode, with vclock comparison and AutoResolver tie-breaking for concurrent edits - **Hydrate RPC**: Downloads file from `.tc` stub metadata, removes stub after successful hydration - **Unsync RPC**: Removes path from state cache without deleting remote or local data - **Watch RPC**: Streams filesystem events (created/modified/deleted) using `notify` crate with recursive watching - **Mount RPC**: Spawns `tcfs mount` subprocess with active mount tracking - **Unmount RPC**: Runs `fusermount3 -u` (fallback `fusermount -u`), cleans up tracked subprocess - `sync_root` config option: local directory root for auto-pull file downloads - ConflictResolved NATS events published after resolution, merged by remote peers - 10 new tests: 4 conflict resolution integration tests + 6 vclock comparison unit tests ### Changed - `spawn_state_sync_loop` now accepts operator, state cache, sync_root, and storage prefix for auto-pull - `status` RPC reports live `active_mounts` count from tracked subprocess map - All 11 gRPC RPCs now return meaningful responses (zero `UNIMPLEMENTED` stubs remain) ## [0.4.0] - 2026-02-23 ### Added - **Benchmarks**: divan benchmark framework for chunking and encryption throughput (#22) - FastCDC chunking, BLAKE3 hashing, zstd compress/decompress, XChaCha20-Poly1305 encrypt/decrypt - `task bench` command for running all benchmarks - `docs/BENCHMARKS.md` populated with real measurements (BLAKE3: 1.39 GB/s, zstd: 1.26 GB/s) - **Chunk integrity verification**: BLAKE3 hash verified per-chunk on download and against manifest file hash (#23) - **Graceful shutdown**: SIGTERM/SIGINT handler flushes state cache, publishes DeviceOffline, sends systemd STOPPING=1 (#23) - **Health endpoints**: `/healthz` (liveness) and `/readyz` (readiness with S3 check) on metrics HTTP server (#23) - **7 integration tests**: push/pull round-trip, dedup, integrity, tree push, device-aware sync using in-memory backend (#23) - **Fleet deployment guide**: `docs/ops/fleet-deployment.md` covering NATS access, credential distribution, daemon startup (#22) - **macOS launchd plist**: `dist/com.tummycrypt.tcfsd.plist` for automatic daemon startup (#22) - RFC 0002: Darwin File Integration Strategy — FileProvider as primary macOS/iOS path (#21) - RFC 0003: iOS File Provider with UniFFI bridge design (#22) - `tcfs-file-provider` crate skeleton for macOS/iOS FileProvider extension (#22) - `docs/tex/fileprovider.tex` LaTeX design document (#21) ### Changed - Storage retry improved: 5 retries with jitter (was 3 without jitter) + OpenDAL logging layer (#23) - gRPC `serve()` now supports graceful shutdown via async signal (#23) - Metrics server operator handle shared with health endpoint for live readiness checks (#23) ### Fixed - Resolved RFC 0001 open questions (NATS access path, credential distribution, daemon startup) (#22) ## [0.3.0] - 2026-02-22 ### Added - Multi-machine fleet sync with vector clocks and conflict resolution (#18, #19) - `VectorClock` implementation with `tick()`, `merge()`, `partial_cmp()`, `is_concurrent()` - `SyncManifest` v2 (JSON format with vector clocks, backward-compatible v1 text fallback) - Device identity system with auto-enrollment and S3-backed `DeviceRegistry` - CLI `device` subcommand: `enroll`, `list`, `revoke`, `status` - NATS JetStream real-time state sync (`StateEvent` enum with 6 event types) - Per-device durable NATS consumers with hierarchical subjects (`STATE.{device_id}.{type}`) - `ResolveConflict` gRPC RPC (11 total RPCs) - `.git` directory sync safety: lock detection, git bundle mode, cooperative locking - Config-driven file collection (`sync_git_dirs`, `exclude_patterns`, `sync_hidden_dirs`) - Interactive conflict resolver in CLI (`keep_local`, `keep_remote`, `keep_both`, `defer`) - TUI Conflicts tab for pending conflict review - MCP `resolve_conflict` and `device_status` tools (8 total tools) - NixOS and Home Manager module options for fleet sync - `examples/lab-fleet/` with per-machine config fragments - 18 proptest properties (8 vector clock, 2 crypto round-trip, 5 simulation, 3 integration) - RFC 0001: Fleet sync integration plan - LaTeX design documents (Architecture, Protocol, Security) with CI-built PDFs - Mermaid architecture diagrams in docs site - Link checking with lychee ### Changed - CLI `push`/`pull` now use device-aware upload/download with vector clock tracking - Daemon publishes `DeviceOnline` event on NATS connect and `FileSynced` on push - `tcfs-sync` NATS feature is now always enabled in `tcfsd` (fleet sync is core) - Status RPC returns `device_id`, `device_name`, and `conflict_mode` - Manifest format upgraded from newline-delimited text to JSON (v2) ## [0.2.5] - 2026-02-21 ### Fixed - Bind metrics server to `0.0.0.0` in K8s configmap for health probes (#14) - Add `imagePullSecrets` for private GHCR container registry (#13) - Disable KEDA `ScaledObject` and `ServiceMonitor` CRDs in Civo deploy (#12) - Update container image repo and S3 endpoint for in-cluster SeaweedFS (#11) ### Added - MCP server (`tcfs-mcp`) for AI agent integration with 6 tools (#10) - Civo K8s deployment with NATS + SeaweedFS in `tcfs` namespace ## [0.2.1] - 2026-02-21 ### Added - gRPC RPCs: `push` (client-streaming), `pull` (server-streaming), `sync_status` (#9) - TUI dashboard with 4 tabs: Dashboard, Config, Mounts, Secrets (#7) - `tcfs-sops` crate for SOPS+age fleet secret propagation (#7) ### Fixed - macOS `fuse3` `FileAttr` missing `crtime`/`flags` fields - Darwin `apple_sdk` migration for nixpkgs-unstable 2026 - Homebrew formula retry logic + container build amd64-only (#5) ### Security - Removed committed TLS certificates and private keys from tracking (#8) ## [0.2.0] - 2026-02-21 ### Changed - Version bump for release pipeline (no functional changes beyond v0.2.1 pre-releases) ## [0.1.0] - 2026-02-21 ### Added - Rust monorepo with 14 workspace crates - Core daemon (`tcfsd`) with gRPC over Unix domain socket - CLI (`tcfs`): `status`, `config show`, `push`, `pull`, `sync-status`, `mount`, `unmount`, `unsync` - FUSE driver for Linux with on-demand hydration via `.tc` stubs - Windows Cloud Files API skeleton (`tcfs-cloudfilter`) - E2E encryption: XChaCha20-Poly1305, Argon2id key derivation, BIP-39 recovery - Content-defined chunking (FastCDC) with BLAKE3 hashing and zstd compression - Secrets management: SOPS/age decryption, KeePassXC integration - OpenDAL-based S3/SeaweedFS storage backend - Sync engine with JSON state cache and NATS JetStream messaging - K8s worker mode with KEDA auto-scaling - Prometheus metrics endpoint with systemd `sd_notify(READY=1)` - Cross-platform release pipeline: Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64 - Container image publishing path for `tcfsd`; current registry and architecture proof are tracked in the release evidence docs. - Nix flake with NixOS module and Home Manager module - Homebrew formula, `.deb`/`.rpm` packages, install scripts - 77 tests, cargo-deny license/advisory checks, security audit CI [0.5.0]: https://github.com/tinyland-inc/tummycrypt/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/tinyland-inc/tummycrypt/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/tinyland-inc/tummycrypt/compare/v0.2.5...v0.3.0 [0.2.5]: https://github.com/tinyland-inc/tummycrypt/compare/v0.2.1...v0.2.5 [0.2.1]: https://github.com/tinyland-inc/tummycrypt/compare/v0.1.0...v0.2.1 [0.2.0]: https://github.com/tinyland-inc/tummycrypt/releases/tag/v0.2.0 [0.1.0]: https://github.com/tinyland-inc/tummycrypt/releases/tag/v0.1.0