From bef088c34ce5f9d84c34e893e888e65540673cd9 Mon Sep 17 00:00:00 2001 From: Michel Paulissen <862400+MichelPaulissen@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:44:27 +0200 Subject: [PATCH] Public dry run dryrun-529dbe43b301 --- DISASMER_PUBLIC_TREE.json | 4 +- crates/disasmer-cli/src/main.rs | 226 +++++++++++++++++++++++++--- scripts/cli-first-contract-smoke.js | 2 + 3 files changed, 205 insertions(+), 27 deletions(-) diff --git a/DISASMER_PUBLIC_TREE.json b/DISASMER_PUBLIC_TREE.json index 616b69d..2a62307 100644 --- a/DISASMER_PUBLIC_TREE.json +++ b/DISASMER_PUBLIC_TREE.json @@ -1,7 +1,7 @@ { "kind": "disasmer-filtered-public-tree", - "source_commit": "22f391eb394afc47797118ce3a8b98cc36ba2a45", - "release_name": "dryrun-22f391eb394a", + "source_commit": "529dbe43b301d0436dbb4739b399daf4709b89ea", + "release_name": "dryrun-529dbe43b301", "filtered_out": [ "private/**", "experiments/**", diff --git a/crates/disasmer-cli/src/main.rs b/crates/disasmer-cli/src/main.rs index 085ece3..a482e80 100644 --- a/crates/disasmer-cli/src/main.rs +++ b/crates/disasmer-cli/src/main.rs @@ -9,7 +9,8 @@ use anyhow::{Context, Result}; use clap::{Args, Parser, Subcommand}; use disasmer_core::{ BrowserLoginFlow, BundleIdentityInputs, BundleMetadata, Capability, CliLoginFlow, Digest, - NodeCapabilities, ProjectModel, SelectedInput, SourceProviderKind, SourceProviderManifest, + LimitKind, NodeCapabilities, ProjectModel, ResourceLimits, SelectedInput, SourceProviderKind, + SourceProviderManifest, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -1290,18 +1291,30 @@ fn debug_attach_report(args: DebugAttachArgs) -> Result { })) } -fn quota_status_report(args: QuotaStatusArgs) -> Result { +fn quota_status_report(args: QuotaStatusArgs, cwd: PathBuf) -> Result { + let config = read_project_config(&cwd)?; + let effective_scope = effective_project_scope(&args.scope, config.as_ref()); + let coordinator = effective_scope.coordinator.clone(); + let attached_nodes = + list_attached_nodes_if_available(coordinator.as_deref(), &effective_scope)?; + let task_events = + list_task_events_if_available(coordinator.as_deref(), &effective_scope, None)?; + let current_usage = quota_current_usage(&attached_nodes, task_events.as_ref()); Ok(json!({ "command": "quota status", - "tenant": args.scope.tenant, - "project": args.scope.project, - "coordinator": args.scope.coordinator, + "tenant": effective_scope.tenant, + "project": effective_scope.project, + "user": effective_scope.user, + "coordinator": coordinator, + "project_config": config, "policy_surface": "generic public quota categories; hosted tuning remains private policy", - "limits": { - "artifact_download_bytes": "community-tier policy before dispatch", - "rendezvous_attempts": "community-tier policy before dispatch", - "hosted_wasm": "fuel/memory/wall-clock/state/logs/metadata/ui/api limits before start" - }, + "limits": quota_limits_value(), + "current_usage": current_usage, + "attached_nodes": attached_nodes, + "task_events": task_events, + "next_blocked_action": quota_next_blocked_action(¤t_usage), + "community_tier_language": true, + "billing_required": false, "private_abuse_heuristics_exposed": false, })) } @@ -1469,6 +1482,17 @@ fn human_report(value: &Value) -> String { if let Some(current_usage) = value.pointer("/quota_posture/current_usage") { lines.push(format!("quota usage: {}", compact_json(current_usage))); } + if let Some(current_usage) = value.get("current_usage") { + lines.push(format!("quota usage: {}", compact_json(current_usage))); + } + if let Some(next_blocked_action) = value.get("next_blocked_action") { + if !next_blocked_action.is_null() { + lines.push(format!( + "next blocked action: {}", + compact_json(next_blocked_action) + )); + } + } if let Some(flow) = value.get("human_flow") { if let Some(device) = flow.get("Device") { lines.push("flow: device".to_owned()); @@ -1839,7 +1863,10 @@ fn main() -> Result<()> { command: QuotaCommands::Status(args), } => { let json_output = args.scope.json; - emit_report("a_status_report(args)?, json_output)?; + emit_report( + "a_status_report(args, std::env::current_dir()?)?, + json_output, + )?; } Commands::Admin { command } => { let (report, json_output) = match command { @@ -2131,6 +2158,19 @@ fn task_event_count(task_events: Option<&Value>) -> usize { } fn project_quota_posture(attached_nodes: &Value, task_events: Option<&Value>) -> Value { + let current_usage = quota_current_usage(attached_nodes, task_events); + let next_blocked_action = quota_next_blocked_action(¤t_usage); + json!({ + "source": "cli_project_status_summary", + "current_usage": current_usage, + "limits": quota_limits_value(), + "next_blocked_action": next_blocked_action, + "billing_required": false, + "private_abuse_heuristics_exposed": false, + }) +} + +fn quota_current_usage(attached_nodes: &Value, task_events: Option<&Value>) -> Value { let attached_node_count = attached_nodes .get("count") .and_then(Value::as_u64) @@ -2140,23 +2180,51 @@ fn project_quota_posture(attached_nodes: &Value, task_events: Option<&Value>) -> .and_then(Value::as_u64) .unwrap_or(0); json!({ - "source": "cli_project_status_summary", - "current_usage": { - "attached_nodes": attached_node_count, - "online_nodes": online_node_count, - "observed_task_events": task_event_count(task_events), - }, - "limits": { - "artifact_download_bytes": "checked before download link creation", - "rendezvous_attempts": "checked before rendezvous plan creation", - "hosted_wasm": "checked before hosted coordinator-side work starts", - }, - "next_blocked_action": null, - "billing_required": false, - "private_abuse_heuristics_exposed": false, + "attached_nodes": attached_node_count, + "online_nodes": online_node_count, + "observed_task_events": task_event_count(task_events), + "artifact_download_bytes": 0, + "rendezvous_attempts": 0, + "hosted_wasm_processes": 0, }) } +fn quota_limits_value() -> Value { + let limits = ResourceLimits::community_tier_defaults(); + json!({ + "api_calls": limits.limit(&LimitKind::ApiCall), + "spawns": limits.limit(&LimitKind::Spawn), + "log_bytes": limits.limit(&LimitKind::LogBytes), + "metadata_bytes": limits.limit(&LimitKind::MetadataBytes), + "debug_read_bytes": limits.limit(&LimitKind::DebugReadBytes), + "ui_events": limits.limit(&LimitKind::UiEvent), + "rendezvous_attempts": limits.limit(&LimitKind::RendezvousAttempt), + "artifact_download_bytes": limits.limit(&LimitKind::ArtifactDownloadBytes), + "hosted_fuel": limits.limit(&LimitKind::HostedFuel), + "hosted_memory_bytes": limits.limit(&LimitKind::HostedMemoryBytes), + "hosted_wall_clock_ms": limits.limit(&LimitKind::HostedWallClockMs), + "hosted_state_bytes": limits.limit(&LimitKind::HostedStateBytes), + "hosted_tuning_private": true, + }) +} + +fn quota_next_blocked_action(current_usage: &Value) -> Value { + if current_usage + .get("online_nodes") + .and_then(Value::as_u64) + .unwrap_or(0) + == 0 + { + return json!({ + "action": "node_work_requires_online_attached_node", + "category": "capability", + "quota_related": false, + "message": "no online attached node is visible for work that requires a user node" + }); + } + Value::Null +} + fn login_plan(args: LoginArgs) -> LoginPlan { login_plan_with_nonce(args, login_nonce()) } @@ -3822,6 +3890,114 @@ mod tests { ); } + #[test] + fn quota_status_uses_project_config_and_generic_public_limits() { + let temp = tempfile::tempdir().unwrap(); + write_project_config( + temp.path(), + &ProjectConfig { + tenant: "tenant-quota".to_owned(), + project: "project-quota".to_owned(), + user: "user".to_owned(), + coordinator: None, + }, + ) + .unwrap(); + + let status = quota_status_report( + QuotaStatusArgs { + scope: CliScopeArgs { + coordinator: None, + tenant: "tenant".to_owned(), + project: "project".to_owned(), + user: "user".to_owned(), + json: false, + }, + }, + temp.path().to_path_buf(), + ) + .unwrap(); + + assert_eq!(status["command"], "quota status"); + assert_eq!(status["tenant"], "tenant-quota"); + assert_eq!(status["project"], "project-quota"); + assert_eq!(status["current_usage"]["attached_nodes"], 0); + assert_eq!(status["limits"]["artifact_download_bytes"], 268_435_456); + assert_eq!(status["community_tier_language"], true); + assert_eq!(status["billing_required"], false); + assert_eq!( + status["next_blocked_action"]["action"], + "node_work_requires_online_attached_node" + ); + } + + #[test] + fn quota_status_queries_public_coordinator_usage() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap().to_string(); + let server = std::thread::spawn(move || { + for _ in 0..2 { + 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(); + if line.contains("\"type\":\"list_node_descriptors\"") { + assert!(line.contains("\"tenant\":\"tenant-live\"")); + stream + .write_all( + br#"{"type":"node_descriptors","descriptors":[{"id":"node-a","online":true},{"id":"node-b","online":false}],"actor":"user"}"#, + ) + .unwrap(); + stream.write_all(b"\n").unwrap(); + } else if line.contains("\"type\":\"list_task_events\"") { + assert!(line.contains("\"project\":\"project-live\"")); + stream + .write_all( + br#"{"type":"task_events","events":[{"process":"vp-live","task":"task-a"},{"process":"vp-live","task":"task-b"}]}"#, + ) + .unwrap(); + stream.write_all(b"\n").unwrap(); + } else { + panic!("unexpected coordinator request: {line}"); + } + } + }); + + let temp = tempfile::tempdir().unwrap(); + write_project_config( + temp.path(), + &ProjectConfig { + tenant: "tenant-live".to_owned(), + project: "project-live".to_owned(), + user: "user".to_owned(), + coordinator: Some(addr.clone()), + }, + ) + .unwrap(); + + let status = quota_status_report( + QuotaStatusArgs { + scope: CliScopeArgs { + coordinator: None, + tenant: "tenant".to_owned(), + project: "project".to_owned(), + user: "user".to_owned(), + json: false, + }, + }, + temp.path().to_path_buf(), + ) + .unwrap(); + server.join().unwrap(); + + assert_eq!(status["coordinator"], addr); + assert_eq!(status["current_usage"]["attached_nodes"], 2); + assert_eq!(status["current_usage"]["online_nodes"], 1); + assert_eq!(status["current_usage"]["observed_task_events"], 2); + assert!(status["next_blocked_action"].is_null()); + assert_eq!(status["task_events"]["response"]["type"], "task_events"); + } + #[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 c1e30ec..1b58d95 100644 --- a/scripts/cli-first-contract-smoke.js +++ b/scripts/cli-first-contract-smoke.js @@ -108,6 +108,8 @@ for (const [name, pattern] of [ ["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/], ["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/], ["project coordinator status coverage", /fn project_status_queries_public_coordinator_state\(\)/], + ["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\(\)/], ["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\(\)/], ]) {