Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/cmd/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ pub fn format_info(pkg: &Package, platform: &Platform, nu: Option<&NuVersion>) -
));
}

if ver.is_provisional() {
let reason = ver
.deferral_reason
.as_deref()
.unwrap_or("reason not recorded");
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
Comment thread
tonythethompson marked this conversation as resolved.
Outdated
out.push_str(&format!(" evidence: provisional — {reason}\n"));
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Outdated
}

if let Some(ref source) = ver.source {
out.push_str(&format!(" source git: {}\n", source.git));
out.push_str(&format!(" source rev: {}\n", source.rev));
Expand Down Expand Up @@ -161,6 +169,8 @@ mod tests {
}),
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
}
}
Expand Down Expand Up @@ -195,6 +205,33 @@ mod tests {
assert!(out.contains("cargo_name: nu_plugin_highlight"), "{out}");
}

#[test]
fn format_info_notes_provisional_with_reason() {
let mut pkg = sample_plugin(false);
pkg.versions[0].evidence_tier = Some("provisional".to_string());
pkg.versions[0].deferral_reason = Some("requires cloud credentials".to_string());
let out = format_info(&pkg, &linux_platform(), None);
assert!(
out.contains("evidence: provisional — requires cloud credentials"),
"{out}"
);
}

#[test]
fn format_info_notes_provisional_without_reason() {
let mut pkg = sample_plugin(false);
pkg.versions[0].evidence_tier = Some("provisional".to_string());
let out = format_info(&pkg, &linux_platform(), None);
assert!(out.contains("reason not recorded"), "{out}");
}

#[test]
fn format_info_omits_evidence_line_when_proven() {
let pkg = sample_plugin(false);
let out = format_info(&pkg, &linux_platform(), None);
assert!(!out.contains("evidence:"), "{out}");
}

#[test]
fn format_info_omits_source_lines_when_absent() {
let pkg = sample_plugin(false);
Expand Down
43 changes: 40 additions & 3 deletions src/cmd/search.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::core::nu_version::NuVersion;
use crate::core::package::PackageType;
use crate::core::package::{PackageType, VersionEntry};
use crate::core::platform::Platform;
use crate::core::registry::RegistryManager;
use crate::core::resolve::{Incompatibility, Resolver};
Expand Down Expand Up @@ -101,10 +101,18 @@ pub fn execute(args: &SearchArgs, root: &Path) -> Result<()> {
}
};

let provisional_marker = provisional_marker(display_entry);

println!(
" {}/{} v{} [{}]{}
" {}/{} v{} [{}]{}{}
{}",
pkg.id.owner, pkg.id.name, version_label, pkg.package_type, status, pkg.description
pkg.id.owner,
pkg.id.name,
version_label,
pkg.package_type,
status,
provisional_marker,
pkg.description
);
}

Expand Down Expand Up @@ -140,6 +148,15 @@ fn format_search_header(nu: Option<&NuVersion>, triple: &str) -> String {
}
}

/// Row suffix marking a provisional (not lifecycle-tested) displayed version.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
fn provisional_marker(entry: Option<&VersionEntry>) -> &'static str {
if entry.map(|v| v.is_provisional()).unwrap_or(false) {
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Outdated
" [provisional]"
} else {
""
}
}

/// Row status suffix (leading space + brackets), or empty.
///
/// Plugins get a hard evaluated verdict. Modules use not-ABI-locked wording.
Expand Down Expand Up @@ -251,6 +268,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
}
}
Expand Down Expand Up @@ -336,6 +355,24 @@ mod tests {
assert!(status.is_empty());
}

#[test]
fn provisional_marker_shown_for_provisional_entry() {
let mut pkg = sample_pkg_typed(PackageType::Plugin, "*", vec![]);
pkg.versions[0].evidence_tier = Some("provisional".to_string());
assert_eq!(provisional_marker(Some(&pkg.versions[0])), " [provisional]");
}

#[test]
fn provisional_marker_empty_for_proven_entry() {
let pkg = sample_pkg_typed(PackageType::Plugin, "*", vec![]);
assert_eq!(provisional_marker(Some(&pkg.versions[0])), "");
}

#[test]
fn provisional_marker_empty_for_none() {
assert_eq!(provisional_marker(None), "");
}

#[test]
fn hidden_footer_points_at_info_not_sync() {
let footer = format_hidden_footer(
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/try_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
}
}
Expand Down
56 changes: 56 additions & 0 deletions src/core/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ pub struct VersionEntry {
/// `None` for plugins, scripts, and completions.
#[serde(default)]
pub activation: Option<RegistryActivationSpec>,
/// Evidence tier for this version. `None` means "proven" (legacy
/// entries and any entry with full lifecycle evidence).
#[serde(default)]
pub evidence_tier: Option<String>,
/// Required alongside `evidence_tier: "provisional"`: why
/// lifecycle-prove was deferred for this version.
#[serde(default)]
pub deferral_reason: Option<String>,
}

impl VersionEntry {
/// Whether this version is provisional (structural validation only, no
/// full lifecycle evidence). Absent `evidence_tier` means proven.
pub fn is_provisional(&self) -> bool {
self.evidence_tier.as_deref() == Some("provisional")
Comment thread
tonythethompson marked this conversation as resolved.
Outdated
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -269,6 +285,46 @@ mod tests {
assert!(entry.source.is_none());
}

#[test]
fn version_entry_absent_evidence_tier_is_not_provisional() {
let json = r#"{
"version": "0.25.2",
"nu_version": ">=0.113.0 <0.114.0",
"artifact": { "kind": "binary", "targets": {} }
}"#;
let entry: VersionEntry = serde_json::from_str(json).unwrap();
assert!(!entry.is_provisional());
}

#[test]
fn version_entry_provisional_evidence_tier() {
let json = r#"{
"version": "0.25.2",
"nu_version": ">=0.113.0 <0.114.0",
"evidence_tier": "provisional",
"deferral_reason": "requires cloud credentials",
"artifact": { "kind": "binary", "targets": {} }
}"#;
let entry: VersionEntry = serde_json::from_str(json).unwrap();
assert!(entry.is_provisional());
assert_eq!(
entry.deferral_reason.as_deref(),
Some("requires cloud credentials")
);
}

#[test]
fn version_entry_proven_evidence_tier_is_not_provisional() {
let json = r#"{
"version": "0.25.2",
"nu_version": ">=0.113.0 <0.114.0",
"evidence_tier": "proven",
"artifact": { "kind": "binary", "targets": {} }
}"#;
let entry: VersionEntry = serde_json::from_str(json).unwrap();
assert!(!entry.is_provisional());
}

#[test]
fn parse_version_entry_with_source() {
let json = r#"{
Expand Down
2 changes: 2 additions & 0 deletions src/core/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
}],
}
Expand Down
6 changes: 6 additions & 0 deletions src/core/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
},
VersionEntry {
version: semver::Version::new(1, 0, 0),
Expand All @@ -743,6 +745,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
},
],
}
Expand Down Expand Up @@ -1055,6 +1059,8 @@ mod tests {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/install/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,17 @@ pub fn install_package(
install_dir.display()
);

if resolved.is_provisional() {
let reason = resolved
.deferral_reason
.as_deref()
.unwrap_or("reason not recorded");
println!(
"{} This package has not been lifecycle-tested. It passed integrity checks. ({reason})",
console::style("!").yellow()
);
Comment thread
tonythethompson marked this conversation as resolved.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
}

Ok(InstallResult {
installed: true,
package: lock_key,
Expand Down
8 changes: 8 additions & 0 deletions tests/install_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ fn integration_full_install_from_signed_registry() {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
Comment thread
tonythethompson marked this conversation as resolved.
}],
};

Expand Down Expand Up @@ -381,6 +383,8 @@ fn integration_resolve_exact_rejects_incompatible() {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
};

Expand Down Expand Up @@ -463,6 +467,8 @@ fn integration_snapshot_before_install() {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
};

Expand Down Expand Up @@ -533,6 +539,8 @@ fn integration_snapshot_before_install() {
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}],
};

Expand Down
2 changes: 2 additions & 0 deletions tests/support/active_update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@ fn write_dual_version_registry(
source: None,
dependencies: BTreeMap::new(),
activation: None,
evidence_tier: None,
deferral_reason: None,
}
};

Expand Down
Loading