From 13d4d524c4534080da0ab5366f50a4df886ec704 Mon Sep 17 00:00:00 2001 From: Michel Paulissen <862400+MichelPaulissen@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:09:07 +0200 Subject: [PATCH] Public dry run dryrun-dece97efaef9 --- DISASMER_PUBLIC_TREE.json | 4 +- crates/disasmer-cli/src/main.rs | 155 ++++++++++++++++++++++++++++ scripts/cli-first-contract-smoke.js | 1 + 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/DISASMER_PUBLIC_TREE.json b/DISASMER_PUBLIC_TREE.json index bcca47f..3ce2de1 100644 --- a/DISASMER_PUBLIC_TREE.json +++ b/DISASMER_PUBLIC_TREE.json @@ -1,7 +1,7 @@ { "kind": "disasmer-filtered-public-tree", - "source_commit": "01b2c7b06dd306582f1a83dce6d6646658854db7", - "release_name": "dryrun-01b2c7b06dd3", + "source_commit": "dece97efaef95e21b322410d4ae5dd8ad7824df6", + "release_name": "dryrun-dece97efaef9", "filtered_out": [ "private/**", "experiments/**", diff --git a/crates/disasmer-cli/src/main.rs b/crates/disasmer-cli/src/main.rs index 71d7c2e..71eab03 100644 --- a/crates/disasmer-cli/src/main.rs +++ b/crates/disasmer-cli/src/main.rs @@ -1122,10 +1122,13 @@ fn process_restart_report(args: ProcessRestartArgs) -> Result { "project": args.scope.project, "process": args.process, }))?; + let restart_request = process_restart_request_summary(&response, !args.yes); return Ok(json!({ "command": "process restart", "coordinator": coordinator, + "process": args.process, "requires_confirmation": !args.yes, + "restart_request": restart_request, "response": response, "coordinator_session_requests": session.requests(), })); @@ -1135,6 +1138,11 @@ fn process_restart_report(args: ProcessRestartArgs) -> Result { "status": "requires_coordinator", "requires_confirmation": !args.yes, "process": args.process, + "restart_request": { + "status": "requires_coordinator", + "operation": "restart_virtual_process", + "explicit_user_action": true, + }, })) } @@ -1149,10 +1157,15 @@ fn process_cancel_report(args: ProcessCancelArgs) -> Result { "node": args.node, "task": args.task, }))?; + let cancel_request = process_cancel_request_summary(&response, !args.yes); return Ok(json!({ "command": "process cancel", "coordinator": coordinator, "requires_confirmation": !args.yes, + "process": args.process, + "node": args.node, + "task": args.task, + "cancel_request": cancel_request, "response": response, "coordinator_session_requests": session.requests(), })); @@ -1164,6 +1177,12 @@ fn process_cancel_report(args: ProcessCancelArgs) -> Result { "process": args.process, "node": args.node, "task": args.task, + "cancel_request": { + "status": "requires_coordinator", + "operation": "task_scoped_cancel", + "whole_process_cancel_available": false, + "explicit_user_action": true, + }, })) } @@ -2348,6 +2367,60 @@ fn artifact_name_from_path(path: &str) -> String { path.rsplit('/').next().unwrap_or(path).to_owned() } +fn process_restart_request_summary(response: &Value, requires_confirmation: bool) -> Value { + if response.get("type").and_then(Value::as_str) != Some("process_started") { + return json!({ + "status": response.get("type").and_then(Value::as_str).unwrap_or("coordinator_response"), + "operation": "restart_virtual_process", + "accepted": false, + "requires_confirmation": requires_confirmation, + "explicit_user_action": true, + "error": response.get("message").cloned().unwrap_or(Value::Null), + }); + } + + json!({ + "status": "process_started", + "operation": "restart_virtual_process", + "accepted": true, + "process": response.get("process").cloned().unwrap_or(Value::Null), + "coordinator_epoch": response.get("epoch").cloned().unwrap_or(Value::Null), + "requires_confirmation": requires_confirmation, + "explicit_user_action": true, + "website_required": false, + "single_active_process_boundary": true, + }) +} + +fn process_cancel_request_summary(response: &Value, requires_confirmation: bool) -> Value { + if response.get("type").and_then(Value::as_str) != Some("task_cancellation_requested") { + return json!({ + "status": response.get("type").and_then(Value::as_str).unwrap_or("coordinator_response"), + "operation": "task_scoped_cancel", + "accepted": false, + "requires_confirmation": requires_confirmation, + "explicit_user_action": true, + "whole_process_cancel_available": false, + "error": response.get("message").cloned().unwrap_or(Value::Null), + }); + } + + json!({ + "status": "task_cancellation_requested", + "operation": "task_scoped_cancel", + "accepted": true, + "process": response.get("process").cloned().unwrap_or(Value::Null), + "task": response.get("task").cloned().unwrap_or(Value::Null), + "node": response.get("node").cloned().unwrap_or(Value::Null), + "requires_confirmation": requires_confirmation, + "explicit_user_action": true, + "website_required": false, + "whole_process_cancel_available": false, + "node_must_poll_task_control": true, + "surviving_state_visibility": "task and artifact state remains visible after terminal task events are reported", + }) +} + fn artifact_download_session_summary(response: &Value) -> Value { if response.get("type").and_then(Value::as_str) != Some("artifact_download_link") { return json!({ @@ -4426,6 +4499,88 @@ mod tests { assert_eq!(export["export_plan"]["authorization_digest_present"], true); } + #[test] + fn process_restart_and_cancel_reports_expose_control_boundaries() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap().to_string(); + let server = std::thread::spawn(move || { + let restart_response = r#"{"type":"process_started","process":"vp","epoch":42}"#; + let cancel_response = r#"{"type":"task_cancellation_requested","process":"vp","task":"compile-linux","node":"node-a"}"#; + for expected in ["start_process", "cancel_task"] { + let (mut stream, _) = listener.accept().unwrap(); + let mut reader = BufReader::new(stream.try_clone().unwrap()); + let mut line = String::new(); + reader.read_line(&mut line).unwrap(); + assert!(line.contains(&format!(r#""type":"{expected}""#))); + assert!(line.contains(r#""tenant":"tenant""#)); + assert!(line.contains(r#""project":"project""#)); + assert!(line.contains(r#""process":"vp""#)); + if expected == "start_process" { + stream.write_all(restart_response.as_bytes()).unwrap(); + } else { + assert!(line.contains(r#""node":"node-a""#)); + assert!(line.contains(r#""task":"compile-linux""#)); + stream.write_all(cancel_response.as_bytes()).unwrap(); + } + stream.write_all(b"\n").unwrap(); + } + }); + let scope = CliScopeArgs { + coordinator: Some(addr), + tenant: "tenant".to_owned(), + project: "project".to_owned(), + user: "user".to_owned(), + json: false, + }; + + let restart = process_restart_report(ProcessRestartArgs { + scope: scope.clone(), + process: "vp".to_owned(), + yes: true, + }) + .unwrap(); + let cancel = process_cancel_report(ProcessCancelArgs { + scope, + process: "vp".to_owned(), + node: "node-a".to_owned(), + task: "compile-linux".to_owned(), + yes: false, + }) + .unwrap(); + server.join().unwrap(); + + assert_eq!(restart["restart_request"]["status"], "process_started"); + assert_eq!( + restart["restart_request"]["operation"], + "restart_virtual_process" + ); + assert_eq!(restart["restart_request"]["accepted"], true); + assert_eq!(restart["restart_request"]["process"], "vp"); + assert_eq!(restart["restart_request"]["coordinator_epoch"], 42); + assert_eq!(restart["restart_request"]["requires_confirmation"], false); + assert_eq!(restart["restart_request"]["website_required"], false); + + assert_eq!( + cancel["cancel_request"]["status"], + "task_cancellation_requested" + ); + assert_eq!(cancel["cancel_request"]["operation"], "task_scoped_cancel"); + assert_eq!(cancel["cancel_request"]["accepted"], true); + assert_eq!(cancel["cancel_request"]["process"], "vp"); + assert_eq!(cancel["cancel_request"]["task"], "compile-linux"); + assert_eq!(cancel["cancel_request"]["node"], "node-a"); + assert_eq!(cancel["cancel_request"]["requires_confirmation"], true); + assert_eq!(cancel["cancel_request"]["website_required"], false); + assert_eq!( + cancel["cancel_request"]["whole_process_cancel_available"], + false + ); + assert_eq!( + cancel["cancel_request"]["node_must_poll_task_control"], + true + ); + } + #[test] fn build_command_reuses_bundle_inspection_without_full_repo_upload() { let temp = tempfile::tempdir().unwrap(); diff --git a/scripts/cli-first-contract-smoke.js b/scripts/cli-first-contract-smoke.js index e07ce5e..5980250 100644 --- a/scripts/cli-first-contract-smoke.js +++ b/scripts/cli-first-contract-smoke.js @@ -112,6 +112,7 @@ for (const [name, pattern] of [ ["quota coordinator usage coverage", /fn quota_status_queries_public_coordinator_usage\(\)/], ["task event summary coverage", /fn process_task_log_and_artifact_reports_summarize_task_events\(\)/], ["artifact download/export report coverage", /fn artifact_download_and_export_reports_expose_safe_session_boundaries\(\)/], + ["process control report coverage", /fn process_restart_and_cancel_reports_expose_control_boundaries\(\)/], ["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/], ["safe coordinator-required plans", /fn node_enroll_and_process_commands_have_safe_plan_without_coordinator\(\)/], ]) {