From 7c05d077a1cbe52a7029b90c636ab852642e19ba Mon Sep 17 00:00:00 2001 From: Michel Paulissen <862400+MichelPaulissen@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:02:47 +0200 Subject: [PATCH] Public dry run dryrun-01b2c7b06dd3 --- DISASMER_PUBLIC_TREE.json | 4 +- crates/disasmer-cli/src/main.rs | 181 ++++++++++++++++++++++++++++ scripts/cli-first-contract-smoke.js | 1 + 3 files changed, 184 insertions(+), 2 deletions(-) diff --git a/DISASMER_PUBLIC_TREE.json b/DISASMER_PUBLIC_TREE.json index 65f91a8..bcca47f 100644 --- a/DISASMER_PUBLIC_TREE.json +++ b/DISASMER_PUBLIC_TREE.json @@ -1,7 +1,7 @@ { "kind": "disasmer-filtered-public-tree", - "source_commit": "7a9befca72b4465a27fbd8ab7a6f8d86f4387996", - "release_name": "dryrun-7a9befca72b4", + "source_commit": "01b2c7b06dd306582f1a83dce6d6646658854db7", + "release_name": "dryrun-01b2c7b06dd3", "filtered_out": [ "private/**", "experiments/**", diff --git a/crates/disasmer-cli/src/main.rs b/crates/disasmer-cli/src/main.rs index ff39cd7..71d7c2e 100644 --- a/crates/disasmer-cli/src/main.rs +++ b/crates/disasmer-cli/src/main.rs @@ -1231,9 +1231,13 @@ fn artifact_download_report(args: ArtifactDownloadArgs) -> Result { "token_nonce": nonce, "now_epoch_seconds": 0, }))?; + let download_session = artifact_download_session_summary(&response); return Ok(json!({ "command": "artifact download", "coordinator": coordinator, + "artifact": args.artifact, + "max_bytes": args.max_bytes, + "download_session": download_session, "response": response, "coordinator_session_requests": session.requests(), })); @@ -1243,6 +1247,11 @@ fn artifact_download_report(args: ArtifactDownloadArgs) -> Result { "status": "requires_coordinator", "artifact": args.artifact, "max_bytes": args.max_bytes, + "download_session": { + "status": "requires_coordinator", + "link_issued": false, + "explicit_user_action_required": true, + }, })) } @@ -1259,10 +1268,14 @@ fn artifact_export_report(args: ArtifactExportArgs) -> Result { "direct_connectivity": true, "failure_reason": "", }))?; + let export_plan = artifact_export_plan_summary(&response, &args.to); return Ok(json!({ "command": "artifact export", "coordinator": coordinator, + "artifact": args.artifact, "to": args.to, + "receiver_node": args.receiver_node, + "export_plan": export_plan, "response": response, "coordinator_session_requests": session.requests(), })); @@ -1273,6 +1286,12 @@ fn artifact_export_report(args: ArtifactExportArgs) -> Result { "artifact": args.artifact, "to": args.to, "receiver_node": args.receiver_node, + "export_plan": { + "status": "requires_coordinator", + "explicit_user_action": true, + "local_bytes_written_by_cli": false, + "default_durable_store_assumed": false, + }, })) } @@ -2329,6 +2348,72 @@ fn artifact_name_from_path(path: &str) -> String { path.rsplit('/').next().unwrap_or(path).to_owned() } +fn artifact_download_session_summary(response: &Value) -> Value { + if response.get("type").and_then(Value::as_str) != Some("artifact_download_link") { + return json!({ + "status": response.get("type").and_then(Value::as_str).unwrap_or("coordinator_response"), + "link_issued": false, + "explicit_user_action_required": true, + "error": response.get("message").cloned().unwrap_or(Value::Null), + }); + } + + let link = response.get("link").unwrap_or(&Value::Null); + json!({ + "status": "download_link_issued", + "link_issued": true, + "explicit_user_action_required": true, + "coordinator_preflight": "completed_before_link_issued", + "tenant": link.get("tenant").cloned().unwrap_or(Value::Null), + "project": link.get("project").cloned().unwrap_or(Value::Null), + "process": link.get("process").cloned().unwrap_or(Value::Null), + "artifact": link.get("artifact").cloned().unwrap_or(Value::Null), + "actor": link.get("actor").cloned().unwrap_or(Value::Null), + "source": link.get("source").cloned().unwrap_or(Value::Null), + "url_path": link.get("url_path").cloned().unwrap_or(Value::Null), + "expires_at_epoch_seconds": link.get("expires_at_epoch_seconds").cloned().unwrap_or(Value::Null), + "max_bytes": link.get("max_bytes").cloned().unwrap_or(Value::Null), + "token_material_returned": false, + "scoped_token_digest_present": link.get("scoped_token_digest").is_some(), + "policy_context_digest_present": link.get("policy_context_digest").is_some(), + "cross_tenant_usable": false, + "default_durable_store_assumed": false, + }) +} + +fn artifact_export_plan_summary(response: &Value, to: &Path) -> Value { + if response.get("type").and_then(Value::as_str) != Some("artifact_export_plan") { + return json!({ + "status": response.get("type").and_then(Value::as_str).unwrap_or("coordinator_response"), + "explicit_user_action": true, + "local_path": to, + "local_bytes_written_by_cli": false, + "default_durable_store_assumed": false, + "error": response.get("message").cloned().unwrap_or(Value::Null), + }); + } + + let plan = response.get("plan").unwrap_or(&Value::Null); + json!({ + "status": "transfer_plan_created", + "explicit_user_action": true, + "local_path": to, + "local_bytes_written_by_cli": false, + "writes_require_data_plane_followup": true, + "default_durable_store_assumed": false, + "source_node": response.get("source_node").cloned().unwrap_or(Value::Null), + "receiver_node": response.get("receiver_node").cloned().unwrap_or(Value::Null), + "transport": plan.get("transport").cloned().unwrap_or(Value::Null), + "artifact": plan.pointer("/scope/object/Artifact").cloned().unwrap_or(Value::Null), + "tenant": plan.pointer("/scope/tenant").cloned().unwrap_or(Value::Null), + "project": plan.pointer("/scope/project").cloned().unwrap_or(Value::Null), + "process": plan.pointer("/scope/process").cloned().unwrap_or(Value::Null), + "coordinator_assisted_rendezvous": plan.get("coordinator_assisted_rendezvous").cloned().unwrap_or(Value::Null), + "coordinator_bulk_relay_allowed": plan.get("coordinator_bulk_relay_allowed").cloned().unwrap_or(Value::Null), + "authorization_digest_present": plan.get("authorization_digest").is_some(), + }) +} + fn task_event_count(task_events: Option<&Value>) -> usize { task_events .and_then(|task_events| task_events.pointer("/response/events")) @@ -4245,6 +4330,102 @@ mod tests { assert_eq!(artifacts["default_durable_store_assumed"], false); } + #[test] + fn artifact_download_and_export_reports_expose_safe_session_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 download_response = concat!( + r#"{"type":"artifact_download_link","link":{"artifact":"app.txt","source":{"RetainedNode":"node-a"},"url_path":"/artifacts/tenant/project/vp/app.txt","scoped_token_digest":"sha256:token","expires_at_epoch_seconds":60,"tenant":"tenant","project":"project","process":"vp","actor":{"User":"user"},"max_bytes":2048,"policy_context_digest":"sha256:policy"}}"# + ); + let export_response = concat!( + r#"{"type":"artifact_export_plan","source_node":"node-a","receiver_node":"node-b","plan":{"transport":"NativeQuic","scope":{"tenant":"tenant","project":"project","process":"vp","object":{"Artifact":"app.txt"},"authorization_subject":"artifact-export:node-a-to-node-b"},"source":{"node":"node-a","advertised_addr":"quic://node-a","public_key_fingerprint":"sha256:source"},"destination":{"node":"node-b","advertised_addr":"quic://node-b","public_key_fingerprint":"sha256:destination"},"authorization_digest":"sha256:auth","coordinator_assisted_rendezvous":true,"coordinator_bulk_relay_allowed":false}}"# + ); + for expected in ["create_artifact_download_link", "export_artifact_to_node"] { + 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#""artifact":"app.txt""#)); + if expected == "create_artifact_download_link" { + assert!(line.contains(r#""max_bytes":2048"#)); + stream.write_all(download_response.as_bytes()).unwrap(); + } else { + assert!(line.contains(r#""receiver_node":"node-b""#)); + stream.write_all(export_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 download = artifact_download_report(ArtifactDownloadArgs { + scope: scope.clone(), + artifact: "app.txt".to_owned(), + max_bytes: 2048, + }) + .unwrap(); + let export = artifact_export_report(ArtifactExportArgs { + scope, + artifact: "app.txt".to_owned(), + to: PathBuf::from("/tmp/app.txt"), + receiver_node: "node-b".to_owned(), + }) + .unwrap(); + server.join().unwrap(); + + assert_eq!( + download["download_session"]["status"], + "download_link_issued" + ); + assert_eq!(download["download_session"]["tenant"], "tenant"); + assert_eq!(download["download_session"]["project"], "project"); + assert_eq!(download["download_session"]["process"], "vp"); + assert_eq!( + download["download_session"]["source"]["RetainedNode"], + "node-a" + ); + assert_eq!(download["download_session"]["max_bytes"], 2048); + assert_eq!( + download["download_session"]["token_material_returned"], + false + ); + assert_eq!( + download["download_session"]["scoped_token_digest_present"], + true + ); + assert_eq!(download["download_session"]["cross_tenant_usable"], false); + assert_eq!( + download["download_session"]["default_durable_store_assumed"], + false + ); + + assert_eq!(export["export_plan"]["status"], "transfer_plan_created"); + assert_eq!(export["export_plan"]["source_node"], "node-a"); + assert_eq!(export["export_plan"]["receiver_node"], "node-b"); + assert_eq!(export["export_plan"]["artifact"], "app.txt"); + assert_eq!(export["export_plan"]["local_path"], "/tmp/app.txt"); + assert_eq!(export["export_plan"]["local_bytes_written_by_cli"], false); + assert_eq!( + export["export_plan"]["default_durable_store_assumed"], + false + ); + assert_eq!( + export["export_plan"]["coordinator_bulk_relay_allowed"], + false + ); + assert_eq!(export["export_plan"]["authorization_digest_present"], 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 edaeef6..e07ce5e 100644 --- a/scripts/cli-first-contract-smoke.js +++ b/scripts/cli-first-contract-smoke.js @@ -111,6 +111,7 @@ for (const [name, pattern] of [ ["quota local status coverage", /fn quota_status_uses_project_config_and_generic_public_limits\(\)/], ["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\(\)/], ["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\(\)/], ]) {