diff --git a/vault_sync_daemon.py b/vault_sync_daemon.py index 19be996..a0ffbdc 100644 --- a/vault_sync_daemon.py +++ b/vault_sync_daemon.py @@ -8,6 +8,9 @@ structurally mirrored, in BOTH directions, for changes made by ANY tool Rules (confirmed with Ryan 2026-09-15): FORWARD (/vault -> /data): + - any non-`.md` file created or moved INTO /vault (a binary dropped or + saved there by mistake -- /vault is notes-only by design) -> relocate + it to the matching path under /data instead of leaving it in /vault. - rename/move of an ID folder in /vault -> mirror the rename in /data. - delete of an ID folder in /vault -> do NOT delete /data's copy. Instead: move the /data folder into the NEAREST existing `.09` archive @@ -309,6 +312,49 @@ def handle_vault_moved(src_path, dest_path): ) +def handle_vault_binary_created(path): + """ + /vault is notes-only by design (see vault-librarian skill). Any + non-`.md` file that lands there -- Ryan saving a photo/PDF by hand, + an agent writing to the wrong tree, a sync client dropping a stray + file -- gets relocated to the matching path under /data, never left + in /vault. Idempotent: no-op if the file is already gone (raced with + another mover) or the /data destination already exists (name clash -- + log and leave the vault copy in place rather than silently overwriting + or deleting real content). + """ + if not os.path.isfile(path): + return + if path.endswith(".md"): + return + if _in_skip(path, VAULT): + return + if not _debounced("vault_binary_created", path): + return + rel = os.path.relpath(path, VAULT) + if rel.split(os.sep)[0] == vl.SYSTEM_META: + return # "0 System area" has no /data counterpart at all + data_path = os.path.join(DATA, rel) + if os.path.exists(data_path): + log(f"SKIP relocate (already exists in /data, leaving vault copy): {rel}") + ntfy_post( + "Vault sync: binary needs manual move", + f"Non-.md file in /vault couldn't relocate -- /data already " + f"has {rel}. Left in place, resolve by hand.", + tags="warning", + priority="high", + ) + return + os.makedirs(os.path.dirname(data_path), exist_ok=True) + shutil.move(path, data_path) + log(f"RELOCATED non-md file /vault -> /data: {rel}") + ntfy_post( + "Vault sync: moved binary out of /vault", + f"{rel} isn't a note -- moved to /data.", + tags="package", + ) + + def handle_journal_file_created(path): """ A new dated journal entry (YYYY-MM-DD.md) appeared under R.16.11 -- @@ -689,6 +735,7 @@ class VaultHandler(FileSystemEventHandler): def on_moved(self, event): if not event.is_directory: + _event_queue.put(("vault_file_moved", event.src_path, event.dest_path)) return _event_queue.put(("vault_moved", event.src_path, event.dest_path)) @@ -728,7 +775,11 @@ def _worker_loop(): elif kind == "vault_file_created": _, path = item time.sleep(0.5) # let the writer (Obsidian/editor) finish writing content + handle_vault_binary_created(path) handle_journal_file_created(path) + elif kind == "vault_file_moved": + _, src_path, dest_path = item + handle_vault_binary_created(dest_path) elif kind == "vault_moved": _, src_path, dest_path = item handle_vault_moved(src_path, dest_path) @@ -797,7 +848,8 @@ def reconcile(): handle_data_created(dirpath) data_count += 1 vault_count = 0 - for dirpath, dirnames, _ in os.walk(VAULT): + vault_file_count = 0 + for dirpath, dirnames, filenames in os.walk(VAULT): dirnames.sort() if dirpath == VAULT: continue @@ -807,8 +859,14 @@ def reconcile(): with _lock: handle_vault_created(dirpath) vault_count += 1 - log(f"reconcile: swept {data_count} /data dirs, {vault_count} /vault dirs " - f"in {time.time() - t0:.1f}s") + for fname in filenames: + if fname.endswith(".md"): + continue + with _lock: + handle_vault_binary_created(os.path.join(dirpath, fname)) + vault_file_count += 1 + log(f"reconcile: swept {data_count} /data dirs, {vault_count} /vault dirs, " + f"{vault_file_count} /vault non-md files in {time.time() - t0:.1f}s") def _reconcile_loop():