Proposal: degraded native-Windows write mode behind an explicit reduced-guarantees flag
Motivation
v2.1.0 (#150) made native Windows a supported read-only platform: inspection, dry-runs, and retrieval work natively, while vault writes fail closed with UNSUPPORTED_PLATFORM and point to WSL. That boundary is deliberate — mutation safety is built on POSIX directory-descriptor confinement that native Windows cannot provide.
Real-world feedback since release: WSL itself can be a nontrivial hurdle. One user verified native read-only works, then hit the write refusal — and found wsl --install completed but wsl --status / wsl -l -v hung indefinitely (Hyper-V / Virtualization-Based-Security conflict class, survived a reboot). For users in that position the current posture is all-or-nothing: WSL works perfectly, or zero write capability.
Short-term, docs/windows-wsl.md now covers WSL troubleshooting. This issue tracks the longer-term ask: a degraded native-Windows write mode, default-off, behind an explicit reduced-guarantees flag.
What full confinement guarantees today (what would be traded)
- Descriptor pinning: the vault root, its parent,
.vault-meta, and every runtime directory are held asO_DIRECTORY|O_NOFOLLOWdescriptors for the whole mutation window; concurrent alias/namespace swaps fail closed (_lock_entry_matchesdev/ino re-verification, ~8 checks per write via_assert_transaction_namespaces). - Advisory lock on the vault inode:
fcntl.flock(root_fd, LOCK_EX|LOCK_NB)serializes writers across.vault-metareplacement. - fsync-ordered lock publication: directory fsyncs order
mutation.lockcreation andowner.jsonpublication across crashes. - Staleness probing:
os.kill(pid, 0)+ quarantine-rename reaping. - No recursive path deletion, ever: runtime teardown unlinks only descriptor-pinned, schema-known entries.
Proposed architecture (honest deltas)
Lock (wire-compatible with claude-obsidian.mutation-lock.v1):
- Keep the on-disk shape:
.vault-meta/mutation.lock/directory +owner.json(O_CREAT|O_EXCL) — both primitives are portable oncedir_fd=is dropped. - Serialization:
msvcrt.lockingbyte-range lock on a stable file inside the lock directory. Documented delta: Windows byte-range locks are mandatory, per-file, released on handle close — the "serialize the vault inode across runtime-namespace replacement" property is lost (a swapped.vault-metayields a different lock file). - No auto-reaping on Windows: PID liveness probing is unreliable there; stale locks require explicit
transaction recover --force-stale-lock, matching the existing "never automate the override" doctrine (docs/compound-vault-guide.md). - Identity checks become cached
(st_dev, st_ino)lstat re-derivations — a point-in-time check with a documented TOCTOU window, the same guarantee level as the (already shipped) degraded read mode.
Journal/runtime layer — path-mode ports needed for roughly 12 fd-anchored functions: _OperationStore, _RuntimeStore, _open_runtime_directory_at, _bounded_runtime_names (fd-scandir), _read_runtime_bytes_at, _atomic_runtime_write_at (also needs os.replace semantics — os.rename cannot overwrite on Windows), the recovery readers/validators, and _restore_journal plumbing (whose leaf helpers already have path branches from v2.1.0).
The hardest problem — runtime teardown: _remove_pinned_runtime_tree_at exists precisely because the design refuses recursive path deletion. Proposal: replace with bounded schema-validated deletion — only known entry names, depth ≤ 2, per-level assert_unaliased_directory (symlink/junction rejection), refuse anything unexpected and leave it for manual recovery.
Consent gate:
- New CLI flag
--allow-degraded-confinementfollowing the house--allow-egress/--allow-remote-ollamaconvention: default-deny, the refusal error names the flag. - Threaded through all four enforcement chokepoints (
apply_bundle,command_init, capture runtime + queue lock). - Echoed in result JSON and the journal (precedent:
forced_stale_lock). - The approval hash binds the flag: a plan reviewed under full confinement cannot be replayed with degraded confinement, and vice versa.
- Declared in
config/capabilities.jsonconfirmation surfaces and noted in the product contract.
Also in scope: fixing the read-only asymmetry where capture queue list is refused on native Windows (the capture runtime layer currently has no path-mode reads at all).
Non-goals: git checkpointing (independently Linux/macOS-only), the bash setup scripts and shell lock tooling (#117), FAT/exFAT/network-share vaults (unstable file identity stays fail-closed), auto-reaping on Windows.
Test/CI plan
- POSIX simulation coverage (the
windows_mode()pattern fromtests/test_windows_compat.py) plus a newtests/test_windows_degraded_write.pyadded to the windows-smoke allowlist (repo convention: explicit allowlists over in-test skips). - A meaningful subset of
test_transaction.pyscenarios (plain apply/rollback/journal/idempotency) becomes portable and should gain native runs; fd-pinning race tests and reap-semantics tests stay POSIX-only.
Size and risk
Size: L — comparable to the v2.1.0 effort. Risks: the teardown redesign is security-sensitive; mandatory-lock semantics can surprise (e.g. an open handle in Obsidian blocking rename-over); TOCTOU windows are real and must be documented, not hidden; every new branch needs both simulated and native coverage before ship.
Feedback wanted
- From Windows users blocked on WSL (Hyper-V/VBS conflicts especially): would a default-off, reduced-guarantees write mode meet your need, and is the explicit-flag friction acceptable?
- From anyone relying on the confinement guarantees: objections to the deltas above, or additional invariants the degraded mode must still hold?
Source: AgriciDaniel/claude-obsidian