Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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-<id>.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/<device>/**` (#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 <abspath>`** 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 <path>`** (`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