fix: второй стабилизационный pass vault layout — sync payload, bindings, vaultPath, tests
sync_apply.go:
- applyRemoteNodeCreate: полный payload (template_id/fs_path/sort_order/archived),
INSERT сохраняет все поля, для folder-like создаётся физическая папка.
- applyRemoteNodeUpdate: принимает fs_path/template_id/archived,
физическое переименование папки при изменении title/fs_path.
- applyRemoteNodeMove: принимает fs_path, обновляет parent_id+fs_path,
физически перемещает папку (folder-like) или file record (note/file).
bindings_nodes.go:
- MoveNode: node.FsPath = newFsPath после UpdateFsPath;
sync.RecordOp отправляет новый fs_path; note/file move to root — файл в vault root.
- RenameNode: EntityFile для file, EntityNote для note;
коллизия → генерация уникального имени; файл переименовывается только после os.Rename.
- DeleteNode: единый вызов a.files.DeleteNodeAndChildren(), дублирование удалено.
- Исправлен deadlock с SetMaxOpenConns(1) — Query/Exec больше не конфликтуют.
files.Service.vaultPath: filepath.Rel-based проверка,
sibling-prefix escape (/tmp/vault vs /tmp/vault_evil) отклоняется.
VaultCheck: SQL JOIN с n.deleted_at IS NULL, чтобы удалённые узлы
не показывались как missing files.
Добавлены тесты: RenameFileNodeUsesEntityFile, MoveNoteToRoot,
DeleteFolderLeavesVaultCheckHealthy, SyncNodeCreatePreservesFields,
VaultPathSiblingPrefixEscape.