libvctrl_handler 5.2.0

Fundamental contracts for building a version control system – no implementations, only traits and types
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
# libvctrl_handler

Fundamental contracts for building a version control system — no implementations, only
traits, types, and validation. `libvctrl_handler` is the "constitution" layer of the
`libvctrl` ecosystem: it defines _what_ a VCS object model looks like without prescribing
_how_ it is stored, serialised, signed, or transported. Every other crate in the workspace
implements or consumes these contracts.

- **Crate:** `libvctrl_handler` 5.2.0 (library, `std`-only, zero external runtime dependencies)
- **Language:** Rust, edition 2024 — MSRV **1.96.0**
- **License:** MIT
- **Repository:** https://github.com/mroczect/libvctrl
- **Documentation:** https://docs.rs/libvctrl_handler

> Most users should depend on the **`libvctrl` facade**, which re-exports this crate
> alongside the reference implementation and the crypto primitives. Reach for
> `libvctrl_handler` directly only when you are implementing a custom backend or need the
> contract surface without the reference implementation.

---

## Overview

`libvctrl_handler` is a pure contracts crate. It contains no concrete implementations —
no storage backends, no serializers, no hashing engines. Instead it provides:

- **Traits** defining every major VCS operation: object storage, encoding, decoding,
  hashing, indexing, reference management, reflogs, remote transport, packing, signing,
  verification, revision walking, diffing, blame, and configuration.
- **Immutable data types** representing Git objects (`Blob`, `Tree`, `Commit`, `Tag`) and
  supporting structures (`Hash`, `UserID`, `TreeEntry`, `CommitMeta`, deltas, merges,
  reflog entries).
- **Constants** enforcing safe size and count limits to prevent resource exhaustion.
- **Validation functions** that define the security boundary for names, references, tree
  entries, and hashes.
- A unified **error type** (`VctrlError`) that all trait implementations must return.

The crate depends only on the Rust standard library. It is `std`-only and exposes no
crate-level feature flags. There is no `no_std` build path at present.

---

## Architecture

`libvctrl_handler` is the foundation of a strictly layered, one-way dependency graph. It
sits at the bottom; the reference implementation (`libvctrl_core`) implements its traits;
the facade (`libvctrl`) re-exports both; and the command crates (`libvctrl_plumbing`,
`libvctrl_porcelain`) build on top.

```mermaid
flowchart TD
    subgraph Apps["Application layer"]
        FACADE["libvctrl<br/>facade (re-exports)"]
        PL["libvctrl_plumbing"]
        PO["libvctrl_porcelain"]
    end

    subgraph Ref["Reference implementation"]
        CORE["libvctrl_core<br/>codec, builders, stores, hasher"]
    end

    subgraph Contracts["Contract layer — libvctrl_handler (this crate)"]
        HANDLER["traits + types + constants<br/>enums + errors + macros + validation<br/>std-only, zero dependencies"]
    end

    subgraph Crypto["Cryptography"]
        SHA["libvctrl_sha512<br/>raw SHA-512 engine"]
    end

    FACADE --> HANDLER
    FACADE --> CORE
    FACADE --> SHA
    PL --> CORE
    PO --> CORE
    CORE --> HANDLER
    CORE --> SHA
```

### Internal module organization

The crate is separated into seven modules, each with a single responsibility. The crate
root (`lib.rs`) re-exports the most commonly used items for ergonomic access.

```mermaid
flowchart LR
    LIB["lib.rs<br/>crate root + root re-exports"]
    LIB --> CONST["constants<br/>limits + entry_mode"]
    LIB --> ENUMS["enums<br/>EntryKind"]
    LIB --> ERRORS["errors<br/>VctrlError"]
    LIB --> MACROS["macros<br/>vctrl_error_other!"]
    LIB --> TRAITS["traits/core<br/>17 contracts"]
    LIB --> TYPES["types/core<br/>14 immutable data types"]
    LIB --> VAL["validation<br/>4 input checks"]

    VAL -.gates construction.-> TYPES
    TRAITS -.consume / return.-> TYPES
    TYPES -.return.-> ERRORS
    TRAITS -.return.-> ERRORS
    ENUMS -.used by.-> TYPES
    CONST -.used by.-> TYPES
    CONST -.used by.-> VAL
```

### Invalid states are unrepresentable

A core design idiom is that all public data types are constructed via **fallible
constructors** that enforce invariants at construction time. Once built, objects are
immutable and expose only `&self` accessors. This pushes validation to the boundary of the
system and guarantees that invalid objects cannot exist at runtime.

```mermaid
flowchart LR
    INPUT[Raw input<br/>name / hash bytes / ref name / tree entry]
    INPUT --> VALFN[validation function<br/>validate_name / validate_hash_bytes / ...]
    VALFN -->|invalid| ERR1[VctrlError<br/>InvalidName / InvalidHashLength / ...]
    VALFN -->|valid| CTOR[Fallible constructor<br/>Blob::new / Tree::new / UserID::new / ...]
    CTOR -->|invariant violated| ERR2[VctrlError]
    CTOR -->|ok| OBJ[Immutable validated object<br/>only &self accessors]
    OBJ -.Send + Sync.-> THREADS[Cross-thread consumers]
```

---

## Core Features

- **Pure contracts.** No implementations — only traits, types, constants, and
  validation. Backend authors implement the traits; tool authors depend on the types.
- **Invalid states unrepresentable.** All data types use fallible constructors that reject
  malformed input at construction time; objects are immutable thereafter.
- **Resource-exhaustion prevention.** Hard `MAX_*` limits act as fail-fast circuit
  breakers during construction, bounding memory allocation against malicious input.
- **Strong typing over raw integers.** Git mode bits are represented by the `EntryKind`
  enum with `const fn` conversions to and from raw `u32` modes, preventing invalid
  combinations at compile time.
- **Forward-compatible enums and errors.** `EntryKind` and `VctrlError` are
  `#[non_exhaustive]`, allowing new variants without breaking API compatibility.
- **Thread-safe contracts.** Traits carry `Send + Sync` bounds so backends can be shared
  across threads; `&mut self` is required only for write operations.
- **Cloneable I/O errors.** `std::io::Error` is wrapped in `Arc` inside `VctrlError` so
  the error remains `Clone` despite `io::Error` not being `Clone`.
- **Compile-time computation.** `const fn` is used where possible (`EntryKind::mode`,
  `from_mode`) to shift work to compile time.
- **Strict safety.** `#![forbid(unsafe_code)]` and a comprehensive set of denied Clippy
  and documentation lints are inherited from the workspace.

---

## Technology Stack

- **Language:** Rust (edition 2024, MSRV 1.96.0)
- **Dependencies:** none (standard library only)
- **Dev-dependencies:** `criterion` 0.8 (with `cargo_bench_support` feature)
- **Lint policy:** workspace-inherited, `#![forbid(unsafe_code)]`, denied `missing_docs`,
  `rust_2018_idioms`, and a broad set of Clippy lints (including `pedantic` and `nursery`
  groups). See the repository for the authoritative lint configuration.
- **Feature flags:** none.

> Note on the MSRV field: this crate's `Cargo.toml` does not yet declare
> `rust-version = "1.96"`. The workspace standard is Rust 1.96.0, and this README
> documents that as the MSRV. Adding `rust-version = "1.96"` to
> `libvctrl_handler/Cargo.toml` is recommended in a future maintenance pass.

---

## Project Structure

```text
libvctrl_handler/
├── Cargo.toml
├── benches/
│   └── handler_bench.rs
└── src/
    ├── lib.rs
    ├── constants.rs
    ├── errors.rs
    ├── macros.rs
    ├── enums/
    │   ├── mod.rs
    │   └── core/
    │       ├── mod.rs
    │       └── entry_kind.rs
    ├── traits/
    │   ├── mod.rs
    │   └── core/
    │       ├── mod.rs
    │       ├── blame.rs
    │       ├── config.rs
    │       ├── decoder.rs
    │       ├── diff.rs
    │       ├── encoder.rs
    │       ├── hasher.rs
    │       ├── index.rs
    │       ├── object_store.rs
    │       ├── pack.rs
    │       ├── ref_store.rs
    │       ├── reflog.rs
    │       ├── remote.rs
    │       ├── revwalk.rs
    │       ├── signer.rs
    │       ├── transport.rs
    │       └── verifier.rs
    ├── types/
    │   ├── mod.rs
    │   └── core/
    │       ├── mod.rs
    │       ├── blob.rs
    │       ├── commit.rs
    │       ├── delta.rs
    │       ├── hash.rs
    │       ├── merge.rs
    │       ├── reflog.rs
    │       ├── tag.rs
    │       ├── tree.rs
    │       └── user_id.rs
    └── validation/
        ├── mod.rs
        ├── hash.rs
        └── name.rs
```

---

## Getting Started

### Prerequisites

- Rust toolchain **1.96.0** or newer (edition 2024 is required)
- Cargo

No system libraries or external services are required.

### Installation

For most users, depend on the facade instead:

```toml
[dependencies]
libvctrl = "2.2"
```

To depend on `libvctrl_handler` directly (contracts only, no reference implementation):

```toml
[dependencies]
libvctrl_handler = "5.2"
```

Or via Cargo:

```bash
cargo add libvctrl_handler
```

Because the crate has no external dependencies, adding it is essentially zero-weight
beyond the standard library.

### Configuration

`libvctrl_handler` exposes **no crate-level feature flags** and requires **no runtime
configuration**. The contract surface is fixed; behavioural configuration (such as the
`sha384` / `opt_size` crypto features) lives at the facade or `libvctrl_sha512` level.

---

## Usage

### Create a Hash and inspect an EntryKind

```rust
use libvctrl_handler::{EntryKind, Hash};

// Hash requires exactly 64 bytes (SHA-512).
let raw_bytes = [0_u8; 64];
let hash = Hash::from_bytes(&raw_bytes);
assert!(hash.is_ok());

// Git object modes are accessible via the strongly-typed EntryKind enum.
let blob_mode = EntryKind::Blob.mode();
assert_eq!(blob_mode, 0o100_644);
```

### Convert between EntryKind and raw mode bits

```rust
use libvctrl_handler::enums::EntryKind;

// Enum -> raw mode (const fn, usable in const contexts).
assert_eq!(EntryKind::Executable.mode(), 0o100_755);

// Raw mode -> Enum (graceful on invalid input).
assert_eq!(EntryKind::from_mode(0o120_000), Some(EntryKind::Symlink));
assert_eq!(EntryKind::from_mode(0o000_000), None);
```

### Construct ad-hoc errors with the macro

```rust
use libvctrl_handler::{VctrlError, vctrl_error_other};

let err = vctrl_error_other!("missing configuration file: {} (code {})", "config.toml", 404);
assert_eq!(
    err.to_string(),
    "missing configuration file: config.toml (code 404)"
);
```

### Wrap an I/O error

```rust
use libvctrl_handler::VctrlError;
use std::io::{self, ErrorKind};

let io_err = io::Error::new(ErrorKind::NotFound, "file missing");
let vctrl_err = VctrlError::from_io(io_err);

// The error is cloneable despite wrapping std::io::Error.
let cloned = vctrl_err.clone();
assert_eq!(vctrl_err, cloned);
```

### Implement a trait for a custom backend

The traits are designed to be implemented by backend authors. The `Blame` trait
illustrates the idiom: `Send + Sync` bounds for thread safety, `&self` for read
operations, and `VctrlError` as the unified failure type.

```rust
use libvctrl_handler::traits::core::blame::{Blame, BlameEntry};
use libvctrl_handler::{Hash, VctrlError};

struct MockRepo;

impl Blame for MockRepo {
    fn blame_file(&self, path: &str) -> Result<Vec<BlameEntry>, VctrlError> {
        let hash = Hash::from_bytes(&[0_u8; 64])?;
        let entry = BlameEntry::new(hash, 1, 10, path.to_string(), None)?;
        Ok(vec![entry])
    }
}

let repo = MockRepo;
let entries = repo.blame_file("src/main.rs").unwrap();
assert_eq!(entries.len(), 1);
```

---

## API Reference / Core Modules

Full API documentation is published at <https://docs.rs/libvctrl_handler>. The summary
below describes each module and its public surface.

### `constants` — Limits and protocol values

Centralises all magic numbers so that limits are uniformly enforced at the type
construction level. Two categories: resource-exhaustion circuit breakers and Git protocol
constants.

| Constant             | Value          | Purpose                                                  |
| -------------------- | -------------- | -------------------------------------------------------- |
| `HASH_LENGTH`        | `64`           | SHA-512 hash length in bytes; enables fixed-size arrays. |
| `MAX_NAME_LENGTH`    | `255`          | Upper bound on file/directory/reference names (bytes).   |
| `MAX_BLOB_SIZE`      | `100 * 1024^2` | Maximum blob size (100 MiB); DoS circuit breaker.        |
| `MAX_TREE_ENTRIES`   | `100_000`      | Maximum entries per tree; bounds parse/diff cost.        |
| `MAX_MESSAGE_LENGTH` | `1024 * 1024`  | Maximum commit/tag message length (1 MiB).               |
| `MAX_PARENT_COUNT`   | `0xFFFF`       | Maximum parent commits (u16 range).                      |

The `entry_mode` submodule exposes the Git protocol mode bits:

| Constant     | Value       | Meaning          |
| ------------ | ----------- | ---------------- |
| `BLOB`       | `0o100_644` | Regular file     |
| `EXECUTABLE` | `0o100_755` | Executable file  |
| `SYMLINK`    | `0o120_000` | Symbolic link    |
| `TREE`       | `0o40_000`  | Directory (tree) |
| `SUBMODULE`  | `0o160_000` | Submodule commit |

### `enums` — Strongly-typed protocol kinds

- **`EntryKind`** — a `#[non_exhaustive]` enum (`Blob`, `Executable`, `Symlink`, `Tree`,
  `Submodule`) classifying tree entries. Provides `const fn mode() -> u32` and
  `const fn from_mode(u32) -> Option<Self>` for lossless conversion to and from raw Git
  mode bits. Consumers must include a `_` catch-all when matching.

### `errors` — Unified error type

- **`VctrlError`** — a `#[non_exhaustive]`, `Clone + Debug + PartialEq + Eq` enum that all
  fallible operations across the ecosystem return. Implements `Display` and `std::error::Error`.
  I/O errors are stored as `Arc<std::io::Error>` to make the error `Clone`. A manual
  `PartialEq` compares `io::Error::kind()` and string representation.

| Variant                        | Meaning                                                      |
| ------------------------------ | ------------------------------------------------------------ |
| `CorruptedData(String)`        | Data was corrupted or malformed.                             |
| `DuplicateParent`              | A commit contains duplicate parent hashes.                   |
| `ExceededMaxSize(String)`      | A size or count limit was exceeded.                          |
| `InvalidBlameRange`            | An invalid blame range was specified (zero start or count).  |
| `InvalidEmail(String)`         | An email address was invalid.                                |
| `InvalidHashLength(usize)`     | Hash length did not match `HASH_LENGTH`.                     |
| `InvalidName(String)`          | A name was empty, too long, or contained control characters. |
| `InvalidTimezoneOffset(i16)`   | Timezone offset outside `-1440..=1440`.                      |
| `InvalidTreeStructure(String)` | Tree entries unsorted or duplicated.                         |
| `IoError(Arc<io::Error>)`      | An I/O error occurred.                                       |
| `ObjectNotFound(Hash)`         | An object with the given hash was not found.                 |
| `Other(String)`                | Any error not covered by the above.                          |
| `RefNotFound(String)`          | A reference with the given name was not found.               |
| `SerializationError(String)`   | A serialization/deserialization error occurred.              |

`VctrlError::from_io(io::Error)` is the canonical constructor for I/O failures.

### `macros` — Error-construction helpers

- **`vctrl_error_other!`** — an exported declarative macro that wraps `format!` into
  `VctrlError::Other`. Uses `$crate` for absolute path resolution so the macro remains
  correct even when imported via glob from another crate.

### `traits` — The 17 backend contracts

All traits live under `traits::core` and are re-exported at the crate root. They are
grouped by domain:

**Serialization and content addressing**

| Trait     | Purpose                                                       |
| --------- | ------------------------------------------------------------- |
| `Encoder` | Serialise objects (`Blob`, `Tree`, `Commit`, `Tag`) to bytes. |
| `Decoder` | Parse bytes back into validated objects; the trust boundary.  |
| `Hasher`  | Compute a content-address `Hash` from a byte stream.          |

**Object and reference storage**

| Trait         | Purpose                                                      |
| ------------- | ------------------------------------------------------------ |
| `ObjectStore` | Store and retrieve encoded object bytes keyed by `Hash`.     |
| `RefStore`    | Manage named references (branches, tags) pointing at hashes. |
| `ReflogStore` | Append and read reflog entries for a reference.              |
| `Index`       | Manage the staged index of path-to-hash mappings.            |

**History traversal and analysis**

| Trait        | Purpose                                                             |
| ------------ | ------------------------------------------------------------------- |
| `RevWalk`    | Traverse the commit graph from a set of heads.                      |
| `Blame`      | Attribute file line ranges to commits (`BlameEntry`).               |
| `TreeDiffer` | Compute tree-level deltas (`TreeDelta`, `FileDelta`, `ChangeKind`). |

**Remote and packfile**

| Trait        | Purpose                                               |
| ------------ | ----------------------------------------------------- |
| `Transport`  | Abstract network transport for remote operations.     |
| `Remote`     | Manage remote configuration and endpoint interaction. |
| `PackReader` | Decode Git packfile streams into objects.             |
| `PackWriter` | Encode objects into Git packfile streams.             |

**Cryptographic integrity**

| Trait      | Purpose                                        |
| ---------- | ---------------------------------------------- |
| `Signer`   | Produce cryptographic signatures over objects. |
| `Verifier` | Verify cryptographic signatures over objects.  |

**Configuration**

| Trait         | Purpose                                                                                                           |
| ------------- | ----------------------------------------------------------------------------------------------------------------- |
| `ConfigStore` | Read and write sectioned configuration values (`Option`-returning reads for sparse keys; `&mut self` for writes). |

All traits require `Send + Sync`. Write operations take `&mut self`; read operations take
`&self`. `BlameEntry` is the companion struct for `Blame`, constructed via a fallible
`new()` that rejects zero start lines or counts.

### `types` — The 14 immutable data types

All types live under `types/core` and are re-exported at the crate root. Each is immutable
after construction and built through a fallible constructor.

| Type          | Purpose                                                                    |
| ------------- | -------------------------------------------------------------------------- |
| `Blob`        | A file's raw content, bounded by `MAX_BLOB_SIZE`.                          |
| `Tree`        | A sorted collection of `TreeEntry` objects, bounded by `MAX_TREE_ENTRIES`. |
| `TreeEntry`   | A single directory entry: name, `EntryKind`, and target `Hash`.            |
| `Commit`      | A commit object: tree hash, parents, author/committer, message, metadata.  |
| `CommitMeta`  | Timestamp, timezone offset, and optional encoding for a commit.            |
| `Tag`         | An annotated tag: name, target hash, optional tagger, message, metadata.   |
| `Hash`        | A fixed 64-byte SHA-512 content address (`Copy`, `Send`, `Sync`).          |
| `UserID`      | A name and email pair with validation.                                     |
| `ReflogEntry` | A single reflog record (old/new hash, committer, message).                 |
| `ChangeKind`  | The kind of change in a diff (added, modified, deleted, etc.).             |
| `FileDelta`   | A per-file change between two trees.                                       |
| `TreeDelta`   | An aggregate tree-level diff.                                              |
| `Conflict`    | A merge conflict representation.                                           |
| `MergeResult` | The outcome of a merge operation.                                          |

### `validation` — Input checks

Pure functions that define the security boundary for raw inputs, intended to be applied
_before_ attempting object construction so that malformed data fails fast.

| Function                   | Validates                                                                           |
| -------------------------- | ----------------------------------------------------------------------------------- |
| `validate_hash_bytes`      | That a byte slice is exactly `HASH_LENGTH` bytes.                                   |
| `validate_name`            | That a name is non-empty, within `MAX_NAME_LENGTH`, and free of control characters. |
| `validate_ref_name`        | That a reference name is structurally valid (e.g. `refs/heads/main`).               |
| `validate_tree_entry_name` | That a tree entry name is a valid path component.                                   |

---

## Testing

Run the crate's test suite with Cargo:

```bash
cargo test
```

The crate includes unit tests for constructors, validation, `EntryKind` conversions, and
tree sorting/duplicate detection. To run the entire workspace test suite from the
repository root:

```bash
cargo test --workspace
```

To run the benchmark suite:

```bash
cargo bench -p libvctrl_handler
```

To verify the strict lint policy is satisfied:

```bash
cargo clippy --workspace --all-targets -- -D warnings
cargo doc --workspace --no-deps
```

---

## Contributing

Contributions are welcome. The crate enforces `#![forbid(unsafe_code)]` and inherits a
strict, denied Clippy and documentation-lint policy from the workspace; all public items
must be documented.

For contribution guidelines, code style, and the full lint configuration, see the
repository's `CONTRIBUTING.md` and the workspace root `README.md`:

- Repository: https://github.com/mroczect/libvctrl

When contributing to `libvctrl_handler`, preserve the contracts-only invariant: no
concrete implementations belong here. New behaviours belong in `libvctrl_core`; new
contracts and types belong here; new user-facing commands belong in `libvctrl_plumbing`
or `libvctrl_porcelain`.

---

## Ecosystem

`libvctrl_handler` is the foundation of a larger workspace. The related crates are listed
below; each has its own documentation.

| Crate                | Role                                                     | Documentation                      |
| -------------------- | -------------------------------------------------------- | ---------------------------------- |
| `libvctrl`           | Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl           |
| `libvctrl_core`      | Reference implementations of these contracts             | https://docs.rs/libvctrl_core      |
| `libvctrl_sha512`    | Zero-dependency SHA-512 / HMAC / HKDF primitives         | https://docs.rs/libvctrl_sha512    |
| `libvctrl_plumbing`  | Command-level VCS operations built on `libvctrl_core`    | https://docs.rs/libvctrl_plumbing  |
| `libvctrl_porcelain` | High-level, user-facing VCS operations                   | https://docs.rs/libvctrl_porcelain |

The dependency flow is strictly one-way: `libvctrl_handler` is the foundation,
`libvctrl_core` implements its contracts, the facade re-exports both, and
`libvctrl_plumbing` / `libvctrl_porcelain` build on `libvctrl_core`.

```mermaid
flowchart LR
    H[libvctrl_handler<br/>contracts] --> C[libvctrl_core<br/>reference impl]
    C --> PL[libvctrl_plumbing]
    C --> PO[libvctrl_porcelain]
    H --> F[libvctrl<br/>facade]
    C --> F
```

---

## License

Licensed under the MIT License. See the repository for the full license text.