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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
//! Thin delegate onto [`tatara_lisp::hash::hex_blake3_of_json`] — the
//! workspace-wide ONE substrate owner of the `serde_json::to_vec(v)
//! .unwrap_or_default()` + `hex::encode(blake3::hash(&bytes).as_bytes())`
//! two-line chain every `T: Serialize` → BLAKE3-hex identity slot in
//! this crate restated by hand pre-lift.
//!
//! Pre-lift the SAME two-line chain was hand-authored at TWO workspace-
//! visible sites in `tatara-ui` past the ★★ PRIME-DIRECTIVE ≥ 2
//! duplication threshold — each projecting a `T: Serialize` value onto
//! its 64-lowercase-hex BLAKE3 identity string:
//!
//! * [`crate::theme::ThemeSpec::id`] — the content-addressable
//! [`crate::theme::ThemeId`] a `(deftheme …)` spec derives. Feeds
//! the `ThemeRegistry::resolve` cache key + the identity token
//! `tatara replay <theme-id>` receives.
//! * [`crate::event::EventStream::run_hash`] — the run-identity
//! [`String`] a `Vec<UiEvent>` produces. Feeds the `tatara replay
//! <hash>` reproduce-past-run path pinned in the crate's own
//! `stream_run_hash_is_deterministic` test.
//!
//! Both sites walked the SAME two-link chain — serialize `self` via
//! `serde_json::to_vec` with `.unwrap_or_default()` for the residual-
//! error corner, then hand the byte slice to
//! `hex::encode(blake3::hash(&bytes).as_bytes())` — differing only in
//! how the returned 64-char `String` is subsequently wrapped
//! ([`crate::theme::ThemeId`]-newtype at the theme site, bare `String`
//! at the event-stream site). Post-lift each callsite reads
//! `hex_blake3_of_json(self)` and the two-link chain lives at ONE
//! substrate owner.
//!
//! ### Post-lift redirect (workspace-wide unification)
//!
//! Since a follow-up round routed `tatara-terreiro::compute_id` and
//! `tatara-nix::store::StoreHash::of` — the third and fourth
//! consumers of the same identity-projection shape — the substrate is
//! now owned at `tatara_lisp::hash::hex_blake3_of_json`, upstream of
//! every current consumer (`tatara-lisp` is depended on by every
//! `#[derive(TataraDomain)]` crate). This function stays here as a
//! thin re-export delegate so in-crate callsites keep their local
//! `crate::hash::hex_blake3_of_json` spelling, but the composition is
//! authored at the workspace root of the identity-projection axis.
//! Production code in this crate no longer reaches for `blake3` or
//! `hex` directly (the substrate does), so both demote to
//! `[dev-dependencies]` in the manifest — a future re-introduction of
//! the two-line chain here would ALSO need to re-promote both deps,
//! surfacing the drift at review time.
//!
//! Sibling in shape to `tatara_process::hash::hex_blake3` (the flat
//! `hex::encode(blake3::hash(bytes).as_bytes())` half — the byte-input
//! projector without the `serde_json::to_vec` prefix) and to
//! `tatara_process::three_pillar::pillar_bytes` (the `serde_json::to_vec
//! (v).unwrap_or_default()` half — the value-to-bytes projector without
//! the hex-BLAKE3 tail). Those two live in `tatara-process` because
//! that crate owns the three-pillar attestation surface; the workspace-
//! wide value-to-hex-BLAKE3 identity projector lives at
//! `tatara_lisp::hash` because every downstream consumer already
//! depends on `tatara-lisp` for its `#[derive(TataraDomain)]`.
//!
//! ### `T: Serialize + ?Sized`
//!
//! The `?Sized` relaxation matches the sibling
//! `tatara_process::three_pillar::pillar_bytes` bound so the primitive
//! accepts BOTH owned receivers (`&ThemeSpec`, `&EventStream`) AND
//! unsized borrows (`&str` — a future consumer that reaches for the
//! BLAKE3-hex of a string literal without an owned re-materialization).
//! Every consumer receives its handle through a `&` reference, so the
//! bound is not load-bearing at the two current callsites — but
//! removing it would silently reject the `&str` corner at the type
//! level.
//!
//! ### `#[must_use]`
//!
//! Every consumer either stores the returned hex into an identity
//! newtype ([`crate::theme::ThemeId`]) or feeds it directly to a
//! `tatara replay <hash>` command line. Dropping the return means the
//! hash was computed for no observable reason; the attribute surfaces
//! that as a warning at every consumer site.
//!
//! Theory anchor: THEORY.md §V.3 (three-pillar attestation — the
//! canonical `serde_json → BLAKE3 → hex` value-identity projection is
//! defined at the substrate; this composer routes `tatara-ui`'s two
//! run/theme-identity slots onto the SAME shape). THEORY.md §VI.1
//! (generation over composition — the two-line chain recurred at TWO
//! hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
//! trigger, and is lifted to ONE substrate owner here).
use Serialize;
/// The canonical `"blake3:"` scheme prefix — a thin `pub use`
/// re-export delegate onto [`tatara_lisp::hash::BLAKE3_SCHEME_PREFIX`],
/// the workspace-wide ONE substrate owner of the scheme literal on
/// the READ (predicate) AND WRITE (compose) axes.
///
/// ## Why the substrate lives at `tatara-lisp`, not here
///
/// The scheme-prefix surface was opened LOCAL to this crate in an
/// earlier round (commit `e9baa2b`), owning the two `Renderer`
/// WRITE-side callsites and the negative-form READ pin at
/// [`tests::hex_blake3_of_json_is_64_lowercase_hex_chars`]. That
/// resolved the two `tatara-ui` write sites but left the scheme
/// literal owned at the CLI-UX-crate layer, forcing every
/// `tatara-lisp`-depending crate downstream of the identity-projection
/// axis (`tatara-nix::store::StoreHash` producers, `tatara-terreiro`
/// snapshot-identity emitters, any future `#[derive(TataraDomain)]`
/// consumer that wraps a bare BLAKE3 hex into the canonical
/// three-pillar wire form) that reached for the same scheme-prefixed
/// wrap to either re-author `format!("blake3:{X}", …)` OR take a new
/// dep on `tatara-ui`.
///
/// The lift now places the constant at the ONE crate every current +
/// future consumer of the identity-projection axis already depends on
/// — [`hex_blake3_of_json`] (the bare-hex sibling) already lives at
/// `tatara_lisp::hash` for the exact same reason, and this constant
/// now lives alongside it. `tatara-ui`'s existing spelling
/// (`crate::hash::BLAKE3_SCHEME_PREFIX`) is preserved via this
/// `pub use` re-export so no in-crate consumer has to change its
/// import path.
///
/// See the substrate owner's docstring at
/// [`tatara_lisp::hash::BLAKE3_SCHEME_PREFIX`] for the full
/// `tatara-engine::pillar_hash` (compute-and-wrap) axis-partition
/// discussion.
pub use BLAKE3_SCHEME_PREFIX;
/// The [`BLAKE3_SCHEME_PREFIX`]-prefixed wire form of an already-
/// computed BLAKE3 hex handle — a thin `pub use` re-export delegate
/// onto [`tatara_lisp::hash::blake3_scheme_display`], the
/// workspace-wide ONE substrate owner of the `format!("blake3:{<hex>}")`
/// one-line wrap chain the render surface restated at TWO WRITE sites
/// pre-lift ([`crate::render::Renderer::artifact`] +
/// [`crate::render::Renderer::summary`]).
///
/// The re-export keeps in-crate spellings
/// (`crate::hash::blake3_scheme_display(<hash>)`) stable while the
/// canonical owner sits at the same layer as the bare-hex sibling
/// [`hex_blake3_of_json`] that every downstream identity-projection
/// consumer already reaches through. See the substrate owner's
/// docstring at [`tatara_lisp::hash::blake3_scheme_display`] for the
/// pre-lift consumer inventory, the `H: Display` polymorphism
/// rationale, and the `tatara-engine::pillar_hash` (compute-and-wrap)
/// axis-partition.
pub use blake3_scheme_display;
/// The lowercase-64-hex BLAKE3 digest of `v`'s canonical JSON
/// serialization — a thin delegate onto
/// [`tatara_lisp::hash::hex_blake3_of_json`], the workspace-wide ONE
/// substrate owner of the two-line
/// `serde_json::to_vec(v).unwrap_or_default()` +
/// `hex::encode(blake3::hash(&bytes).as_bytes())` chain every
/// `T: Serialize` → identity-string projector walks. This re-export
/// keeps in-crate callsites spelled as
/// `crate::hash::hex_blake3_of_json` for locality while the byte-
/// projection is authored at the workspace root.
///
/// # Invariants
///
/// - **Length:** the returned string is always exactly 64 chars
/// (BLAKE3's 32-byte digest encoded as lowercase hex).
/// - **Charset:** every char is one of `[0-9a-f]` (lowercase).
/// - **Determinism:** byte-identical output across runs for the same
/// input value's canonical JSON — pinned at
/// [`tests::hex_blake3_of_json_is_deterministic_for_identical_input`].
/// - **Residual arm:** a value whose `serde::Serialize` impl returns
/// an error (unreachable for every current consumer whose derived
/// `Serialize` is infallible) hashes the empty byte slice — matching
/// the pre-lift `.unwrap_or_default()` corner byte-for-byte.
///
/// Byte-shape parity with the pre-lift hand-authored two-line chain is
/// pinned at
/// [`tests::hex_blake3_of_json_matches_pre_lift_hand_authored_chain_bytewise`],
/// so a regression that reshaped the internal composition (a switch to
/// `blake3::hash(x).to_hex().to_string()`, a swap of `hex::encode`'s
/// `.as_bytes()` slice for a different byte-projection) still passes
/// only because it observably produces the same bytes; the pin fixes
/// the OBSERVABLE contract.
Sized>