Skip to content

Commit 10a2ae0

Browse files
committed
merge: preserve the consumed renderer-transition union across every HARD rebuild (an unrelated fold no longer re-heals a healed class)
2 parents 2018d7d + f03572c commit 10a2ae0

1 file changed

Lines changed: 191 additions & 4 deletions

File tree

‎crates/mc-module/src/transform.rs‎

Lines changed: 191 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,12 +3963,19 @@ fn apply_once(
39633963
}
39643964
timings.transition_detection += elapsed_ms(transition_post_detection_started_at);
39653965
let transition_newly_consumed = !newly_consumed_transition_classes.is_empty();
3966+
let mut committed_transition_classes = consumed_transition_classes.clone();
3967+
committed_transition_classes.extend(newly_consumed_transition_classes);
3968+
// A HARD rebuild reconstructs the frozen-unit vector from data-plane survivors, bypassing
3969+
// durability handling. Reinsert the full consumed union afterward so an unrelated fold cannot
3970+
// make an already-healed class eligible for another renderer transition.
3971+
if transition_newly_consumed
3972+
|| (matches!(plan, PassPlan::Hard | PassPlan::MigrateHard)
3973+
&& !committed_transition_classes.is_empty())
3974+
{
3975+
preserve_transition_consumed_marker(&mut core, &committed_transition_classes);
3976+
}
39663977
let transition_committed = transition_renderer_active(&core) || transition_newly_consumed;
39673978
if transition_newly_consumed {
3968-
let mut committed_transition_classes = consumed_transition_classes.clone();
3969-
committed_transition_classes.extend(newly_consumed_transition_classes);
3970-
preserve_transition_consumed_marker(&mut core, &committed_transition_classes);
3971-
39723979
// Record consumption after any bust that renders an affected shape. If the shape first
39733980
// appeared during a pass that was already invalidating the cache for another reason, its
39743981
// changed bytes rode that invalidation and replay must not trigger a second one.
@@ -11235,6 +11242,47 @@ pub(crate) mod tests {
1123511242
unit
1123611243
}
1123711244

11245+
fn unstamped_opencode_tool_pair(mid: &str, ordinal: u64, call_id: &str) -> CkIngressMessage {
11246+
let native_message = json!({
11247+
"absolute_ordinal": ordinal,
11248+
"info": { "id": mid, "role": "assistant" },
11249+
"parts": [{
11250+
"type": "tool",
11251+
"tool": "read",
11252+
"callID": call_id,
11253+
"state": {
11254+
"status": "completed",
11255+
"input": { "path": "pair.txt" },
11256+
"output": "historical output"
11257+
}
11258+
}]
11259+
});
11260+
let decoded = crate::codec::decode_opencode(&[native_message]);
11261+
let mut projected = decoded.messages.into_iter().next().unwrap();
11262+
projected.ck.content = projected
11263+
.ck
11264+
.content
11265+
.into_iter()
11266+
.map(|block| ck_wire::CkWireBlock::bare(block.kind))
11267+
.collect();
11268+
projected.ck.mark_modified();
11269+
projected
11270+
}
11271+
11272+
fn stored_transition_marker_classes(
11273+
store: &McStore,
11274+
session_id: &str,
11275+
) -> BTreeSet<RendererTransitionClass> {
11276+
let loaded = store.load(session_id).unwrap();
11277+
let marker = loaded
11278+
.core
11279+
.frozen_units
11280+
.iter()
11281+
.find(|unit| unit.key == TRANSITION_CONSUMED_KEY)
11282+
.expect("renderer transition marker must be durable");
11283+
serde_json::from_str(&marker.frozen_payload).unwrap()
11284+
}
11285+
1123811286
#[test]
1123911287
fn served_fingerprint_records_cold_start_and_attributes_middle_changes() {
1124011288
let dir = tempfile::tempdir().unwrap();
@@ -23700,6 +23748,145 @@ pub(crate) mod tests {
2370023748
);
2370123749
}
2370223750

23751+
#[test]
23752+
fn consumed_transition_marker_survives_unrelated_hard_and_store_restart() {
23753+
let dir = tempfile::tempdir().unwrap();
23754+
let session = "transition-hard-survivor";
23755+
let messages = vec![
23756+
unstamped_opencode_tool_pair("survivor-pair", 1, "survivor-call"),
23757+
item("survivor-tail", 2, "stable tail"),
23758+
];
23759+
let initial_store = store(dir.path());
23760+
let request = active_opencode_req(session, "cfg0", messages.clone());
23761+
assert_eq!(run(&initial_store, &request, &spine()).action, "HARD");
23762+
append_historical_frozen_reductions(
23763+
&initial_store,
23764+
session,
23765+
&[
23766+
red_unit("survivor-pair#0", "skeleton", "historical call shell"),
23767+
red_unit("survivor-pair#1", "drop", "historical result shell"),
23768+
],
23769+
);
23770+
23771+
let transition = run(&initial_store, &request, &spine());
23772+
assert_eq!(transition.action, "HARD");
23773+
assert_eq!(
23774+
transition.materialize_reason.as_deref(),
23775+
Some("renderer_transition")
23776+
);
23777+
let expected = BTreeSet::from([RendererTransitionClass::UnmatchedPair]);
23778+
assert_eq!(
23779+
stored_transition_marker_classes(&initial_store, session),
23780+
expected
23781+
);
23782+
23783+
let unrelated_request = active_opencode_req(session, "cfg1", messages);
23784+
let unrelated = run(&initial_store, &unrelated_request, &spine());
23785+
assert_eq!(unrelated.action, "HARD");
23786+
assert_eq!(
23787+
unrelated.materialize_reason.as_deref(),
23788+
Some("epoch_change")
23789+
);
23790+
assert_eq!(
23791+
stored_transition_marker_classes(&initial_store, session),
23792+
expected
23793+
);
23794+
23795+
drop(initial_store);
23796+
let reopened = store(dir.path());
23797+
assert_eq!(
23798+
stored_transition_marker_classes(&reopened, session),
23799+
expected
23800+
);
23801+
let replay = run(&reopened, &unrelated_request, &spine());
23802+
assert_eq!(replay.action, "SOFT+");
23803+
assert_ne!(
23804+
replay.materialize_reason.as_deref(),
23805+
Some("renderer_transition")
23806+
);
23807+
assert_eq!(
23808+
stored_transition_marker_classes(&reopened, session),
23809+
expected
23810+
);
23811+
}
23812+
23813+
#[test]
23814+
fn consumed_class_does_not_mask_a_new_renderer_transition_class() {
23815+
let dir = tempfile::tempdir().unwrap();
23816+
let store = store(dir.path());
23817+
let session = "transition-new-class";
23818+
let messages = vec![
23819+
unstamped_opencode_tool_pair("new-class-pair", 1, "new-class-unmatched"),
23820+
reasoning_tool_shell_message("new-class-reasoning", 2, "new-class-poisoned", false),
23821+
tool_result(
23822+
"new-class-result",
23823+
3,
23824+
"new-class-poisoned",
23825+
"historical tool output",
23826+
),
23827+
item("new-class-tail", 4, "stable tail"),
23828+
];
23829+
let request = active_opencode_req(session, "cfg0", messages);
23830+
assert_eq!(run(&store, &request, &spine()).action, "HARD");
23831+
append_historical_frozen_reductions(
23832+
&store,
23833+
session,
23834+
&[
23835+
red_unit("new-class-pair#0", "skeleton", "historical call shell"),
23836+
red_unit("new-class-pair#1", "drop", "historical result shell"),
23837+
],
23838+
);
23839+
23840+
let unmatched_transition = run(&store, &request, &spine());
23841+
assert_eq!(unmatched_transition.action, "HARD");
23842+
assert_eq!(
23843+
unmatched_transition.materialize_reason.as_deref(),
23844+
Some("renderer_transition")
23845+
);
23846+
let unmatched_only = BTreeSet::from([RendererTransitionClass::UnmatchedPair]);
23847+
assert_eq!(
23848+
stored_transition_marker_classes(&store, session),
23849+
unmatched_only,
23850+
"classes without consumed shapes must not be pre-recorded"
23851+
);
23852+
23853+
append_historical_frozen_reductions(
23854+
&store,
23855+
session,
23856+
&[
23857+
red_unit(
23858+
"new-class-reasoning#1",
23859+
"skeleton",
23860+
"historical reasoning call shell",
23861+
),
23862+
red_unit(
23863+
"new-class-result#0",
23864+
"drop",
23865+
"historical reasoning result shell",
23866+
),
23867+
],
23868+
);
23869+
let new_class_transition = run(&store, &request, &spine());
23870+
assert_eq!(new_class_transition.action, "HARD");
23871+
assert_eq!(
23872+
new_class_transition.materialize_reason.as_deref(),
23873+
Some("renderer_transition")
23874+
);
23875+
let expected = BTreeSet::from([
23876+
RendererTransitionClass::PoisonedReasoning,
23877+
RendererTransitionClass::UnmatchedPair,
23878+
]);
23879+
assert_eq!(stored_transition_marker_classes(&store, session), expected);
23880+
23881+
let replay = run(&store, &request, &spine());
23882+
assert_eq!(replay.action, "SOFT+");
23883+
assert_ne!(
23884+
replay.materialize_reason.as_deref(),
23885+
Some("renderer_transition")
23886+
);
23887+
assert_eq!(stored_transition_marker_classes(&store, session), expected);
23888+
}
23889+
2370323890
#[test]
2370423891
fn unmatched_native_pair_is_unique_before_transition_and_replays_after_salted_bust() {
2370523892
let native_message = json!({

0 commit comments

Comments
 (0)