From c836abc40503c4c22ad5485b27e3e85a51d3c652 Mon Sep 17 00:00:00 2001 From: Michel Paulissen <862400+MichelPaulissen@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:10:15 +0200 Subject: [PATCH] Report self-hosted admin bootstrap path --- DISASMER_PUBLIC_TREE.json | 4 +- crates/disasmer-cli/src/main.rs | 157 +++++++++++++++++++++++++++- scripts/cli-first-contract-smoke.js | 11 ++ 3 files changed, 168 insertions(+), 4 deletions(-) diff --git a/DISASMER_PUBLIC_TREE.json b/DISASMER_PUBLIC_TREE.json index 805c668..da1d7bf 100644 --- a/DISASMER_PUBLIC_TREE.json +++ b/DISASMER_PUBLIC_TREE.json @@ -1,7 +1,7 @@ { "kind": "disasmer-filtered-public-tree", - "source_commit": "714ecfe0fc03dec4fa3ca48b8f8760a7aeead338", - "release_name": "dryrun-714ecfe0fc03", + "source_commit": "41a0af41b5289a1cac004531798bb5269c9bd749", + "release_name": "dryrun-41a0af41b528", "filtered_out": [ "private/**", "experiments/**", diff --git a/crates/disasmer-cli/src/main.rs b/crates/disasmer-cli/src/main.rs index fc3c3b3..a13751c 100644 --- a/crates/disasmer-cli/src/main.rs +++ b/crates/disasmer-cli/src/main.rs @@ -2258,8 +2258,9 @@ fn admin_status_report(args: AdminStatusArgs) -> Result { } fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result { + let scope = args.scope.clone(); let new_project = args.scope.project.clone(); - project_init_report( + let project_init = project_init_report( ProjectInitArgs { scope: args.scope, new_project, @@ -2267,7 +2268,92 @@ fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result".to_owned()); + let tenant = scope.tenant.clone(); + let project = scope.project.clone(); + let user = scope.user.clone(); + Ok(json!({ + "command": "admin bootstrap", + "mode": if scope.coordinator.is_some() { "public_coordinator_api" } else { "self_hosted_local" }, + "tenant": tenant.clone(), + "project": project.clone(), + "user": user, + "coordinator": scope.coordinator, + "private_website_required": false, + "self_hosted_cli_only": true, + "project_config_written": project_init + .get("project_config_written") + .cloned() + .unwrap_or_else(|| json!(false)), + "project_init": project_init, + "admin_surfaces": { + "coordinator": "disasmer-coordinator", + "project": "disasmer project init/status/list/select", + "node": "disasmer node enroll/list/status/revoke", + "process": "disasmer run/process status/process restart/process cancel", + "logs": "disasmer logs", + "artifacts": "disasmer artifact list/download/export", + "quota": "disasmer quota status", + "policy": "disasmer admin status/suspend-tenant", + }, + "bootstrap_sequence": [ + { + "step": "start_self_hosted_coordinator", + "command": "disasmer-coordinator --listen 127.0.0.1:0", + "private_website_required": false, + }, + { + "step": "create_or_link_project", + "command": "disasmer project init --yes", + "completed": true, + "private_website_required": false, + }, + { + "step": "create_node_enrollment_grant", + "command": format!( + "disasmer node enroll --coordinator {coordinator} --tenant {} --project-id {}", + tenant, project + ), + "private_website_required": false, + }, + { + "step": "attach_worker_node", + "command": format!( + "disasmer node attach --coordinator {coordinator} --tenant {} --project-id {} --worker", + tenant, project + ), + "private_website_required": false, + }, + { + "step": "run_process", + "command": format!( + "disasmer run --coordinator {coordinator} --tenant {} --project-id {}", + tenant, project + ), + "private_website_required": false, + }, + { + "step": "inspect_status_logs_artifacts", + "commands": [ + "disasmer process status", + "disasmer task list", + "disasmer logs", + "disasmer artifact list", + "disasmer quota status", + ], + "private_website_required": false, + }, + { + "step": "revoke_access", + "command": "disasmer admin revoke-node --node --yes", + "private_website_required": false, + } + ], + })) } fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result { @@ -7158,6 +7244,73 @@ mod tests { } } + #[test] + fn admin_bootstrap_reports_self_hosted_cli_only_path() { + let temp = tempfile::tempdir().unwrap(); + let report = admin_bootstrap_report( + AdminBootstrapArgs { + scope: CliScopeArgs { + coordinator: None, + tenant: "team".to_owned(), + project: "self-hosted".to_owned(), + user: "admin".to_owned(), + json: false, + }, + name: "Self Hosted".to_owned(), + yes: true, + }, + temp.path().to_path_buf(), + ) + .unwrap(); + + assert_eq!(report["command"], "admin bootstrap"); + assert_eq!(report["mode"], "self_hosted_local"); + assert_eq!(report["private_website_required"], false); + assert_eq!(report["self_hosted_cli_only"], true); + assert_eq!(report["project_config_written"], true); + assert_eq!(report["project_init"]["command"], "project init"); + assert_eq!(report["project_init"]["private_website_required"], false); + assert_eq!( + report["project_init"]["project_config"]["project"], + "self-hosted" + ); + assert_eq!( + report["admin_surfaces"]["node"], + "disasmer node enroll/list/status/revoke" + ); + let steps = report["bootstrap_sequence"].as_array().unwrap(); + for expected in [ + "start_self_hosted_coordinator", + "create_or_link_project", + "create_node_enrollment_grant", + "attach_worker_node", + "run_process", + "inspect_status_logs_artifacts", + "revoke_access", + ] { + assert!( + steps.iter().any(|step| step["step"] == expected), + "missing bootstrap step {expected}" + ); + } + assert!(steps.iter().all(|step| { + step.get("private_website_required") + .and_then(Value::as_bool) + .unwrap_or(false) + == false + })); + assert!(steps.iter().any(|step| step + .get("command") + .and_then(Value::as_str) + .unwrap_or("") + .contains("disasmer node enroll"))); + assert!(steps.iter().any(|step| step + .get("command") + .and_then(Value::as_str) + .unwrap_or("") + .contains("disasmer admin revoke-node"))); + } + #[test] fn top_level_help_exposes_primary_workflow_without_auth() { let mut command = Cli::command(); diff --git a/scripts/cli-first-contract-smoke.js b/scripts/cli-first-contract-smoke.js index 45ff46c..855dfed 100644 --- a/scripts/cli-first-contract-smoke.js +++ b/scripts/cli-first-contract-smoke.js @@ -107,6 +107,11 @@ expect( "doctor node readiness criteria", /`disasmer doctor` reports missing local dependencies[\s\S]*explicit node readiness summary[\s\S]*missing local dependencies[\s\S]*node next actions/ ); +expect( + criteria, + "admin bootstrap self-hosted sequence criteria", + /admin bootstrap now reports a CLI-only self-hosted sequence[\s\S]*node enrollment\/attach[\s\S]*quota status[\s\S]*node revoke/ +); expect( criteria, "quota resource-category criteria", @@ -213,6 +218,7 @@ for (const [name, pattern] of [ ["CLI agent workflow actor coverage", /fn run_with_agent_public_key_sends_attributable_workflow_actor\(\)/], ["CLI node revoke coverage", /fn node_revoke_reports_scoped_credential_revocation\(\)/], ["CLI admin public API coverage", /fn admin_status_and_suspend_use_public_coordinator_api\(\)/], + ["CLI admin bootstrap coverage", /fn admin_bootstrap_reports_self_hosted_cli_only_path\(\)/], ["CLI debug attach coverage", /fn debug_attach_reports_public_authorization\(\)/], ["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/], ["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/], @@ -460,6 +466,11 @@ expect( "CLI artifact export keeps content out of reports", /fn artifact_stream_summary[\s\S]*content_material_returned_in_report[\s\S]*false/ ); +expect( + cli, + "CLI exposes admin bootstrap self-hosted path", + /fn admin_bootstrap_report[\s\S]*self_hosted_cli_only[\s\S]*bootstrap_sequence[\s\S]*create_node_enrollment_grant[\s\S]*attach_worker_node[\s\S]*inspect_status_logs_artifacts[\s\S]*revoke_access/ +); expect( coordinator, "coordinator exposes conservative artifact stream content",