treeward (n.) A filesystem ward bound to a directory tree, keeping watch for corruption, silent edits, and other lurking horrors.
A command-line file integrity tool that maintains SHA-256 checksums and metadata for directory trees. Treeward uses a
distributed approach where each directory contains a .treeward file tracking its immediate children, allowing
directories to be moved independently while maintaining integrity information.
(Aspirationally the intent is to add repair capability in the future.)
- Efficient incremental updates - By default, only checksums files that are new or have changed metadata (mtime/size)
- Fingerprint validation - Prevents TOCTOU race conditions with cryptographic fingerprints
- Multiple verification modes - Fast metadata checks, selective checksumming, or full integrity audits
- Distributed ward model - Each directory tracks only its immediate children (non-recursive per-directory), allowing moving directories around as self-contained warded units.
- Dry run support - Preview what would be changed without writing any files
- Automation-friendly - Clean exit codes and simple output for monitoring and CI/CD
brew install scode/dist-tap/treewardIf you want a local build instead, use:
cargo install --path .Initialize a directory tree:
# From within the directory
cd /path/to/project
treeward init
# Or without changing directory
treeward -C /path/to/project initCheck what has changed:
treeward statusUpdate ward files to record new state:
treeward updateVerify integrity (automation/monitoring):
treeward verifyPerforms first-time initialization of .treeward files in a directory tree. Checksums all files and creates ward
metadata.
# Initialize current directory
treeward init
# Initialize specific directory (without cd)
treeward -C /path/to/project init
# Preview without writing files
treeward init --dry-run
# Safe initialization with fingerprint validation
FP=$(treeward status | grep '^Fingerprint:' | cut -d' ' -f2)
treeward init --fingerprint $FPNote: Fails if already initialized. Use treeward update for subsequent changes, or treeward update --allow-init
for idempotent behavior.
Updates existing .treeward files to reflect current state. Only checksums new or modified files (efficient for
incremental changes).
# Update current directory
treeward update
# Update specific directory (without cd)
treeward -C /path/to/project update
# Preview what would be updated
treeward update --dry-run
# Update with fingerprint validation (safe workflow)
FP=$(treeward status | grep '^Fingerprint:' | cut -d' ' -f2)
treeward update --fingerprint $FP
# Update or initialize (idempotent, for scripts)
treeward update --allow-initUpdate modes:
treeward update- Fails if not initialized (safe, explicit)treeward update --allow-init- Works whether initialized or not (idempotent)
Compares current filesystem state against existing .treeward files to detect changes.
# Fast metadata-only check (default)
treeward status
# Verify checksums for files with changed metadata
treeward status --verify
# Always verify checksums for all files (detect silent corruption)
treeward status --always-verify
# Show detailed per-entry diff (implies --verify)
treeward status --diff--diff shows field-level changes for modified and removed entries. It implies --verify (files with changed metadata
are checksummed). If used with --always-verify, all files are checksummed and diff output includes any detected
checksum changes.
M data.json
size: 1.2 KB -> 1.5 KB
sha256: abc123def456... -> 789xyz012345...
R oldfile.txt
was: file (256 bytes, sha256: abc123def456...)
Change types:
Added- New files, directories, or symlinks not in the wardRemoved- Entries in the ward that no longer existPossiblyModified- Files whose metadata (mtime/size) differs from wardModified- Content differs (checksum mismatch when verified), symlink target changed, or entry type changed
Fingerprints:
Every status check produces a unique fingerprint representing the exact changeset. Use it with init --fingerprint or
update --fingerprint to ensure you're applying exactly the changes you reviewed:
treeward status > review.txt
cat review.txt # Review changes
FP=$(grep '^Fingerprint:' review.txt | cut -d' ' -f2)
treeward update --fingerprint $FPVerifies integrity of all files by checksumming everything and comparing against the ward. Designed for automation and monitoring.
# Verify current directory
treeward verify
# Verify specific directory (without cd)
treeward -C /path/to/data verify
# Use in scripts (exit code 0 = success)
treeward -C /critical/data verify || alert_admin
# Cron job
0 2 * * * /usr/local/bin/treeward -C /data verify || mail admin
# CI/CD pipeline
treeward -C ./dist verify && deploy.shExit codes:
0- All files match their wards (success)non-zero- Changes detected or errors encountered (failure)
# 1. Initialize a directory tree
cd /path/to/project
treeward init
# 2. Make changes to your files
# ... edit, add, remove files ...
# 3. Check what changed
treeward status
# 4. Update ward files to record new state
treeward update
# 5. Periodically verify integrity
treeward verifycd /my/project
treeward init
# ... work on project ...
treeward status --verifytreeward status > review.txt
cat review.txt # Review changes carefully
FP=$(grep '^Fingerprint:' review.txt | cut -d' ' -f2)
treeward update --fingerprint $FPtreeward -C /critical/data status --always-verify#!/bin/bash
if ! treeward -C /data verify; then
echo "Integrity check failed!" | mail admin@example.com
exit 1
fi- name: Verify build artifacts
run: |
# ... build process ...
treeward -C ./dist update --allow-init
treeward -C ./dist verify# Works whether initialized or not
treeward update --allow-initEach directory contains a .treeward TOML file with metadata only for its immediate children (files,
subdirectories, symlinks). This allows:
- Directories to be moved independently
- Incremental verification of subdirectories
- Clear organization of integrity metadata
Example .treeward file:
[metadata]
version = 1
[entries."README.md"]
type = "file"
sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
mtime_nanos = 1234567890123456789
size = 1024
[entries."src"]
type = "dir"
[entries."link"]
type = "symlink"
symlink_target = "target/path"When updating, treeward:
- Compares filesystem metadata (mtime/size) against ward
- By default, only checksums files that are new or have changed metadata
- Reuses checksums for unchanged files
- Only rewrites
.treewardfiles if contents changed
This makes subsequent updates very fast - only changed files are checksummed by default. With --always-verify,
initialization and updates checksum every file.
Before and after checksumming a file, treeward compares mtime to detect changes during the read operation. If detected, it returns an error (no retry logic). Note that detection can never be guaranteed and should be considered a courtesy best effort.
Fingerprints prevent time-of-check-time-of-use race conditions:
- Run
treeward statusto review changes and get a fingerprint - Review the changes
- Run
treeward update --fingerprint <FINGERPRINT>to apply those exact changes
If files change between status and update, the fingerprint won't match and the update fails without writing any ward files.
Fingerprints are SHA-256 hashes computed from the list of changes (path + status type). They serve two purposes:
- TOCTOU protection - Ensure the changes you reviewed are exactly what gets applied
- Idempotency check - Detect when the filesystem has drifted from what you expected
Matching checksum policies
The status and update/init commands support the same verification flags:
- Default (no flag): Only compare metadata (mtime/size). Files with changed metadata show as
M?(PossiblyModified). --verify: Checksum files whose metadata differs. Confirms whether content actually changed (M) or just metadata (M?upgrades to clean if unchanged).--always-verify: Checksum all files regardless of metadata. Detects silent corruption.
When using --fingerprint, you must use the same verification flags with both commands:
# Correct: both use --verify
treeward status --verify
treeward update --verify --fingerprint $FP
# Correct: both use default (no flag)
treeward status
treeward update --fingerprint $FP
# Wrong: mismatched flags cause fingerprint mismatch
treeward status --verify
treeward update --fingerprint $FP # Will fail!Why matching flags matter
The verification flags control what status type is reported for fingerprint purposes. This is separate from what treeward computes internally:
status(default) reportsM?for files with changed metadata, without checksummingupdate(default) internally checksums files to build ward entries, but still reportsM?for fingerprint consistency
This design ensures fingerprints match between status and update when using the same flags, even though update
does more work internally. The fingerprint reflects what you reviewed, not the internal computation.
If you use mismatched flags, fingerprints will differ because the reported status types differ:
treeward status --verify # Reports M (checksummed, confirmed change)
treeward update --fingerprint $FP # Reports M? (default mode), fingerprint mismatch!If you don't need fingerprint validation, simply omit --fingerprint:
treeward status
treeward update # Always succeeds, no fingerprint check- Non-recursive directory model - Each directory has its own
.treewardfile containing metadata only for its immediate children - Deterministic serialization -
BTreeMapensures stable TOML output - Error handling philosophy - Corrupted ward files and permission errors are fatal; never silently skip problems
- High-precision timestamps - Nanosecond-precision timestamps for accurate modification detection
- Security-conscious - SHA-256 checksums, concurrent modification detection, TOCTOU protection
Standard Rust idioms apply:
cargo testcargo fmtcargo clippy -- -D warnings
See LICENSE file for details.
