diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-25 03:41:44 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-25 10:45:33 +0300 |
| commit | 5950c62cec76f86578817b54e45c7bf5d52add67 (patch) | |
| tree | 1e37336351a179970ae94d4a4d4260116215d8b0 | |
| parent | 247f86aa0997a0a6b1a209f38b90e4580c842e1b (diff) | |
| download | fparkan-5950c62cec76f86578817b54e45c7bf5d52add67.tar.xz fparkan-5950c62cec76f86578817b54e45c7bf5d52add67.zip | |
ci: tighten supply-chain fallback policy
| -rw-r--r-- | fixtures/acceptance/coverage.tsv | 2 | ||||
| -rw-r--r-- | xtask/src/main.rs | 61 |
2 files changed, 61 insertions, 2 deletions
diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index a39009a..14d4132 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -11,7 +11,7 @@ S0-ARCH-003 covered cargo xtask policy rejects platform/render adapter dependenc S0-ARCH-004 covered cargo xtask policy scans workspace-owned Rust/TOML for unsafe constructs and workspace lints forbid unsafe_code S0-ARCH-005 covered cargo xtask policy rejects Python source files, Python shebangs, and Python CI workflow steps while allowing docs requirements.txt S0-ARCH-006 covered cargo xtask policy rejects non-fparkan package directories under crates/ -S0-ARCH-007 covered cargo xtask ci runs fmt, policy, workspace test, clippy, rustdoc warnings, cargo-deny or built-in supply-chain fallback, and strict acceptance audit +S0-ARCH-007 covered cargo xtask ci runs fmt, policy, workspace test, clippy, rustdoc warnings, cargo-deny with reviewed deny.toml, and strict acceptance audit; built-in supply-chain fallback is opt-in local-only and forbidden when CI is set S0-ARCH-008 covered cargo xtask policy rejects moving Rust toolchains and workspace rust-version drift S0-ARCH-009 covered .github/workflows/ci.yml runs a pinned MSRV backend-neutral crate job S0-ARCH-010 covered cargo xtask acceptance audit emits commit_sha, rust_toolchain, and msrv metadata into the JSON artifact diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 242f25f..e4023ce 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -38,6 +38,7 @@ const CI_ACCEPTANCE_ROADMAP: &str = "fixtures/acceptance/stage_0_roadmap.md"; const CI_ACCEPTANCE_COVERAGE: &str = "fixtures/acceptance/coverage.tsv"; const CI_ACCEPTANCE_REPORT: &str = "target/fparkan/acceptance/stage-0-audit.json"; const STAGE_PACKAGE_MANIFEST: &str = "fixtures/acceptance/stage_packages.toml"; +const SUPPLY_CHAIN_POLICY_CONFIG: &str = "deny.toml"; const REQUIRED_NATIVE_SMOKE_PLATFORMS: &[&str] = &["macos"]; const APPROVED_REGISTRY_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index"; const SUPPLY_CHAIN_BANNED_PACKAGES: &[&str] = &["native-tls", "openssl", "openssl-sys"]; @@ -192,6 +193,7 @@ fn run_cargo_fmt_check() -> Result<(), String> { } fn run_cargo_deny() -> Result<(), String> { + validate_supply_chain_policy_config(&workspace_relative_path(SUPPLY_CHAIN_POLICY_CONFIG))?; let cargo_deny = std::env::var_os("CARGO_DENY").unwrap_or_else(|| "cargo-deny".into()); let version_output = match Command::new(&cargo_deny).arg("--version").output() { Ok(output) => output, @@ -238,11 +240,15 @@ fn run_cargo_deny() -> Result<(), String> { const PINNED_CARGO_DENY_VERSION: &str = "0.19.9"; fn handle_cargo_deny_fallback(reason: &str) -> Result<(), String> { - if std::env::var_os(ALLOW_SUPPLY_CHAIN_FALLBACK_ENV).is_some() { + if allow_supply_chain_fallback() { eprintln!( "{reason}; running built-in supply-chain policy fallback because {ALLOW_SUPPLY_CHAIN_FALLBACK_ENV} is set" ); run_builtin_supply_chain_policy(Path::new(".")) + } else if std::env::var_os(ALLOW_SUPPLY_CHAIN_FALLBACK_ENV).is_some() && ci_env_active() { + Err(format!( + "{reason}; {ALLOW_SUPPLY_CHAIN_FALLBACK_ENV} is for local developer convenience only and is forbidden when CI is set" + )) } else { Err(format!( "{reason}; install cargo-deny {PINNED_CARGO_DENY_VERSION} or explicitly opt into the fallback with {ALLOW_SUPPLY_CHAIN_FALLBACK_ENV}=1" @@ -250,6 +256,32 @@ fn handle_cargo_deny_fallback(reason: &str) -> Result<(), String> { } } +fn validate_supply_chain_policy_config(path: &Path) -> Result<(), String> { + if path.is_file() { + Ok(()) + } else { + Err(format!( + "reviewed supply-chain policy config is missing: {}", + path.display() + )) + } +} + +fn allow_supply_chain_fallback() -> bool { + std::env::var_os(ALLOW_SUPPLY_CHAIN_FALLBACK_ENV).is_some() && !ci_env_active() +} + +fn ci_env_active() -> bool { + ci_env_value_is_active(std::env::var("CI").ok().as_deref()) +} + +fn ci_env_value_is_active(value: Option<&str>) -> bool { + value.is_some_and(|value| { + let trimmed = value.trim(); + !trimmed.is_empty() && trimmed != "0" && !trimmed.eq_ignore_ascii_case("false") + }) +} + fn run_builtin_supply_chain_policy(root: &Path) -> Result<(), String> { let mut failures = Vec::new(); validate_workspace_license(root, &mut failures)?; @@ -2748,6 +2780,33 @@ source = "git+https://example.invalid/repo" } #[test] + fn supply_chain_policy_config_must_exist() -> Result<(), String> { + let root = temp_dir("supply-chain-config"); + fs::create_dir_all(&root).map_err(|err| err.to_string())?; + + let missing = root.join("deny.toml"); + assert!(validate_supply_chain_policy_config(&missing).is_err()); + + fs::write(&missing, "[graph]\nall-features = true\n").map_err(|err| err.to_string())?; + assert_eq!(validate_supply_chain_policy_config(&missing), Ok(())); + + fs::remove_dir_all(root).map_err(|err| err.to_string())?; + Ok(()) + } + + #[test] + fn ci_env_truthy_values_are_detected() { + assert!(ci_env_value_is_active(Some("true"))); + assert!(ci_env_value_is_active(Some("1"))); + assert!(ci_env_value_is_active(Some("yes"))); + assert!(!ci_env_value_is_active(None)); + assert!(!ci_env_value_is_active(Some(""))); + assert!(!ci_env_value_is_active(Some("0"))); + assert!(!ci_env_value_is_active(Some("false"))); + assert!(!ci_env_value_is_active(Some(" FALSE "))); + } + + #[test] fn detects_forbidden_domain_dependencies() { assert!(!is_forbidden_domain_dependency("fparkan-render-vulkan")); assert!(is_forbidden_domain_dependency("sdl2")); |
