Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3273,7 +3273,7 @@ impl LatestCodeTextGenerationV1 {
));
};
drop(slot);
let mut publish_claim = TextHeadOpenClaimV1::new(&self.text_projection_build);
let _publish_claim = TextHeadOpenClaimV1::new(&self.text_projection_build);
let CodeTextArtifactBuildV1 {
builder,
source,
Expand Down Expand Up @@ -3312,18 +3312,23 @@ impl LatestCodeTextGenerationV1 {
)
.map_err(map_text_artifact_error)?;
let needs_clone_successor = !reader.has_clone_fingerprints();
let prior = reader.verified_artifact().clone();
// Match the cold-open path: install owners first, then publish Ready.
// Publishing Ready before a failed install (admission ceiling / shrink)
// would leave dashboard/MCP progress claiming a ready generation that
// cannot serve queries.
self.install_artifact_owners(reader, reader_reservation)?;
self.publish_text_progress_phase(CodeIndexBuildPhaseV1::Ready, 0, 0);
if needs_clone_successor {
let source = store.open_sealed_source(&sealed_identity, control)?;
let build =
self.begin_clone_successor(descriptor, prior, sealed_identity, source, control)?;
drop(publish_claim.install(TextHeadOpenBuildV1::CloneSuccessor(build)));
// `begin_clone_successor` copies the whole prior lexical artifact
// before the first page walk. Doing that here kept this advance,
// and the publication pass awaiting it, inside `reconcile_in_progress`
// for the copy. Exact and lexical serving are already installed;
// the copy is not a freshness precondition. Leave the slot pending
// so the retained driver starts the successor after the seat,
// without the receipt guard. The claim stays armed: its drop
// restores only `HeadOpening`, so `CloneSuccessorPending` survives
// and parked wakes are notified.
self.text_projection_build.retain_clone_successor_retry()?;
return Ok(false);
}
Ok(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,95 @@ fn clone_successor_keeps_lexical_owners_ready_and_cas_replaces_v14() {
assert_eq!(v16_revision, 16);
}

/// Exact and lexical readiness is not the clone-successor copy.
///
/// The publication advance that installs those owners used to call
/// `begin_clone_successor` before returning, and that call copies the whole
/// prior lexical artifact. The freshness receipt awaits that advance, so
/// status stayed non-current for the copy. The successor must still be
/// reported as backfill, and the next advance is what writes its staging file.
#[test]
fn lexical_readiness_leaves_the_clone_successor_uncopied() {
let fixture = GitFixture::new(&[(
"src/lib.rs",
"pub fn alpha() { one(); two(); three(); four(); five(); six(); seven(); eight(); nine(); ten(); }\n",
)]);
let store = TempDir::new().expect("store root");
let mut scheduler = scheduler(
&fixture,
store.path().to_path_buf(),
Arc::new(SharedCodeIndexBytePoolV1::default()),
);
published(scheduler.reconcile_now().expect("publish generation"));
let latest = scheduler.latest_complete().expect("latest generation");
while !latest.query_owners_are_ready() {
latest.advance_text_serving(1).expect("advance V14 build");
}
let tracedecay_contracts::code_index_freshness::CodeCloneIndexStatusV1::Backfilling {
observation,
} = latest.clone_index_status(false, None)
else {
panic!(
"a generation without clone fingerprints must report backfill once lexical owners serve, got {:?}",
latest.clone_index_status(false, None)
);
};
assert_eq!(observation.coverage.completed_source_pages, 0);
assert!(
observation.coverage.total_source_pages > 0,
"the pending successor must name the sealed page count it has not visited"
);
// Status falls back to the published artifact's bytes when the successor
// has not created a staging file, so the bytes field cannot prove the
// copy stayed off this advance. The slot and the artifacts directory can.
assert!(
matches!(
&*latest.text_projection_build.lock_slot(),
super::super::CodeTextProjectionSlotV1::CloneSuccessorPending
),
"owner readiness must leave the successor pending"
);
let staging_names = |root: &std::path::Path| {
std::fs::read_dir(code_text_artifacts_root(root))
.expect("artifacts root")
.map(|entry| entry.expect("artifact entry").file_name())
.filter(|name| name.to_string_lossy().ends_with(".staging"))
.collect::<Vec<_>>()
};
assert!(
staging_names(store.path()).is_empty(),
"owner readiness copied the prior lexical artifact: {:?}",
staging_names(store.path())
);

latest
.advance_text_serving(1)
.expect("the retained successor advance copies the prior artifact");
assert!(latest.query_owners_are_ready());
assert!(
!matches!(
&*latest.text_projection_build.lock_slot(),
super::super::CodeTextProjectionSlotV1::CloneSuccessorPending
),
"the next advance must take the pending successor"
);

while latest.text_projection_needs_work() {
latest
.advance_text_serving(16)
.expect("finish clone successor");
}
let revision: i64 = rusqlite::Connection::open(active_text_artifact_path(store.path()))
.expect("open finished artifact")
.query_row(
"SELECT format_revision FROM artifact_state WHERE singleton = 1",
[],
|row| row.get(0),
)
.expect("read finished revision");
assert_eq!(revision, 16);
}

#[test]
fn clone_status_distinguishes_unavailable_backfill_partial_ready_and_stale() {
let fixture = GitFixture::new(&[(
Expand Down
Loading