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
// The impossible-feature guards below (async-store, sha256) reference features
// intentionally not declared in Cargo.toml; build.rs registers them via
// `cargo::rustc-check-cfg` so rustc recognizes them without any cfg-lint allow.
// justifies: docs.rs builds with --cfg docsrs from Cargo.toml so feature-gated public API can show doc(cfg) badges; local stable docs add batpak_stable_docs to avoid nightly-only attributes.
// justifies: src/lib.rs makes production expect() sites deliberate invariant escape hatches instead of ambient convenience panics.
// cast_possible_truncation and cast_sign_loss are enforced via [lints.clippy] in Cargo.toml.
// Each intentional cast has an inline #[allow] with a justification comment.
//! batpak is an embedded, sync-first event store for Rust: an append-only,
//! hash-chained journal with typed events, verifiable receipts, deterministic
//! replay, and derived projections — no server, no async runtime.
//!
//! The store keeps immutable events in append-only segment files, tracks
//! causation metadata, and evaluates caller-defined gates before commit;
//! state is rebuilt by replaying the log into typed projections, all through
//! a synchronous API.
//!
//! Use it when you need a tamper-evident, replayable record of what happened:
//! every event is hash-bound to its per-entity ancestor with Blake3, every
//! accepted write returns a verifiable (optionally Ed25519-signed) receipt,
//! and projections are derived views rebuilt from the log by construction.
//!
//! Most callers start with the eight-job path: open a [`Store`](crate::store::Store), append typed
//! events, page commit order with
//! [`Store::query_entries_after`](crate::store::Store::query_entries_after),
//! point-read with [`Store::get`](crate::store::Store::get), walk bounded
//! hash-chain ancestry with
//! [`Store::walk_ancestors`](crate::store::Store::walk_ancestors), verify
//! receipts with
//! [`Store::verify_append_receipt`](crate::store::Store::verify_append_receipt),
//! project derived state with [`Store::project`](crate::store::Store::project),
//! then close the store. Gates and [`Pipeline`](crate::pipeline::Pipeline) are
//! advanced batteries for caller-owned evaluation before commit.
//!
//! ```no_run
//! use batpak::prelude::*;
//!
//! #[derive(serde::Serialize, serde::Deserialize, EventPayload)]
//! #[batpak(category = 0xF, type_id = 1)]
//! struct ThingHappened {
//! value: i64,
//! }
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let dir = tempfile::tempdir()?;
//! let store = Store::open(StoreConfig::new(dir.path()))?;
//! let coord = Coordinate::new("entity:a", "scope:1")?;
//!
//! let receipt = store.append_typed(&coord, &ThingHappened { value: 42 })?;
//! let stored = store.get(receipt.event_id)?;
//!
//! assert_eq!(stored.coordinate.entity(), "entity:a");
//! assert_eq!(stored.event.header.event_id, receipt.event_id);
//! # Ok(())
//! # }
//! ```
//!
//! **Reading order:**
//! 1. [`coordinate`]: Identify entities and scopes.
//! 2. [`event`]: Structure typed payloads and projection inputs.
//! 3. [`store`]: Persist, page, point-read, walk, verify, and project.
//! 4. [`guard`] and [`pipeline`]: Add caller-defined write evaluation.
//! 5. [`artifact`], [`registry`], [`transition`], [`reservation`], and
//! [`schema`]: Advanced substrate batteries for envelopes, ledgers,
//! transition evidence, reservation mechanics, and drift reports.
//!
//! **Fail-closed defaults (verifiability).** A store refuses to open on an
//! ambiguous or undecodable payload registry:
//! [`EventPayloadValidation`](crate::event::EventPayloadValidation) defaults to
//! `FailFast`, so a duplicate-kind collision or an incomplete upcast chain is
//! rejected at [`Store::open`](crate::store::Store::open) (opt out explicitly
//! with `Warn`/`Silent`). A binary that registers `EventPayload` types but may
//! never open a store should call
//! [`verify_registry`](crate::event::verify_registry) once at startup (or enable
//! the non-default `startup-registry-check` feature for automatic enforcement),
//! since the derive's own collision test is `#[cfg(test)]`-only and a release
//! binary would otherwise see no check. Receipt signing is governed by
//! [`SigningPolicy`](crate::store::SigningPolicy): the default `Optional`
//! permits a keyless store, while `Required` refuses to open without a signing
//! key so an unsigned receipt is never accepted. A configured signer fails the
//! append closed rather than silently emitting an unsigned receipt unless
//! [`StoreConfig::with_signing_downgrade_allowed`](crate::store::StoreConfig::with_signing_downgrade_allowed)
//! opts in.
//!
//! **On-demand integrity.** [`Store::verify_chain`](crate::store::Store::verify_chain)
//! recomputes the full blake3 hash chain over every committed event and returns
//! a [`ChainVerificationReport`](crate::store::ChainVerificationReport); opt into
//! [`ChainVerification::Recompute`](crate::store::ChainVerification::Recompute)
//! to run that pass automatically at open and fail closed on tamper. For
//! ancestry, [`Store::walk_ancestors_outcome`](crate::store::Store::walk_ancestors_outcome)
//! returns an [`AncestorWalk`](crate::store::AncestorWalk) whose
//! [`AncestryBoundary`](crate::store::AncestryBoundary) makes a truncated lineage
//! (for example, a retention-dropped mid-chain parent) observable instead of
//! indistinguishable from a complete walk to genesis.
//!
//! **Payload encryption & crypto-shred (opt-in).** Off by default: a default
//! build writes plaintext payloads and pulls no crypto dependency. Enabling the
//! non-default `payload-encryption` cargo feature and calling
//! `StoreConfig::with_payload_encryption`
//! seals every payload at rest under a per-scope 256-bit XChaCha20-Poly1305 key
//! (a pure-Rust AEAD; key and nonce bytes come from the OS CSPRNG, and key
//! material zeroizes on drop and never appears in `Debug`/`Display` output).
//! `KeyScopeGranularity` chooses which
//! events share a key — and therefore what a single erasure destroys:
//! `PerEntity` (default, one key per entity, across all kinds), `PerCategory`
//! (one key per event-kind category), `PerTypeId` (one key per full kind), or
//! `PerEvent` (one key per individual event, the finest).
//! `Store::shred_scope` then crypto-shreds a
//! scope: it destroys that scope's KEY and flushes the keyset durable, making
//! every payload sealed under it permanently unrecoverable. A later read of a
//! shredded payload reports
//! `StoreError::PayloadShredded` (or
//! a `ReadDisposition::Shredded` value via
//! `Store::get_shreddable`) — never
//! corruption and never the raw ciphertext.
//!
//! Shredding destroys only the key, never any event frame: the ciphertext and
//! its Blake3 chain identity survive on disk, so
//! [`verify_chain`](crate::store::Store::verify_chain), receipts, and signatures
//! stay intact — identity is taken over the STORED CIPHERTEXT, not the plaintext.
//! Erasure is EXACTLY this explicit op; tombstone/retention compaction never
//! auto-destroys a key, so a coarse scope (the default `PerEntity`) is erased
//! only when the caller names that entity by selector.
//!
//! *Threat model — keys at rest.* The keyset lives inside the store's own data
//! directory, next to the ciphertext it protects. What crypto-shred DOES buy:
//! once a scope's key is destroyed AND that destruction is flushed, the scope's
//! payloads are unrecoverable even to an operator with full disk access —
//! deletion becomes cryptographically effective rather than a best-effort
//! overwrite. What it does NOT buy: it does not protect a disk image captured
//! *before* the shred (the key was still present then), and a stolen live data
//! directory yields both key and ciphertext. Holding the keyset OUT of the data
//! directory — a separate volume, an OS keyring, or an external KMS — is a
//! deployment concern, outside the core mechanism. batpak only ever observes
//! "the key for scope X was destroyed"; the layer above maps that erasure to its
//! own policy.
//!
//! *Snapshot/fork portability.* Because the keyset never travels with the
//! ciphertext it opens, a `Store::snapshot_with_evidence` / `Store::fork` of an
//! encryption-active store FAILS CLOSED by default
//! (`StoreError::KeysetNotPortable`): a keyless copy is silently unrestorable,
//! and a copy carrying the keyset could resurrect crypto-shredded data. Opt into
//! a keys-excluded copy with `KeysetPolicy::ExcludeKeys` (managing the keyset
//! out-of-band); restoring one without its keyset reports
//! `StoreError::KeysetMissing`, never a `Shredded` lookalike.
//!
//! ```
//! # #[cfg(feature = "payload-encryption")] {
//! use batpak::prelude::*;
//! use batpak::store::{KeyScopeGranularity, ShredScope};
//!
//! #[derive(serde::Serialize, serde::Deserialize, EventPayload)]
//! #[batpak(category = 0xF, type_id = 1)]
//! struct ThingHappened {
//! value: i64,
//! }
//!
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let dir = tempfile::tempdir()?;
//! // PerEntity (the default): one key covers every payload of an entity, so a
//! // single shred erases all of that entity's payloads at once.
//! let store = Store::open(
//! StoreConfig::new(dir.path())
//! .with_payload_encryption(KeyScopeGranularity::PerEntity),
//! )?;
//! let coord = Coordinate::new("entity:a", "scope:1")?;
//! let receipt = store.append_typed(&coord, &ThingHappened { value: 42 })?;
//!
//! // While the key is live the payload reads back transparently.
//! assert_eq!(
//! store.get(receipt.event_id)?.event.header.event_id,
//! receipt.event_id,
//! );
//!
//! // Destroy this entity's key: its plaintext is now permanently gone...
//! store.shred_scope(ShredScope::Entity(&coord))?;
//! assert!(matches!(
//! store.get(receipt.event_id),
//! Err(StoreError::PayloadShredded { .. })
//! ));
//!
//! // ...yet identity survives: the chain still verifies over the ciphertext.
//! assert!(store.verify_chain()?.is_intact());
//! # Ok(())
//! # }
//! # run().unwrap();
//! # }
//! ```
//!
//! **Cargo features (all non-default).** A default build enables none of these
//! and pulls none of their dependencies. `payload-encryption` adds the
//! crypto-shred surface above (`with_payload_encryption` / `shred_scope`,
//! `XChaCha20-Poly1305` + an OS CSPRNG). `startup-registry-check` runs
//! [`verify_registry`](crate::event::verify_registry) automatically before
//! `main` via one process-wide constructor, so a release binary that registers
//! `EventPayload` types but never opens a store still aborts on a kind collision;
//! the always-on, portable path is the explicit `verify_registry()` call
//! described above, which needs no constructor.
// Width invariant: batpak stores ids/offsets/lengths compactly as `u32`/`u64` and
// converts to `usize` only transiently for slicing. `u32 -> usize` is lossless only
// when `usize` is at least 32 bits, so a <32-bit target is rejected here at compile
// time. This makes the `usize::try_from(u32).unwrap_or(..)` conversions throughout
// the store provably-total (their `Err` arm is unreachable) without any `#[allow]`.
compile_error!;
/// Crate-level substrate: canonical artifact body digest vs envelope digest.
/// Entity and scope addressing for events.
/// Stable named-field MessagePack encoding helpers.
/// Event types, headers, and sourcing traits.
/// Caller-defined gate evaluation before event commitment.
/// UUID v7 identifier generation.
/// Result-like type for pipeline operations.
/// Propose-evaluate-commit workflow.
/// Common re-exports for convenient use.
/// Crate-level substrate: generic signed registry rows composing artifact envelopes.
/// Crate-level substrate: generic reservation ledger mechanics.
/// Deterministic schema/fixture snapshot drift evidence.
/// Persistent event storage and querying.
/// Crate-level substrate: generic state transition events and reports.
/// Compile-time state machine transitions.
/// Module declarations in DEPENDENCY ORDER:
/// wire → coordinate → outcome → event → guard → pipeline → store → typestate → id → prelude
/// Serde serialization helpers.
// serde helpers — no deps, must come first
/// Back-compatible alias for batpak-scoped named-field MessagePack helpers.
///
/// This is not a cross-protocol canonicalization surface; protocols with their
/// own canonical byte rules must apply those rules outside batpak core.
pub use crateencoding as canonical;
/// Internal types referenced by `#[derive(EventPayload)]` generated code.
/// Not part of the public API; may change without notice.
/// Fuzz-only decode entry points for the workspace-excluded `batpak-fuzz`
/// cargo-fuzz crate (GAUNT-FUZZ-1). Gated behind `dangerous-test-hooks` and
/// `#[doc(hidden)]`: a default build never compiles it, so there is no
/// production API-surface change. See the module docs for the wrapper contract.
/// Deterministic-simulation entry points for the `sim_is_deterministic`
/// integration test (GAUNT-SIM-2c). Gated behind `dangerous-test-hooks` and
/// `#[doc(hidden)]`: a default build never compiles it, so there is no
/// production API-surface change. Exposes only the seeded-workload driver and
/// `BATPAK_SEED` replay helper over the `pub(crate)` simulation backends.
// Self-alias for path hygiene in derive-generated code.
// `batpak-macros` emits absolute `::batpak::...` paths (see ADR-0010 and
// `crates/macros/src/lib.rs:151-166`). `pub extern crate self as batpak;`
// makes `::batpak::...` resolve to `self::...` from inside the library
// crate itself, so `#[derive(EventPayload)]` / `#[derive(EventSourced)]` /
// `#[derive(MultiEventReactor)]` all work identically in downstream crates
// AND in in-workspace tests/examples/unit modules. The downstream fixture
// at `fixtures/downstream/` proves the outward direction; the in-crate
// test added with this seam proves the inward direction.
pub extern crate self as batpak;
// Crate-root re-exports for the typed payload binding and dispatch-layer
// derives. Traits live in `batpak::event::...`; the derive macros are
// generated by the `batpak-macros` proc-macro crate. Mirroring both at the
// crate root follows the serde pattern: a single `use batpak::EventPayload`
// (or `EventSourced`) brings in both the trait (type namespace) and the
// derive (macro namespace).
pub use crate;
pub use ;
/// compile_error guards for impossible configurations:
// async-store is intentionally undeclared in Cargo.toml; build.rs registers the
// cfg via `cargo::rustc-check-cfg` so this INV-STORE-SYNC-ONLY guard (ADR-0001)
// compiles warning-free in src/lib.rs.
compile_error!;
// sha256 is intentionally undeclared in Cargo.toml; build.rs registers the cfg
// via `cargo::rustc-check-cfg` so this blake3-only guard (INV-STORE-SYNC-ONLY)
// compiles warning-free in src/lib.rs.
compile_error!;