# Invar
**The Unchanging Key for Changing Data**
A high-precision CLI tool for generating deterministic, content-addressable identifiers from any data source. Built for CI/CD pipelines, build systems, and content-addressed storage.
[](https://github.com/swyrknt/invar-cli/actions/workflows/ci.yml)
[](LICENSE)
## Overview
Invar generates immutable structural state roots (Invar Keys) from arbitrary input data. Unlike traditional hashing, Invar processes data byte-by-byte through a mathematical synthesis engine, producing keys that represent the exact structural identity of your data.
### Key Properties
- **Deterministic**: Identical input always produces identical keys
- **Sensitive**: A single byte change produces a completely different key
- **Structural**: Keys represent the exact structural identity of data
- **Order-independent**: Directory hashing produces consistent keys regardless of file system ordering
## Installation
### Pre-built Binaries
Download from [GitHub Releases](https://github.com/swyrknt/invar-cli/releases):
```bash
# Linux (x86_64)
curl -LO https://github.com/swyrknt/invar-cli/releases/latest/download/invar-linux-x86_64.tar.gz
tar -xzf invar-linux-x86_64.tar.gz
sudo mv invar /usr/local/bin/
# macOS (Apple Silicon)
curl -LO https://github.com/swyrknt/invar-cli/releases/latest/download/invar-darwin-aarch64.tar.gz
tar -xzf invar-darwin-aarch64.tar.gz
sudo mv invar /usr/local/bin/
```
### Using Cargo
```bash
cargo install invar
```
### From Source
```bash
git clone https://github.com/swyrknt/invar-cli.git
cd invar-cli
cargo install --path .
```
### Docker
```bash
docker pull ghcr.io/swyrknt/invar-cli:latest
# Usage
```
## Quick Start
```bash
# Generate key from a file
invar gen --file package-lock.json
# Generate key from stdin
# Generate key from inline data
invar gen --data "Hello, World!"
# Generate key from a directory
invar gen --directory ./src
# Verify data matches an expected key
invar check --file package-lock.json --expected <key>
# Watch for changes
invar watch ./src --recursive
```
## Features
### File and Data Hashing
```bash
# From file
invar gen --file config.json
# From stdin
# From inline string
invar gen --data "my-data-v1.0"
# JSON output with metadata
invar gen --file data.txt --output json
```
### Directory Hashing
Hash entire directories with deterministic, order-independent results:
```bash
# Hash a directory recursively
invar gen --directory ./src
# Include hidden files
invar gen --directory ./src --include-hidden
# JSON output with file count and size
invar gen --directory ./src --output json
# Output: {"key": "abc123...", "file_count": 42, "total_bytes": 123456}
```
### Ignore Patterns
Invar respects `.gitignore` and `.invarignore` files by default:
```bash
# Default behavior (respects .gitignore and .invarignore)
invar gen --directory ./src
# Disable .gitignore
invar gen --directory ./src --no-gitignore
# Disable .invarignore
invar gen --directory ./src --no-invarignore
```
Create a `.invarignore` file for invar-specific exclusions:
```
# .invarignore - patterns specific to invar
*.log
build/
dist/
*.mp4
```
### Streaming Mode
Process large files with constant memory usage:
```bash
# Stream large files
invar gen --file large-video.mp4 --stream
# With progress reporting (every N MB)
invar gen --file large-video.mp4 --stream --progress 100
```
### Watch Mode
Monitor files or directories for changes:
```bash
# Watch a single file
invar watch config.yaml
# Watch a directory recursively
invar watch ./src --recursive
# Custom debounce interval (milliseconds)
invar watch ./src --recursive --debounce 1000
# JSON output for scripting
invar watch ./src --recursive --output json
```
### Verification
```bash
# Generate a key
KEY=$(invar gen --file package-lock.json)
# Later, verify the file hasn't changed
invar check --file package-lock.json --expected "$KEY"
# Verify a directory
invar check --directory ./src --expected "$KEY"
```
## Use Cases
### CI/CD Cache Keys
```yaml
# GitHub Actions
- name: Generate cache key
run: echo "CACHE_KEY=$(invar gen --directory src)" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
key: build-${{ env.CACHE_KEY }}
```
### Build System Integration
```bash
# Skip rebuild if source unchanged
CURRENT=$(invar gen --directory src)
CACHED=$(cat .build-cache 2>/dev/null || echo "")
if [ "$CURRENT" = "$CACHED" ]; then
echo "Source unchanged, skipping build"
else
make build
echo "$CURRENT" > .build-cache
fi
```
### Content-Addressable Storage
```bash
# Store files by content hash
KEY=$(invar gen --file document.pdf)
cp document.pdf "storage/$KEY.pdf"
```
### Configuration Versioning
```bash
# Detect configuration changes
if ! invar check --file config.yaml --expected "$STORED_KEY"; then
echo "Configuration changed, reloading..."
reload_configuration
fi
```
## Output Formats
### Text (Default)
```bash
$ invar gen --data "test"
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3a1e3...
```
### JSON
```bash
$ invar gen --data "test" --output json
{
"key": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3...",
"length_bytes": 32
}
$ invar gen --directory ./src --output json
{
"key": "abc123...",
"file_count": 42,
"total_bytes": 123456,
"skipped": []
}
```
## Command Reference
### `invar gen` (alias: `g`)
Generate an Invar key from input data.
| `--file <PATH>` | `-f` | Read from file |
| `--data <STRING>` | `-d` | Read from string |
| `--directory <PATH>` | | Hash directory recursively |
| `--stream` | | Use streaming mode (low memory) |
| `--progress <MB>` | | Report progress every N MB |
| `--output <FORMAT>` | `-o` | Output format: `text` or `json` |
| `--include-hidden` | | Include hidden files (directories) |
| `--follow-links` | | Follow symbolic links |
| `--no-gitignore` | | Ignore .gitignore patterns |
| `--no-invarignore` | | Ignore .invarignore patterns |
### `invar check` (alias: `c`)
Verify data produces an expected key.
| `--expected <KEY>` | `-e` | Expected 64-character hex key |
| *(plus all gen options)* | | |
**Exit codes:** `0` = match, `1` = mismatch or error
### `invar watch` (alias: `w`)
Watch for file changes and report new keys.
| `<PATH>` | | File or directory to watch |
| `--recursive` | `-r` | Watch subdirectories |
| `--debounce <MS>` | | Debounce interval (default: 500) |
| `--output <FORMAT>` | `-o` | Output format: `text` or `json` |
## How It Works
Invar uses the [Distinction Calculus](https://crates.io/crates/koru-lambda-core) to generate structural identifiers:
1. Initialize the root Distinction (D0)
2. Process each byte of input data
3. Convert each byte to a canonical Distinction via binary path encoding
4. Synthesize with the running root: `D_root = synthesize(D_root, D_byte)`
5. Return the final Distinction ID as a 64-character hex string
This ensures:
- **Byte-level precision**: Every byte affects the final key
- **Structural uniqueness**: Different data produces different keys
- **Mathematical determinism**: Same data always produces same key
## Examples
See the [examples](examples/) directory for:
- GitHub Actions cache workflows
- GitLab CI pipelines
- Docker build optimization
- Monorepo selective testing
## Development
```bash
# Build
cargo build --release
# Test (87 tests)
cargo test
# Lint
cargo clippy -- -D warnings
# Format
cargo fmt
```
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) at your option.
## Acknowledgments
- Built on [koru-lambda-core](https://crates.io/crates/koru-lambda-core)
- Named after [Invar](https://en.wikipedia.org/wiki/Invar), an alloy with minimal thermal expansion