feat(yachay): notebooks reproducibles — yachay-core + demo
yachay-core: notebook como secuencia de celdas (orden de lectura) + DAG de dependencias (orden de ejecución). Celdas markdown/código/embed con content_hash BLAKE3; editar una propaga staleness a descendientes; digest Merkle por celda (content_hash ‖ digests upstream) y notebook_digest que certifica reproducibilidad. Demo CLI en apps/yachay. 14 tests. Sin kernel ni UI, #![forbid(unsafe_code)]. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//! La celda — la unidad de un notebook.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Identificador de una celda dentro de su notebook.
|
||||
pub type CellId = u64;
|
||||
|
||||
/// Qué clase de contenido lleva una celda.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CellKind {
|
||||
/// Prosa en markdown.
|
||||
Markdown,
|
||||
/// Código ejecutable en un lenguaje.
|
||||
Code { language: String },
|
||||
/// Una visualización de un módulo brahman (`"dominium"`, `"pineal"`,
|
||||
/// `"takiy"`) — yachay integra el ecosistema.
|
||||
Embed { module: String },
|
||||
}
|
||||
|
||||
impl CellKind {
|
||||
/// Etiqueta estable que distingue las clases en el hash de contenido.
|
||||
fn tag(&self) -> &'static str {
|
||||
match self {
|
||||
CellKind::Markdown => "md",
|
||||
CellKind::Code { .. } => "code",
|
||||
CellKind::Embed { .. } => "embed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Estado de frescura de una celda respecto de sus dependencias.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CellState {
|
||||
/// Al día: su resultado corresponde a las fuentes actuales.
|
||||
Fresh,
|
||||
/// Obsoleta: ella o una dependencia cambió y falta re-ejecutar.
|
||||
Stale,
|
||||
/// Su última ejecución falló.
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Una celda del notebook: contenido + sus dependencias lógicas.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Cell {
|
||||
pub id: CellId,
|
||||
pub kind: CellKind,
|
||||
/// El texto fuente — markdown, código o el spec del embed.
|
||||
pub source: String,
|
||||
/// Celdas prerrequisito (deben ejecutarse antes).
|
||||
pub depends_on: Vec<CellId>,
|
||||
pub state: CellState,
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// Hash BLAKE3 del contenido propio de la celda — clase + fuente.
|
||||
/// No incluye dependencias; eso es el [`crate::Notebook::digest`].
|
||||
pub fn content_hash(&self) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
h.update(self.kind.tag().as_bytes());
|
||||
h.update(b"\0");
|
||||
match &self.kind {
|
||||
CellKind::Code { language } => {
|
||||
h.update(language.as_bytes());
|
||||
}
|
||||
CellKind::Embed { module } => {
|
||||
h.update(module.as_bytes());
|
||||
}
|
||||
CellKind::Markdown => {}
|
||||
}
|
||||
h.update(b"\0");
|
||||
h.update(self.source.as_bytes());
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cell(kind: CellKind, source: &str) -> Cell {
|
||||
Cell { id: 1, kind, source: source.into(), depends_on: vec![], state: CellState::Stale }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_content_hashes_equal() {
|
||||
let a = cell(CellKind::Markdown, "hola");
|
||||
let b = cell(CellKind::Markdown, "hola");
|
||||
assert_eq!(a.content_hash(), b.content_hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kind_changes_the_hash() {
|
||||
let md = cell(CellKind::Markdown, "x");
|
||||
let code = cell(CellKind::Code { language: "rust".into() }, "x");
|
||||
assert_ne!(md.content_hash(), code.content_hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_changes_the_hash() {
|
||||
let rust = cell(CellKind::Code { language: "rust".into() }, "1+1");
|
||||
let python = cell(CellKind::Code { language: "python".into() }, "1+1");
|
||||
assert_ne!(rust.content_hash(), python.content_hash());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user