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
//! Warehouse — append-only columnar store backing nornir's bench/symbol
//! history.
//!
//! The canonical backend is [`iceberg::IcebergWarehouse`] (Apache Iceberg via
//! `iceberg-rust` over the single-file **skade-katalog** redb catalogue).
//! Every derived fact is an Iceberg row keyed by git SHA. A `RemoteWarehouse`
//! (Arrow Flight to `nornir-server`, Phase 5) will reuse the same [`Warehouse`]
//! trait surface; the trait is the durable abstraction.
/// Iceberg writer/reader for the `clone_events` stream (PLAN #6) — per-member
/// clone/fetch/republish populate outcomes, readable by a thin viz/CLI client.
/// Iceberg writer/reader for the `airgap_events` DAG (EPIC AIRGAP / AIRGAP8) —
/// the durable twin of the lean `nornir-airgap` JSONL event sink.
/// Static extractor for the fn → warehouse-table access fact (AUT7 / EPIC ARCH
/// n-002): the syn pass + the accessor → table map.
/// Iceberg writer/reader for the `warehouse_access_edges` fact table.
/// Generative-LLM abstraction (EPIC #39): ONE `Generator` trait, three real
/// backends (candle / mistralrs / onnx) behind the `generator(spec)` factory,
/// plus `mock` and an off-by-default `ollama` client. Feeds the bake-off.
use Path;
use Result;
use RecordBatch;
use Uuid;
use crateBenchRun;
use crateStorage;
// Re-export skade's backend-neutral scan predicate so trait callers can build
// pushdown filters (`ScanFilter::eq("repo", repo)`, `is_in`, `range`) without
// naming any engine type. This is the durable predicate surface a future
// `SkadeWarehouse` / DuckDB backend implements identically; the Iceberg backend
// lowers it to an `iceberg::expr::Predicate` inside skade.
pub use ;
/// The durable storage seam for nornir's warehouse (bench/test/release/symbol
/// history, etc.). **Sync** by design — every method blocks internally on the
/// backend's own runtime (`IcebergWarehouse` owns a tokio `Runtime` and calls
/// `block_on`), so the ~150 sync call sites never have to become async.
///
/// Two layers:
///
/// 1. A **generic Arrow core** ([`append_arrow`](Warehouse::append_arrow),
/// [`scan_arrow`](Warehouse::scan_arrow),
/// [`scan_filtered`](Warehouse::scan_filtered),
/// [`scan_limited`](Warehouse::scan_limited)) plus table/catalog lifecycle
/// ([`ensure_table`](Warehouse::ensure_table),
/// [`ensure_columns`](Warehouse::ensure_columns),
/// [`table_names`](Warehouse::table_names)). This is what the sibling
/// modules (`dep_graph`, `release_events`, `test_results`, …) need: build a
/// `RecordBatch` from `iceberg_schema::*` and append / scan it by table
/// name, without reaching for the raw catalog. Backends only have to make
/// these honest and every typed method composes over them.
///
/// 2. **Named per-table convenience methods** ([`append_bench_run`] /
/// [`query_bench_runs`]) kept as thin wrappers so existing callers are
/// untouched. (P1 keeps just the two that were already on the trait; the
/// rest stay as inherent `IcebergWarehouse` methods until the next phase
/// migrates consumers onto the trait.)
///
/// [`IcebergWarehouse`](iceberg::IcebergWarehouse) is the **default** (and
/// today the only) implementation: it delegates each method to its existing
/// inherent body, which already wraps skade (`skade::append` / `read_*` /
/// `ingest_parallel`). A future `SkadeWarehouse` (reusing a `skade::Warehouse`
/// + `Table` handle) or a DuckDB backend plugs in behind this same trait — see
/// `.nornir/warehouse-trait-skade-migration.md` for the P2/P3/P4 plan.
///
/// `iceberg_schema.rs` remains the per-table shape source of truth; the trait
/// is table-name + `RecordBatch` based and stays schema-agnostic.
/// Open the configured warehouse. Returns a boxed trait object so
/// callers don't bake an impl into their signatures. All local storage
/// kinds resolve to the Iceberg backend; only `remote` is distinct.
/// Open the configured warehouse for **read-only** use, tolerating a live
/// `nornir-server` that already holds the exclusive redb lock on
/// `catalog.redb`. When the catalog is locked, this opens a copied-aside
/// read-only snapshot (logged with a WARNING) instead of hard-failing, so a
/// dev-side `nornir` CLI read (the `docs_fresh` gate, docs render, etc.) can
/// never be wedged by the running server. Mutating callers must use
/// [`open`] — the snapshot is read-only by construction.
/// Resolve the on-disk warehouse root for a local storage config.
///
/// Precedence (must match `config::Loaded::warehouse_root` and the server's
/// resolution in `bin/nornir-server.rs`):
/// 1. explicit `[storage].local_path` → `<workspace_root>/<local_path>/warehouse`
/// (the repo-/workspace-local home; the recommended default for a CLI),
/// 2. otherwise the home-derived `<home>/.nornir/warehouse` default
/// (`config::warehouse_default_root`), which the live server also defaults
/// to — a collision risk, hence reads route through [`open_read_only`].