v0.10.0: análise/prova/teste das críticas de engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos jepa/pensamento/moe/python em 26 submódulos (API estável via re-exports; 'Amdahl de compilação' refutada por experimento controlado, builds frios neutros), checkpoints atômicos SHA-256 puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; correções da proposta: sem destruir estado anterior, manifesto com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 (fronteira tipada da convexidade N-2; alegação compile-time corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do controlador refutada no fonte; Vec<Var>; adaptador MoeComPapel bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia PT-BR com checkpoints íntegros
06e4b6f verified Download src/checkpoint.rs from PowerMachine/khtst-rust: direct link, hf CLI and curl.
- Browser
- Download file 15.5 kB
-
https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/checkpoint.rs
- Command line
-
hf download hf://PowerMachine/khtst-rust/src/checkpoint.rs
-
curl -L -o checkpoint.rs https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/checkpoint.rs
15.5 kB
| //! Checkpoints atômicos com prova de integridade criptográfica (v0.10.0). | |
| //! | |
| //! **Crítica Qwen3.8 lote 3, nº 3 ("Checkpointing Não-Atômico") — ACEITA | |
| //! COM CORREÇÕES (r-35 §4).** A proposta original tinha três falhas: | |
| //! | |
| //! 1. **Destruição do estado anterior:** `File::create(caminho_pesos)` | |
| //! trunca o checkpoint ANTERIOR antes de escrever o novo — uma queda | |
| //! de energia no meio da escrita perde AMBOS. A atomicidade real exige | |
| //! escrita em `.tmp` + `rename(2)` (o POSIX garante troca atômica). | |
| //! 2. **Manifesto sem barreira:** `fs::write(caminho_meta, ...)` sem | |
| //! `sync_all` — o manifesto pode não atingir o disco quando os pesos | |
| //! já atingiram (ou vice-versa), violando a própria ordenação alegada. | |
| //! 3. **Dependência nova (bincode) + `unwrap()`** em caminho de I/O. | |
| //! | |
| //! Esta implementação: SHA-256 **puro em Rust** (FIPS 180-4, sem crates | |
| //! externas — crates.io inacessível no ambiente), escrita tmp→fsync→ | |
| //! rename→fsync(dir) para AMBOS os arquivos, e manifesto lado-a-lado | |
| //! `{caminho}.manifest.json` com hash + tamanho. | |
| //! | |
| //! **Teo CP-1 (atomicidade da troca).** `rename(2)` é atômico em relação | |
| //! a leitores concorrentes e crashes: em qualquer instante o caminho | |
| //! resolve OU para o arquivo antigo OU para o novo, nunca uma mistura. | |
| //! Logo um crash em qualquer ponto deixa o caminho final = checkpoint | |
| //! antigo (crash antes do rename) ou novo (depois). ∎ (garantia POSIX) | |
| //! | |
| //! **Teo CP-2 (durabilidade ordenada).** Com a disciplina | |
| //! `write(W.tmp) → fsync(W.tmp) → rename(W) → write(M.tmp) → | |
| //! fsync(M.tmp) → rename(M) → fsync(dir)`, após reboot: se `M` existe, | |
| //! então o rename de `W` foi durável (executado e journalizado antes) e | |
| //! os blocos de dados de `W` foram forçados por `fsync(W.tmp)` — portanto | |
| //! `SHA-256(W) = M.hash` (a menos de colisão, CP-3). Se `M` não existe, | |
| //! o estado é tratado como INVÁLIDO (não silenciosamente errado). ∎ | |
| //! | |
| //! **Teo CP-3 (detecção de corrupção).** Para W' ≠ W com |W'| = |W|, | |
| //! `P[SHA-256(W') = SHA-256(W)] ≤ 2^-256` (ataque direto) e, para | |
| //! qualquer par distinto, ≤ 2^-128 (limite de aniversário). Truncamentos | |
| //! e gravações parciais são capturados DETERMINISTICAMENTE pelo campo | |
| //! `tamanho`. Nota honesta: a alegação original "matematicamente | |
| //! idêntico" é exagerada — o correto é indistinguibilidade computacional | |
| //! com probabilidade de colisão ≤ 2^-128. ∎ | |
| //! | |
| //! **Compatibilidade (regra da casa: nada é removido):** checkpoints da | |
| //! v0.9.0 (sem manifesto) continuam carregáveis pelos `carregar` legados; | |
| //! `carregar_verificado` exige manifesto e recusa o contrário. | |
| use candle_core::Result; | |
| use std::fs::File; | |
| use std::io::Write; | |
| use std::path::Path; | |
| pub const VERSAO_CHECKPOINT: &str = "0.10.0"; | |
| // --------------------------------------------------------------------------- | |
| // SHA-256 — FIPS 180-4, implementação autocontida (sem dependências) | |
| // --------------------------------------------------------------------------- | |
| const K_SHA256: [u32; 64] = [ | |
| 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, | |
| 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, | |
| 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, | |
| 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
| 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, | |
| 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, | |
| 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, | |
| 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
| 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, | |
| 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, | |
| 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, | |
| ]; | |
| /// SHA-256 de `dados` — 32 bytes (FIPS 180-4 §6.2.2). | |
| pub fn sha256(dados: &[u8]) -> [u8; 32] { | |
| let mut h: [u32; 8] = [ | |
| 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, | |
| 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, | |
| ]; | |
| // padding: 0x80, zeros, comprimento de bit big-endian em 64 bits | |
| let bitlen = (dados.len() as u64).wrapping_mul(8); | |
| let mut msg = dados.to_vec(); | |
| msg.push(0x80); | |
| while msg.len() % 64 != 56 { | |
| msg.push(0); | |
| } | |
| msg.extend_from_slice(&bitlen.to_be_bytes()); | |
| for bloco in msg.chunks_exact(64) { | |
| let mut w = [0u32; 64]; | |
| for (i, j) in (0..64).step_by(4).enumerate() { | |
| w[i] = u32::from_be_bytes([bloco[j], bloco[j + 1], bloco[j + 2], bloco[j + 3]]); | |
| } | |
| for t in 16..64 { | |
| let s0 = w[t - 15].rotate_right(7) ^ w[t - 15].rotate_right(18) ^ (w[t - 15] >> 3); | |
| let s1 = w[t - 2].rotate_right(17) ^ w[t - 2].rotate_right(19) ^ (w[t - 2] >> 10); | |
| w[t] = w[t - 16] | |
| .wrapping_add(s0) | |
| .wrapping_add(w[t - 7]) | |
| .wrapping_add(s1); | |
| } | |
| let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) = | |
| (h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]); | |
| for t in 0..64 { | |
| let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); | |
| let ch = (e & f) ^ ((!e) & g); | |
| let t1 = hh | |
| .wrapping_add(s1) | |
| .wrapping_add(ch) | |
| .wrapping_add(K_SHA256[t]) | |
| .wrapping_add(w[t]); | |
| let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); | |
| let maj = (a & b) ^ (a & c) ^ (b & c); | |
| let t2 = s0.wrapping_add(maj); | |
| hh = g; g = f; f = e; e = d.wrapping_add(t1); | |
| d = c; c = b; b = a; a = t1.wrapping_add(t2); | |
| } | |
| h[0] = h[0].wrapping_add(a); h[1] = h[1].wrapping_add(b); | |
| h[2] = h[2].wrapping_add(c); h[3] = h[3].wrapping_add(d); | |
| h[4] = h[4].wrapping_add(e); h[5] = h[5].wrapping_add(f); | |
| h[6] = h[6].wrapping_add(g); h[7] = h[7].wrapping_add(hh); | |
| } | |
| let mut saida = [0u8; 32]; | |
| for (i, x) in h.iter().enumerate() { | |
| saida[i * 4..i * 4 + 4].copy_from_slice(&x.to_be_bytes()); | |
| } | |
| saida | |
| } | |
| /// SHA-256 em hexadecimal minúsculo (64 caracteres). | |
| pub fn sha256_hex(dados: &[u8]) -> String { | |
| sha256(dados).iter().map(|b| format!("{b:02x}")).collect() | |
| } | |
| // --------------------------------------------------------------------------- | |
| // salva / carrega atômicos | |
| // --------------------------------------------------------------------------- | |
| /// Caminho do manifesto lado-a-lado de `caminho`. | |
| pub fn caminho_manifesto(caminho: &str) -> String { | |
| format!("{caminho}.manifest.json") | |
| } | |
| fn err_io(e: std::io::Error, ctx: &str) -> candle_core::Error { | |
| candle_core::Error::Msg(format!("{ctx}: {e}")) | |
| } | |
| /// fsync de um diretório (durabilidade do rename — POSIX). Unix apenas; | |
| /// em outras plataformas é no-op documentado. | |
| fn fsync_dir(dir: &Path) -> std::io::Result<()> { | |
| { | |
| let d = File::open(dir)?; | |
| d.sync_all()?; | |
| } | |
| { | |
| let _ = dir; | |
| } | |
| Ok(()) | |
| } | |
| /// Escrita atômica de `conteudo` em `caminho` com fsync + rename e | |
| /// manifesto `{caminho}.manifest.json` (hash SHA-256 + tamanho). | |
| /// | |
| /// Ordem (Teo CP-2): write(tmp) → fsync(tmp) → rename(final) para o | |
| /// conteúdo e para o manifesto; fsync(dir) ao final. | |
| pub fn salvar_atomico(caminho: &str, conteudo: &str) -> Result<()> { | |
| let dados = conteudo.as_bytes(); | |
| let hash = sha256_hex(dados); | |
| let tmp = format!("{caminho}.tmp"); | |
| // 1) conteúdo: tmp → fsync → rename (Teo CP-1: troca atômica) | |
| { | |
| let mut f = File::create(&tmp).map_err(|e| err_io(e, "checkpoint: criar tmp"))?; | |
| f.write_all(dados).map_err(|e| err_io(e, "checkpoint: escrever tmp"))?; | |
| f.sync_all().map_err(|e| err_io(e, "checkpoint: fsync tmp"))?; | |
| } | |
| std::fs::rename(&tmp, caminho).map_err(|e| err_io(e, "checkpoint: rename"))?; | |
| // 2) manifesto: mesma disciplina (a proposta original usava fs::write | |
| // simples — sem barreira, o manifesto pode perder-se sozinho) | |
| let manifest = serde_json::json!({ | |
| "versao": VERSAO_CHECKPOINT, | |
| "algoritmo": "sha256", | |
| "hash_sha256": hash, | |
| "tamanho": dados.len(), | |
| }); | |
| let mtxt = serde_json::to_string(&manifest) | |
| .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; | |
| let mtmp = format!("{}.manifest.json.tmp", caminho); | |
| { | |
| let mut f = File::create(&mtmp).map_err(|e| err_io(e, "manifesto: criar tmp"))?; | |
| f.write_all(mtxt.as_bytes()).map_err(|e| err_io(e, "manifesto: escrever tmp"))?; | |
| f.sync_all().map_err(|e| err_io(e, "manifesto: fsync tmp"))?; | |
| } | |
| std::fs::rename(&mtmp, caminho_manifesto(caminho)) | |
| .map_err(|e| err_io(e, "manifesto: rename"))?; | |
| // 3) durabilidade dos renames (POSIX: rename journalizado no dir) | |
| if let Some(pai) = Path::new(caminho).parent() { | |
| fsync_dir(pai).map_err(|e| err_io(e, "checkpoint: fsync dir"))?; | |
| } | |
| Ok(()) | |
| } | |
| /// Lê `caminho` verificando o manifesto (hash + tamanho). Recusa | |
| /// manifesto ausente ou divergente — Teo CP-3. | |
| pub fn carregar_verificado(caminho: &str) -> Result<String> { | |
| let dados = std::fs::read(caminho).map_err(|e| err_io(e, "checkpoint: ler"))?; | |
| let mtxt = std::fs::read_to_string(caminho_manifesto(caminho)) | |
| .map_err(|_| candle_core::Error::Msg(format!( | |
| "checkpoint sem manifesto (legado v0.9.0?): {caminho}")))?; | |
| let m: serde_json::Value = serde_json::from_str(&mtxt) | |
| .map_err(|e| candle_core::Error::Msg(format!("manifesto ilegível: {e}")))?; | |
| let hash_esp = m["hash_sha256"].as_str().unwrap_or(""); | |
| let tam_esp = m["tamanho"].as_u64().unwrap_or(u64::MAX); | |
| let hash_rec = sha256_hex(&dados); | |
| if (dados.len() as u64) != tam_esp { | |
| candle_core::bail!( | |
| "checkpoint corrompido: tamanho {} ≠ manifesto {} ({caminho})", | |
| dados.len(), tam_esp); | |
| } | |
| if hash_rec != hash_esp { | |
| candle_core::bail!( | |
| "checkpoint corrompido: SHA-256 divergente ({caminho})"); | |
| } | |
| String::from_utf8(dados) | |
| .map_err(|e| candle_core::Error::Msg(format!("utf8: {e}"))) | |
| } | |
| /// Verificação sem exceção (para uso em Python/CLI antes de resume). | |
| pub fn validar(caminho: &str) -> bool { | |
| carregar_verificado(caminho).is_ok() | |
| } | |
| mod testes_checkpoint { | |
| use super::*; | |
| fn tmp_dir(nome: &str) -> std::path::PathBuf { | |
| let d = std::env::temp_dir().join(format!("khtst_cp_{nome}_{}", std::process::id())); | |
| let _ = std::fs::remove_dir_all(&d); | |
| std::fs::create_dir_all(&d).unwrap(); | |
| d | |
| } | |
| /// CP-0a — SHA-256 contra os vetores oficiais FIPS 180-4. | |
| fn cp0a_sha256_vetores_fips() { | |
| assert_eq!( | |
| sha256_hex(b""), | |
| "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); | |
| assert_eq!( | |
| sha256_hex(b"abc"), | |
| "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); | |
| assert_eq!( | |
| sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), | |
| "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); | |
| // vetor de 4 blocos (256 bytes) — exercita o laço de blocos | |
| let longo = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn".repeat(4); | |
| assert_eq!(sha256_hex(longo.as_bytes()).len(), 64); | |
| } | |
| /// CP-1a — salvar produz final + manifesto; tmp não permanece; | |
| /// carregar_verificado devolve o conteúdo bit-a-bit. | |
| fn cp1a_salvar_carregar_roundtrip() { | |
| let d = tmp_dir("rt"); | |
| let p = d.join("ck.json"); | |
| salvar_atomico(p.to_str().unwrap(), "{\"v0\":[1.0,2.0]}").unwrap(); | |
| assert!(p.exists()); | |
| assert!(d.join("ck.json.manifest.json").exists()); | |
| assert!(!d.join("ck.json.tmp").exists()); | |
| assert!(!d.join("ck.json.manifest.json.tmp").exists()); | |
| assert_eq!(carregar_verificado(p.to_str().unwrap()).unwrap(), | |
| "{\"v0\":[1.0,2.0]}"); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| /// CP-3a — adulteração de 1 byte é detectada (hash divergente). | |
| fn cp3a_adulteracao_detectada() { | |
| let d = tmp_dir("t"); | |
| let p = d.join("ck.json"); | |
| salvar_atomico(p.to_str().unwrap(), "conteudo original").unwrap(); | |
| std::fs::write(&p, "conteudo originalX").unwrap(); | |
| assert!(carregar_verificado(p.to_str().unwrap()).is_err()); | |
| assert!(!validar(p.to_str().unwrap())); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| /// CP-3b — truncamento detectado pelo tamanho (determinístico). | |
| fn cp3b_truncamento_detectado() { | |
| let d = tmp_dir("tr"); | |
| let p = d.join("ck.json"); | |
| salvar_atomico(p.to_str().unwrap(), "0123456789").unwrap(); | |
| std::fs::write(&p, "0123").unwrap(); | |
| assert!(carregar_verificado(p.to_str().unwrap()).is_err()); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| /// CP-4a — manifesto ausente ⇒ recusa (estado legado é do `carregar`). | |
| fn cp4a_manifesto_ausente_recusado() { | |
| let d = tmp_dir("leg"); | |
| let p = d.join("ck.json"); | |
| salvar_atomico(p.to_str().unwrap(), "x").unwrap(); | |
| std::fs::remove_file(d.join("ck.json.manifest.json")).unwrap(); | |
| assert!(carregar_verificado(p.to_str().unwrap()).is_err()); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| /// CP-1b — falha de escrita PRESERVA o checkpoint anterior | |
| /// (atomicidade prática: dir somente-leitura impede o create do tmp). | |
| fn cp1b_falha_preserva_anterior() { | |
| let d = tmp_dir("fa"); | |
| let p = d.join("ck.json"); | |
| salvar_atomico(p.to_str().unwrap(), "estado_bom").unwrap(); | |
| // dir somente-leitura: File::create(tmp) falha | |
| let mut perms = std::fs::metadata(&d).unwrap().permissions(); | |
| { | |
| use std::os::unix::fs::PermissionsExt; | |
| perms.set_mode(0o555); | |
| } | |
| std::fs::set_permissions(&d, perms).unwrap(); | |
| let r = salvar_atomico(p.to_str().unwrap(), "estado_novo"); | |
| let mut perms2 = std::fs::metadata(&d).unwrap().permissions(); | |
| { | |
| use std::os::unix::fs::PermissionsExt; | |
| perms2.set_mode(0o755); | |
| } | |
| std::fs::set_permissions(&d, perms2).unwrap(); | |
| assert!(r.is_err(), "escrita deveria falhar em dir somente-leitura"); | |
| // o anterior permanece ÍNTEGRO e válido (Teo CP-1) | |
| assert_eq!(carregar_verificado(p.to_str().unwrap()).unwrap(), "estado_bom"); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| /// CP-5a — manifesto trocado entre arquivos (hash de outro) recusa. | |
| fn cp5a_manifesto_de_outro_recusado() { | |
| let d = tmp_dir("sw"); | |
| let a = d.join("a.json"); | |
| let b = d.join("b.json"); | |
| salvar_atomico(a.to_str().unwrap(), "AAAA").unwrap(); | |
| salvar_atomico(b.to_str().unwrap(), "BBBB").unwrap(); | |
| std::fs::copy(d.join("b.json.manifest.json"), d.join("a.json.manifest.json")).unwrap(); | |
| assert!(carregar_verificado(a.to_str().unwrap()).is_err()); | |
| let _ = std::fs::remove_dir_all(&d); | |
| } | |
| } | |