Skip to content

Commit b4c8e15

Browse files
olwangclaude
andcommitted
rsscript: registry-level review-risk badges
Surfaces the existing risk classification + capability summary as a compact, machine-readable badge set a registry can render per package — the §20.1-F item. - PackageReview gains `badges: Vec<String>` derived from `risk` + the review summary: `risk:<level>`, plus `native`, `unsafe`, `async`, `parallel`, `unknown-capability`, `has-errors`. Pure restatement of existing review evidence (no new analysis), so badges never disagree with risk/summary. - The registry index entry (rss.registry.index.v1) carries the subset derivable from its own authoritative fields (`risk:<level>`, `native`, `unsafe`), kept consistent with the entry's risk/native/unsafe_apis. - Risk-label formatting shared via package_risk_label (no duplication). Tested on the s3-iam native package (risk:unknown + native + unknown-capability). Documented in package-manager §18.3 and spec §20.1-F. Remaining (out of repo): rendering badges in a registry UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6b79b03 commit b4c8e15

6 files changed

Lines changed: 90 additions & 10 deletions

File tree

crates/rsscript/src/package/publish.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ fn publish_check(
178178
}
179179
}
180180

181+
/// Registry-index review-risk badges, derived from the entry's own authoritative
182+
/// fields (its aggregate publish `risk` plus the native/unsafe boundary signals),
183+
/// so the badges never disagree with the rest of the index entry. The richer
184+
/// capability badge set lives on `PackageReview::badges` (which has the full
185+
/// review summary).
186+
fn registry_index_badges(risk: PackageRisk, native: bool, unsafe_boundary: bool) -> Vec<String> {
187+
let mut badges = vec![format!("risk:{}", super::package_risk_label(risk))];
188+
if native {
189+
badges.push("native".to_string());
190+
}
191+
if unsafe_boundary {
192+
badges.push("unsafe".to_string());
193+
}
194+
badges
195+
}
196+
181197
fn package_registry_index_entry(
182198
package: &LoadedPackage,
183199
lock_entry: &PackageLockPackage,
@@ -193,6 +209,8 @@ fn package_registry_index_entry(
193209
.as_ref()
194210
.and_then(|native| native.rust.as_ref())
195211
.is_some_and(|native| native.enabled);
212+
let unsafe_boundary = package_index_unsafe_boundary(&package.manifest, native_check);
213+
let badges = registry_index_badges(risk, native, unsafe_boundary);
196214
PackageRegistryIndexEntry {
197215
schema: "rss.registry.index.v1".to_string(),
198216
name: package.manifest.package.name.clone(),
@@ -213,7 +231,8 @@ fn package_registry_index_entry(
213231
has_default: virtual_package.has_default,
214232
provider: virtual_package.provider.clone(),
215233
}),
216-
unsafe_boundary: package_index_unsafe_boundary(&package.manifest, native_check),
234+
unsafe_boundary,
235+
badges,
217236
dependencies: package_index_dependencies(&package.manifest.dependencies),
218237
features: package_index_features(&package.manifest, root_features),
219238
footprint_default: package_index_footprint(native, tree_summary),

crates/rsscript/src/package/review.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,16 @@ pub(super) fn review_package_dir_with_features(
264264
.then_with(|| left.name.cmp(&right.name))
265265
});
266266

267+
let badges = package_review_badges(risk, &summary);
268+
267269
Ok(PackageReview {
268270
schema: super::types::PACKAGE_REVIEW_SCHEMA.to_string(),
269271
producer: super::types::ArtifactProducer::current(),
270272
package: super::package_identity(manifest),
271273
manifest_path: package.manifest_path.display().to_string(),
272274
risk,
273275
reasons,
276+
badges,
274277
features,
275278
virtual_package,
276279
implements,
@@ -1859,6 +1862,33 @@ fn package_unknown_api_count(
18591862
.count()
18601863
}
18611864

1865+
/// Derive the compact review-risk badge set from the classified `risk` and the
1866+
/// capability `summary`. Badges are stable, lowercase, machine-readable labels a
1867+
/// registry can render per package; they restate signals already in the review
1868+
/// (no new analysis), so they never disagree with `risk`/`summary`.
1869+
fn package_review_badges(risk: PackageRisk, summary: &PackageReviewSummary) -> Vec<String> {
1870+
let mut badges = vec![format!("risk:{}", super::package_risk_label(risk))];
1871+
if summary.native_apis > 0 {
1872+
badges.push("native".to_string());
1873+
}
1874+
if summary.unsafe_apis > 0 {
1875+
badges.push("unsafe".to_string());
1876+
}
1877+
if summary.async_apis > 0 {
1878+
badges.push("async".to_string());
1879+
}
1880+
if summary.parallel_apis > 0 {
1881+
badges.push("parallel".to_string());
1882+
}
1883+
if summary.unknown_apis > 0 {
1884+
badges.push("unknown-capability".to_string());
1885+
}
1886+
if summary.errors > 0 {
1887+
badges.push("has-errors".to_string());
1888+
}
1889+
badges
1890+
}
1891+
18621892
fn package_risk(
18631893
manifest: &Manifest,
18641894
native: Option<&PackageNativeRustReview>,

crates/rsscript/src/package/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ pub struct PackageReview {
3535
pub manifest_path: String,
3636
pub risk: PackageRisk,
3737
pub reasons: Vec<String>,
38+
/// Compact, machine-readable review-risk badges derived from `risk` and the
39+
/// capability `summary` (e.g. `risk:elevated`, `native`, `unsafe`, `async`,
40+
/// `unknown-capability`). A registry surfaces these as per-package badges.
41+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
42+
pub badges: Vec<String>,
3843
pub features: Vec<String>,
3944
pub virtual_package: Option<PackageVirtual>,
4045
pub implements: Vec<PackageProviderImplementation>,
@@ -209,6 +214,10 @@ pub struct PackageRegistryIndexEntry {
209214
pub virtual_package: Option<PackageVirtual>,
210215
#[serde(rename = "unsafe_apis")]
211216
pub unsafe_boundary: bool,
217+
/// Review-risk badges (same set as `PackageReview::badges`) carried into the
218+
/// registry index so a registry can render them without re-deriving.
219+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
220+
pub badges: Vec<String>,
212221
pub dependencies: BTreeMap<String, String>,
213222
pub features: BTreeMap<String, Vec<String>>,
214223
pub footprint_default: PackageRegistryFootprint,

crates/rsscript/tests/s3_iam_reir_demo_e2e.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ fn s3_iam_reir_demo_missing_capability_binding_is_unknown_not_safe() {
340340

341341
assert_eq!(review.risk, rsscript::PackageRisk::Unknown);
342342
assert_eq!(review.summary.unknown_apis, 1);
343+
// Review-risk badges restate the classified risk + capability signals as a
344+
// compact, registry-renderable set; they must agree with risk/summary.
345+
assert!(
346+
review.badges.contains(&"risk:unknown".to_string()),
347+
"badges: {:?}",
348+
review.badges
349+
);
350+
assert!(review.badges.contains(&"native".to_string()));
351+
assert!(review.badges.contains(&"unknown-capability".to_string()));
343352
assert!(
344353
review
345354
.reasons

docs/RSScript_Package_Manager_Design_v0.6.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,6 +2778,7 @@ Implemented v1 schema shape:
27782778
},
27792779
"risk": "elevated",
27802780
"reasons": ["native Rust wrapper enabled", "returns Result"],
2781+
"badges": ["risk:elevated", "native"],
27812782
"features": [],
27822783
"implements": [],
27832784
"dependencies": [],
@@ -2849,6 +2850,15 @@ Implemented v1 schema shape:
28492850
}
28502851
```
28512852

2853+
`badges` is a compact, machine-readable set of review-risk badges derived from
2854+
`risk` and the capability `summary` (`risk:<level>`, plus `native`, `unsafe`,
2855+
`async`, `parallel`, `unknown-capability`, `has-errors` as applicable). They
2856+
restate signals already in the review — never new analysis — so a registry can
2857+
render per-package badges without re-deriving them. The registry index entry
2858+
(`rss.registry.index.v1`) carries the subset derivable from its own authoritative
2859+
fields (`risk:<level>`, `native`, `unsafe`), kept consistent with the entry's
2860+
`risk`/`native`/`unsafe_apis`.
2861+
28522862
`exports` records the normalized public contract surface, not only callable
28532863
functions. Current package review metadata uses `kind` values such as
28542864
`type`, `sum_type`, `type_alias`, `const`, `function`, `protocol`, and

docs/RSScript_v0.7_Spec.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5301,15 +5301,18 @@ E. Sum type hardening
53015301
implicit variant coercion.
53025302
53035303
F. Registry-level review-risk badges
5304-
- the package registry should surface review-risk signals as first-class
5305-
quality badges: native, unsafe, unknown, mutating/retaining ratios.
5306-
- this reuses the package review metadata already produced by package tooling
5307-
(section 18.3) rather than inventing a separate scoring system.
5308-
- package-manager registry previews expose these signals through
5309-
`rss.registry.index.v1` fields such as `review_schema`, `unsafe_apis`,
5310-
selected feature sets, and default graph footprint. The language spec does
5311-
not define registry scoring beyond requiring those signals to derive from
5312-
compiler/package review evidence.
5304+
- the package registry surfaces review-risk signals as first-class quality
5305+
badges: a `badges` field on the package review (`rss.review.package.v1`) and
5306+
the registry index (`rss.registry.index.v1`) carries compact labels —
5307+
`risk:<level>`, `native`, `unsafe`, `async`, `parallel`,
5308+
`unknown-capability`, `has-errors` — derived from the classified `risk` and
5309+
capability `summary` (package-manager §18.3).
5310+
- badges restate review evidence already produced by package tooling; they
5311+
invent no separate scoring system and never disagree with `risk`/`summary`.
5312+
- remaining (registry-side, out of this repo): rendering the badges in a
5313+
registry UI / preview. The language + package tooling now emit the badge data;
5314+
the language spec does not define registry scoring beyond requiring those
5315+
signals to derive from compiler/package review evidence.
53135316
53145317
G. Capability objects (explicit dynamic dispatch)
53155318
- RSScript does not adopt Rust-style `dyn Trait` with vtable coercion.

0 commit comments

Comments
 (0)