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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright (c) 2026 2youg1 and the sprawling contributors
//! The Ledger port: the only write entrance to history (seam list,
//! ARCHITECTURE section 3), plus the chain rule both adapters and replay
//! share.
//! The part `crates/kernel/spec/Ledger.lean` specifies this module.
//!
//! Contract owned here:
//! - implementations own seq/prev assignment and byte production; callers
//! never serialize. `Ok(ref)` means the record is durable in that
//! adapter's medium; `Err` means nothing observable was appended (torn
//! bytes are the reopen path's business).
//! - the chain hashes the canonical line bytes exactly, excluding the
//! line terminator; the genesis line's prev is 32 zero bytes.
//! - the port is write-only: production reads go through projections.
//! The read-back needed for verification lives behind the
//! `conformance` feature and never ships in the production surface.
use crate::error::AxError;
use crate::event::{EventDraft, EventRef};
use crate::locator::B3Hash;
/// prev of the genesis line: 64 zero hex digits.
pub const GENESIS_PREV: B3Hash = B3Hash::from_bytes([0; 32]);
/// Chain rule: prev(line k+1) = blake3(canonical bytes of line k).
/// One hash function, one home; jsonl, the in-memory ledger and replay
/// all call this exact function.
pub fn chain_hash(raw_line: &[u8]) -> B3Hash {
B3Hash::from_bytes(*blake3::hash(raw_line).as_bytes())
}
/// The only write entrance to history.
pub trait Ledger {
fn append(&mut self, draft: EventDraft) -> Result<EventRef, AxError>;
/// Every draft in one wave, answered positionally.
///
/// **A durable write costs one disk barrier, and a barrier costs the
/// same whether one record or fifty ride on it.** Measured on one
/// windows-x86_64 NVMe machine: 585 µs per record at one record per
/// barrier against 13 µs at fifty, of which the write itself is
/// about 2.5 µs. A caller that already holds a wave therefore pays
/// forty times what the disk asks by handing it over one at a time.
///
/// The contract of [`Ledger::append`] holds for every element: when
/// this answers `Ok`, every record in it is durable, and the refs
/// come back in the order the drafts were given. The default
/// implementation appends one at a time, which is correct and is
/// what a store with no batch of its own can honestly offer.
///
/// # Errors
/// The first refusal ends the wave. Records before it may already be
/// durable, which is the same promise a single append makes when the
/// one after it fails.
fn append_all(&mut self, drafts: Vec<EventDraft>) -> Result<Vec<EventRef>, AxError> {
let mut refs = Vec::with_capacity(drafts.len());
for draft in drafts {
refs.push(self.append(draft)?);
}
Ok(refs)
}
}
#[cfg(feature = "conformance")]
pub mod conformance {
//! V3: one assertion suite for every implementation. A stand-in that
//! cannot pass this suite is not an implementation of the port.
use super::{GENESIS_PREV, Ledger, chain_hash};
use crate::address::Address;
use crate::error::AxError;
use crate::event::{EventDraft, EventKind, EventRecord, Payload, RunId, Seq, TimeMs};
/// Verification-only read-back. Production callers never read through
/// the Ledger handle; this trait exists so the suite can compare raw
/// bytes, and it compiles only under the `conformance` feature.
pub trait LedgerInspect {
fn raw_lines(&self) -> Result<Vec<Vec<u8>>, AxError>;
}
/// Fixed draft script: deterministic on purpose so assertion six
/// (byte-identical logs from two fresh instances) is meaningful.
fn script() -> Vec<EventDraft> {
let run = RunId::from_bytes([1; 16]);
let parsed = Address::parse("lab");
assert!(parsed.is_ok(), "fixture address must parse: {parsed:?}");
let addr = parsed.ok();
vec![
EventDraft {
run: RunId::CITY,
t: TimeMs::new(1),
who: "city".to_owned(),
addr: None,
kind: EventKind::CityInitialized,
data: Payload::empty(),
ig: false,
},
EventDraft {
run: RunId::CITY,
t: TimeMs::new(2),
who: "city".to_owned(),
addr,
kind: EventKind::BuildingCreated,
data: Payload::empty(),
ig: false,
},
EventDraft {
run,
t: TimeMs::new(3),
who: "planner@lab.1".to_owned(),
addr: None,
kind: EventKind::RunStarted,
data: Payload::empty(),
ig: false,
},
EventDraft {
run,
t: TimeMs::new(4),
who: "planner@lab.1".to_owned(),
addr: None,
kind: EventKind::ToolCalled,
data: Payload::empty(),
ig: false,
},
EventDraft {
run,
t: TimeMs::new(5),
who: "planner@lab.1".to_owned(),
addr: None,
kind: EventKind::RunFrozen,
data: Payload::empty(),
ig: true,
},
]
}
fn collect_lines<L: Ledger + LedgerInspect>(ledger: &mut L) -> Vec<Vec<u8>> {
for (index, draft) in script().into_iter().enumerate() {
let kind = draft.kind;
let appended = ledger.append(draft);
assert!(
appended.is_ok(),
"append {index} ({kind:?}) must succeed: {appended:?}"
);
let Ok(echo) = appended else {
return Vec::new();
};
// Assertion five (half): the echo points at what was appended.
assert_eq!(echo.kind(), kind, "ref kind must echo the draft");
assert_eq!(
echo.seq(),
Seq::new(u64::try_from(index).unwrap_or(u64::MAX))
);
}
let lines = ledger.raw_lines();
assert!(lines.is_ok(), "raw_lines must succeed: {lines:?}");
let Ok(lines) = lines else { return Vec::new() };
lines
}
/// The suite (`crates/kernel/spec/Ledger.lean` §8-9, six assertions). `fresh` must yield an
/// empty ledger each call.
pub fn assert_ledger_conformance<L, F>(mut fresh: F)
where
L: Ledger + LedgerInspect,
F: FnMut() -> L,
{
let mut first = fresh();
let lines = collect_lines(&mut first);
assert_eq!(lines.len(), script().len(), "one line per draft");
let mut prev = GENESIS_PREV;
for (index, line) in lines.iter().enumerate() {
let parsed = EventRecord::parse_line(line);
assert!(parsed.is_ok(), "line {index} must parse: {parsed:?}");
let Ok(record) = parsed else { return };
// Assertion one and three: genesis prev, then chain continuity.
assert_eq!(
record.prev(),
prev,
"line {index}: prev must hash the previous line"
);
// Assertion two: contiguous seq from FIRST.
assert_eq!(
record.seq(),
Seq::new(u64::try_from(index).unwrap_or(u64::MAX)),
"line {index}: seq must be contiguous"
);
// Assertion five: v is pinned.
assert_eq!(
record.v(),
crate::consts_external::EVENT_LOG_V,
"line {index}: v must be EVENT_LOG_V"
);
// Assertion four: the writer is canonical.
let echo = record.canonical_line();
assert!(echo.is_ok(), "line {index} must reserialize");
let Ok(echo) = echo else { return };
assert_eq!(
&echo, line,
"line {index}: adapter bytes must be the canonical bytes"
);
prev = chain_hash(line);
}
// Assertion six: determinism across fresh instances.
let mut second = fresh();
let again = collect_lines(&mut second);
assert_eq!(
lines, again,
"two fresh instances fed the same drafts must produce identical bytes"
);
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code"
)]
mod tests {
use super::*;
#[test]
fn genesis_prev_is_sixty_four_zeros() {
assert_eq!(GENESIS_PREV.to_string(), "0".repeat(64));
}
#[test]
fn chain_hash_is_plain_blake3_of_the_line_bytes() {
let line = b"{\"v\":1}";
assert_eq!(
chain_hash(line).to_string(),
blake3::hash(line).to_hex().to_string()
);
}
}
#[cfg(all(test, feature = "conformance"))]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code"
)]
mod conformance_self_test {
use super::conformance::{LedgerInspect, assert_ledger_conformance};
use super::*;
use crate::error::{AxCode, AxError};
use crate::event::{EventDraft, EventRecord, EventRef, Seq};
/// Minimal reference implementation: proves the suite runs against any
/// implementation before citysim provides the second one.
struct VecLedger {
lines: Vec<Vec<u8>>,
next_seq: Seq,
prev: B3Hash,
}
impl VecLedger {
fn new() -> Self {
VecLedger {
lines: Vec::new(),
next_seq: Seq::FIRST,
prev: GENESIS_PREV,
}
}
}
impl Ledger for VecLedger {
fn append(&mut self, draft: EventDraft) -> Result<EventRef, AxError> {
let record = EventRecord::from_draft(draft, self.next_seq, self.prev);
let line = record.canonical_line()?;
self.prev = chain_hash(&line);
self.next_seq = self.next_seq.next()?;
self.lines.push(line);
Ok(record.to_ref())
}
}
impl LedgerInspect for VecLedger {
fn raw_lines(&self) -> Result<Vec<Vec<u8>>, AxError> {
Ok(self.lines.clone())
}
}
#[test]
fn the_reference_implementation_passes_the_suite() {
assert_ledger_conformance(VecLedger::new);
}
#[test]
fn a_chain_breaking_implementation_fails_the_suite() {
struct BrokenPrev(VecLedger);
impl Ledger for BrokenPrev {
fn append(&mut self, draft: EventDraft) -> Result<EventRef, AxError> {
let echo = self.0.append(draft)?;
self.0.prev = GENESIS_PREV; // never advances: chain breaks at line 2
Ok(echo)
}
}
impl LedgerInspect for BrokenPrev {
fn raw_lines(&self) -> Result<Vec<Vec<u8>>, AxError> {
self.0.raw_lines()
}
}
let outcome = std::panic::catch_unwind(|| {
assert_ledger_conformance(|| BrokenPrev(VecLedger::new()));
});
assert!(outcome.is_err(), "the suite must bite a broken chain");
}
/// A wave handed over at once answers positionally and leaves the
/// same log as its drafts appended one at a time: the default
/// `append_all` is that promise for a store with no batch of its own.
#[test]
fn a_wave_appended_at_once_is_the_log_of_its_drafts_one_by_one() {
let drafts: Vec<EventDraft> = [
crate::event::EventKind::CityInitialized,
crate::event::EventKind::RunStarted,
crate::event::EventKind::RunFrozen,
]
.into_iter()
.enumerate()
.map(|(at, kind)| EventDraft {
run: crate::event::RunId::CITY,
t: crate::event::TimeMs::new(u64::try_from(at).unwrap()),
who: "city".to_owned(),
addr: None,
kind,
data: crate::event::Payload::empty(),
ig: false,
})
.collect();
let mut one_by_one = VecLedger::new();
let singly: Vec<EventRef> = drafts
.iter()
.map(|draft| one_by_one.append(draft.clone()).unwrap())
.collect();
let mut at_once = VecLedger::new();
let waved = at_once.append_all(drafts).unwrap();
assert_eq!(waved, singly);
assert_eq!(
at_once.raw_lines().unwrap(),
one_by_one.raw_lines().unwrap()
);
}
#[test]
fn suite_error_paths_use_invalid_args() {
// Anchors the AxCode used when a draft cannot even serialize.
let err = AxError::failure(AxCode::InvalidArgs, "x", "y").with_recovery("z");
assert_eq!(err.code(), &AxCode::InvalidArgs);
}
}