fix: better validate for dev_brain and 50k reminder not triggering rebuild
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -568,14 +568,15 @@ impl Generator {
|
||||
mut progress: impl FnMut(u32, u32, Option<f32>),
|
||||
mut phase: impl FnMut(&'static str),
|
||||
) -> Result<GenerationOutput, String> {
|
||||
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<CheckpointSelection, String> {
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user