commit 22787c02c6493165ba1c68b52e1d7688928206de
parent adc1b18196a5f45c2ee7f0da37561bec96e34ec3
Author: triesap <tyson@radroots.org>
Date: Fri, 21 Aug 2026 03:23:24 +0000
test: restore release coverage gates
- add exact negative-path coverage for trade, transport, and FFI boundaries
- exercise release-artifact and source-lock policy drift independently
- preserve governed coverage thresholds and normalization semantics
- qualify the required crates before service build integration
Diffstat:
11 files changed, 1452 insertions(+), 3 deletions(-)
diff --git a/crates/event_codec/src/authoring/mod.rs b/crates/event_codec/src/authoring/mod.rs
@@ -428,6 +428,11 @@ mod tests {
}
#[test]
+ fn plan_digest_parser_rejects_a_wrong_length_before_hex_decoding() {
+ assert_eq!(PlanDigest::parse_hex("a"), Err(PlanDigestError));
+ }
+
+ #[test]
fn every_bound_authoring_field_changes_the_plan_digest() {
let base = plan(ALICE, 1_700_000_000, Vec::new(), "hello");
let variants = [
diff --git a/crates/event_codec/tests/event_ref.rs b/crates/event_codec/tests/event_ref.rs
@@ -65,6 +65,16 @@ fn parse_event_ref_tag_rejects_noncanonical_event_pointer_shapes() {
let tag = vec![
"e".to_string(),
hex_64('5'),
+ common::AUTHOR.to_string(),
+ KIND_POST.to_string(),
+ RELAY_PRIMARY_WSS.to_string(),
+ ];
+ let err = parse_event_ref_tag(&tag, "e").unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("e")));
+
+ let tag = vec![
+ "e".to_string(),
+ hex_64('5'),
hex_64('6'),
KIND_POST.to_string(),
];
diff --git a/crates/event_codec/tests/parser_robustness_corpus.rs b/crates/event_codec/tests/parser_robustness_corpus.rs
@@ -46,6 +46,21 @@ fn corpus_rejects_oversized_input_before_json_allocation() {
}
#[test]
+fn signed_event_decoder_accepts_valid_input_and_rejects_oversize_before_parsing() {
+ let encoded = encode(&valid_event());
+ decode::signed_event(encoded.as_str()).expect("ID-valid signed event");
+
+ let raw = " ".repeat(DEFAULT_RAW_JSON_MAX_BYTES + 1);
+ assert_eq!(
+ decode::signed_event(raw.as_str()).expect_err("oversized signed event"),
+ DecodeError::InputTooLarge {
+ max: DEFAULT_RAW_JSON_MAX_BYTES,
+ actual: DEFAULT_RAW_JSON_MAX_BYTES + 1,
+ }
+ );
+}
+
+#[test]
fn corpus_rejects_tag_count_overflow_before_id_verification() {
let mut value = valid_event();
object(&mut value).insert(
diff --git a/crates/event_codec_wasm/src/lib.rs b/crates/event_codec_wasm/src/lib.rs
@@ -1057,6 +1057,7 @@ pub fn group_roles_tags(group_json: &str) -> Result<String, RadrootsJsValue> {
#[cfg(test)]
mod tests {
use super::*;
+ use core::num::NonZeroU64;
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::envelope::kind::{
KIND_FARM_FILE_METADATA, KIND_FILE_METADATA, KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_SOURCE,
@@ -1073,6 +1074,7 @@ mod tests {
FarmWorkspaceRelayMode, RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION,
RADROOTS_FARM_WORKSPACE_SCHEMA,
};
+ use radroots_event::id::TradeId;
use radroots_event::knowledge::{
AddressableRef, KnowledgeCitationSpan, KnowledgeFieldContext, KnowledgeLocation,
KnowledgeLocationPrecision, KnowledgeNodeRef, KnowledgeObservation,
@@ -1097,6 +1099,12 @@ mod tests {
ReportFileTarget, ReportType, SocialFarmAnchor, SocialLocation, SocialMediaDimensions,
SocialTarget,
};
+ use radroots_trade::evidence::{
+ RadrootsTradeEvidenceManifestSourceResultV1, RadrootsTradeEvidencePolicyDigestV1,
+ RadrootsTradeEvidenceScopePrerequisitesV1, RadrootsTradeEvidenceSourceCompletionV1,
+ RadrootsTradeEvidenceSourceIdV1, RadrootsTradeEvidenceSourceRequirementV1,
+ RadrootsTradeEvidenceSourceResultDigestV1, RadrootsTradeEvidenceSourceResultV1,
+ };
fn sample_listing() -> OperationalListing {
let quantity =
@@ -2490,5 +2498,110 @@ mod tests {
let secret = "private-report-content";
let error = rhi_evidence_report_parse_json(secret).expect_err("malformed report");
assert!(!format!("{error:?}").contains(secret));
+
+ let decision_vectors: serde_json::Value = serde_json::from_str(include_str!(
+ "../../../contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json"
+ ))
+ .expect("RHI decision corpus");
+ let superseding = decision_vectors["vectors"]
+ .as_array()
+ .expect("RHI decision vectors")
+ .iter()
+ .find(|entry| entry["id"] == "rhi_evidence_attestation_superseding_002")
+ .expect("superseding vector");
+ let superseding_content = superseding["expected"]["canonical_event_content_utf8"]
+ .as_str()
+ .expect("superseding canonical content");
+ let superseding_report: serde_json::Value = serde_json::from_str(
+ &rhi_evidence_report_parse_json(superseding_content)
+ .expect("superseding report projection"),
+ )
+ .expect("superseding report JSON");
+ assert_eq!(superseding_report["outcome"], "valid");
+ assert_eq!(
+ superseding_report["supersession"]["report_id"],
+ "7777777777777777777777777777777777777777777777777777777777777777"
+ );
+ assert_eq!(
+ superseding_report["supersession"]["event_id"],
+ "8888888888888888888888888888888888888888888888888888888888888888"
+ );
+ }
+
+ #[test]
+ fn evidence_manifest_projection_covers_every_admitted_vocabulary() {
+ assert_eq!(
+ coverage_name(RadrootsTradeEvidenceCoverageV1::Missing),
+ "missing"
+ );
+ assert_eq!(
+ coverage_name(RadrootsTradeEvidenceCoverageV1::Partial),
+ "partial"
+ );
+ assert_eq!(
+ coverage_name(RadrootsTradeEvidenceCoverageV1::ScopeSatisfied),
+ "scope_satisfied"
+ );
+ assert_eq!(
+ coverage_name(RadrootsTradeEvidenceCoverageV1::Unsupported),
+ "unsupported"
+ );
+ assert_eq!(outcome_name(RadrootsTradeEvidenceOutcomeV1::Valid), "valid");
+ assert_eq!(
+ outcome_name(RadrootsTradeEvidenceOutcomeV1::Invalid),
+ "invalid"
+ );
+ assert_eq!(
+ outcome_name(RadrootsTradeEvidenceOutcomeV1::Indeterminate),
+ "indeterminate"
+ );
+ assert_eq!(
+ attestation_outcome_name(RadrootsRhiEvidenceAttestationOutcomeV1::Valid),
+ "valid"
+ );
+ assert_eq!(
+ attestation_outcome_name(RadrootsRhiEvidenceAttestationOutcomeV1::Invalid),
+ "invalid"
+ );
+ assert_eq!(
+ attestation_outcome_name(RadrootsRhiEvidenceAttestationOutcomeV1::Indeterminate),
+ "indeterminate"
+ );
+
+ let source = RadrootsTradeEvidenceManifestSourceResultV1::new(
+ RadrootsTradeEvidenceSourceIdV1::parse("typed_source").expect("source id"),
+ RadrootsTradeEvidenceSourceResultV1::new(
+ RadrootsTradeEvidenceSourceRequirementV1::Required,
+ RadrootsTradeEvidenceSourceCompletionV1::Complete,
+ 0,
+ )
+ .expect("source result"),
+ RadrootsTradeEvidenceSourceResultDigestV1::from_bytes([0x33; 32]),
+ );
+ let manifest = RadrootsTradeEvidenceManifestV1::new(
+ TradeId::from_bytes([0x11; 16]),
+ NonZeroU64::new(1).expect("nonzero generation"),
+ RadrootsTradeEvidencePolicyDigestV1::from_bytes([0x22; 32]),
+ 1_800_000_000,
+ RadrootsTradeEvidenceScopePrerequisitesV1::Satisfied,
+ [source],
+ [],
+ )
+ .expect("manifest");
+ let canonical_hex = hex::encode(manifest.canonical_bytes());
+ let projected: serde_json::Value = serde_json::from_str(
+ &trade_evidence_manifest_parse_json(&canonical_hex).expect("manifest projection"),
+ )
+ .expect("manifest projection JSON");
+ assert_eq!(projected["coverage"], "scope_satisfied");
+ assert_eq!(projected["canonical_bytes_hex"], canonical_hex);
+
+ assert!(trade_evidence_manifest_parse_json("0").is_err());
+ assert!(
+ trade_evidence_manifest_parse_json(&"0".repeat(
+ radroots_trade::evidence::RADROOTS_TRADE_EVIDENCE_MANIFEST_MAXIMUM_BYTES * 2 + 2
+ ))
+ .is_err()
+ );
}
}
diff --git a/crates/mobile_ffi/src/dto.rs b/crates/mobile_ffi/src/dto.rs
@@ -2298,9 +2298,12 @@ fn require_schema(schema_version: u16) -> Result<(), RadrootsAppError> {
#[cfg(test)]
mod tests {
+ use core::num::NonZeroU64;
use std::io::Write;
use std::os::fd::AsRawFd;
+ use radroots_event::id::TradeId;
+
use super::*;
fn rhi_attestation_fixture() -> serde_json::Value {
@@ -2354,6 +2357,139 @@ mod tests {
}
#[test]
+ fn evidence_manifest_and_supersession_project_the_complete_vocabulary() {
+ use radroots_sdk::trade::{
+ RadrootsTradeEvidenceManifestSourceResultV1, RadrootsTradeEvidenceManifestV1,
+ RadrootsTradeEvidencePolicyDigestV1, RadrootsTradeEvidenceScopePrerequisitesV1,
+ RadrootsTradeEvidenceSourceCompletionV1, RadrootsTradeEvidenceSourceIdV1,
+ RadrootsTradeEvidenceSourceRequirementV1, RadrootsTradeEvidenceSourceResultDigestV1,
+ RadrootsTradeEvidenceSourceResultV1,
+ };
+
+ assert_eq!(
+ FfiTradeEvidenceCoverage::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Missing
+ ),
+ FfiTradeEvidenceCoverage::Missing
+ );
+ assert_eq!(
+ FfiTradeEvidenceCoverage::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Partial
+ ),
+ FfiTradeEvidenceCoverage::Partial
+ );
+ assert_eq!(
+ FfiTradeEvidenceCoverage::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::ScopeSatisfied
+ ),
+ FfiTradeEvidenceCoverage::ScopeSatisfied
+ );
+ assert_eq!(
+ FfiTradeEvidenceCoverage::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Unsupported
+ ),
+ FfiTradeEvidenceCoverage::Unsupported
+ );
+ assert_eq!(
+ FfiTradeEvidenceOutcome::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Valid
+ ),
+ FfiTradeEvidenceOutcome::Valid
+ );
+ assert_eq!(
+ FfiTradeEvidenceOutcome::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Invalid
+ ),
+ FfiTradeEvidenceOutcome::Invalid
+ );
+ assert_eq!(
+ FfiTradeEvidenceOutcome::from(
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Indeterminate
+ ),
+ FfiTradeEvidenceOutcome::Indeterminate
+ );
+
+ let source = RadrootsTradeEvidenceManifestSourceResultV1::new(
+ RadrootsTradeEvidenceSourceIdV1::parse("typed_source").expect("source id"),
+ RadrootsTradeEvidenceSourceResultV1::new(
+ RadrootsTradeEvidenceSourceRequirementV1::Required,
+ RadrootsTradeEvidenceSourceCompletionV1::Complete,
+ 0,
+ )
+ .expect("source result"),
+ RadrootsTradeEvidenceSourceResultDigestV1::from_bytes([0x33; 32]),
+ );
+ let manifest = RadrootsTradeEvidenceManifestV1::new(
+ TradeId::from_bytes([0x11; 16]),
+ NonZeroU64::new(1).expect("nonzero generation"),
+ RadrootsTradeEvidencePolicyDigestV1::from_bytes([0x22; 32]),
+ 1_800_000_000,
+ RadrootsTradeEvidenceScopePrerequisitesV1::Satisfied,
+ [source],
+ [],
+ )
+ .expect("manifest");
+ let projected = parse_trade_evidence_manifest(manifest.canonical_bytes().to_vec())
+ .expect("manifest projection");
+ assert_eq!(projected.coverage, FfiTradeEvidenceCoverage::ScopeSatisfied);
+ assert_eq!(
+ projected.canonical_bytes_hex,
+ hex::encode(manifest.canonical_bytes())
+ );
+
+ let decisions: serde_json::Value = serde_json::from_str(include_str!(
+ "../../../contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json"
+ ))
+ .expect("RHI decision corpus");
+ let superseding = decisions["vectors"]
+ .as_array()
+ .expect("RHI decision vectors")
+ .iter()
+ .find(|entry| entry["id"] == "rhi_evidence_attestation_superseding_002")
+ .expect("superseding vector");
+ let report = parse_rhi_evidence_report(
+ superseding["expected"]["canonical_event_content_utf8"]
+ .as_str()
+ .expect("canonical event content")
+ .to_owned(),
+ )
+ .expect("superseding report");
+ assert_eq!(report.outcome, FfiTradeEvidenceOutcome::Valid);
+ assert_eq!(
+ report.supersedes_report_id.as_deref(),
+ Some("7777777777777777777777777777777777777777777777777777777777777777")
+ );
+ assert_eq!(
+ report.supersedes_event_id.as_deref(),
+ Some("8888888888888888888888888888888888888888888888888888888888888888")
+ );
+ }
+
+ #[test]
+ fn transport_policy_enums_map_every_closed_variant() {
+ assert_eq!(
+ FfiCancellationPolicy::PreservePublishedRequest.core(),
+ Phase1CancellationPolicy::PreservePublishedRequest
+ );
+ assert_eq!(
+ FfiCancellationPolicy::LocalCooperative.core(),
+ Phase1CancellationPolicy::LocalCooperative
+ );
+ assert_eq!(
+ radroots_sdk::transport::BlossomHostKind::from(FfiBlossomHostKind::Native),
+ radroots_sdk::transport::BlossomHostKind::Native
+ );
+ assert_eq!(
+ radroots_sdk::transport::BlossomHostKind::from(FfiBlossomHostKind::Simulator),
+ radroots_sdk::transport::BlossomHostKind::Simulator
+ );
+ assert_eq!(
+ radroots_sdk::transport::BlossomHostKind::from(FfiBlossomHostKind::PhysicalDevice),
+ radroots_sdk::transport::BlossomHostKind::PhysicalDevice
+ );
+ }
+
+ #[test]
fn evidence_errors_do_not_render_untrusted_content() {
let secret = "mobile-private-evidence";
let error = parse_rhi_evidence_report(secret.to_owned()).expect_err("malformed report");
diff --git a/crates/sdk_ffi/src/lib.rs b/crates/sdk_ffi/src/lib.rs
@@ -439,10 +439,14 @@ pub mod v1 {
#[cfg(test)]
mod tests {
+ use core::num::NonZeroU64;
+
+ use radroots_event::id::TradeId;
+
use super::v1::{
- CapabilityAvailability, Error, MobileClient, SignedEvent, TradeEvidenceOutcome,
- parse_rhi_evidence_report, prepare_rhi_evidence_attestation,
- validate_rhi_evidence_attestation,
+ CapabilityAvailability, Error, MobileClient, SignedEvent, TradeEvidenceCoverage,
+ TradeEvidenceOutcome, parse_rhi_evidence_report, parse_trade_evidence_manifest,
+ prepare_rhi_evidence_attestation, validate_rhi_evidence_attestation,
};
fn assert_send_sync<T: Send + Sync>() {}
@@ -530,6 +534,128 @@ mod tests {
}
#[test]
+ fn manifest_supersession_and_closed_evidence_vocabularies_cross_the_ffi() {
+ use radroots_sdk::trade::{
+ RadrootsTradeEvidenceManifestSourceResultV1, RadrootsTradeEvidenceManifestV1,
+ RadrootsTradeEvidencePolicyDigestV1, RadrootsTradeEvidenceScopePrerequisitesV1,
+ RadrootsTradeEvidenceSourceCompletionV1, RadrootsTradeEvidenceSourceIdV1,
+ RadrootsTradeEvidenceSourceRequirementV1, RadrootsTradeEvidenceSourceResultDigestV1,
+ RadrootsTradeEvidenceSourceResultV1,
+ };
+
+ for (source, expected) in [
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Missing,
+ TradeEvidenceCoverage::Missing,
+ ),
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Partial,
+ TradeEvidenceCoverage::Partial,
+ ),
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::ScopeSatisfied,
+ TradeEvidenceCoverage::ScopeSatisfied,
+ ),
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceCoverageV1::Unsupported,
+ TradeEvidenceCoverage::Unsupported,
+ ),
+ ] {
+ assert_eq!(TradeEvidenceCoverage::from(source), expected);
+ }
+ for (source, expected) in [
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Valid,
+ TradeEvidenceOutcome::Valid,
+ ),
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Invalid,
+ TradeEvidenceOutcome::Invalid,
+ ),
+ (
+ radroots_sdk::trade::RadrootsTradeEvidenceOutcomeV1::Indeterminate,
+ TradeEvidenceOutcome::Indeterminate,
+ ),
+ ] {
+ assert_eq!(TradeEvidenceOutcome::from(source), expected);
+ }
+
+ let source = RadrootsTradeEvidenceManifestSourceResultV1::new(
+ RadrootsTradeEvidenceSourceIdV1::parse("typed_source").expect("source id"),
+ RadrootsTradeEvidenceSourceResultV1::new(
+ RadrootsTradeEvidenceSourceRequirementV1::Required,
+ RadrootsTradeEvidenceSourceCompletionV1::Complete,
+ 0,
+ )
+ .expect("source result"),
+ RadrootsTradeEvidenceSourceResultDigestV1::from_bytes([0x33; 32]),
+ );
+ let manifest = RadrootsTradeEvidenceManifestV1::new(
+ TradeId::from_bytes([0x11; 16]),
+ NonZeroU64::new(1).expect("nonzero generation"),
+ RadrootsTradeEvidencePolicyDigestV1::from_bytes([0x22; 32]),
+ 1_800_000_000,
+ RadrootsTradeEvidenceScopePrerequisitesV1::Satisfied,
+ [source],
+ [],
+ )
+ .expect("manifest");
+ let projected = parse_trade_evidence_manifest(manifest.canonical_bytes().to_vec())
+ .expect("manifest projection");
+ assert_eq!(projected.coverage, TradeEvidenceCoverage::ScopeSatisfied);
+ assert_eq!(
+ projected.canonical_bytes_hex,
+ hex::encode(manifest.canonical_bytes())
+ );
+ assert!(parse_trade_evidence_manifest(Vec::new()).is_err());
+
+ let decisions: serde_json::Value = serde_json::from_str(include_str!(
+ "../../../contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json"
+ ))
+ .expect("RHI decision corpus");
+ let superseding = decisions["vectors"]
+ .as_array()
+ .expect("RHI decision vectors")
+ .iter()
+ .find(|entry| entry["id"] == "rhi_evidence_attestation_superseding_002")
+ .expect("superseding vector");
+ let report = parse_rhi_evidence_report(
+ superseding["expected"]["canonical_event_content_utf8"]
+ .as_str()
+ .expect("canonical event content")
+ .to_owned(),
+ )
+ .expect("superseding report");
+ assert_eq!(report.outcome, TradeEvidenceOutcome::Valid);
+ let supersession = report.supersession.expect("supersession");
+ assert_eq!(
+ supersession.report_id,
+ "7777777777777777777777777777777777777777777777777777777777777777"
+ );
+ assert_eq!(
+ supersession.event_id,
+ "8888888888888888888888888888888888888888888888888888888888888888"
+ );
+ }
+
+ #[test]
+ fn evidence_entry_points_classify_pre_plan_and_signed_event_failures() {
+ assert!(prepare_rhi_evidence_attestation("invalid".to_owned(), 0).is_err());
+ assert!(
+ validate_rhi_evidence_attestation(SignedEvent {
+ id: String::new(),
+ author_pubkey: String::new(),
+ created_at_unix_s: 0,
+ kind: 0,
+ tags: Vec::new(),
+ content: String::new(),
+ signature: String::new(),
+ })
+ .is_err()
+ );
+ }
+
+ #[test]
fn ffi_contract_errors_are_bounded_and_secret_safe() {
let secret = "private-report-content";
let error = parse_rhi_evidence_report(secret.to_owned()).expect_err("malformed report");
diff --git a/crates/trade/src/evidence_manifest.rs b/crates/trade/src/evidence_manifest.rs
@@ -874,6 +874,55 @@ mod tests {
}
#[test]
+ fn manifest_components_expose_exact_typed_evidence() {
+ let manifest = manifest();
+ let source = &manifest.sources()[0];
+ let observation = &manifest.observations()[0];
+
+ assert_eq!(source.source_id().as_str(), "source-a");
+ assert_eq!(
+ source.result().requirement(),
+ RadrootsTradeEvidenceSourceRequirementV1::Required
+ );
+ assert_eq!(
+ source.result().completion(),
+ RadrootsTradeEvidenceSourceCompletionV1::Complete
+ );
+ assert_eq!(source.result().admitted_event_count(), 1);
+ assert_eq!(source.result_digest().as_bytes(), &[0x41; 32]);
+
+ assert_eq!(observation.source_id().as_str(), "source-a");
+ assert_eq!(observation.mutation_id().as_bytes(), &[0x31; 32]);
+ assert_eq!(observation.event_id().as_bytes(), &[0x51; 32]);
+ assert_eq!(observation.signed_event_digest().as_bytes(), &[0x61; 32]);
+ assert_eq!(observation.provenance_digest().as_bytes(), &[0x71; 32]);
+ assert_eq!(manifest.digest().as_bytes().len(), 32);
+
+ assert_eq!(
+ RadrootsTradeEvidencePolicyDigestV1::sha256(b"policy").as_bytes(),
+ RadrootsTradeEvidencePolicyDigestV1::from_bytes(Sha256::digest(b"policy").into())
+ .as_bytes()
+ );
+ assert_eq!(
+ RadrootsTradeEvidenceSourceResultDigestV1::sha256(b"result").as_bytes(),
+ RadrootsTradeEvidenceSourceResultDigestV1::from_bytes(Sha256::digest(b"result").into())
+ .as_bytes()
+ );
+ assert_eq!(
+ RadrootsTradeSignedEventDigestV1::sha256(b"event").as_bytes(),
+ RadrootsTradeSignedEventDigestV1::from_bytes(Sha256::digest(b"event").into())
+ .as_bytes()
+ );
+ assert_eq!(
+ RadrootsTradeEvidenceProvenanceDigestV1::sha256(b"provenance").as_bytes(),
+ RadrootsTradeEvidenceProvenanceDigestV1::from_bytes(
+ Sha256::digest(b"provenance").into()
+ )
+ .as_bytes()
+ );
+ }
+
+ #[test]
fn construction_is_permutation_invariant_and_parser_is_strict() {
let first = manifest();
let second = RadrootsTradeEvidenceManifestV1::new(
diff --git a/crates/trade/src/evidence_report.rs b/crates/trade/src/evidence_report.rs
@@ -754,6 +754,66 @@ mod tests {
}
#[test]
+ fn report_accessors_preserve_the_exact_attestation_projection() {
+ let current =
+ RadrootsRhiEvidenceReportV1::from_canonical_content(CURRENT_REPORT.as_bytes())
+ .expect("current report vector");
+
+ assert_eq!(
+ current.contract_id(),
+ RADROOTS_RHI_EVIDENCE_REPORT_CONTRACT_ID
+ );
+ assert_eq!(
+ current.contract_version(),
+ RADROOTS_RHI_EVIDENCE_REPORT_CONTRACT_VERSION
+ );
+ assert_eq!(current.issuer_public_key().to_hex(), "aa".repeat(32));
+ assert_eq!(current.trade_id().to_hex(), "11".repeat(16));
+ assert_eq!(current.claim_mutation_id().to_hex(), "22".repeat(32));
+ assert_eq!(
+ current.outcome(),
+ RadrootsTradeEvidenceOutcomeV1::Indeterminate
+ );
+ assert_eq!(
+ current.reason_codes()[0].as_str(),
+ "required_source_incomplete"
+ );
+ assert_eq!(current.projection_digest().as_bytes(), &[0x66; 32]);
+ assert_eq!(current.evidence_manifest_digest().as_bytes(), &[0x44; 32]);
+ assert_eq!(current.evidence_policy_digest().as_bytes(), &[0x55; 32]);
+ assert_eq!(current.observed_at_unix_s(), 1_800_000_000);
+ assert_eq!(current.trade_generation(), NonZeroU64::new(7).unwrap());
+ assert_eq!(
+ current.reducer_contract_id(),
+ RADROOTS_TRADE_REDUCER_CONTRACT_ID
+ );
+ assert_eq!(
+ current.reducer_contract_version(),
+ RADROOTS_TRADE_REDUCER_VERSION
+ );
+ assert_eq!(
+ current.attestation_method(),
+ RADROOTS_RHI_EVIDENCE_ATTESTATION_METHOD
+ );
+ assert_eq!(current.statement_digest().as_bytes().len(), 32);
+
+ let superseding =
+ RadrootsRhiEvidenceReportV1::from_canonical_content(SUPERSEDING_REPORT.as_bytes())
+ .expect("superseding report vector");
+ let supersession = superseding.supersession().expect("supersession");
+ assert_eq!(supersession.report_id().as_bytes(), &[0x77; 32]);
+ assert_eq!(supersession.event_id().as_bytes(), &[0x88; 32]);
+
+ assert_eq!(
+ RadrootsTradeEvidenceProjectionDigestV1::sha256(b"projection").as_bytes(),
+ RadrootsTradeEvidenceProjectionDigestV1::from_bytes(
+ Sha256::digest(b"projection").into()
+ )
+ .as_bytes()
+ );
+ }
+
+ #[test]
fn construction_is_canonical_manifest_bound_and_permutation_invariant() {
let manifest = manifest(RadrootsTradeEvidenceCoverageV1::ScopeSatisfied);
let a = RadrootsRhiEvidenceReportV1::new(
diff --git a/crates/transport_nostr/src/status.rs b/crates/transport_nostr/src/status.rs
@@ -858,4 +858,57 @@ mod tests {
FetchTargetState::FailedRetryable
);
}
+
+ #[test]
+ fn capability_evidence_rejects_unsupported_and_stale_observations() {
+ let backoff = ReconnectBackoff::new(10, 40).expect("backoff");
+
+ let mut unsupported = MutableEvidence::unsupported();
+ unsupported.begin(10);
+ unsupported.record(true, false, 10, backoff);
+ assert_eq!(unsupported.public.state(), RelayEvidenceState::Unsupported);
+ assert!(!unsupported.may_attempt(u64::MAX));
+
+ let mut evidence = MutableEvidence::unobserved();
+ evidence.begin(10);
+ evidence.begin(9);
+ assert_eq!(evidence.public.last_attempt_unix_ms(), Some(10));
+
+ evidence.record(false, true, 10, backoff);
+ evidence.record(true, false, 9, backoff);
+ assert_eq!(evidence.public.state(), RelayEvidenceState::Unavailable);
+ assert!(!evidence.may_attempt(19));
+ assert!(evidence.may_attempt(20));
+
+ let (read_only, canonical, writable) = tracker();
+ read_only.record_read(&canonical, true, false, 1);
+ read_only.record_read(&writable, true, false, 1);
+ let report = read_only.report();
+ assert_eq!(report.state(), RelayAggregateState::ReadOnly);
+ assert_eq!(report.read_availability(), Availability::Available);
+ assert_eq!(report.write_availability(), Availability::Unavailable);
+
+ let read_only_profile = crate::RelayProfile::explicit(
+ crate::RelayProfileKind::Public,
+ [crate::RelayEndpoint::new(
+ "wss://read-only.example",
+ crate::RelayUrlPolicy::Public,
+ crate::RelayAccess::ReadOnly,
+ )
+ .expect("read-only endpoint")],
+ )
+ .expect("read-only profile");
+ let read_only_config = Config::from_profile(read_only_profile);
+ let read_only_relay = read_only_config.relays()[0].clone();
+ let read_only_tracker = StatusTracker::new(&read_only_config);
+ read_only_tracker.record_read(&read_only_relay, true, false, 1);
+ let report = read_only_tracker.report();
+ assert_eq!(report.state(), RelayAggregateState::ReadOnly);
+ assert_eq!(report.read_availability(), Availability::Available);
+ assert_eq!(report.write_availability(), Availability::Unavailable);
+ assert_eq!(
+ report.relays()[0].write().state(),
+ RelayEvidenceState::Unsupported
+ );
+ }
}
diff --git a/tools/xtask/src/service_release_artifacts.rs b/tools/xtask/src/service_release_artifacts.rs
@@ -1593,6 +1593,10 @@ fn validate_contract_inner(workspace_root: &Path) -> Result<(), ReleaseArtifactE
)?;
let decision = serde_json::from_slice::<ReleaseDecision>(&bytes)
.map_err(|_| ReleaseArtifactError::InvalidContract)?;
+ validate_decision(&decision)
+}
+
+fn validate_decision(decision: &ReleaseDecision) -> Result<(), ReleaseArtifactError> {
let errors = [
ReleaseArtifactError::InvalidContract,
ReleaseArtifactError::InvalidServiceRoot,
@@ -1890,6 +1894,37 @@ version = "0.1.0-alpha"
}
}
+ fn sample_cargo_metadata() -> CargoMetadata {
+ let root_id = "path+file:///fixture#fixture-service@0.1.0-alpha";
+ let dependency_id = "registry+https://example.invalid#index@0.1.0-alpha";
+ CargoMetadata {
+ packages: vec![
+ package(root_id, "fixture-service", None, None, Some("MIT"), true),
+ package(
+ dependency_id,
+ "dependency",
+ Some("registry+https://github.com/rust-lang/crates.io-index"),
+ Some(&"a".repeat(64)),
+ Some("Apache-2.0"),
+ false,
+ ),
+ ],
+ workspace_members: vec![root_id.to_owned()],
+ resolve: Some(CargoResolve {
+ nodes: vec![
+ CargoNode {
+ id: dependency_id.to_owned(),
+ dependencies: Vec::new(),
+ },
+ CargoNode {
+ id: root_id.to_owned(),
+ dependencies: vec![dependency_id.to_owned()],
+ },
+ ],
+ }),
+ }
+ }
+
#[test]
fn contract_matches_the_checked_in_decision() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
@@ -1900,6 +1935,60 @@ version = "0.1.0-alpha"
}
#[test]
+ fn contract_rejects_every_independent_governed_field_drift() {
+ let root = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("workspace root");
+ let bytes = fs::read(root.join(CONTRACT_RELATIVE)).expect("decision");
+ let canonical = serde_json::from_slice::<serde_json::Value>(&bytes).expect("decision json");
+ for (pointer, replacement) in [
+ ("/schema", serde_json::json!("other")),
+ ("/contract_version", serde_json::json!(2)),
+ ("/decision_state", serde_json::json!("draft")),
+ ("/command", serde_json::json!("other")),
+ ("/modes", serde_json::json!([])),
+ ("/required_arguments", serde_json::json!([])),
+ ("/service_metadata_path", serde_json::json!("other")),
+ ("/service_metadata_fields", serde_json::json!([])),
+ ("/supported_targets", serde_json::json!([])),
+ ("/input_inventory", serde_json::json!([])),
+ ("/excluded_parent_owned_inputs", serde_json::json!([])),
+ ("/service_root_inventory", serde_json::json!([])),
+ ("/output_inventory", serde_json::json!([])),
+ ("/canonical_json", serde_json::json!("other")),
+ ("/checksum_format", serde_json::json!("other")),
+ ("/sbom_format", serde_json::json!("other")),
+ ("/provenance_posture", serde_json::json!("other")),
+ ("/protected_material_scan_scope", serde_json::json!("other")),
+ ("/source_cleanliness", serde_json::json!("other")),
+ ("/revision_stability", serde_json::json!("other")),
+ ("/no_protected_material", serde_json::json!(false)),
+ ("/maximums/text_input_bytes", serde_json::json!(1)),
+ ("/maximums/generated_document_bytes", serde_json::json!(1)),
+ ("/maximums/service_cargo_lock_bytes", serde_json::json!(1)),
+ ("/maximums/service_flake_lock_bytes", serde_json::json!(1)),
+ ("/maximums/binary_bytes", serde_json::json!(1)),
+ ("/maximums/source_bundle_bytes", serde_json::json!(1)),
+ ("/maximums/oci_bytes", serde_json::json!(1)),
+ ("/maximums/cargo_metadata_bytes", serde_json::json!(1)),
+ ("/maximums/packages", serde_json::json!(1)),
+ ("/maximums/workspace_packages", serde_json::json!(1)),
+ ("/negative_error_codes", serde_json::json!([])),
+ ] {
+ let mut drifted = canonical.clone();
+ *drifted.pointer_mut(pointer).expect("governed field") = replacement;
+ let decision = serde_json::from_value::<ReleaseDecision>(drifted)
+ .expect("structurally valid drift");
+ assert_eq!(
+ validate_decision(&decision),
+ Err(ReleaseArtifactError::InvalidContract),
+ "accepted drift at {pointer}"
+ );
+ }
+ }
+
+ #[test]
fn exact_inventory_and_limits_are_literal() {
assert_eq!(INPUT_NAMES.len(), 8);
assert_eq!(OUTPUT_NAMES.len(), 18);
@@ -2007,6 +2096,517 @@ version = "0.1.0-alpha"
}
#[test]
+ fn package_metadata_rejects_each_independent_field_drift() {
+ let invalid = |package: CargoPackage, workspace_member| {
+ assert_eq!(
+ validate_metadata_package(&package, workspace_member),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ );
+ };
+ for field in ["name", "version", "id"] {
+ for value in [
+ String::new(),
+ "x".repeat(MAX_TEXT_FIELD_BYTES + 1),
+ "x\ny".into(),
+ ] {
+ let mut candidate =
+ package("root", "fixture-service", None, None, Some("MIT"), true);
+ match field {
+ "name" => candidate.name = value,
+ "version" => candidate.version = value,
+ "id" => candidate.id = value,
+ _ => unreachable!(),
+ }
+ invalid(candidate, true);
+ }
+ }
+
+ for license in [
+ Some(""),
+ Some("bad\nlicense"),
+ Some("-----BEGIN PRIVATE KEY-----"),
+ ] {
+ invalid(
+ package("root", "fixture-service", None, None, license, true),
+ true,
+ );
+ }
+ for source in [
+ Some(""),
+ Some("path+file:///private"),
+ Some("git+ssh://git@github.com/private/repo"),
+ ] {
+ invalid(
+ package(
+ "dep",
+ "dep",
+ source,
+ Some(&"a".repeat(64)),
+ Some("MIT"),
+ false,
+ ),
+ false,
+ );
+ }
+ invalid(
+ package(
+ "dep",
+ "dep",
+ None,
+ Some(&"a".repeat(64)),
+ Some("MIT"),
+ false,
+ ),
+ false,
+ );
+ invalid(
+ package(
+ "dep",
+ "dep",
+ Some("registry+https://github.com/rust-lang/crates.io-index"),
+ Some(&"a".repeat(64)),
+ None,
+ false,
+ ),
+ false,
+ );
+ for checksum in ["a".repeat(63), "g".repeat(64)] {
+ invalid(
+ package(
+ "root",
+ "fixture-service",
+ None,
+ Some(&checksum),
+ Some("MIT"),
+ true,
+ ),
+ true,
+ );
+ }
+ }
+
+ #[test]
+ fn supply_chain_graph_rejects_each_independent_structural_drift() {
+ let invalid = |cargo| {
+ assert!(matches!(
+ build_supply_chain_documents(&sample_metadata(), cargo),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ ));
+ };
+
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages.clear();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.workspace_members.clear();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages[0].version = "0.2.0".into();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages[0].targets.clear();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages[0].name = "other".into();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages.push(package(
+ "second-root",
+ "fixture-service",
+ None,
+ None,
+ Some("MIT"),
+ true,
+ ));
+ cargo.workspace_members.push("second-root".into());
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages[1].id = cargo.packages[0].id.clone();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.workspace_members.push("missing".into());
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.resolve = None;
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.resolve.as_mut().expect("resolve").nodes.clear();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ let duplicate = cargo.resolve.as_ref().expect("resolve").nodes[0].id.clone();
+ cargo.resolve.as_mut().expect("resolve").nodes[1].id = duplicate;
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.resolve.as_mut().expect("resolve").nodes[0].id = "missing".into();
+ invalid(cargo);
+ let mut cargo = sample_cargo_metadata();
+ cargo.resolve.as_mut().expect("resolve").nodes[1]
+ .dependencies
+ .push("missing".into());
+ invalid(cargo);
+
+ let root_id = "path+file:///fixture#fixture-service@0.1.0-alpha";
+ let root_only = CargoMetadata {
+ packages: vec![package(
+ root_id,
+ "fixture-service",
+ None,
+ None,
+ Some("MIT"),
+ true,
+ )],
+ workspace_members: vec![root_id.into()],
+ resolve: Some(CargoResolve {
+ nodes: vec![CargoNode {
+ id: root_id.into(),
+ dependencies: Vec::new(),
+ }],
+ }),
+ };
+ let (_, notices) =
+ build_supply_chain_documents(&sample_metadata(), root_only).expect("root-only graph");
+ assert!(notices.contains("No third-party Cargo packages are present."));
+ }
+
+ #[test]
+ fn file_admission_predicates_reject_each_independent_drift() {
+ let root = TempDir::new().expect("file fixture");
+ let regular = root.path().join("regular");
+ write_file(®ular, b"bytes");
+ let empty = root.path().join("empty");
+ write_file(&empty, b"");
+ let oversized = root.path().join("oversized");
+ fs::File::create(&oversized)
+ .and_then(|file| file.set_len(6))
+ .expect("sparse file");
+ assert!(matches!(
+ hash_regular(root.path(), 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert!(matches!(
+ hash_regular(&oversized, 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert!(matches!(
+ validate_regular_input(&empty, 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert!(matches!(
+ validate_regular_input(root.path(), 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert!(matches!(
+ validate_regular_input(&oversized, 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert_eq!(
+ validate_absolute_directory(®ular, ReleaseArtifactError::InvalidInputRoot),
+ Err(ReleaseArtifactError::InvalidInputRoot)
+ );
+ assert_eq!(
+ validate_absolute_directory(
+ Path::new("relative"),
+ ReleaseArtifactError::InvalidInputRoot
+ ),
+ Err(ReleaseArtifactError::InvalidInputRoot)
+ );
+ assert_eq!(
+ output_maximum("unknown"),
+ Err(ReleaseArtifactError::GenerationFailure)
+ );
+
+ #[cfg(unix)]
+ {
+ let symlink = root.path().join("symlink");
+ std::os::unix::fs::symlink(®ular, &symlink).expect("symlink");
+ assert!(matches!(
+ hash_regular(&symlink, 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert!(matches!(
+ validate_regular_input(&symlink, 5),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ ));
+ assert_eq!(
+ validate_absolute_directory(&symlink, ReleaseArtifactError::InvalidInputRoot),
+ Err(ReleaseArtifactError::InvalidInputRoot)
+ );
+ }
+ }
+
+ #[test]
+ fn release_metadata_and_text_admission_reject_each_field_drift() {
+ let root = TempDir::new().expect("metadata fixture");
+ let canonical = r#"[workspace.metadata.radroots.service_release]
+service = "fixture_service"
+service_package = "fixture-service"
+binary_name = "fixture-service"
+version = "0.1.0-alpha"
+"#;
+ for (from, to) in [
+ ("fixture_service", "Fixture"),
+ (
+ "service_package = \"fixture-service\"",
+ "service_package = \"fixture--service\"",
+ ),
+ (
+ "binary_name = \"fixture-service\"",
+ "binary_name = \"fixture--service\"",
+ ),
+ ("version = \"0.1.0-alpha\"", "version = \"invalid\""),
+ ] {
+ write_file(
+ &root.path().join("Cargo.toml"),
+ canonical.replacen(from, to, 1).as_bytes(),
+ );
+ assert!(matches!(
+ read_release_metadata(root.path()),
+ Err(ReleaseArtifactError::InvalidServiceMetadata)
+ ));
+ }
+ write_file(
+ &root.path().join("Cargo.toml"),
+ canonical
+ .replacen("0.1.0-alpha", &"a".repeat(129), 1)
+ .as_bytes(),
+ );
+ assert!(matches!(
+ read_release_metadata(root.path()),
+ Err(ReleaseArtifactError::InvalidServiceMetadata)
+ ));
+
+ for (name, bytes) in [
+ ("plain.txt", b"contains\0nul".as_slice()),
+ ("config.schema.json", b"not json".as_slice()),
+ ("config.example.toml", b"not = [toml".as_slice()),
+ ] {
+ let path = root.path().join(name);
+ write_file(&path, bytes);
+ assert_eq!(
+ validate_text_artifact(&path, name),
+ Err(ReleaseArtifactError::InvalidInputArtifact)
+ );
+ }
+ assert_eq!(
+ write_generated(&root.path().join("empty-generated"), b""),
+ Err(ReleaseArtifactError::GenerationFailure)
+ );
+ }
+
+ #[test]
+ fn output_scope_remote_and_inventory_reject_each_drift() {
+ let root = TempDir::new().expect("output fixture");
+ let service = root.path().join("service");
+ let input = root.path().join("input");
+ let output_parent = root.path().join("output");
+ fs::create_dir(&service).expect("service");
+ fs::create_dir(&input).expect("input");
+ fs::create_dir(&output_parent).expect("output");
+ assert_eq!(
+ validate_output_parent(Path::new("relative"), &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+ assert_eq!(
+ validate_output_parent(root.path(), &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+ let file_output = output_parent.join("file");
+ write_file(&file_output, b"file");
+ assert_eq!(
+ validate_output_parent(&file_output, &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+
+ let inventory = root.path().join("inventory");
+ fs::create_dir(&inventory).expect("inventory");
+ fs::create_dir(inventory.join("directory-entry")).expect("directory entry");
+ assert_eq!(
+ directory_inventory(&inventory, ReleaseArtifactError::InvalidInputRoot),
+ Err(ReleaseArtifactError::InvalidInputRoot)
+ );
+ fs::remove_dir(inventory.join("directory-entry")).expect("remove entry");
+ for index in 0..=OUTPUT_NAMES.len() {
+ write_file(&inventory.join(format!("file-{index}")), b"x");
+ }
+ assert_eq!(
+ directory_inventory(&inventory, ReleaseArtifactError::InvalidInputRoot),
+ Err(ReleaseArtifactError::InvalidInputRoot)
+ );
+
+ write_file(&service.join("fixture"), b"fixture");
+ initialize_git(&service, "https://example.invalid/service");
+ assert_eq!(
+ git_remote(&service),
+ Err(ReleaseArtifactError::InvalidServiceRoot)
+ );
+ git(
+ &service,
+ &[
+ "remote",
+ "set-url",
+ "origin",
+ "ssh://git@github.com/user/repo\nbad",
+ ],
+ );
+ assert_eq!(
+ git_remote(&service),
+ Err(ReleaseArtifactError::InvalidServiceRoot)
+ );
+ git(
+ &service,
+ &[
+ "remote",
+ "set-url",
+ "origin",
+ &format!("https://github.com/{}", "a".repeat(MAX_TEXT_FIELD_BYTES)),
+ ],
+ );
+ assert_eq!(
+ git_remote(&service),
+ Err(ReleaseArtifactError::InvalidServiceRoot)
+ );
+ }
+
+ #[test]
+ fn remaining_release_boundaries_fail_closed() {
+ let fixture = ReleaseFixture::new();
+ assert_eq!(
+ fixture.check(&fixture.output_a),
+ Err(ReleaseArtifactError::StaleOutput)
+ );
+
+ let source_lock = ServiceSourceLockV1::from_canonical_bytes(
+ &fs::read(fixture.service.join(LOCK_FILENAME)).expect("source lock"),
+ )
+ .expect("source lock");
+ fs::write(fixture.service.join("flake.lock"), b"different").expect("flake drift");
+ assert_eq!(
+ validate_source_lock_files(&fixture.service, &source_lock),
+ Err(ReleaseArtifactError::InvalidSourceLock)
+ );
+
+ let mut packages = sample_cargo_metadata();
+ packages.packages = (0..=MAX_PACKAGES)
+ .map(|index| {
+ package(
+ &format!("id-{index}"),
+ "dep",
+ None,
+ None,
+ Some("MIT"),
+ false,
+ )
+ })
+ .collect();
+ assert!(matches!(
+ build_supply_chain_documents(&sample_metadata(), packages),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ ));
+ let mut workspace = sample_cargo_metadata();
+ workspace.workspace_members = (0..=MAX_WORKSPACE_PACKAGES)
+ .map(|index| format!("member-{index}"))
+ .collect();
+ assert!(matches!(
+ build_supply_chain_documents(&sample_metadata(), workspace),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ ));
+ for (target_name, kind) in [("other", "bin"), ("fixture-service", "lib")] {
+ let mut cargo = sample_cargo_metadata();
+ cargo.packages[0].targets = vec![CargoTarget {
+ name: target_name.into(),
+ kind: vec![kind.into()],
+ }];
+ assert!(matches!(
+ build_supply_chain_documents(&sample_metadata(), cargo),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ ));
+ }
+ let mut outside_workspace = sample_cargo_metadata();
+ outside_workspace.workspace_members = vec!["other".into()];
+ assert!(matches!(
+ build_supply_chain_documents(&sample_metadata(), outside_workspace),
+ Err(ReleaseArtifactError::InvalidPackageInventory)
+ ));
+
+ let scope = TempDir::new().expect("scope fixture");
+ let service = scope.path().join("service");
+ let input_parent = TempDir::new().expect("input scope");
+ let input = input_parent.path().join("input");
+ fs::create_dir(&service).expect("service");
+ fs::create_dir(&input).expect("input");
+ assert_eq!(
+ validate_output_parent(&scope.path().join("bad\\name"), &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+ assert_eq!(
+ validate_output_parent(input_parent.path(), &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+ #[cfg(unix)]
+ {
+ let foreign = scope.path().join("foreign");
+ fs::create_dir(&foreign).expect("foreign");
+ let output = scope.path().join("output-link");
+ std::os::unix::fs::symlink(&foreign, &output).expect("output symlink");
+ assert_eq!(
+ validate_output_parent(&output, &service, &input),
+ Err(ReleaseArtifactError::InvalidOutputRoot)
+ );
+ }
+
+ write_file(&service.join("tracked"), b"tracked");
+ initialize_git(&service, "https://github.com/radrootslabs/service");
+ let child = service.join("child");
+ fs::create_dir(&child).expect("child");
+ assert_eq!(
+ validate_git_root(&child),
+ Err(ReleaseArtifactError::InvalidServiceRoot)
+ );
+ write_file(&service.join("untracked"), b"dirty");
+ assert_eq!(
+ validate_clean_git(&service),
+ Err(ReleaseArtifactError::DirtyServiceSource)
+ );
+
+ assert_eq!(
+ verify_bundle(
+ &fixture.input.join("service-source.bundle"),
+ &"a".repeat(40)
+ ),
+ Err(ReleaseArtifactError::InvalidSourceBundle)
+ );
+ assert_eq!(
+ write_generated(
+ &scope.path().join("oversized-generated"),
+ &vec![b'x'; MAX_GENERATED_DOCUMENT_BYTES as usize + 1]
+ ),
+ Err(ReleaseArtifactError::GenerationFailure)
+ );
+ }
+
+ #[test]
+ fn identifier_predicates_reject_each_independent_boundary() {
+ for value in ["", "1service", "service_", "service__name", "service-name"] {
+ assert!(!valid_snake_identifier(value), "{value}");
+ }
+ assert!(!valid_snake_identifier(&"a".repeat(129)));
+ for value in ["", "1service", "service-", "service--name", "service_name"] {
+ assert!(!valid_kebab_identifier(value), "{value}");
+ }
+ assert!(!valid_kebab_identifier(&"a".repeat(129)));
+ assert!(!valid_metadata_text(""));
+ assert!(!valid_metadata_text(&"a".repeat(MAX_TEXT_FIELD_BYTES + 1)));
+ assert!(!valid_metadata_text("bad\rvalue"));
+ assert!(!valid_metadata_text("-----BEGIN PRIVATE KEY-----"));
+ assert!(!valid_public_source("registry+http://example.invalid"));
+ assert!(!valid_public_source(
+ "registry+https://user@github.com/private"
+ ));
+ assert!(!valid_lower_hex("a", 2));
+ assert!(!valid_lower_hex("ag", 2));
+ }
+
+ #[test]
fn binary_archive_is_reproducible_and_metadata_is_fixed() {
let root = TempDir::new().expect("archive fixture");
let source = root.path().join("service");
diff --git a/tools/xtask/src/service_source_lock_command.rs b/tools/xtask/src/service_source_lock_command.rs
@@ -1344,6 +1344,288 @@ radroots_service_host = {{ git = "{LIB_REPOSITORY}", rev = "{revision}", version
}
#[test]
+ fn flake_lock_rejects_each_independent_fixed_field_drift() {
+ let fixture = Fixture::new();
+ let bytes = fs::read(fixture.service.join(FLAKE_LOCK)).expect("flake lock");
+ let canonical = serde_json::from_slice::<serde_json::Value>(&bytes).expect("flake json");
+ for (pointer, replacement) in [
+ ("/version", serde_json::json!(6)),
+ ("/nodes/root/inputs/lib", serde_json::json!("other")),
+ ("/nodes/lib/locked/type", serde_json::json!("git")),
+ ("/nodes/lib/locked/owner", serde_json::json!("other")),
+ ("/nodes/lib/locked/repo", serde_json::json!("other")),
+ ("/nodes/lib/locked/rev", serde_json::json!("other")),
+ (
+ "/nodes/lib/locked/narHash",
+ serde_json::json!("sha256-invalid"),
+ ),
+ ("/nodes/lib/original/type", serde_json::json!("git")),
+ ("/nodes/lib/original/owner", serde_json::json!("other")),
+ ("/nodes/lib/original/repo", serde_json::json!("other")),
+ ("/nodes/lib/original/rev", serde_json::json!("other")),
+ ] {
+ let mut drifted = canonical.clone();
+ *drifted.pointer_mut(pointer).expect("governed field") = replacement;
+ assert_eq!(
+ validate_flake_lock(
+ &serde_json::to_vec(&drifted).expect("flake json"),
+ &fixture.revision
+ ),
+ Err(CommandError::InvalidFlakeLock),
+ "accepted drift at {pointer}"
+ );
+ }
+
+ for (section, field) in [
+ ("locked", "extra"),
+ ("original", "extra"),
+ ("original", "ref"),
+ ] {
+ let mut drifted = canonical.clone();
+ drifted["nodes"]["lib"][section]
+ .as_object_mut()
+ .expect("flake section")
+ .insert(field.into(), serde_json::json!("unexpected"));
+ assert_eq!(
+ validate_flake_lock(
+ &serde_json::to_vec(&drifted).expect("flake json"),
+ &fixture.revision
+ ),
+ Err(CommandError::InvalidFlakeLock),
+ "accepted {section}.{field}"
+ );
+ }
+
+ let mut duplicate = canonical.clone();
+ duplicate["nodes"]["lib2"] = duplicate["nodes"]["lib"].clone();
+ assert_eq!(
+ validate_flake_lock(
+ &serde_json::to_vec(&duplicate).expect("flake json"),
+ &fixture.revision
+ ),
+ Err(CommandError::InvalidFlakeLock)
+ );
+ }
+
+ #[test]
+ fn service_metadata_and_catalog_reject_independent_field_drift() {
+ let fixture = Fixture::new();
+ let bytes = fs::read(fixture.service.join(CARGO_MANIFEST)).expect("manifest");
+ let canonical = parse_toml(&bytes, CommandError::InvalidCargoManifest).expect("manifest");
+ for (field, replacement) in [
+ ("service", toml::Value::Integer(1)),
+ ("host_feature_profile", toml::Value::String("other".into())),
+ ("config_contract_version", toml::Value::Integer(0)),
+ ("state_contract_version", toml::Value::Integer(0)),
+ ("admin_contract_version", toml::Value::Integer(0)),
+ ("status_contract_version", toml::Value::Integer(0)),
+ ("provider_contract_version", toml::Value::Integer(0)),
+ ] {
+ let mut drifted = canonical.clone();
+ drifted["workspace"]["metadata"]["radroots"]["service_source_lock"][field] =
+ replacement;
+ assert_eq!(
+ parse_service_metadata(&drifted),
+ Err(CommandError::InvalidServiceMetadata),
+ "accepted metadata drift at {field}"
+ );
+ }
+ let mut extra = canonical;
+ extra["workspace"]["metadata"]["radroots"]["service_source_lock"]
+ .as_table_mut()
+ .expect("metadata table")
+ .insert("extra".into(), toml::Value::Integer(1));
+ assert_eq!(
+ parse_service_metadata(&extra),
+ Err(CommandError::InvalidServiceMetadata)
+ );
+
+ let catalog = br#"schema = "radroots.workspace.catalog.v2"
+architecture = "radroots.crates.release.v2"
+version = "0.1.0-alpha"
+package_count = 2
+
+[[package]]
+name = "radroots_core"
+
+[[package]]
+name = "radroots_service_host"
+"#;
+ let canonical = parse_toml(catalog, CommandError::InvalidSourceArchive).expect("catalog");
+ for (field, replacement) in [
+ ("schema", toml::Value::String("other".into())),
+ ("architecture", toml::Value::String("other".into())),
+ ("version", toml::Value::String("other".into())),
+ ("package_count", toml::Value::Integer(1)),
+ ] {
+ let mut drifted = canonical.clone();
+ drifted[field] = replacement;
+ assert_eq!(
+ catalog_package_names(toml::to_string(&drifted).expect("catalog").as_bytes()),
+ Err(CommandError::InvalidSourceArchive),
+ "accepted catalog drift at {field}"
+ );
+ }
+ let mut duplicate = canonical.clone();
+ duplicate["package"][1]["name"] = toml::Value::String("radroots_core".into());
+ assert_eq!(
+ catalog_package_names(toml::to_string(&duplicate).expect("catalog").as_bytes()),
+ Err(CommandError::InvalidSourceArchive)
+ );
+ let mut missing_host = canonical;
+ missing_host["package"][1]["name"] = toml::Value::String("radroots_other".into());
+ assert_eq!(
+ catalog_package_names(toml::to_string(&missing_host).expect("catalog").as_bytes()),
+ Err(CommandError::InvalidSourceArchive)
+ );
+ }
+
+ #[test]
+ fn manifest_and_cargo_lock_reject_each_independent_dependency_drift() {
+ let revision = "a".repeat(40);
+ let canonical = format!(
+ "[dependencies]\nradroots_service_host = {{ git = \"{LIB_REPOSITORY}\", rev = \"{revision}\", version = \"{LIB_VERSION_REQUIREMENT}\" }}\n"
+ );
+ for (from, to) in [
+ (LIB_REPOSITORY, "https://example.invalid/lib"),
+ (LIB_VERSION_REQUIREMENT, "=0.2.0"),
+ (revision.as_str(), "invalid"),
+ ] {
+ let value =
+ toml::from_str::<toml::Value>(&canonical.replacen(from, to, 1)).expect("manifest");
+ assert_eq!(
+ validate_manifest_node(
+ &value,
+ None,
+ false,
+ false,
+ None,
+ &mut ManifestState::default()
+ ),
+ Err(CommandError::InvalidCargoManifest)
+ );
+ }
+ for forbidden in ["branch", "tag", "path"] {
+ let mutated = canonical.replacen(
+ "version = \"=0.1.0-alpha\"",
+ &format!("version = \"=0.1.0-alpha\", {forbidden} = \"forbidden\""),
+ 1,
+ );
+ let value = toml::from_str::<toml::Value>(&mutated).expect("manifest");
+ assert_eq!(
+ validate_manifest_node(
+ &value,
+ None,
+ false,
+ false,
+ None,
+ &mut ManifestState::default()
+ ),
+ Err(CommandError::InvalidCargoManifest),
+ "accepted {forbidden}"
+ );
+ }
+ let value = toml::from_str::<toml::Value>(&canonical).expect("manifest");
+ assert_eq!(
+ validate_manifest_node(
+ &value,
+ None,
+ true,
+ false,
+ None,
+ &mut ManifestState::default()
+ ),
+ Err(CommandError::InvalidCargoManifest)
+ );
+ let mut state = ManifestState {
+ revision: Some("b".repeat(40)),
+ ..ManifestState::default()
+ };
+ assert_eq!(
+ validate_manifest_node(&value, None, false, false, None, &mut state),
+ Err(CommandError::InvalidCargoManifest)
+ );
+
+ let packages = BTreeSet::from([HOST_PACKAGE.to_owned(), "radroots_core".to_owned()]);
+ let expected = format!("git+{LIB_REPOSITORY}?rev={revision}#{revision}");
+ for document in [
+ format!(
+ "version = 4\n\n[[package]]\nname = \"{HOST_PACKAGE}\"\nversion = \"0.1.0-alpha\"\n"
+ ),
+ format!(
+ "version = 4\n\n[[package]]\nname = \"{HOST_PACKAGE}\"\nversion = \"0.1.0-alpha\"\nsource = \"other\"\n"
+ ),
+ format!(
+ "version = 4\n\n[[package]]\nname = \"{HOST_PACKAGE}\"\nversion = \"0.2.0\"\nsource = \"{expected}\"\n"
+ ),
+ format!(
+ "version = 4\n\n[[package]]\nname = \"radroots_core\"\nversion = \"0.1.0-alpha\"\nsource = \"{expected}\"\n"
+ ),
+ "version = 4\n".to_owned(),
+ ] {
+ assert_eq!(
+ validate_cargo_lock(document.as_bytes(), &revision, &packages),
+ Err(CommandError::InvalidCargoLock)
+ );
+ }
+ }
+
+ #[test]
+ fn archive_and_manifest_filesystem_admission_is_fail_closed() {
+ let root = TempDir::new().expect("filesystem fixture");
+ let empty = root.path().join("empty");
+ fs::write(&empty, b"").expect("empty file");
+ assert!(matches!(
+ validate_archive(Path::new("relative"), &"a".repeat(40)),
+ Err(CommandError::InvalidSourceArchive)
+ ));
+ assert!(matches!(
+ validate_archive(root.path(), &"a".repeat(40)),
+ Err(CommandError::InvalidSourceArchive)
+ ));
+ assert!(matches!(
+ validate_archive(&empty, &"a".repeat(40)),
+ Err(CommandError::InvalidSourceArchive)
+ ));
+
+ let no_manifest = root.path().join("no-manifest");
+ fs::create_dir(&no_manifest).expect("empty tree");
+ assert_eq!(
+ validate_cargo_manifests(&no_manifest, None),
+ Err(CommandError::InvalidCargoManifest)
+ );
+ let no_dependency = root.path().join("no-dependency");
+ fs::create_dir(&no_dependency).expect("manifest tree");
+ fs::write(
+ no_dependency.join(CARGO_MANIFEST),
+ "[workspace]\nresolver = \"3\"\n",
+ )
+ .expect("manifest");
+ assert_eq!(
+ validate_cargo_manifests(&no_dependency, None),
+ Err(CommandError::InvalidCargoManifest)
+ );
+
+ #[cfg(unix)]
+ {
+ let archive_link = root.path().join("archive-link");
+ std::os::unix::fs::symlink(&empty, &archive_link).expect("archive symlink");
+ assert!(matches!(
+ validate_archive(&archive_link, &"a".repeat(40)),
+ Err(CommandError::InvalidSourceArchive)
+ ));
+ let manifest_link_root = root.path().join("manifest-link");
+ fs::create_dir(&manifest_link_root).expect("manifest symlink tree");
+ std::os::unix::fs::symlink(&empty, manifest_link_root.join(CARGO_MANIFEST))
+ .expect("manifest symlink");
+ assert_eq!(
+ validate_cargo_manifests(&manifest_link_root, None),
+ Err(CommandError::InvalidCargoManifest)
+ );
+ }
+ }
+
+ #[test]
fn source_lock_output_rejects_symlink_replacement() {
let fixture = Fixture::new();
let target = fixture.root.path().join("foreign");