libnoa 0.1.1

AI-native distributed version control system with per-agent workspace isolation, JSONL append-only logs, snapshot-based history, and full git protocol compatibility
Documentation
# Дизайн объектного хранилища

## Обзор

noa использует контентно-адресуемую модель хранения, вдохновлённую Git, но с
подключаемой архитектурой бэкенда. Объекты адресуются по хешу SHA-256
и хранятся как непрозрачные блобы.

## Типы объектов

### Блоб (Blob)

Сырое содержимое файла. Идентифицируется по `SHA256(content)`.

```rust
pub struct BlobId(pub String); // hex-закодированный SHA-256
```

Без дельта-сжатия. Каждое уникальное содержимое создаёт ровно один блоб.
Дублирующееся содержимое автоматически дедуплицируется по хешу.

### Дерево (Tree)

Список директории. Сопоставляет пути с дочерними записями (блобы или поддеревья).

```rust
pub struct TreeEntry {
    pub name: String,
    pub kind: TreeEntryKind, // Blob или Tree
    pub hash: String,        // SHA-256 дочернего элемента
}

pub struct TreeId(pub String); // SHA-256(msgpack(entries))
```

Деревья сериализуются в MessagePack для компактности и быстрой десериализации.

## Определение трейта

```rust
#[async_trait]
pub trait ObjectStore: Send + Sync {
    async fn put_blob(&self, data: &[u8]) -> Result<BlobId>;
    async fn get_blob(&self, id: &BlobId) -> Result<Vec<u8>>;
    async fn put_tree(&self, entries: Vec<TreeEntry>) -> Result<TreeId>;
    async fn get_tree(&self, id: &TreeId) -> Result<Vec<TreeEntry>>;
}
```

## Бэкенды

### RedbObjectStore (Локальный)

Использует встроенное хранилище ключ-значение [redb](https://github.com/cberner/redb).

- Две таблицы: `blobs` (ключ: байты хеша, значение: байты содержимого) и
  `trees` (ключ: байты хеша, значение: msgpack записи)
- Чтение без копирования через файлы, отображаемые в память
- ACID-транзакции с автоматическим восстановлением после сбоев
- Один писатель, много читателей через MVCC
- Не требует внешнего демона

### MinioObjectStore (Удалённый)

Использует S3-совместимый API через `aws-sdk-s3`.

- Адресация в стиле путей: `<bucket>/blobs/<hash>`, `<bucket>/trees/<hash>`
- Поддерживает любой S3-совместимый бэкенд (MinIO, AWS S3, GCS и т.д.)
- Автоматические повторные попытки с экспоненциальной задержкой
- Подходит для распределённых развёртываний

## Проектные решения

### Почему SHA-256 вместо SHA-1?

Git использует SHA-1, который криптографически сломан (атака SHAttered, 2017).
SHA-256 устойчив к коллизиям и широко доступен.

### Почему без дельта-сжатия?

1. **Простота**: Дельта-сжатие (pack-файлы Git) значительно усложняет систему
   (сопоставление скользящим окном, тонкие паки, цепочки дельт).
2. **Производительность записи**: Прямая запись блобов — O(1). Дельта-сжатие
   требует чтения существующих объектов.
3. **Рабочая нагрузка ИИ-агентов**: Агенты часто перегенерируют целые файлы.
   Старые версии эфемерны — цепочки дельт будут короткими и многочисленными.
4. **Разгрузка на бэкенд**: S3/MinIO обрабатывают дедупликацию на уровне хранения.

### Почему MessagePack для деревьев?

- На 30-50% компактнее JSON для данных с большим количеством бинарных данных
- Гибкая схема (не нужны определения protobuf)
- Поддержка в экосистеме Rust через `rmp-serde`
- Быстрая десериализация

### Почему redb вместо SQLite?

- **Безопасность типов**: redb использует обобщения Rust для определений таблиц
- **Производительность**: redb оптимизирован для рабочих нагрузок Rust (чтение без копирования)
- **Простота**: Одна зависимость, без связывания с C-библиотекой
- **Безопасность при сбоях**: Журнал упреждающей записи redb проще, чем режим WAL SQLite

Компромисс: у redb меньшее сообщество и меньше инструментов, чем у SQLite.
Для варианта использования noa (встроенное бинарное хранилище) этот компромисс оправдан.