main.rs (43005B)
1 #![cfg_attr(coverage_nightly, feature(coverage_attribute))] 2 #![forbid(unsafe_code)] 3 #![recursion_limit = "256"] 4 5 // These release-qualification modules are executable integration boundaries: 6 // their governed lanes invoke external toolchains, fuzzers, package builds, 7 // advisory scanners, SBOM generators, and target checks. They are exercised 8 // by their dedicated release gates and must not recursively execute inside 9 // xtask's unit-coverage process. 10 mod advisory_snapshot; 11 #[cfg_attr(coverage_nightly, coverage(off))] 12 mod api_qualification; 13 #[cfg_attr(coverage_nightly, coverage(off))] 14 mod architecture; 15 mod artifact_admission; 16 mod bounded_process; 17 #[cfg_attr(coverage_nightly, coverage(off))] 18 mod build_control; 19 mod build_output; 20 #[cfg_attr(coverage_nightly, coverage(off))] 21 mod catalog; 22 #[cfg_attr(coverage_nightly, coverage(off))] 23 mod consolidation; 24 #[cfg_attr(coverage_nightly, coverage(off))] 25 mod contract; 26 mod coverage; 27 #[cfg_attr(coverage_nightly, coverage(off))] 28 mod dto_roots; 29 mod exact_tree_archive; 30 #[cfg_attr(coverage_nightly, coverage(off))] 31 mod fuzz_qualification; 32 #[cfg_attr(coverage_nightly, coverage(off))] 33 mod generate; 34 #[cfg_attr(coverage_nightly, coverage(off))] 35 mod hygiene; 36 #[cfg_attr(coverage_nightly, coverage(off))] 37 mod portable_qualification; 38 #[cfg_attr(coverage_nightly, coverage(off))] 39 mod release_graph; 40 mod release_preflight; 41 #[cfg_attr(coverage_nightly, coverage(off))] 42 mod release_qualification; 43 mod rshr_202_step_298_gate; 44 mod rshr_202_step_298_platform; 45 mod rshr_202_step_299_gate; 46 mod rshr_202_step_304_gate; 47 mod rshr_202_step_305_gate; 48 mod rshr_202_step_306_gate; 49 mod safe_artifact_io; 50 #[cfg_attr(coverage_nightly, coverage(off))] 51 mod safety_qualification; 52 #[cfg_attr(coverage_nightly, coverage(off))] 53 mod sdk_generation; 54 mod service_build_qualification; 55 mod service_release_artifacts; 56 mod service_repro_install; 57 mod service_source_lock; 58 mod service_source_lock_command; 59 mod service_source_lock_v3; 60 #[cfg_attr(coverage_nightly, coverage(off))] 61 mod supply_chain_qualification; 62 #[cfg_attr(coverage_nightly, coverage(off))] 63 mod target_qualification; 64 65 use clap::{Parser, Subcommand, ValueEnum}; 66 use std::env; 67 use std::path::{Path, PathBuf}; 68 use std::process::ExitCode; 69 70 #[derive(Debug, Parser)] 71 #[command(name = "xtask", disable_help_subcommand = true)] 72 struct Cli { 73 #[command(subcommand)] 74 command: XtaskCommand, 75 } 76 77 #[derive(Debug, Subcommand)] 78 enum XtaskCommand { 79 AdvisorySnapshotSelfTest, 80 #[command(hide = true)] 81 AdvisorySnapshotFixtureScanner { 82 #[arg(long, default_value = "report")] 83 mode: String, 84 }, 85 Architecture, 86 ArchitectureCi, 87 ArchitectureSourceExportCi, 88 BoundedProcessSelfTest, 89 SafeArtifactIoSelfTest, 90 CheckApiBoundaries, 91 CheckDependencyBoundaries, 92 Check { 93 #[arg(long)] 94 group: String, 95 #[arg(long, value_enum, default_value_t = GroupOperation::Check)] 96 operation: GroupOperation, 97 #[arg(long)] 98 execute: bool, 99 #[arg(long)] 100 include_reserved: bool, 101 }, 102 Catalog { 103 #[command(subcommand)] 104 command: CatalogCommand, 105 }, 106 #[command(trailing_var_arg = true)] 107 Contract { 108 #[arg(allow_hyphen_values = true)] 109 args: Vec<String>, 110 }, 111 #[command(trailing_var_arg = true)] 112 Consolidation { 113 #[arg(allow_hyphen_values = true)] 114 args: Vec<String>, 115 }, 116 #[command(trailing_var_arg = true)] 117 Coverage { 118 #[arg(allow_hyphen_values = true)] 119 args: Vec<String>, 120 }, 121 #[command(name = "dto-roots", trailing_var_arg = true)] 122 DtoRoots { 123 #[arg(allow_hyphen_values = true)] 124 args: Vec<String>, 125 }, 126 #[command(trailing_var_arg = true)] 127 Generate { 128 #[arg(allow_hyphen_values = true)] 129 args: Vec<String>, 130 }, 131 #[command(trailing_var_arg = true)] 132 Hygiene { 133 #[arg(allow_hyphen_values = true)] 134 args: Vec<String>, 135 }, 136 #[command(trailing_var_arg = true)] 137 Release { 138 #[arg(allow_hyphen_values = true)] 139 args: Vec<String>, 140 }, 141 #[command(name = "rshr-step-298-gate", hide = true)] 142 RshrStep298Gate { 143 #[arg(long)] 144 step: u16, 145 #[arg(long)] 146 check_id: String, 147 #[arg(long)] 148 source_revision: String, 149 #[arg(long)] 150 source_tree: String, 151 #[arg(long)] 152 candidate_digest: String, 153 #[arg(long)] 154 platform: String, 155 #[arg(long)] 156 execution_request_sha256: String, 157 }, 158 #[command(name = "rshr-step-298-platform-probe", hide = true)] 159 RshrStep298PlatformProbe, 160 #[command(name = "rshr-step-299-gate", hide = true)] 161 RshrStep299Gate { 162 #[arg(long)] 163 step: u16, 164 #[arg(long)] 165 check_id: String, 166 #[arg(long)] 167 source_revision: String, 168 #[arg(long)] 169 source_tree: String, 170 #[arg(long)] 171 candidate_digest: String, 172 #[arg(long)] 173 platform: String, 174 #[arg(long)] 175 execution_request_sha256: String, 176 }, 177 #[command(name = "rshr-step-304-gate", hide = true)] 178 RshrStep304Gate { 179 #[arg(long)] 180 step: u16, 181 #[arg(long)] 182 check_id: String, 183 #[arg(long)] 184 source_revision: String, 185 #[arg(long)] 186 source_tree: String, 187 #[arg(long)] 188 candidate_digest: String, 189 #[arg(long)] 190 platform: String, 191 #[arg(long)] 192 execution_request_sha256: String, 193 }, 194 #[command(name = "rshr-step-305-gate", hide = true)] 195 RshrStep305Gate { 196 #[arg(long)] 197 step: u16, 198 #[arg(long)] 199 check_id: String, 200 #[arg(long)] 201 source_revision: String, 202 #[arg(long)] 203 source_tree: String, 204 #[arg(long)] 205 candidate_digest: String, 206 #[arg(long)] 207 platform: String, 208 #[arg(long)] 209 execution_request_sha256: String, 210 }, 211 #[command(name = "rshr-step-306-gate", hide = true)] 212 RshrStep306Gate { 213 #[arg(long)] 214 step: u16, 215 #[arg(long)] 216 check_id: String, 217 #[arg(long)] 218 source_revision: String, 219 #[arg(long)] 220 source_tree: String, 221 #[arg(long)] 222 candidate_digest: String, 223 #[arg(long)] 224 platform: String, 225 #[arg(long)] 226 execution_request_sha256: String, 227 }, 228 SourceLock { 229 #[arg(long)] 230 consumer_root: PathBuf, 231 }, 232 ServiceSourceLock { 233 #[arg(long, value_enum)] 234 mode: ServiceSourceLockMode, 235 #[arg(long)] 236 service_root: PathBuf, 237 #[arg(long)] 238 source_archive: PathBuf, 239 }, 240 ServiceReleaseArtifacts { 241 #[arg(long, value_enum)] 242 mode: ServiceReleaseArtifactMode, 243 #[arg(long)] 244 service_root: PathBuf, 245 #[arg(long)] 246 lib_root: PathBuf, 247 #[arg(long)] 248 input_root: PathBuf, 249 #[arg(long)] 250 output_root: PathBuf, 251 #[arg(long)] 252 target: String, 253 #[arg(long)] 254 source_date_epoch: u32, 255 #[arg(long)] 256 candidate_digest: String, 257 }, 258 ServiceReproInstall { 259 #[arg(long)] 260 plan: PathBuf, 261 #[arg(long)] 262 source_root: PathBuf, 263 #[arg(long)] 264 root_preimage_root: PathBuf, 265 #[arg(long)] 266 predecessor_artifact_root: PathBuf, 267 #[arg(long)] 268 git_executable: PathBuf, 269 #[arg(long)] 270 adapter_executable: PathBuf, 271 #[arg(long)] 272 output: PathBuf, 273 }, 274 Source { 275 #[command(subcommand)] 276 command: SourceCommand, 277 }, 278 Artifact { 279 #[arg(long, value_enum)] 280 product: ArtifactProduct, 281 #[arg(long, value_enum)] 282 target: ArtifactTarget, 283 #[arg(long, value_enum)] 284 language: ArtifactLanguage, 285 #[arg(long, value_enum)] 286 mode: ArtifactMode, 287 #[arg(long)] 288 consumer_root: PathBuf, 289 #[arg(long)] 290 source_root: PathBuf, 291 #[arg(long)] 292 output: PathBuf, 293 #[arg(long)] 294 source_date_epoch: u64, 295 #[arg(long)] 296 builder_id: String, 297 #[arg(long, value_delimiter = ',')] 298 features: Vec<String>, 299 }, 300 } 301 302 #[derive(Clone, Copy, Debug, Subcommand)] 303 enum CatalogCommand { 304 Check, 305 Write, 306 } 307 308 #[derive(Clone, Copy, Debug, ValueEnum)] 309 enum GroupOperation { 310 Check, 311 Test, 312 Clippy, 313 } 314 315 #[derive(Clone, Copy, Debug, ValueEnum)] 316 enum ArtifactMode { 317 Check, 318 Write, 319 } 320 321 #[derive(Clone, Copy, Debug, ValueEnum)] 322 enum SourceMode { 323 Prefetch, 324 Offline, 325 } 326 327 #[derive(Clone, Copy, Debug, ValueEnum)] 328 enum ServiceSourceLockMode { 329 Check, 330 Write, 331 } 332 333 #[derive(Clone, Copy, Debug, ValueEnum)] 334 enum ServiceReleaseArtifactMode { 335 Check, 336 Write, 337 } 338 339 #[derive(Clone, Copy, Debug, ValueEnum)] 340 enum ArtifactProduct { 341 Sdk, 342 } 343 344 impl ArtifactProduct { 345 fn as_str(self) -> &'static str { 346 match self { 347 Self::Sdk => "sdk", 348 } 349 } 350 } 351 352 #[derive(Clone, Copy, Debug, ValueEnum)] 353 enum ArtifactTarget { 354 Typescript, 355 Wasm, 356 Ffi, 357 Linux, 358 Macos, 359 Windows, 360 } 361 362 impl ArtifactTarget { 363 fn as_str(self) -> &'static str { 364 match self { 365 Self::Typescript => "typescript", 366 Self::Wasm => "wasm", 367 Self::Ffi => "ffi", 368 Self::Linux => "linux", 369 Self::Macos => "macos", 370 Self::Windows => "windows", 371 } 372 } 373 } 374 375 #[derive(Clone, Copy, Debug, ValueEnum)] 376 enum ArtifactLanguage { 377 Typescript, 378 Swift, 379 Kotlin, 380 Javascript, 381 } 382 383 impl ArtifactLanguage { 384 fn as_str(self) -> &'static str { 385 match self { 386 Self::Typescript => "typescript", 387 Self::Swift => "swift", 388 Self::Kotlin => "kotlin", 389 Self::Javascript => "javascript", 390 } 391 } 392 } 393 394 #[derive(Debug, Subcommand)] 395 enum SourceCommand { 396 Materialize { 397 #[arg(long)] 398 consumer_root: PathBuf, 399 #[arg(long)] 400 cache_root: PathBuf, 401 #[arg(long, value_enum)] 402 mode: SourceMode, 403 }, 404 ArchiveVerify { 405 #[arg(long)] 406 archive: PathBuf, 407 #[arg(long)] 408 sha256: String, 409 }, 410 ArchiveCreate { 411 #[arg(long)] 412 source_root: PathBuf, 413 #[arg(long)] 414 revision: String, 415 #[arg(long)] 416 output: PathBuf, 417 }, 418 } 419 420 fn usage() { 421 eprintln!("usage:"); 422 eprintln!(" cargo xtask architecture"); 423 eprintln!(" cargo xtask architecture-ci"); 424 eprintln!(" cargo xtask architecture-source-export-ci"); 425 eprintln!(" cargo xtask advisory-snapshot-self-test"); 426 eprintln!(" cargo xtask bounded-process-self-test"); 427 eprintln!(" cargo xtask safe-artifact-io-self-test"); 428 eprintln!(" cargo xtask check-api-boundaries"); 429 eprintln!(" cargo xtask check-dependency-boundaries"); 430 eprintln!(" cargo xtask check --group <group> [--operation check|test|clippy] [--execute]"); 431 eprintln!(" cargo xtask catalog check|write"); 432 eprintln!(" cargo xtask contract validate"); 433 eprintln!(" cargo xtask contract event-contract-registry-v7 [--write]"); 434 eprintln!(" cargo xtask contract knowledge-manifest [--write]"); 435 eprintln!(" cargo xtask consolidation baseline"); 436 eprintln!(" cargo xtask consolidation history [--archive-root <absolute-directory>]"); 437 eprintln!(" cargo xtask consolidation history-rehearsal"); 438 eprintln!( 439 " cargo xtask consolidation import-verify --source <id> --source-root <absolute-directory> --filtered-root <absolute-directory> --mode <check|write>" 440 ); 441 eprintln!(" cargo xtask dto-roots --check|--write"); 442 eprintln!(" cargo xtask generate protocol --check|--write"); 443 eprintln!(" cargo xtask release preflight"); 444 eprintln!(" cargo xtask release qualify-features"); 445 eprintln!(" cargo xtask release qualify-graph"); 446 eprintln!(" cargo xtask release qualify-api"); 447 eprintln!(" cargo xtask release qualify-fuzz"); 448 eprintln!(" cargo xtask release qualify-portable"); 449 eprintln!(" cargo xtask release qualify-safety"); 450 eprintln!(" cargo xtask release qualify-supply-chain"); 451 eprintln!(" cargo xtask release qualify-targets"); 452 eprintln!(" cargo xtask coverage run-crate --crate <crate> [--out <dir>]"); 453 eprintln!(" cargo xtask coverage required-crates"); 454 eprintln!(" cargo xtask coverage workspace-crates"); 455 eprintln!( 456 " cargo xtask coverage report --scope <scope> --summary <file> --lcov <file> --out <file> [--policy-gate | (--fail-under-exec-lines <pct> --fail-under-functions <pct> --fail-under-regions <pct> --fail-under-branches <pct> [--require-branches])]" 457 ); 458 eprintln!( 459 " cargo xtask coverage report-missing --scope <scope> --out <file> --reason <reason>" 460 ); 461 eprintln!( 462 " cargo xtask coverage refresh-summary [--reports-root <dir>] [--out <file>] [--status-out <file>]" 463 ); 464 eprintln!(" cargo xtask hygiene forbidden-identifiers"); 465 eprintln!( 466 " cargo xtask hygiene prototype-contracts [--config <repo-relative-path>] [--strict|--report-only]" 467 ); 468 eprintln!(" cargo xtask source-lock --consumer-root <absolute-directory>"); 469 eprintln!( 470 " cargo xtask service-source-lock --mode <check|write> --service-root <absolute-directory> --source-archive <absolute-bundle>" 471 ); 472 eprintln!( 473 " cargo xtask service-release-artifacts --mode <check|write> --service-root <absolute-directory> --lib-root <absolute-directory> --input-root <absolute-directory> --output-root <absolute-directory> --target <rust-target> --source-date-epoch <seconds> --candidate-digest <sha256>" 474 ); 475 eprintln!( 476 " cargo xtask service-repro-install --plan <absolute-file> --source-root <absolute-directory> --root-preimage-root <absolute-directory> --predecessor-artifact-root <absolute-directory> --git-executable <absolute-file> --adapter-executable <absolute-file> --output <absolute-file>" 477 ); 478 eprintln!( 479 " cargo xtask source materialize --consumer-root <absolute-directory> --cache-root <absolute-directory> --mode <prefetch|offline>" 480 ); 481 eprintln!(" cargo xtask source archive-verify --archive <bundle> --sha256 <digest>"); 482 eprintln!( 483 " cargo xtask source archive-create --source-root <absolute-directory> --revision <full-sha> --output <absolute-bundle>" 484 ); 485 eprintln!( 486 " cargo xtask artifact --product sdk --target <target> --language <language> --mode <check|write> --consumer-root <absolute-directory> --source-root <absolute-directory> --output <relative-path> --source-date-epoch <seconds> --builder-id <id>" 487 ); 488 } 489 490 fn workspace_root_with_override(override_root: Option<&str>) -> PathBuf { 491 if let Some(raw) = override_root { 492 let trimmed = raw.trim(); 493 if !trimmed.is_empty() { 494 return PathBuf::from(trimmed); 495 } 496 } 497 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 498 let crates_dir = manifest_dir.parent().unwrap_or(manifest_dir); 499 let root = crates_dir.parent().unwrap_or(crates_dir); 500 root.to_path_buf() 501 } 502 503 fn workspace_root() -> PathBuf { 504 let override_root = env::var("RADROOTS_WORKSPACE_ROOT").ok(); 505 workspace_root_with_override(override_root.as_deref()) 506 } 507 508 fn validate_protocol_contracts() -> Result<(), String> { 509 use radroots_protocol::{capability, event, runtime, schema}; 510 511 capability::v1::validate_catalog(capability::v1::CATALOG).map_err(|error| error.to_string())?; 512 event::v1::validate_catalog(event::v1::CATALOG).map_err(|error| error.to_string())?; 513 event::v1::validate_trade_state_vocabulary(event::v1::TRADE_STATE_VOCABULARY) 514 .map_err(|error| error.to_string())?; 515 runtime::v1::validate_catalog(runtime::v1::CATALOG).map_err(|error| error.to_string())?; 516 schema::protocol_v1_registry() 517 .map(|_| ()) 518 .map_err(|error| error.to_string()) 519 } 520 521 fn validate_contract() -> Result<(), String> { 522 validate_protocol_contracts()?; 523 let root = workspace_root(); 524 artifact_admission::validate_contract(&root)?; 525 service_source_lock::validate_contract(&root)?; 526 service_build_qualification::validate_contract(&root)?; 527 service_release_artifacts::validate_contract(&root)?; 528 service_repro_install::validate_contract(&root)?; 529 dto_roots::check(&root)?; 530 generate::protocol::check(&root)?; 531 contract::load_contract_bundle(&root) 532 .and_then(|bundle| contract::validate_contract_bundle(&bundle)) 533 .and_then(|_| contract::validate_canonical_event_boundary(&root)) 534 .and_then(|_| contract::validate_artifact_contracts(&root)) 535 } 536 537 #[cfg_attr(coverage_nightly, coverage(off))] 538 fn release_preflight() -> Result<(), String> { 539 let root = workspace_root(); 540 release_preflight_at(&root) 541 } 542 543 fn release_preflight_at(root: &Path) -> Result<(), String> { 544 use release_preflight::{LaneId, LaneState}; 545 546 release_preflight::execute_all(|lane| { 547 let result = match lane { 548 LaneId::Catalog => catalog::check(root), 549 LaneId::ServiceSourceLockContract => service_source_lock::validate_contract(root), 550 LaneId::ServiceBuildQualificationContract => { 551 service_build_qualification::validate_contract(root) 552 } 553 LaneId::ServiceReleaseArtifactsContract => { 554 service_release_artifacts::validate_contract(root) 555 .and_then(|()| service_repro_install::validate_contract(root)) 556 } 557 LaneId::PublicNativeGroup => build_control::group_plan( 558 root, 559 "public_native", 560 build_control::Operation::Check, 561 false, 562 ) 563 .map(|_| ()), 564 LaneId::PreviewGroup => { 565 build_control::group_plan(root, "preview", build_control::Operation::Check, false) 566 .map(|_| ()) 567 } 568 LaneId::ToolsGroup => { 569 build_control::group_plan(root, "tools", build_control::Operation::Check, false) 570 .map(|_| ()) 571 } 572 LaneId::DtoRoots => dto_roots::check(root), 573 LaneId::ProtocolFreshness => generate::protocol::check(root), 574 LaneId::ArtifactContracts => contract::validate_artifact_contracts(root), 575 LaneId::ReleaseContracts => advisory_snapshot::validate_decision(root) 576 .and_then(|_| contract::validate_release_preflight(root)), 577 }; 578 match result { 579 Ok(()) => LaneState::Pass, 580 Err(error) => { 581 eprintln!("release preflight {} failed: {error}", lane.as_str()); 582 LaneState::Failed 583 } 584 } 585 }) 586 .map(|_| ()) 587 .map_err(|error| error.to_string()) 588 } 589 590 #[cfg_attr(coverage_nightly, coverage(off))] 591 fn run_release(args: &[String]) -> Result<(), String> { 592 match args.first().map(String::as_str) { 593 Some("preflight") => release_preflight(), 594 Some("qualify-features") => release_qualification::run_feature_matrix(&workspace_root()), 595 Some("qualify-graph") => release_graph::run(&workspace_root()), 596 Some("qualify-api") => api_qualification::run(&workspace_root()), 597 Some("qualify-fuzz") => fuzz_qualification::run(&workspace_root()), 598 Some("qualify-portable") => portable_qualification::run(&workspace_root()), 599 Some("qualify-safety") => safety_qualification::run(&workspace_root()), 600 Some("qualify-supply-chain") => supply_chain_qualification::run(&workspace_root()), 601 Some("qualify-targets") => target_qualification::run(&workspace_root()), 602 _ => Err("unknown release subcommand".to_string()), 603 } 604 } 605 606 #[cfg_attr(coverage_nightly, coverage(off))] 607 fn run_contract(args: &[String]) -> Result<(), String> { 608 match args.first().map(String::as_str) { 609 Some("validate") => validate_contract(), 610 Some("event-contract-registry-v7") => match &args[1..] { 611 [] => contract::validate_event_contract_registry_v7_inventory(&workspace_root()), 612 [flag] if flag == "--write" => { 613 contract::write_event_contract_registry_v7_inventory(&workspace_root()) 614 } 615 _ => Err( 616 "event-contract-registry-v7 accepts no arguments or exactly --write".to_string(), 617 ), 618 }, 619 Some("knowledge-manifest") => { 620 if args.get(1).map(String::as_str) == Some("--write") { 621 contract::write_knowledge_contract_manifest(&workspace_root()) 622 } else { 623 contract::validate_knowledge_contract_manifest(&workspace_root()) 624 } 625 } 626 _ => Err("unknown contract subcommand".to_string()), 627 } 628 } 629 630 fn run(args: &[String]) -> Result<(), String> { 631 let cli = Cli::try_parse_from(std::iter::once("xtask").chain(args.iter().map(String::as_str))) 632 .map_err(|error| error.to_string())?; 633 match cli.command { 634 XtaskCommand::AdvisorySnapshotSelfTest => advisory_snapshot::self_test(), 635 XtaskCommand::AdvisorySnapshotFixtureScanner { mode } => { 636 advisory_snapshot::write_fixture_scanner_output(&mode) 637 } 638 XtaskCommand::Architecture => architecture::validate(&workspace_root()), 639 XtaskCommand::ArchitectureCi => { 640 catalog::check(&workspace_root())?; 641 architecture::validate_ci(&workspace_root())?; 642 validate_contract() 643 } 644 XtaskCommand::ArchitectureSourceExportCi => { 645 catalog::check_source_export(&workspace_root())?; 646 architecture::validate_ci(&workspace_root())?; 647 validate_contract() 648 } 649 XtaskCommand::BoundedProcessSelfTest => bounded_process::self_test(), 650 XtaskCommand::SafeArtifactIoSelfTest => { 651 safe_artifact_io::self_test()?; 652 release_preflight::self_test()?; 653 println!("safe artifact I/O self-test: ok"); 654 Ok(()) 655 } 656 XtaskCommand::CheckApiBoundaries => { 657 architecture::validate_api_boundaries(&workspace_root()) 658 } 659 XtaskCommand::CheckDependencyBoundaries => { 660 architecture::validate_dependency_boundaries(&workspace_root()) 661 } 662 XtaskCommand::Check { 663 group, 664 operation, 665 execute, 666 include_reserved, 667 } => { 668 let operation = match operation { 669 GroupOperation::Check => build_control::Operation::Check, 670 GroupOperation::Test => build_control::Operation::Test, 671 GroupOperation::Clippy => build_control::Operation::Clippy, 672 }; 673 let root = workspace_root(); 674 let plan = build_control::group_plan(&root, &group, operation, include_reserved)?; 675 if execute { 676 build_control::execute_group_plan(&root, &plan) 677 } else { 678 build_control::print_plan(&plan); 679 Ok(()) 680 } 681 } 682 XtaskCommand::Catalog { command } => match command { 683 CatalogCommand::Check => catalog::run(&["check".to_owned()], &workspace_root()), 684 CatalogCommand::Write => catalog::run(&["write".to_owned()], &workspace_root()), 685 }, 686 XtaskCommand::Contract { args } => run_contract(&args), 687 XtaskCommand::Consolidation { args } => consolidation::run(&args, &workspace_root()), 688 XtaskCommand::Coverage { args } => coverage::run(&args), 689 XtaskCommand::DtoRoots { args } => dto_roots::run(&args, &workspace_root()), 690 XtaskCommand::Generate { args } => generate::run(&args, &workspace_root()), 691 XtaskCommand::Hygiene { args } => hygiene::run(&args, &workspace_root()), 692 XtaskCommand::Release { args } => run_release(&args), 693 XtaskCommand::RshrStep298Gate { 694 step, 695 check_id, 696 source_revision, 697 source_tree, 698 candidate_digest, 699 platform, 700 execution_request_sha256, 701 } => rshr_202_step_298_gate::run(rshr_202_step_298_gate::Arguments { 702 step, 703 check_id, 704 source_revision, 705 source_tree, 706 candidate_digest, 707 platform, 708 execution_request_sha256, 709 }), 710 XtaskCommand::RshrStep298PlatformProbe => rshr_202_step_298_platform::run(), 711 XtaskCommand::RshrStep299Gate { 712 step, 713 check_id, 714 source_revision, 715 source_tree, 716 candidate_digest, 717 platform, 718 execution_request_sha256, 719 } => rshr_202_step_299_gate::run(rshr_202_step_299_gate::Arguments { 720 step, 721 check_id, 722 source_revision, 723 source_tree, 724 candidate_digest, 725 platform, 726 execution_request_sha256, 727 }), 728 XtaskCommand::RshrStep304Gate { 729 step, 730 check_id, 731 source_revision, 732 source_tree, 733 candidate_digest, 734 platform, 735 execution_request_sha256, 736 } => rshr_202_step_304_gate::run(rshr_202_step_304_gate::Arguments { 737 step, 738 check_id, 739 source_revision, 740 source_tree, 741 candidate_digest, 742 platform, 743 execution_request_sha256, 744 }), 745 XtaskCommand::RshrStep305Gate { 746 step, 747 check_id, 748 source_revision, 749 source_tree, 750 candidate_digest, 751 platform, 752 execution_request_sha256, 753 } => rshr_202_step_305_gate::run(rshr_202_step_305_gate::Arguments { 754 step, 755 check_id, 756 source_revision, 757 source_tree, 758 candidate_digest, 759 platform, 760 execution_request_sha256, 761 }), 762 XtaskCommand::RshrStep306Gate { 763 step, 764 check_id, 765 source_revision, 766 source_tree, 767 candidate_digest, 768 platform, 769 execution_request_sha256, 770 } => rshr_202_step_306_gate::run(rshr_202_step_306_gate::Arguments { 771 step, 772 check_id, 773 source_revision, 774 source_tree, 775 candidate_digest, 776 platform, 777 execution_request_sha256, 778 }), 779 XtaskCommand::SourceLock { consumer_root } => { 780 build_control::validate_consumer(&consumer_root).map(|_| ()) 781 } 782 XtaskCommand::ServiceSourceLock { 783 mode, 784 service_root, 785 source_archive, 786 } => service_source_lock_command::run( 787 match mode { 788 ServiceSourceLockMode::Check => service_source_lock_command::CommandMode::Check, 789 ServiceSourceLockMode::Write => service_source_lock_command::CommandMode::Write, 790 }, 791 &service_root, 792 &source_archive, 793 ), 794 XtaskCommand::ServiceReleaseArtifacts { 795 mode, 796 service_root, 797 lib_root, 798 input_root, 799 output_root, 800 target, 801 source_date_epoch, 802 candidate_digest, 803 } => service_release_artifacts::run(service_release_artifacts::Arguments { 804 mode: match mode { 805 ServiceReleaseArtifactMode::Check => service_release_artifacts::CommandMode::Check, 806 ServiceReleaseArtifactMode::Write => service_release_artifacts::CommandMode::Write, 807 }, 808 service_root: &service_root, 809 lib_root: &lib_root, 810 input_root: &input_root, 811 output_root: &output_root, 812 target: &target, 813 source_date_epoch, 814 candidate_digest: &candidate_digest, 815 }), 816 XtaskCommand::ServiceReproInstall { 817 plan, 818 source_root, 819 root_preimage_root, 820 predecessor_artifact_root, 821 git_executable, 822 adapter_executable, 823 output, 824 } => service_repro_install::run( 825 &workspace_root(), 826 service_repro_install::Arguments { 827 plan: &plan, 828 source_root: &source_root, 829 root_preimage_root: &root_preimage_root, 830 predecessor_artifact_root: &predecessor_artifact_root, 831 git_executable: &git_executable, 832 adapter_executable: &adapter_executable, 833 output: &output, 834 }, 835 ), 836 XtaskCommand::Source { command } => match command { 837 SourceCommand::Materialize { 838 consumer_root, 839 cache_root, 840 mode, 841 } => build_control::materialize( 842 &consumer_root, 843 &cache_root, 844 matches!(mode, SourceMode::Offline), 845 ) 846 .map(|path| { 847 println!("{}", path.display()); 848 }), 849 SourceCommand::ArchiveVerify { archive, sha256 } => { 850 build_control::verify_source_archive(&archive, &sha256) 851 } 852 SourceCommand::ArchiveCreate { 853 source_root, 854 revision, 855 output, 856 } => build_control::create_source_archive(&source_root, &revision, &output).map( 857 |digest| { 858 println!("{digest}"); 859 }, 860 ), 861 }, 862 XtaskCommand::Artifact { 863 product, 864 target, 865 language, 866 mode, 867 consumer_root, 868 source_root, 869 output, 870 source_date_epoch, 871 builder_id, 872 features, 873 } => { 874 let product = product.as_str(); 875 let target = target.as_str(); 876 let language = language.as_str(); 877 let mode = match mode { 878 ArtifactMode::Check => build_control::Mode::Check, 879 ArtifactMode::Write => build_control::Mode::Write, 880 }; 881 build_control::validate_generation_roots( 882 product, 883 target, 884 language, 885 &consumer_root, 886 &source_root, 887 )?; 888 if product == "sdk" { 889 sdk_generation::artifact(&source_root, &consumer_root, target, language, mode)?; 890 } 891 build_control::artifact( 892 product, 893 target, 894 language, 895 mode, 896 &consumer_root, 897 &source_root, 898 &output, 899 source_date_epoch, 900 &builder_id, 901 &features, 902 ) 903 } 904 } 905 } 906 907 fn main_with_args(args: Vec<String>) -> ExitCode { 908 if args.is_empty() { 909 usage(); 910 return ExitCode::from(2); 911 } 912 match run(&args) { 913 Ok(()) => ExitCode::SUCCESS, 914 Err(err) => { 915 eprintln!("{err}"); 916 usage(); 917 ExitCode::from(2) 918 } 919 } 920 } 921 922 #[cfg_attr(coverage_nightly, coverage(off))] 923 fn main() -> ExitCode { 924 main_with_args(env::args().skip(1).collect()) 925 } 926 927 #[cfg(test)] 928 mod tests { 929 use super::*; 930 use std::fs; 931 use std::sync::{Mutex, MutexGuard, OnceLock}; 932 use std::time::{SystemTime, UNIX_EPOCH}; 933 934 fn workspace_lock() -> &'static Mutex<()> { 935 static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); 936 LOCK.get_or_init(|| Mutex::new(())) 937 } 938 939 fn lock_workspace() -> MutexGuard<'static, ()> { 940 match workspace_lock().lock() { 941 Ok(guard) => guard, 942 Err(poison) => poison.into_inner(), 943 } 944 } 945 946 fn unique_temp_dir(prefix: &str) -> PathBuf { 947 let ns = SystemTime::now() 948 .duration_since(UNIX_EPOCH) 949 .expect("system time") 950 .as_nanos(); 951 std::env::temp_dir().join(format!("radroots_xtask_main_{prefix}_{ns}")) 952 } 953 954 #[test] 955 fn workspace_root_resolves() { 956 let root = workspace_root(); 957 assert!(root.join("Cargo.toml").exists()); 958 } 959 960 #[test] 961 fn workspace_root_override_takes_precedence() { 962 let root = workspace_root_with_override(Some("/tmp/radroots-test-root")); 963 assert_eq!(root, PathBuf::from("/tmp/radroots-test-root")); 964 965 let fallback = workspace_root_with_override(Some(" ")); 966 assert!(fallback.join("Cargo.toml").exists()); 967 968 let default_root = workspace_root_with_override(None); 969 assert!(default_root.join("Cargo.toml").exists()); 970 } 971 972 #[test] 973 fn typed_build_control_cli_requires_explicit_modes_and_known_values() { 974 assert!(Cli::try_parse_from(["xtask", "bounded-process-self-test"]).is_ok()); 975 assert!(Cli::try_parse_from(["xtask", "safe-artifact-io-self-test"]).is_ok()); 976 977 let source_args = [ 978 "xtask", 979 "source", 980 "materialize", 981 "--consumer-root", 982 "/tmp/consumer", 983 "--cache-root", 984 "/tmp/cache", 985 ]; 986 assert!(Cli::try_parse_from(source_args).is_err()); 987 assert!(Cli::try_parse_from(source_args.into_iter().chain(["--mode", "prefetch"])).is_ok()); 988 989 assert!( 990 Cli::try_parse_from([ 991 "xtask", 992 "service-source-lock", 993 "--mode", 994 "check", 995 "--service-root", 996 "/tmp/service", 997 ]) 998 .is_err() 999 ); 1000 assert!( 1001 Cli::try_parse_from([ 1002 "xtask", 1003 "service-source-lock", 1004 "--mode", 1005 "write", 1006 "--service-root", 1007 "/tmp/service", 1008 "--source-archive", 1009 "/tmp/lib.bundle", 1010 ]) 1011 .is_ok() 1012 ); 1013 assert!( 1014 Cli::try_parse_from([ 1015 "xtask", 1016 "service-release-artifacts", 1017 "--mode", 1018 "write", 1019 "--service-root", 1020 "/tmp/service", 1021 "--lib-root", 1022 "/tmp/lib", 1023 "--input-root", 1024 "/tmp/input", 1025 "--output-root", 1026 "/tmp/output", 1027 "--target", 1028 "x86_64-unknown-linux-gnu", 1029 ]) 1030 .is_err() 1031 ); 1032 assert!( 1033 Cli::try_parse_from([ 1034 "xtask", 1035 "service-release-artifacts", 1036 "--mode", 1037 "check", 1038 "--service-root", 1039 "/tmp/service", 1040 "--lib-root", 1041 "/tmp/lib", 1042 "--input-root", 1043 "/tmp/input", 1044 "--output-root", 1045 "/tmp/output", 1046 "--target", 1047 "x86_64-unknown-linux-gnu", 1048 "--source-date-epoch", 1049 "1", 1050 "--candidate-digest", 1051 "1111111111111111111111111111111111111111111111111111111111111111", 1052 ]) 1053 .is_ok() 1054 ); 1055 assert!( 1056 Cli::try_parse_from([ 1057 "xtask", 1058 "service-repro-install", 1059 "--plan", 1060 "/tmp/plan.json", 1061 "--source-root", 1062 "/tmp/source", 1063 "--root-preimage-root", 1064 "/tmp/root", 1065 "--predecessor-artifact-root", 1066 "/tmp/predecessor", 1067 "--git-executable", 1068 "/usr/bin/git", 1069 "--adapter-executable", 1070 "/tmp/adapter", 1071 "--output", 1072 "/tmp/result.json", 1073 ]) 1074 .is_ok() 1075 ); 1076 1077 assert!( 1078 Cli::try_parse_from([ 1079 "xtask", 1080 "artifact", 1081 "--product", 1082 "unknown", 1083 "--target", 1084 "wasm", 1085 "--language", 1086 "javascript", 1087 "--mode", 1088 "check", 1089 "--consumer-root", 1090 "/tmp/consumer", 1091 "--source-root", 1092 "/tmp/source", 1093 "--output", 1094 "generated/manifest.json", 1095 "--source-date-epoch", 1096 "1", 1097 "--builder-id", 1098 "fixture", 1099 ]) 1100 .is_err() 1101 ); 1102 } 1103 1104 #[test] 1105 fn artifact_cli_values_preserve_every_governed_identifier() { 1106 assert!(ArtifactProduct::from_str("mobile", false).is_err()); 1107 assert!(ArtifactTarget::from_str("ios", false).is_err()); 1108 assert!(ArtifactTarget::from_str("android", false).is_err()); 1109 assert_eq!([ArtifactProduct::Sdk.as_str()], ["sdk"]); 1110 assert_eq!( 1111 [ 1112 ArtifactTarget::Typescript.as_str(), 1113 ArtifactTarget::Wasm.as_str(), 1114 ArtifactTarget::Ffi.as_str(), 1115 ArtifactTarget::Linux.as_str(), 1116 ArtifactTarget::Macos.as_str(), 1117 ArtifactTarget::Windows.as_str(), 1118 ], 1119 ["typescript", "wasm", "ffi", "linux", "macos", "windows",] 1120 ); 1121 assert_eq!( 1122 [ 1123 ArtifactLanguage::Typescript.as_str(), 1124 ArtifactLanguage::Swift.as_str(), 1125 ArtifactLanguage::Kotlin.as_str(), 1126 ArtifactLanguage::Javascript.as_str(), 1127 ], 1128 ["typescript", "swift", "kotlin", "javascript"] 1129 ); 1130 } 1131 1132 #[test] 1133 fn run_release_and_dispatchers_cover_error_paths() { 1134 let unknown_release = 1135 run_release(&["unknown".to_string()]).expect_err("unknown release subcommand"); 1136 assert!(unknown_release.contains("unknown release subcommand")); 1137 1138 let unknown_contract = 1139 run_contract(&["unknown".to_string()]).expect_err("unknown contract subcommand"); 1140 assert!(unknown_contract.contains("unknown contract subcommand")); 1141 let invalid_registry = run_contract(&[ 1142 "event-contract-registry-v7".to_string(), 1143 "--invalid".to_string(), 1144 ]) 1145 .expect_err("invalid registry-v7 mode"); 1146 assert!(invalid_registry.contains("exactly --write")); 1147 let unknown_root = run(&["unknown".to_string()]).expect_err("unknown command"); 1148 assert!(unknown_root.contains("unrecognized subcommand")); 1149 1150 run(&["architecture".to_string()]).expect("architecture ledger validates"); 1151 1152 let invalid_dto_roots = 1153 run(&["dto-roots".to_string()]).expect_err("dto-roots requires an explicit mode"); 1154 assert!(invalid_dto_roots.contains("--check|--write")); 1155 let invalid_generate = 1156 run(&["generate".to_string()]).expect_err("generate requires a target and mode"); 1157 assert!(invalid_generate.contains("generate protocol --check|--write")); 1158 1159 let removed_sdk = run(&["sdk".to_string(), "validate".to_string()]) 1160 .expect_err("removed sdk command namespace"); 1161 assert!(removed_sdk.contains("unrecognized subcommand")); 1162 } 1163 1164 #[test] 1165 fn release_preflight_exhausts_the_closed_lane_inventory() { 1166 let workspace = tempfile::TempDir::new().expect("create empty workspace"); 1167 let error = release_preflight_at(workspace.path()) 1168 .expect_err("missing required inputs must fail closed"); 1169 assert_eq!(error, "release preflight required lanes did not all pass"); 1170 } 1171 1172 #[test] 1173 fn lock_workspace_recovers_from_poisoned_mutex() { 1174 let handle = std::thread::spawn(|| { 1175 let _guard = workspace_lock().lock().expect("lock workspace"); 1176 panic!("poison workspace lock"); 1177 }); 1178 assert!(handle.join().is_err()); 1179 1180 let _guard = lock_workspace(); 1181 } 1182 1183 #[test] 1184 fn contract_and_coverage_dispatchers_execute() { 1185 let _guard = lock_workspace(); 1186 let out_dir = unique_temp_dir("coverage_dispatch"); 1187 fs::create_dir_all(&out_dir).expect("create out dir"); 1188 1189 run_contract(&["validate".to_string()]).expect("validate contract"); 1190 run(&["dto-roots".to_string(), "--check".to_string()]) 1191 .expect("validate DTO root freshness"); 1192 run(&[ 1193 "generate".to_string(), 1194 "protocol".to_string(), 1195 "--check".to_string(), 1196 ]) 1197 .expect("validate protocol DTO inventory freshness"); 1198 coverage::run(&["help".to_string()]).expect("coverage help"); 1199 coverage::run(&["required-crates".to_string()]).expect("coverage required crates"); 1200 coverage::run(&["workspace-crates".to_string()]).expect("coverage workspace crates"); 1201 1202 let summary_path = out_dir.join("summary.json"); 1203 let lcov_path = out_dir.join("coverage.info"); 1204 let gate_out = out_dir.join("gate-report.json"); 1205 fs::write( 1206 &summary_path, 1207 r#"{"data":[{"totals":{"functions":{"percent":100.0},"lines":{"percent":100.0},"regions":{"percent":100.0}}}]}"#, 1208 ) 1209 .expect("write summary"); 1210 fs::write(&lcov_path, "DA:1,1\nBRDA:1,0,0,1\n").expect("write lcov"); 1211 coverage::run(&[ 1212 "report".to_string(), 1213 "--scope".to_string(), 1214 "main-test".to_string(), 1215 "--summary".to_string(), 1216 summary_path.display().to_string(), 1217 "--lcov".to_string(), 1218 lcov_path.display().to_string(), 1219 "--out".to_string(), 1220 gate_out.display().to_string(), 1221 "--policy-gate".to_string(), 1222 ]) 1223 .expect("coverage report"); 1224 1225 run(&["coverage".to_string(), "help".to_string()]).expect("root run coverage"); 1226 run(&["hygiene".to_string(), "forbidden-identifiers".to_string()]) 1227 .expect("hygiene forbidden identifiers"); 1228 run(&["hygiene".to_string(), "prototype-contracts".to_string()]) 1229 .expect("report prototype contracts"); 1230 1231 let _ = fs::remove_dir_all(out_dir); 1232 } 1233 1234 #[test] 1235 fn usage_and_main_entrypoints_execute() { 1236 usage(); 1237 let empty_code = main_with_args(Vec::new()); 1238 assert_eq!(empty_code, ExitCode::from(2)); 1239 let success_code = main_with_args(vec!["coverage".to_string(), "help".to_string()]); 1240 assert_eq!(success_code, ExitCode::SUCCESS); 1241 let failure_code = main_with_args(vec!["unknown".to_string()]); 1242 assert_eq!(failure_code, ExitCode::from(2)); 1243 let _ = main(); 1244 } 1245 1246 #[test] 1247 fn run_contract_dispatches_validate_command() { 1248 let _guard = lock_workspace(); 1249 validate_protocol_contracts().expect("final protocol contract catalogs"); 1250 run_contract(&["validate".to_string()]).expect("contract validate"); 1251 run_contract(&["event-contract-registry-v7".to_string()]) 1252 .expect("contract registry-v7 inventory"); 1253 run_contract(&["knowledge-manifest".to_string()]).expect("contract knowledge manifest"); 1254 } 1255 }