| 1 | # ADR-0002: Bridge provider keys — reopen чужого `.witdb` |
| 2 | |
| 3 | **Статус:** принято |
| 4 | **Дата:** 2026-06-06 |
| 5 | **Репозиторий:** [AI-Guiders/pywitdb](https://github.com/AI-Guiders/pywitdb) |
| 6 | **Связано:** [ADR-0001](0001-binding-architecture-bridge-and-native.md) |
| 7 | **Upstream:** WitDatabase `ProviderMetadata` (header bytes 48–99), `StorageDetector`, `ConfigurationValidator` |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Решение (одной фразой) |
| 12 | |
| 13 | **`witdb-bridge` не реализует свой reopen-детект** — делегирует открытие `OutWit.Database.Core` (header + `ConfigurationValidator`). В bridge **обязательно подключены** NuGet-пакеты, покрывающие **Tier-1** provider keys прод-контура; чужие/custom keys → явная ошибка `unknown_provider`, не silent corruption. |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Контекст |
| 18 | |
| 19 | WitDatabase — provider-based архитектура (`ProviderRegistry`, `IProvider`). При создании `.witdb` в **database header** (100 байт, magic `WitDB Format 1`) сохраняется `ProviderMetadata`: |
| 20 | |
| 21 | | Поле в header | Persisted | Пример | |
| 22 | |---------------|-----------|--------| |
| 23 | | `StoreProviderKey` | да (16 байт UTF-8) | `btree`, `lsm` | |
| 24 | | `EncryptionProviderKey` | да | `aes-gcm`, `chacha20-poly1305`, `""` | |
| 25 | | `ProviderFeatures` | да (flags byte) | Encryption, Transactions, FileLocking, Mvcc | |
| 26 | | `CacheProviderKey` | **нет** | при reopen — default (`clock`) | |
| 27 | | `JournalProviderKey` | **нет** | при reopen — default builder | |
| 28 | |
| 29 | `StorageDetector` для **файла** читает header → `ConfigurationValidator.GetOpeningHints`. Если magic зашифрован — `RequiresPassword=true`, `EncryptionProvider=unknown` до успешного decrypt. |
| 30 | |
| 31 | **Критичные mismatch при reopen** (`ConfigurationValidator`, non-strict): |
| 32 | |
| 33 | 1. `StoreProviderKey` должен совпасть. |
| 34 | 2. Если store encrypted — нужен encryption provider; тип (`EncryptionProviderKey`) должен совпасть. |
| 35 | |
| 36 | Cache/journal mismatch в non-strict **не** блокируют open. |
| 37 | |
| 38 | Python/pywitdb **не** парсит header сам на MVP — bridge = thin wrapper над Core open path. |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## Tier-1: обязательная поддержка bridge (MVP) |
| 43 | |
| 44 | Сценарии, которые **должны** открываться без доработок — типичный prod-контур door-to-singularity: |
| 45 | |
| 46 | | # | Path shape | Store key | Encryption key | Features (типично) | NuGet в bridge | |
| 47 | |---|------------|-----------|----------------|-------------------|----------------| |
| 48 | | T1a | файл `*.witdb` | `btree` | `""` | Transactions, FileLocking | `OutWit.Database.Core` | |
| 49 | | T1b | файл `*.witdb` | `btree` | `aes-gcm` | Encryption + Transactions + FileLocking | `OutWit.Database.Core` | |
| 50 | | T1c | файл `*.witdb` | `btree` | `aes-gcm` | + Mvcc | `OutWit.Database.Core` | |
| 51 | |
| 52 | **T1b/T1c** — путь Intercom / CascadeIDE (`intercom.witdb`, `UseWitDb` + password). Это **primary contract test** для cross-lang CI. |
| 53 | |
| 54 | ### Built-in keys (Core `ProviderRegistration`) |
| 55 | |
| 56 | Bridge может полагаться на auto-registration Core: |
| 57 | |
| 58 | | Component | Keys | |
| 59 | |-----------|------| |
| 60 | | `IKeyValueStore` | `btree`, `lsm`, `memory` | |
| 61 | | `ICryptoProvider` | `aes-gcm` | |
| 62 | | `IStorage` | `file`, `memory`, `encrypted` | |
| 63 | | `IPageCache` | `clock` (default), `lru` | |
| 64 | | `ITransactionJournal` | `wal`, `rollback` | |
| 65 | |
| 66 | --- |
| 67 | |
| 68 | ## Tier-2: поддержка в v0.1 (явно в CI) |
| 69 | |
| 70 | | # | Path shape | Store key | Encryption key | NuGet | |
| 71 | |---|------------|-----------|----------------|-------| |
| 72 | | T2a | **директория** (LSM) | `lsm` | `""` или `aes-gcm` | Core | |
| 73 | | T2b | файл | `btree` | `chacha20-poly1305` | Core + **`OutWit.Database.Core.BouncyCastle`** | |
| 74 | |
| 75 | BouncyCastle-пакет регистрирует `chacha20-poly1305` через `[ModuleInitializer]` — bridge **обязан** ссылаться на сборку, иначе `ProviderNotFoundException` при reopen WASM-compatible stores. |
| 76 | |
| 77 | --- |
| 78 | |
| 79 | ## Tier-3: вне scope MVP (явный отказ) |
| 80 | |
| 81 | | Случай | Поведение bridge | |
| 82 | |--------|------------------| |
| 83 | | Custom `ICryptoProvider` / `IKeyValueStore` (сторонний NuGet) | `unknown_provider` + stored key в ответе | |
| 84 | | `indexeddb` storage (Blazor) | `unsupported_backend` — не файловый `.witdb` | |
| 85 | | `memory` store | только ephemeral create; reopen файла N/A | |
| 86 | | SQL-only / EF schema mismatch | out of scope KV bridge v0 | |
| 87 | | Provider key длиннее 16 байт | невозможен по формату header (upstream limit) | |
| 88 | |
| 89 | **Не** пытаться fallback на другой engine или игнорировать encryption mismatch. |
| 90 | |
| 91 | --- |
| 92 | |
| 93 | ## Поведение `witdb-bridge` при `open` |
| 94 | |
| 95 | ### Делегирование Core |
| 96 | |
| 97 | ```text |
| 98 | open(path, password?) → WitDatabaseBuilder / WitDatabase.Create(existing) |
| 99 | → StorageDetector.Detect(path) [optional preflight] |
| 100 | → Core build + ConfigurationValidator.ValidateOrThrow |
| 101 | ``` |
| 102 | |
| 103 | Bridge **не** дублирует логику `ConfigurationValidator` в Python. |
| 104 | |
| 105 | ### Ответ `open` (расширение NDJSON, ADR-0001) |
| 106 | |
| 107 | Успех: |
| 108 | |
| 109 | ```json |
| 110 | { |
| 111 | "id": 1, |
| 112 | "ok": true, |
| 113 | "store": { |
| 114 | "path": "intercom.witdb", |
| 115 | "store_provider": "btree", |
| 116 | "encryption_provider": "aes-gcm", |
| 117 | "features": ["encryption", "transactions", "file_locking"] |
| 118 | } |
| 119 | } |
| 120 | ``` |
| 121 | |
| 122 | Ошибки (стабильные `code`): |
| 123 | |
| 124 | | code | Когда | |
| 125 | |------|-------| |
| 126 | | `file_not_found` | path не существует и `create=false` | |
| 127 | | `password_required` | encrypted, password не передан | |
| 128 | | `wrong_password` | decrypt/auth failure | |
| 129 | | `configuration_mismatch` | `ConfigurationMismatchException` | |
| 130 | | `unknown_provider` | `ProviderNotFoundException` — нет регистрации для stored key | |
| 131 | | `unsupported_backend` | indexeddb / non-file backends | |
| 132 | |
| 133 | Python `pywitdb` маппит коды в `WitDbStoreError` / `BridgeNotAvailableError`. |
| 134 | |
| 135 | --- |
| 136 | |
| 137 | ## Зависимости bridge-проекта (зафиксировать) |
| 138 | |
| 139 | `bridge/WitDbBridge/WitDbBridge.csproj`: |
| 140 | |
| 141 | ```xml |
| 142 | <PackageReference Include="OutWit.Database.Core" Version="…" /> |
| 143 | <PackageReference Include="OutWit.Database.Core.BouncyCastle" Version="…" /> |
| 144 | ``` |
| 145 | |
| 146 | Версии — pin в `Directory.Packages.props`; bump только с прогоном contract tests. |
| 147 | |
| 148 | **Явный вызов** `BouncyCastleProviderRegistration.EnsureRegistered()` в `Program.cs` bridge (на случай trimmed publish без ModuleInitializer). |
| 149 | |
| 150 | --- |
| 151 | |
| 152 | ## Contract tests (обязательные до merge bridge PR) |
| 153 | |
| 154 | | Fixture | Writer | Reader | Assert | |
| 155 | |---------|--------|--------|--------| |
| 156 | | `fixtures/t1a-plain-btree.witdb` | C# Core, btree, no encryption | pywitdb `get`/`put` | round-trip KV | |
| 157 | | `fixtures/t1b-aes-intercom-like.witdb` | C# + password, aes-gcm | pywitdb same password | KV + metadata keys | |
| 158 | | `fixtures/t2a-lsm-dir/` | C# LSM directory | pywitdb `open(dir)` | KV | |
| 159 | | `fixtures/t2b-chacha.witdb` | C# + BouncyCastle | pywitdb | KV | |
| 160 | |
| 161 | Каждый fixture commit'ится в репо или генерируется в CI из `tests/fixtures/cs/` (предпочтительно generate → не drift). |
| 162 | |
| 163 | --- |
| 164 | |
| 165 | ## Native tier (v1) — следствие |
| 166 | |
| 167 | Native C ABI **не** обязан реплицировать весь `ProviderRegistry` в Python. Минимум: |
| 168 | |
| 169 | - те же **Tier-1** store+encryption combinations; |
| 170 | - ошибки ABI по кодам, совместимым с bridge (`unknown_provider`, …). |
| 171 | |
| 172 | Полный plugin host в Python — **non-goal**. |
| 173 | |
| 174 | --- |
| 175 | |
| 176 | ## Последствия |
| 177 | |
| 178 | - Bridge binary чуть тяжелее (BouncyCastle ref) — acceptable для dev/CI. |
| 179 | - Чужие custom providers требуют либо добавления их NuGet в bridge, либо native tier upstream — документируем в README. |
| 180 | - Intercom/CascadeIDE `.witdb` — regression anchor для каждого релиза pywitdb. |
| 181 | |
| 182 | --- |
| 183 | |
| 184 | ## Ссылки |
| 185 | |
| 186 | - WitDatabase: `Sources/Core/OutWit.Database.Core/Pages/ProviderMetadata.cs` |
| 187 | - WitDatabase: `Sources/Core/OutWit.Database.Core/Providers/ConfigurationValidator.cs` |
| 188 | - WitDatabase: `Sources/Core/OutWit.Database.Core/Providers/StorageDetector.cs` |
| 189 | - WitDatabase: `Sources/Core/OutWit.Database.Core/EXTENSIBILITY.md` |
| 190 | |