Skip to content

Technical Architecture & Data Reliability Overview

Umi edited this page May 26, 2026 · 7 revisions

Technical Architecture & Data Reliability Overview

Phing is a local-first Markdown note-taking application built on Tauri v2 + React + TypeScript. This document describes the system's internal architecture, with particular focus on the reliability and trust layer that guarantees zero data loss under crash, race, and corruption conditions.


Table of Contents

  1. High-Level Architecture

  2. Data & Reliability Layer

  3. Testing & Robustness


1. High-Level Architecture

1.1 The Tauri v2 Process Model

Phing runs as two cooperating processes: a Rust backend (src-tauri/) and a React/TypeScript frontend (src/). Communication between them is handled entirely through the Tauri IPC bridge — no custom REST server or WebSocket is involved.

Key IPC mechanisms in use:

Mechanism Direction Purpose
@tauri-apps/plugin-fs Frontend → OS All filesystem I/O: read, write, rename, remove, watch
@tauri-apps/plugin-dialog Frontend → OS Native folder picker (open({ directory: true })) and file-save dialogue
trash_file (Rust command) Frontend → Rust Moves a file or folder to the OS Trash via the trash crate
scan_vault_tree (Rust command) Frontend → Rust Returns a typed FileNode tree for the sidebar file tree
rename_path (Rust command) Frontend → Rust Renames files or directories (covers what plugin-fs rename cannot)
confirm_quit (Rust command) Frontend → Rust Sets QuitConfirmed flag and calls app.exit(0)

All filesystem work (atomic writes, snapshot capture, directory scanning) is initiated from TypeScript via the plugin-fs IPC bridge and executes natively through the Rust plugin layer.

The Rust backend registers both plugins and all custom commands at startup:

tauri::Builder::default()
    .plugin(tauri_plugin_opener::init())
    .plugin(tauri_plugin_fs::init())
    .plugin(tauri_plugin_dialog::init())
    .invoke_handler(tauri::generate_handler![
        trash_file,
        confirm_quit,
        scan_vault_tree,
        rename_path,
    ])
    .run(tauri::generate_context!())

tauri-plugin-fs is pinned to 2.5.1 with the watch feature flag enabled in Cargo.toml, which is required for the filesystem event subscription used by the file watcher:

tauri-plugin-fs = { version = "2.5.1", features = ["watch"] }

1.2 Frontend Module Structure

src/
├── App.tsx                    # Root: mounts watcher, orphan recovery, conflict & quit dialogues
├── store/
│   ├── noteStore.ts           # Primary application state (Zustand)
│   ├── vaultStore.ts          # Vault path store (Zustand, persisted to localStorage)
│   └── mindMapStore.ts        # Mind map document state + per-document undo/redo (Zustand)
├── services/
│   ├── vaultService.ts        # Vault scan, atomic write, note read/delete, path helpers
│   └── mindMapService.ts      # Mind map persistence (atomic write + snapshot capture)
├── lib/
│   ├── writeQueue.ts          # Per-ID write serialiser / coalescer
│   ├── recoveryJournal.ts     # Crash-safety: snapshots, orphan recovery, safe read
│   ├── fileWatcher.ts         # VaultFileWatcher + contentHash (FNV-1a)
│   ├── frontmatter.ts         # Note encode/decode (YAML-like frontmatter codec)
│   ├── markdown.ts            # Tiptap extension factory + markdown serialisation helpers
│   ├── mindmap.ts             # MindMapNode tree helpers (add, delete, layout)
│   ├── pockets.ts             # Folder/pocket CRUD + defaults
│   ├── pdfExport.ts           # html2canvas → jsPDF export for notes and mind maps
│   ├── fileTree.ts            # FileNode tree helpers (insert, remove, relPath)
│   └── tauri.ts               # isTauri() environment detection
├── extensions/
│   ├── phingKeyboard.ts       # Custom Tiptap keyboard shortcuts (Cmd+1–5, Cmd+-)
│   ├── wikiLink.tsx           # [[ wiki-link ]] suggestion extension
│   ├── footnote.ts            # Footnote rendering extension
│   └── ...                    # Heading markers, syntax visibility, Zen mode
├── components/
│   ├── Editor.tsx             # Tiptap rich-text editor, autosave, sync indicator, TOC
│   ├── MindMapBoard.tsx       # React Flow board, per-document undo/redo, PDF export
│   ├── Sidebar.tsx            # Pocket list, note list, profile
│   ├── FileTree.tsx           # Vault directory tree with inline rename
│   ├── NoteList.tsx           # Note cards with drag-to-pocket
│   ├── ConfirmDialogue.tsx    # Generic reusable confirmation modal (danger-variant aware)
│   ├── ConflictDialogue.tsx   # External file conflict resolution modal
│   ├── QuitDialogue.tsx       # Save-or-discard quit modal
│   ├── CommandPalette.tsx     # Cmd+K command palette
│   └── Onboarding.tsx         # First-run setup (name, avatar, vault path)
└── __tests__/
    ├── writeQueue.stress.test.ts
    ├── atomicWrite.stress.test.ts
    ├── fileWatcher.stress.test.ts
    └── corruptedFile.stress.test.ts

1.3 State Management: Zustand Stores

Phing uses three independent Zustand stores. There is no Redux, no Context Provider tree, and no global singleton beyond the Zustand instances themselves. Each store is consumed via its own use*Store hook and can be read imperatively (via .getState()) in non-React contexts such as service callbacks.

vaultStore — src/store/vaultStore.ts

The simplest store. Holds a single string: the absolute path to the open vault directory. Persisted to localStorage under phing_vault_path so the app reopens the same vault automatically on next launch without any Tauri IPC round-trip.

noteStore — src/store/noteStore.ts

The primary application store. Manages the full set of notes, pockets (folders), the vault file-tree, user profile, UI state flags, and the current sync state.

interface NoteStore {
  // Data
  notes:          Note[];
  pockets:        Pocket[];
  vaultTree:      FileNode | null;
  profile:        Profile;
  syncState:      SyncState;
  dirtyNoteIds:   Set<string>;

  // UI flags
  selectedNoteId: string | null;
  activePocket:   string;
  isDark:         boolean;
  isZen:          boolean;
  isAcademic:     boolean;
  isLoadingVault: boolean;

  // Write path
  patchNote:        (id: string, patch: Partial<Note>) => void;
  flushNote:        (id: string) => Promise<void>;
  flushActiveNote:  () => Promise<void>;
  reloadNote:       (noteId: string) => Promise<void>;
  moveNote:         (noteId: string, pocketId: string) => Promise<void>;

  // Vault lifecycle
  loadVault:        (path: string) => Promise<void>;
  createNote:       (folder?: string, title?: string) => Promise<Note>;
  deleteNote:       (id: string) => Promise<void>;
  deleteFolder:     (id: string) => void;
  deleteTreeFolder: (absPath: string) => Promise<void>;
  renameTreeFolder: (absOldPath: string, newName: string) => Promise<boolean>;
}

flushNote is the critical path. It orchestrates the write queue, self-write suppression, snapshot capture, and the atomic write, then updates syncState with the result. moveNote is explicitly routed through the WriteQueue to prevent concurrent-write races with any inflight flushNote for the same note — see §2.3.

mindMapStore — src/store/mindMapStore.ts

Manages a list of MindMapDoc documents — each a first-class entity with its own id, title, folder, and a recursive MindMapNode tree. Mutations are debounced through mindMapService, which serialises documents to .phing/mindmaps/ inside the vault using the same atomic write + snapshot pattern as notes.

The store also maintains per-document undo/redo history in two module-level Map<string, UndoEntry[]> structures, keyed by MindMapDoc.id — see §2.6.


2. Data & Reliability Layer

2.1 Note Serialisation: Frontmatter Codec

Every note is stored on disk as a plain Markdown file with a YAML-like frontmatter header. The codec lives in src/lib/frontmatter.ts and consists of two pure functions: encodeNote and decodeNote.

On-disk format:

---
id: 550e8400-e29b-41d4-a716-446655440000
title: "My Note Title"
tags: ["research", "draft"]
folder: "research"
emoji: "🔬"
description: ""
created: 2024-06-01T12:00:00.000Z
updated: 2024-06-01T15:32:00.000Z
---

Note body content in standard Markdown.

Encoding (encodeNote) serialises a Note object to this format. All string values are double-quote-escaped to ensure the format survives adversarial content such as embedded quotes and backslashes.

Decoding (decodeNote) is deliberately defensive:

  • Returns null (never throws) if the input does not start with --- or lacks a closing delimiter.
  • All fields default gracefully: missing id falls back to the provided fallbackId; missing title falls back to "Untitled"; missing tags / folder fall back to [] / "".
  • stripLegacyBody is called on the body to normalise pre-unification notes. Critically, it only strips the legacy # Title + *italic description* pattern when both markers are present — a lone # Heading at the start of a modern note body is never touched.

This defence-in-depth approach is what makes the corrupted-file fallback tests (corruptedFile.stress.test.ts) pass for all 1,000+ randomised mutation inputs.


2.2 Atomic Writes

Sources: src/services/vaultService.tsatomicWriteNote() · src/services/mindMapService.tssaveMindMapDoc()

A direct writeTextFile(abs, content) call is not safe: if the application crashes, loses power, or the OS interrupts the write mid-stream, the file on disk will contain a partial payload. On POSIX systems, a filesystem rename() of a fully-written source onto the target is atomic at the kernel level — the destination is either the old version or the new version, never a mixture of both.

Write sequence (notes):

1.  Compute  abs  = /vault/Research/My Note.md
             tmp  = /vault/Research/My Note.md.<uuid8>.tmp
2.  (If note was renamed/moved) rename oldAbs → abs
3.  If abs exists on disk:
      read existing content
      await captureSnapshot(vault, rel, existing)   ← recovery journal
4.  Encode note to string:
      encoded = encodeNote({ ...note, updatedAt: now() })
5.  writeTextFile(tmp, encoded)       ← crash here → stale .tmp, original safe
6.  rename(tmp, abs)                  ← crash here → .tmp holds new data, original safe
                                          success    → .tmp gone, abs has new data
7.  return rel

The .tmp path includes a short UUID fragment (crypto.randomUUID().slice(0, 8)) to prevent concurrent writes for different notes from colliding on the temp file — even if two notes happen to share the same title.

Crash safety matrix:

Crash point State on disk Recovery path
Before writeTextFile(tmp) Only abs with original content No action needed
After writeTextFile(tmp), before rename abs = original, tmp = new data Orphan recovery renames tmp → abs
After rename completes abs = new data, tmp deleted No action needed
abs never existed (new note), crash mid-rename No abs, tmp = new data Orphan recovery renames tmp → abs

2.3 Write Queue

Source: src/lib/writeQueue.ts

A rapid typist editing a note may produce dozens of patchNote calls per second. Without serialisation, each patch would schedule a flushNote timer, potentially launching concurrent atomicWriteNote calls for the same file and creating races where an older write's rename() could land after a newer one's, silently reverting the file to a stale state.

WriteQueue eliminates this by serialising writes per note ID and coalescing pending writes so that only the most recently enqueued content reaches disk.

class WriteQueue {
  private readonly pending  = new Map<string, PendingEntry>(); // queued but not yet running
  private readonly inflight = new Set<string>();               // currently running

  cancel(id: string): void {
    this.pending.delete(id); // drops a queued-but-not-inflight entry safely
  }
}

Because pending.delete and inflight.add execute synchronously before the first await in flush(), any new enqueue calls that arrive during a write see the inflight flag and park themselves in pending without starting a second flush.

moveNote is also routed through this queue — see the Save Architecture page for the full race-condition analysis and timeline.

Coalescing behaviour under a 100-call synchronous flood:

Generation run function used Subscribers resolved
Gen 1 run from call 0 (dispatched immediately) call 0 only
Gen 2 run from call 99 (last replacement) calls 199
Total writes 2

The invariant is not 'minimum possible writes' but 'the latest content always wins on disk'.


2.4 Deletion Safety

Before any folder or pocket is sent to the OS Trash (or removed from the pocket list), Phing pre-emptively disarms all pending writes for notes inside it:

for (const n of notesInFolder) {
  const timer = flushTimers.get(n.id);
  if (timer) { clearTimeout(timer); flushTimers.delete(n.id); }
  noteWriteQueue.cancel(n.id);
}

This pattern is applied in both deleteTreeFolder (vault file-tree path, calls trash_file) and deleteFolder (pocket/legacy path, calls persistPockets). Without it, a debounce timer firing after the deletion would call mkdir() on the trashed parent directory, silently recreating it on disk. See the Save Architecture page for the detailed before/after timeline.


2.5 Recovery Journal

Source: src/lib/recoveryJournal.ts

The recovery journal provides three independently callable capabilities.

Snapshot Capture

Before every atomicWriteNote or saveMindMapDoc call overwrites an existing file, the current disk content is archived to <vault>/.phing/snapshots/ as a timestamped .bak file. This covers both notes (.md) and mind-map documents (.json).

/vault/.phing/snapshots/
  research__My Note.md.2024-06-01T12-00-00-000Z.bak
  .phing__mindmaps__<uuid>.json.2024-06-01T15-32-00-000Z.bak
  ...
  • Up to 10 snapshots are retained per document; older files are pruned automatically.
  • Snapshot failure is non-fatal: the entire operation is wrapped in try/catch so a snapshot I/O error never blocks the primary write path.

Orphan Recovery

On vault open, App.tsx calls findOrphanedTmpFiles(vaultPath), which recursively walks the vault looking for files ending in .md.tmp or .json.tmp — covering both note and mind-map atomic writes. Each orphan is then passed to recoverOrphanedTmp():

export async function recoverOrphanedTmp(tmpPath: string): Promise<void> {
  const finalPath = tmpPath.replace(/\.tmp$/, '');
  try {
    if (!(await exists(finalPath))) {
      await rename(tmpPath, finalPath); // finalise the interrupted write
    } else {
      await remove(tmpPath);            // rename completed; stale .tmp is safe to remove
    }
  } catch {
    await remove(tmpPath).catch(() => {}); // last resort: remove the orphan
  }
}

Safe Read

safeReadNote(absPath) provides a graceful fallback path for opening notes in a partially-recovered vault:

readTextFile(abs) succeeds?  → return content
         ↓ throws
.tmp sibling exists?  → readTextFile(abs.tmp) succeeds? → return content
                                                ↓ throws
                                               return null

2.6 Mind Map Undo / Redo

Source: src/store/mindMapStore.ts

Why not zundo

The standard Zustand undo middleware (zundo) was considered and explicitly rejected. The undoable data — the MindMapNode tree — is nested inside mindMaps[] at index selectedMindMapId. A partialized selector for { activeTree } does not map to a real store field, making restoration a silent no-op. More critically, middleware-level undo would snapshot the entire store slice, including UI state such as editingId and notePopupId, causing incorrect node selections to be restored on undo.

Architecture

Two module-level Maps provide per-document, isolated history:

interface UndoEntry {
  tree:       MindMapNode;   // full tree snapshot
  selectedId: string | null; // node selection at time of snapshot
}

const undoStacks = new Map<string, UndoEntry[]>(); // docId → past states
const redoStacks = new Map<string, UndoEntry[]>(); // docId → future states

const MAX_UNDO = 50;

Stacks are keyed by MindMapDoc.id so switching between maps never bleeds history. Stacks for a deleted document are cleaned up in deleteMindMap to prevent memory leaks. Any forward mutation calls pushUndo, which also clears the redo stack for that document.

Undo-eligible mutations

pushUndo is called at the top of every structural mutation, immediately before the mutation runs:

Mutation Undo behaviour
addChild Removes the new node; restores parent selection
addSibling Removes the new node; restores prior sibling selection
deleteSelected Restores the deleted node and its entire subtree
commitEdit Restores the previous label text
resetBoard Restores the entire pre-reset tree

updateNote (note-pane content edits) is intentionally excluded — the note pane uses the browser's native text-input undo, which is already correctly scoped.

Selection restoration guard

On undo, if the previously-selected node ID no longer exists in the restored tree (for example, undoing an addChild removes the node that was being edited), selection falls back to the root:

const restoredSelected =
  entry.selectedId && findNode(entry.tree, entry.selectedId)
    ? entry.selectedId
    : entry.tree.id;

Keyboard surface

Shortcut Action
Cmd+Z / Ctrl+Z undo()
Cmd+Shift+Z / Ctrl+Shift+Z redo()

Both shortcuts are handled in the board's onKeyDownCapture handler before the note-popup bail-out, so they remain active whilst the note pane is open.


2.7 Guarded Destructive Operations

Invariant: no operation that permanently destroys document state may be triggered by a single click.

ConfirmDialogue

src/components/ConfirmDialogue.tsx is a generic reusable confirmation modal with open, title, message, confirmLabel, onConfirm, onCancel, and danger? props. When danger is true, the confirm button renders in a red accent and the decorative mark switches from to . The Escape key dismisses via a capture-phase keydown listener.

Current guard: resetBoard

The ↺ reset toolbar button previously called resetBoard() directly — a single click that irreversibly erased the entire canvas. It now calls setConfirmReset(true), rendering a ConfirmDialogue that names the specific map. resetBoard() is only ever called from onConfirm.

Combined with the undo stack (§2.6) and the snapshot archive (§2.5), a user who accidentally confirms a reset has three independent recovery paths: Cmd+Z (instant, in-session), the undo stack (until the session ends), and .phing/snapshots/ (cross-session, durable).


2.8 File Watcher & Conflict Dialogue

Sources: src/lib/fileWatcher.ts · src/components/ConflictDialogue.tsx

VaultFileWatcher

export class VaultFileWatcher {
  private unwatchFn:          UnwatchFn | null = null;
  private readonly suppressMap = new Map<string, number>(); // path → suppress-until ms

  suppressNext(absPath: string, windowMs = 2_000): void {
    this.suppressMap.set(absPath, Date.now() + windowMs);
  }

  async start(vaultPath: string, onEvent: FileChangeHandler): Promise<void> { ... }
  async stop():  Promise<void> { ... }
}

start() calls watch(vaultPath, callback, { recursive: true, delayMs: 400 }). The delayMs: 400 is handled by the native plugin — it coalesces rapid filesystem noise (e.g. a sync client writing then renaming) into a single event.

Inside the callback, per event:

  1. Only paths ending in .md are considered — all other extensions are ignored.
  2. Paths containing /.phing/ are ignored — this prevents snapshot writes triggering false conflicts.
  3. If the path has an active suppress entry (Date.now() < suppressUntil), the event is silently discarded.

Self-write suppression design: suppressNext registers a time-based expiry rather than a one-shot flag. An atomic write produces two OS-level filesystem events on some platforms (one for writeTextFile(tmp) and one for rename(tmp → abs)). A time window suppresses both without needing to track how many events to expect.

contentHash — FNV-1a 32-bit

export function contentHash(str: string): number {
  let h = 0x811c9dc5;
  for (let i = 0; i < str.length; i++) {
    h ^= str.charCodeAt(i);
    h = Math.imul(h, 0x01000193) >>> 0;
  }
  return h;
}

FNV-1a was chosen for its simplicity (zero dependencies, pure TypeScript), speed on typical note sizes, and adequacy for a single-bit-change detector. It is not a cryptographic hash — its sole purpose is cheap equality checking.

ConflictDialogue

When a non-suppressed modify event arrives for a .md file, the app reads the on-disk version, hashes both it and the in-memory version via contentHash, and if the hashes differ raises a ConflictInfo object:

Option Action
Reload from disk reloadNote(noteId) — replaces in-memory state with the on-disk version
Keep local flushNote(noteId) — overwrites the on-disk version with the in-memory version
Dismiss Clears the conflict indicator; no data movement

2.9 Sync State Machine

The editor status indicator is driven by a SyncState object stored in noteStore:

export interface SyncState {
  status:       'synced' | 'saving' | 'dirty' | 'error' | 'conflict';
  lastSyncedAt: string | null;  // ISO 8601 timestamp of last confirmed disk write
  errorMessage: string | null;
}

State transitions:

[ synced ] ──patchNote()──► [ dirty ] ──flushNote()──► [ saving ] ──success──► [ synced ]
                                                       │                 (lastSyncedAt updated)
                                                       error ──────────────► [ error ]
                                                       conflict ──────────────► [ conflict ]

lastSyncedAt is only set when atomicWriteNote has completed successfully — not when the write is merely enqueued. This ensures the indicator accurately reflects what is actually on disk.


3. Testing & Robustness

3.1 Test Infrastructure

Framework: Vitest v4.x
Runner mode: pool: 'forks' — each test file runs in an isolated child process.
Environment: node — no browser globals, no JSDOM overhead.
Test location: src/__tests__/*.stress.test.ts

Module mocking strategy: All four test files mock @tauri-apps/plugin-fs using Vitest's vi.mock(). Mock state is held inside a container object (const state = { vfs: new Map() }) that is mutated per-test via beforeEach rather than reassigned, avoiding Temporal Dead Zone issues with hoisted mock factories.


3.2 Stress Test Suite Overview

File Tests Primary subsystem under test
writeQueue.stress.test.ts 6 WriteQueue coalescing, error propagation, memory
atomicWrite.stress.test.ts 6 atomicWriteNote, recoveryJournal, captureSnapshot
fileWatcher.stress.test.ts 14 VaultFileWatcher, contentHash
corruptedFile.stress.test.ts 12 safeReadNote, decodeNote, encode/decode round-trips

3.3 Test Coverage by Scenario

writeQueue.stress.test.ts — Write Queue Flooding

Scenario Assertion
100 synchronous enqueues coalesce to ≤ 2 writes written.length ≤ 2; last write is content-99
10 enqueues whilst gen-1 inflight coalesce to 1 further write Exactly 2 total writes; gen-2 = last edit
5 notes × 20 edits each run independently Every note ID appears in log; total writes < 100
Error in gen-1 reaches gen-1 subscriber only p1 rejects with boom; pending subs get gen-2 error
1,000 enqueues leave no memory leaks pending.size === 0, inflight.size === 0
100 distinct note IDs run without interference All 100 IDs written exactly once

Notable design insight: A synchronous flood produces exactly 2 writes — not 1. Because flush() runs synchronously up to its first await entry.run(), the inflight flag is set before control returns to the enqueue loop. The invariant is not 'minimum possible writes' but 'latest content always wins on disk.'


atomicWrite.stress.test.ts — Atomic Write & Crash Simulation

Uses an in-memory virtual filesystem as a drop-in replacement for the real OS. vi.mocked(rename).mockRejectedValueOnce(...) injects crashes at the exact point of the rename() call, simulating a mid-write process kill or power loss.

Scenario Assertion
Normal write: tmp created then renamed to final abs exists with correct content; tmp deleted
Crash during rename (new note): tmp survives tmp exists; abs absent; orphan recovery restores abs
Crash during rename (existing note): final safe tmp deleted; old abs content preserved
Snapshot pruning beyond 10-file limit Remaining .bak files ≤ 10 after captureSnapshot
Two concurrent atomicWriteNote calls Final file has valid frontmatter; no corruption
Snapshot .bak files not treated as orphans Only .md.tmp / .json.tmp files returned by findOrphanedTmpFiles

fileWatcher.stress.test.ts — External Watcher Flood

Captures the internal callback that watch() registers and fires synthetic filesystem events directly through it, bypassing the native debounce layer for deterministic testing.

Scenario Assertion
50 events within suppress window Zero delivered
Events resume after suppress window expires First event after expiry is delivered
50 rapid external events All 50 delivered; no exception
Non-.md files silently ignored .png, .json, .gitignore, .md.tmp produce no events
.phing/ snapshot paths filtered Events from /.phing/ produce no callbacks
Event kind resolution (create / modify / remove) Correct kind mapped from raw Tauri event type
Mixed suppressed / unsuppressed paths in one event Only the unsuppressed path is delivered
Rapid stop() / start() cycles 10 cycles complete without throwing
contentHash properties 32-bit unsigned, deterministic, no collisions for 7 test inputs, handles empty string and 100,000-char input

corruptedFile.stress.test.ts — Corrupted File Fallback

Verifies complete fault tolerance for unreadable, empty, garbage, and adversarially-constructed files. No code path in the read pipeline may throw an unhandled exception.

Scenario Assertion
Primary file throws I/O error → .tmp fallback Returns .tmp content
Both primary and .tmp throw result === null
Primary succeeds Returns primary content verbatim; .tmp never consulted
8 garbage strings → decodeNote No throw; returns null for each
Valid frontmatter, empty body note.content === ''
Adversarial titles (SQL injection, XSS, backslashes, emoji, 10,000-char string) encodeNote + decodeNote both complete without throwing
Missing required fields title="Untitled", tags=[], folder=""
No id field note.id === 'my-fallback'
12 extreme encode → decode round-trips decoded.content.trim() === original.content.trim() for all
1,000 randomly-mutated strings None of 5,000 mutation variants throw

Last updated: May 2026 — reflects the Reliability & Trust Layer, Mind Map Undo/Redo, Guarded Destructive Operations, and Deletion Safety implementations.