Round-digest lows 2: length-framed source digest, lock cleanup, honest drift text, window tests #35

Merged
weksa merged 1 commit from weksa-lowering/round-digest-lows-2 into main 2026-11-10 08:56:21 +00:00
3 changed files with 120 additions and 17 deletions

View file

@ -40,8 +40,10 @@ use crate::store::{CorpusStore, EntityStore, InventoryStore, PacketStore, open_c
/// The digest of the prompt builder's source: `context.rs`, then `split.rs`,
/// whose scheme and digest function decide which lines the prompt holds as
/// Train and name every prompt. Any edit to either, comments included, ends a
/// run; code outside those files is not covered.
/// Train and name every prompt. Each file enters the digest behind its length,
/// so the pair cannot be re-cut into another pair with the same digest. Any
/// edit to either, comments included, ends a run; code outside those files is
/// not covered.
pub const PROMPT_SOURCE_DIGEST: u64 =
fnv1a64_of(&[include_bytes!("context.rs"), include_bytes!("split.rs")]);
@ -349,6 +351,9 @@ impl fmt::Display for RunError {
RunError::Exists => f.write_str("the root already holds a run"),
RunError::NoRun => f.write_str("the root holds no run"),
RunError::Malformed(what) => write!(f, "malformed {what}"),
RunError::Drift(DriftOf::Prompts) => f.write_str(
"drift in Prompts (the prompt source digest covers context.rs and split.rs)",
),
RunError::Drift(of) => write!(f, "drift in {of:?}"),
RunError::NoInventory => f.write_str("the inventory holds no revision"),
RunError::Io(kind) => write!(f, "io error {kind:?}"),
@ -517,7 +522,7 @@ impl RunStores {
let head = root.join(INVENTORY_FILE);
copy_new(Path::new(&record.inventory), &head)?;
// The copy is ours from here: any later failure takes it away again, or
// it would refuse every retry.
// it would refuse every retry, and so with the lock the open left beside it.
let pinned = (|| {
let record = RunRecord {
corpus_digest: file_digest(&record.corpus).ok_or(RunError::Store)?,
@ -537,7 +542,7 @@ impl RunStores {
Ok(run)
})();
if pinned.is_err() {
let _ = std::fs::remove_file(&head);
discard_inventory(root);
}
pinned
}
@ -1803,6 +1808,14 @@ mod tests {
"write {fail_at} refused"
);
assert_eq!(out.is_ok(), fail_at == 2);
if fail_at == 2 {
// The fixture accepts: a drifted literal that made it reject
// would write one round and one decision all the same.
match &out {
Ok(CycleOutcome::Decided(d)) => assert_eq!(d.verdict(), Verdict::Accepted),
_ => panic!("the second write was not a decided cycle"),
}
}
}
}
}

View file

@ -25,26 +25,32 @@ pub enum SplitScheme {
}
pub const fn fnv1a64(bytes: &[u8]) -> u64 {
fnv1a64_of(&[bytes])
fnv1a64_fold(0xcbf2_9ce4_8422_2325, bytes)
}
/// FNV-1a 64 of the parts laid end to end.
/// FNV-1a 64 of the parts, each preceded by its length as eight little-endian
/// bytes, so no two different splits of the same bytes share a digest.
pub const fn fnv1a64_of(parts: &[&[u8]]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
let mut p = 0;
while p < parts.len() {
let bytes = parts[p];
let mut i = 0;
while i < bytes.len() {
h ^= bytes[i] as u64;
h = h.wrapping_mul(0x0000_0100_0000_01b3);
i += 1;
}
h = fnv1a64_fold(h, &(parts[p].len() as u64).to_le_bytes());
h = fnv1a64_fold(h, parts[p]);
p += 1;
}
h
}
const fn fnv1a64_fold(mut h: u64, bytes: &[u8]) -> u64 {
let mut i = 0;
while i < bytes.len() {
h ^= bytes[i] as u64;
h = h.wrapping_mul(0x0000_0100_0000_01b3);
i += 1;
}
h
}
/// 16 lowercase hex digits, zero-padded: how a digest is written everywhere.
pub(crate) fn hex16(n: u64) -> String {
format!("{n:016x}")

View file

@ -370,8 +370,8 @@ fn a_store_changed_between_its_digest_and_its_open_is_drift() {
Some(RunError::Drift(DriftOf::Corpus))
);
// The same window at create: nothing is pinned and nothing is left behind,
// so the retry creates.
// The same window at create: nothing is pinned and the inventory copy and its
// lock are taken away again, so the retry creates.
let mut w = world();
let dir = w.dir.path().to_path_buf();
let root = dir.join("run");
@ -389,21 +389,101 @@ fn a_store_changed_between_its_digest_and_its_open_is_drift() {
Some(RunError::Drift(DriftOf::Corpus))
);
assert!(!root.join("inventory.cc").exists());
// Not the copy and not the lock the store's open left beside it.
assert!(!root.join("inventory.cc.lock").exists());
assert_eq!(RunStores::open(&root).err(), Some(RunError::NoRun));
RunStores::create(&root, "t", paths(), "fake-model", "bag").unwrap();
}
/// Runs `change` in the window between the pins and the open, at `open` and at
/// `create`, and returns what each refused with plus what `create` left behind.
fn window(
change: impl Fn(&mut common::World, &Path),
) -> (Option<RunError>, Option<RunError>, bool, Option<RunError>) {
let mut w = world();
let dir = w.dir.path().to_path_buf();
let root = dir.join("run");
run_over(&dir);
let opened = RunStores::open_between(&root, || change(&mut w, &root)).err();
let mut w = world();
let dir = w.dir.path().to_path_buf();
let root = dir.join("run");
let (c, i, e) = (dir.join("c.cc"), dir.join("i.cc"), dir.join("entities.cc"));
let paths = weksa::run::RunPaths {
corpus: &c,
inventory: &i,
entities: &e,
};
let created = RunStores::create_between(&root, "t", paths, "fake-model", "bag", || {
change(&mut w, &root)
})
.err();
let left = root.join("inventory.cc").exists() || root.join("inventory.cc.lock").exists();
(opened, created, left, RunStores::open(&root).err())
}
#[test]
fn an_entity_store_changed_between_its_digest_and_its_open_is_drift() {
let (opened, created, left, after) = window(|w, _| {
w.entities.put(ent("kay", "en-US", &["Kay"])).unwrap();
});
assert_eq!(opened, Some(RunError::Drift(DriftOf::Entities)));
assert_eq!(created, Some(RunError::Drift(DriftOf::Entities)));
assert!(!left);
assert_eq!(after, Some(RunError::NoRun));
}
#[test]
fn a_head_inventory_changed_between_its_digest_and_its_open_is_drift() {
let (opened, created, left, after) = window(|_, root| {
weksa::InventoryStore::open(&root.join("inventory.cc"))
.unwrap()
.admit_revision(
weksa::InventoryRevision {
n: 5,
parent: Some(4),
changes: vec![],
frozen: false,
},
vec![],
)
.unwrap();
});
assert_eq!(opened, Some(RunError::Drift(DriftOf::Inventory)));
assert_eq!(created, Some(RunError::Drift(DriftOf::Inventory)));
assert!(!left);
assert_eq!(after, Some(RunError::NoRun));
}
#[test]
fn source_digests_cover_their_files() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let digest = |name: &str| fnv1a64(&std::fs::read(src.join(name)).unwrap());
// The prompt's partition and names come from split.rs: it is covered too.
let both = [
let (context, split) = (
std::fs::read(src.join("context.rs")).unwrap(),
std::fs::read(src.join("split.rs")).unwrap(),
);
// Each file enters behind its length, so the pair has one framing.
let framed = [
(context.len() as u64).to_le_bytes().as_slice(),
&context,
(split.len() as u64).to_le_bytes().as_slice(),
&split,
]
.concat();
assert_eq!(weksa::run::PROMPT_SOURCE_DIGEST, fnv1a64(&both));
assert_eq!(weksa::run::PROMPT_SOURCE_DIGEST, fnv1a64(&framed));
assert_ne!(
weksa::run::PROMPT_SOURCE_DIGEST,
fnv1a64(&[context.clone(), split.clone()].concat()),
"bare concatenation is not the digest"
);
// Moving the boundary between the parts moves the digest.
assert_ne!(
weksa::split::fnv1a64_of(&[b"ab", b"c"]),
weksa::split::fnv1a64_of(&[b"a", b"bc"])
);
assert_ne!(weksa::run::PROMPT_SOURCE_DIGEST, digest("context.rs"));
assert_eq!(weksa::run::LOWERER_SOURCE_DIGEST, digest("lower.rs"));
assert_ne!(
@ -1387,6 +1467,10 @@ fn errors_echo_no_input() {
format!("{:?}", RunError::Drift(DriftOf::Model)),
"drift in Model"
);
// The Prompts text names the two files its digest covers, and only them.
let prompts = RunError::Drift(DriftOf::Prompts).to_string();
assert!(prompts.contains("context.rs") && prompts.contains("split.rs"));
assert_eq!(format!("{:?}", RunError::Drift(DriftOf::Prompts)), prompts);
}
#[test]