From 90a445cafdee281c1defd930fd4e568f5e5f8142 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 27 Jul 2026 23:08:47 +0200 Subject: [PATCH] fix: better validate for dev_brain and 50k reminder not triggering rebuild --- src/dev_brain.rs | 50 ++++++++++++++++++++++----- src/engine.rs | 89 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 119 insertions(+), 20 deletions(-) diff --git a/src/dev_brain.rs b/src/dev_brain.rs index a6f42a8..64fc09a 100644 --- a/src/dev_brain.rs +++ b/src/dev_brain.rs @@ -72,12 +72,13 @@ sources: - project: Registered project name path: src/example.rs symbol: optional_symbol - revision: exact-clean-git-revision - # Use hash instead of revision when the worktree is dirty or is not Git. + revision: full-or-unique-short-clean-git-revision + # Use `git rev-parse HEAD`, or a unique lowercase hex prefix of at least 7 characters. + # Use hash instead when the worktree is dirty or is not Git. --- ``` -Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. Paths are project-relative and may not escape the registered project. +Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. A revision is the current clean commit's full object ID or a unique lowercase hexadecimal prefix of at least 7 characters. Paths are project-relative and may not escape the registered project. ## Compilation @@ -1081,9 +1082,9 @@ fn validate_source(source: &SourceRecord, projects: &[RegisteredProject]) -> Res )); } if let Some(revision) = &source.revision { - return Ok( - git_state(&project.root).is_some_and(|(current, clean)| clean && current == *revision) - ); + return Ok(git_state(&project.root).is_some_and(|(current, clean)| { + clean && git_revision_matches(&project.root, revision, ¤t) + })); } let expected = source.hash.as_deref().unwrap(); if expected.len() != 64 @@ -1100,6 +1101,29 @@ fn validate_source(source: &SourceRecord, projects: &[RegisteredProject]) -> Res && !git_state(&project.root).is_some_and(|(_, clean)| clean)) } +fn git_revision_matches(root: &Path, revision: &str, current: &str) -> bool { + if revision == current { + return true; + } + if revision.len() < 7 + || revision.len() >= current.len() + || !revision + .bytes() + .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) + { + return false; + } + Command::new("git") + .args(["-C"]) + .arg(root) + .args(["rev-parse", "--verify"]) + .arg(format!("{revision}^{{commit}}")) + .output() + .ok() + .filter(|output| output.status.success()) + .is_some_and(|output| String::from_utf8_lossy(&output.stdout).trim() == current) +} + fn git_state(root: &Path) -> Option<(String, bool)> { let revision = Command::new("git") .args(["-C"]) @@ -1560,7 +1584,7 @@ mod tests { } #[test] - fn clean_git_revisions_become_stale_when_the_worktree_changes() { + fn clean_full_and_short_git_revisions_become_stale_when_the_worktree_changes() { let fixture = Fixture::new(); for arguments in [ vec!["init"], @@ -1586,11 +1610,21 @@ mod tests { ); } let revision = git_state(&fixture.project).unwrap().0; + assert!(git_revision_matches( + &fixture.project, + &revision[..7], + &revision + )); + assert!(!git_revision_matches( + &fixture.project, + &revision[..6], + &revision + )); let topic = fixture .topic("verified", &"0".repeat(64), "Revision-backed knowledge.") .replace( &format!("hash: {}", "0".repeat(64)), - &format!("revision: {revision}"), + &format!("revision: {}", &revision[..7]), ); let mut brain = fixture.brain(); write_topic(&fixture, &mut brain, &topic).unwrap(); diff --git a/src/engine.rs b/src/engine.rs index bb9abe2..dde9d8e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -568,14 +568,15 @@ impl Generator { mut progress: impl FnMut(u32, u32, Option), mut phase: impl FnMut(&'static str), ) -> Result { - let history = messages - .split_last() - .map_or(messages, |(_, history)| history); let checkpoint_present = checkpoint.is_file(); - let selected = self.select_checkpoint( - checkpoint, - conversation_tag(&settings.system_prompt, settings.reasoning_mode, history), - )?; + let selected = self.select_checkpoint(checkpoint, |tag| { + checkpoint_matches_prefix( + tag, + &settings.system_prompt, + settings.reasoning_mode, + messages, + ) + })?; if checkpoint_present && !selected.found { phase(checkpoint_rebuild_activity(selected.incompatible)); } @@ -788,7 +789,10 @@ impl Generator { return Ok(self.checkpoint.clone()); } if let Some(entry) = store.find(key, self.executor.context()) { - if self.select_checkpoint(&entry.checkpoint, entry.tag)?.found { + if self + .select_checkpoint(&entry.checkpoint, |tag| tag == entry.tag)? + .found + { store.touch(&entry)?; self.last_store_tokens = entry.tokens; return Ok(Some(entry.checkpoint)); @@ -955,10 +959,10 @@ impl Generator { fn select_checkpoint( &mut self, checkpoint: &Path, - expected_tag: [u8; 32], + matches: impl Fn([u8; 32]) -> bool, ) -> Result { let resident_hit = self.activate_resident(checkpoint.to_owned())?; - if resident_hit && self.executor.checkpoint_tag() == expected_tag { + if resident_hit && matches(self.executor.checkpoint_tag()) { self.checkpoint = Some(checkpoint.to_owned()); self.metrics.kv_lookup(KvLookup::MemoryHit); return Ok(CheckpointSelection { @@ -976,7 +980,7 @@ impl Generator { incompatible: false, }); } - let found = self.executor.checkpoint_tag() == expected_tag; + let found = matches(self.executor.checkpoint_tag()); self.metrics.kv_lookup(if found { KvLookup::MemoryHit } else { @@ -995,7 +999,7 @@ impl Generator { .load_checkpoint(checkpoint, &mut |bytes| self.metrics.kv_read_bytes(bytes)); self.metrics .kv_read_finished(started.elapsed(), loaded.is_err()); - let found = matches!(loaded, Ok(true)) && self.executor.checkpoint_tag() == expected_tag; + let found = matches!(loaded, Ok(true)) && matches(self.executor.checkpoint_tag()); let incompatible = loaded.is_err(); let lookup = match loaded { Ok(true) if found => KvLookup::DiskHit, @@ -1527,6 +1531,20 @@ fn conversation_tag(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn Sha256::digest(conversation_key(system, reasoning, messages)).into() } +#[cfg(target_os = "macos")] +fn checkpoint_matches_prefix( + checkpoint: [u8; 32], + system: &str, + reasoning: ReasoningMode, + messages: &[ChatTurn], +) -> bool { + // ponytail: appended control messages are few; carry incremental hashes if + // scanning a genuinely changed, very long history becomes measurable. + (0..messages.len()) + .rev() + .any(|end| checkpoint == conversation_tag(system, reasoning, &messages[..end])) +} + #[cfg(target_os = "macos")] fn resident_key(directory: &Path, tag: [u8; 32]) -> PathBuf { let mut name = String::with_capacity(64); @@ -1735,6 +1753,53 @@ mod sampling_tests { assert!(conversation_key("System", ReasoningMode::High, &messages).starts_with(&prefix)); } + #[test] + fn checkpoint_tag_accepts_an_unchanged_prefix_before_reminders() { + let mut messages = vec![ChatTurn { + user: true, + tool: false, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: "Question".into(), + }]; + let checkpoint = conversation_tag("System", ReasoningMode::High, &messages); + messages.extend([ + ChatTurn { + user: false, + tool: true, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: "Tool result".into(), + }, + ChatTurn { + user: false, + tool: false, + system: true, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: "System prompt reminder".into(), + }, + ]); + + assert!(checkpoint_matches_prefix( + checkpoint, + "System", + ReasoningMode::High, + &messages, + )); + assert!(!checkpoint_matches_prefix( + checkpoint, + "Changed", + ReasoningMode::High, + &messages, + )); + } + #[test] fn bootstrap_key_is_the_prefix_before_dynamic_session_context() { let system = "System\n\nProject instructions from AGENTS.md:\n\nkeep this";