Update public restart compatibility metadata

This commit is contained in:
Michel Paulissen 2026-07-04 10:44:15 +02:00
parent f8f91088d5
commit 068c6f00bb
6 changed files with 192 additions and 3 deletions

View file

@ -1,7 +1,7 @@
{ {
"kind": "disasmer-filtered-public-tree", "kind": "disasmer-filtered-public-tree",
"source_commit": "276fb9d6bd1d5a8d5f38df37ccf104664f6f5043", "source_commit": "cb99b1e5f0baa07af19211bd164241b5776f91fe",
"release_name": "dryrun-276fb9d6bd1d", "release_name": "dryrun-cb99b1e5f0ba",
"filtered_out": [ "filtered_out": [
"private/**", "private/**",
"experiments/**", "experiments/**",

View file

@ -6097,6 +6097,28 @@ mod tests {
.large_input_policy .large_input_policy
.supported_handle_types .supported_handle_types
.contains(&"SourceSnapshot".to_owned())); .contains(&"SourceSnapshot".to_owned()));
assert!(
inspection
.metadata
.restart_compatibility
.source_edits_can_restart_from_clean_task_boundary
);
assert!(
inspection
.metadata
.restart_compatibility
.requires_clean_checkpoint_boundary
);
assert_eq!(
inspection.metadata.restart_compatibility.compares_task_abi,
inspection.metadata.task_metadata.task_abi
);
assert!(
inspection
.metadata
.restart_compatibility
.incompatible_changes_require_whole_process_restart
);
assert!(!inspection.metadata.embeds_full_container_images); assert!(!inspection.metadata.embeds_full_container_images);
assert!(inspection assert!(inspection
.metadata .metadata
@ -6239,6 +6261,91 @@ mod tests {
assert_ne!(first.metadata.identity, second.metadata.identity); assert_ne!(first.metadata.identity, second.metadata.identity);
} }
#[test]
fn bundle_rebuild_after_source_edit_keeps_restart_compatibility_contract() {
let temp = tempfile::tempdir().unwrap();
fs::create_dir_all(temp.path().join("src")).unwrap();
fs::write(temp.path().join("Cargo.toml"), "[package]\nname='demo'\n").unwrap();
fs::write(
temp.path().join("src/main.rs"),
"fn main() { println!(\"first\"); }\n",
)
.unwrap();
let inspect = |project: &Path| {
bundle_inspection(
BundleInspectArgs {
project: Some(project.to_path_buf()),
source_provider: None,
disabled_source_providers: Vec::new(),
json: true,
},
PathBuf::from("/unused"),
)
.unwrap()
};
let first = inspect(temp.path());
fs::write(
temp.path().join("src/main.rs"),
"fn main() { println!(\"second\"); }\n",
)
.unwrap();
let rebuilt = inspect(temp.path());
assert_ne!(first.metadata.identity, rebuilt.metadata.identity);
assert_ne!(
first
.metadata
.source_metadata
.selected_inputs
.iter()
.find(|input| input.path == "src/main.rs")
.unwrap()
.digest,
rebuilt
.metadata
.source_metadata
.selected_inputs
.iter()
.find(|input| input.path == "src/main.rs")
.unwrap()
.digest
);
assert_eq!(
first.metadata.task_metadata.task_abi,
rebuilt.metadata.task_metadata.task_abi
);
assert_eq!(
first.metadata.restart_compatibility.compares_task_abi,
rebuilt.metadata.restart_compatibility.compares_task_abi
);
assert!(
rebuilt
.metadata
.restart_compatibility
.source_edits_can_restart_from_clean_task_boundary
);
assert!(
rebuilt
.metadata
.restart_compatibility
.requires_clean_checkpoint_boundary
);
assert!(
rebuilt
.metadata
.restart_compatibility
.discards_unflushed_task_local_changes
);
assert!(
rebuilt
.metadata
.restart_compatibility
.incompatible_changes_require_whole_process_restart
);
}
#[test] #[test]
fn source_provider_manifest_digest_does_not_include_local_project_path() { fn source_provider_manifest_digest_does_not_include_local_project_path() {
let first = tempfile::tempdir().unwrap(); let first = tempfile::tempdir().unwrap();
@ -7712,6 +7819,25 @@ mod tests {
.iter() .iter()
.any(|handle| handle == "Artifact") .any(|handle| handle == "Artifact")
); );
assert_eq!(
report["bundle"]["metadata"]["restart_compatibility"]
["source_edits_can_restart_from_clean_task_boundary"],
true
);
assert_eq!(
report["bundle"]["metadata"]["restart_compatibility"]
["requires_clean_checkpoint_boundary"],
true
);
assert_eq!(
report["bundle"]["metadata"]["restart_compatibility"]["compares_task_abi"],
report["bundle"]["metadata"]["task_metadata"]["task_abi"]
);
assert_eq!(
report["bundle"]["metadata"]["restart_compatibility"]
["incompatible_changes_require_whole_process_restart"],
true
);
assert_eq!(report["status"], "built"); assert_eq!(report["status"], "built");
assert_eq!(report["scheduled_work"], false); assert_eq!(report["scheduled_work"], false);
} }

View file

@ -28,6 +28,7 @@ pub struct BundleMetadata {
pub source_metadata: BundleSourceMetadata, pub source_metadata: BundleSourceMetadata,
pub debug_metadata: BundleDebugMetadata, pub debug_metadata: BundleDebugMetadata,
pub large_input_policy: BundleLargeInputPolicy, pub large_input_policy: BundleLargeInputPolicy,
pub restart_compatibility: BundleRestartCompatibility,
pub environments: Vec<EnvironmentResource>, pub environments: Vec<EnvironmentResource>,
pub selected_inputs: Vec<SelectedInput>, pub selected_inputs: Vec<SelectedInput>,
pub embeds_full_container_images: bool, pub embeds_full_container_images: bool,
@ -65,6 +66,17 @@ pub struct BundleLargeInputPolicy {
pub supported_handle_types: Vec<String>, pub supported_handle_types: Vec<String>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleRestartCompatibility {
pub source_edits_can_restart_from_clean_task_boundary: bool,
pub requires_clean_checkpoint_boundary: bool,
pub compares_task_abi: Digest,
pub compares_environment_digests: bool,
pub compares_serialized_args: bool,
pub discards_unflushed_task_local_changes: bool,
pub incompatible_changes_require_whole_process_restart: bool,
}
impl BundleIdentityInputs { impl BundleIdentityInputs {
pub fn identity(&self) -> Digest { pub fn identity(&self) -> Digest {
let mut parts = vec![ let mut parts = vec![
@ -136,6 +148,15 @@ impl BundleIdentityInputs {
"VFS".to_owned(), "VFS".to_owned(),
], ],
}, },
restart_compatibility: BundleRestartCompatibility {
source_edits_can_restart_from_clean_task_boundary: true,
requires_clean_checkpoint_boundary: true,
compares_task_abi: self.task_abi.clone(),
compares_environment_digests: true,
compares_serialized_args: true,
discards_unflushed_task_local_changes: true,
incompatible_changes_require_whole_process_restart: true,
},
environments: self.environments.clone(), environments: self.environments.clone(),
selected_inputs: self.selected_inputs.clone(), selected_inputs: self.selected_inputs.clone(),
embeds_full_container_images: false, embeds_full_container_images: false,
@ -227,6 +248,25 @@ mod tests {
.large_input_policy .large_input_policy
.supported_handle_types .supported_handle_types
.contains(&"Artifact".to_owned())); .contains(&"Artifact".to_owned()));
assert!(
metadata
.restart_compatibility
.source_edits_can_restart_from_clean_task_boundary
);
assert!(
metadata
.restart_compatibility
.requires_clean_checkpoint_boundary
);
assert_eq!(
metadata.restart_compatibility.compares_task_abi,
inputs.task_abi
);
assert!(
metadata
.restart_compatibility
.incompatible_changes_require_whole_process_restart
);
assert_eq!(metadata.environments.len(), 1); assert_eq!(metadata.environments.len(), 1);
assert!(!metadata.embeds_full_container_images); assert!(!metadata.embeds_full_container_images);
} }

View file

@ -28,7 +28,7 @@ pub use auth::{
}; };
pub use bundle::{ pub use bundle::{
BundleDebugMetadata, BundleIdentityInputs, BundleLargeInputPolicy, BundleMetadata, BundleDebugMetadata, BundleIdentityInputs, BundleLargeInputPolicy, BundleMetadata,
BundleSourceMetadata, BundleTaskMetadata, SelectedInput, BundleRestartCompatibility, BundleSourceMetadata, BundleTaskMetadata, SelectedInput,
}; };
pub use capability::{Capability, CapabilityReportError, EnvironmentBackend, NodeCapabilities, Os}; pub use capability::{Capability, CapabilityReportError, EnvironmentBackend, NodeCapabilities, Os};
pub use checkpoint::{ pub use checkpoint::{

View file

@ -207,6 +207,7 @@ for (const [name, pattern] of [
["process control report coverage", /fn process_restart_and_cancel_reports_expose_control_boundaries\(\)/], ["process control report coverage", /fn process_restart_and_cancel_reports_expose_control_boundaries\(\)/],
["task restart report coverage", /fn task_restart_reports_clean_boundary_requirements\(\)/], ["task restart report coverage", /fn task_restart_reports_clean_boundary_requirements\(\)/],
["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/], ["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/],
["bundle rebuild restart compatibility coverage", /fn bundle_rebuild_after_source_edit_keeps_restart_compatibility_contract\(\)/],
["inspect missing environment coverage", /fn bundle_inspect_reports_missing_environment_references_before_schedule\(\)/], ["inspect missing environment coverage", /fn bundle_inspect_reports_missing_environment_references_before_schedule\(\)/],
["inspect source provider override coverage", /fn bundle_inspect_reports_source_provider_overrides_before_schedule\(\)/], ["inspect source provider override coverage", /fn bundle_inspect_reports_source_provider_overrides_before_schedule\(\)/],
["build missing environment gate coverage", /fn build_blocks_before_schedule_on_missing_environment_reference\(\)/], ["build missing environment gate coverage", /fn build_blocks_before_schedule_on_missing_environment_reference\(\)/],
@ -396,6 +397,11 @@ expect(
"CLI build coverage verifies large input handle posture", "CLI build coverage verifies large input handle posture",
/large_input_policy[\s\S]*selected_inputs_are_content_digests[\s\S]*selected_input_bytes_included[\s\S]*silent_task_argument_serialization[\s\S]*supported_handle_types/ /large_input_policy[\s\S]*selected_inputs_are_content_digests[\s\S]*selected_input_bytes_included[\s\S]*silent_task_argument_serialization[\s\S]*supported_handle_types/
); );
expect(
cli,
"CLI bundle coverage verifies restart compatibility metadata",
/restart_compatibility[\s\S]*source_edits_can_restart_from_clean_task_boundary[\s\S]*requires_clean_checkpoint_boundary[\s\S]*compares_task_abi[\s\S]*incompatible_changes_require_whole_process_restart/
);
expect( expect(
cli, cli,
"CLI blocks build before scheduling unsafe inputs", "CLI blocks build before scheduling unsafe inputs",
@ -436,6 +442,7 @@ for (const [name, pattern] of [
["doctor reachability JSON mode", /doctorJson\.coordinator_reachability\.status/], ["doctor reachability JSON mode", /doctorJson\.coordinator_reachability\.status/],
["bundle inspect JSON mode", /\["bundle", "inspect", "--project", project, "--json"\]/], ["bundle inspect JSON mode", /\["bundle", "inspect", "--project", project, "--json"\]/],
["bundle inspect large input JSON mode", /large_input_policy[\s\S]*SourceSnapshot/], ["bundle inspect large input JSON mode", /large_input_policy[\s\S]*SourceSnapshot/],
["bundle inspect restart compatibility JSON mode", /restart_compatibility[\s\S]*source_edits_can_restart_from_clean_task_boundary/],
["auth expiry posture", /token_expiry_posture[\s\S]*expires_at/], ["auth expiry posture", /token_expiry_posture[\s\S]*expires_at/],
]) { ]) {
expect(outputModeSmoke, name, pattern); expect(outputModeSmoke, name, pattern);

View file

@ -112,5 +112,21 @@ assert.strictEqual(
false false
); );
assert(inspectJson.metadata.large_input_policy.supported_handle_types.includes("SourceSnapshot")); assert(inspectJson.metadata.large_input_policy.supported_handle_types.includes("SourceSnapshot"));
assert.strictEqual(
inspectJson.metadata.restart_compatibility.source_edits_can_restart_from_clean_task_boundary,
true
);
assert.strictEqual(
inspectJson.metadata.restart_compatibility.requires_clean_checkpoint_boundary,
true
);
assert.strictEqual(
inspectJson.metadata.restart_compatibility.compares_task_abi,
inspectJson.metadata.task_metadata.task_abi
);
assert.strictEqual(
inspectJson.metadata.restart_compatibility.incompatible_changes_require_whole_process_restart,
true
);
console.log("CLI output mode smoke passed"); console.log("CLI output mode smoke passed");