Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- **Rust Holmes validation gate review fixes**: Law evidence validation now
continues artifact checks when structure validation emits warning-only
diagnostics, rejects duplicate artifact roles after workspace-relative path
normalization, and validates artifact `sha256` digests with artifact-specific
diagnostics instead of allowing malformed digest anchors into later
traceability gates.
normalization, requires schema versions on every present artifact reference,
and validates artifact `sha256` digests with artifact-specific diagnostics
instead of allowing malformed digest anchors into later traceability gates.
- **Rust Holmes assurance review fixes**: The new Holmes artifact locator now
returns stable Holmes diagnostics for invalid and escaping paths, rejects
platform-specific backslash and drive-path input before normalization, the
Expand Down
30 changes: 18 additions & 12 deletions crates/wesley-holmes/src/domain/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::versioning::{ArtifactFamily, VersionRegistry};
pub struct ArtifactRef {
/// Workspace-relative artifact path.
pub path: String,
/// Optional artifact-local schema version.
/// Artifact-local schema version required for present artifact references.
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_version: Option<String>,
/// Optional expected SHA-256 digest.
Expand All @@ -31,6 +31,12 @@ impl ArtifactRef {
}
}

/// Attach an artifact-local schema version.
pub fn with_schema_version(mut self, schema_version: impl Into<String>) -> Self {
self.schema_version = Some(schema_version.into());
self
}

fn is_blank(&self) -> bool {
self.path.trim().is_empty()
}
Expand Down Expand Up @@ -335,17 +341,17 @@ impl HolmesLawEvidenceBundle {
);
}

if let Some(schema_version) = artifact.schema_version.as_deref() {
let schema_field = format!("{}.schemaVersion", bundle_artifact.field_path);
match version_registry.classify(bundle_artifact.family, Some(schema_version)) {
Ok(version_check) => diagnostics.extend(
version_check
.diagnostics
.into_iter()
.map(|diagnostic| diagnostic.at_field(schema_field.clone())),
),
Err(diagnostic) => diagnostics.push(diagnostic.at_field(schema_field)),
}
let schema_field = format!("{}.schemaVersion", bundle_artifact.field_path);
match version_registry
.classify(bundle_artifact.family, artifact.schema_version.as_deref())
{
Ok(version_check) => diagnostics.extend(
version_check
.diagnostics
.into_iter()
.map(|diagnostic| diagnostic.at_field(schema_field.clone())),
),
Err(diagnostic) => diagnostics.push(diagnostic.at_field(schema_field)),
}

if let Some(sha256) = artifact.sha256.as_deref() {
Expand Down
41 changes: 37 additions & 4 deletions crates/wesley-holmes/tests/foundation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,35 @@ fn artifact_sha256_fields_must_be_canonical_when_present() {
);
}

#[test]
fn artifact_schema_versions_are_required_for_every_present_artifact_reference() {
let mut bundle = valid_evidence_bundle();
bundle.artifacts.law_diff.schema_version = None;
bundle.artifacts.law_coverage.schema_version = None;
bundle.artifacts.law_capabilities.schema_version = None;
bundle.artifacts.contract_bundle_manifest.schema_version = None;
bundle.artifacts.policy = Some(ArtifactRef::new("evidence/policy.json"));

let result = bundle.validate_structure(&VersionRegistry::default());

assert_eq!(result.status, LawEvidenceValidationStatus::Invalid);
assert_diagnostic(
&result.diagnostics,
HolmesDiagnosticCode::HlawSchemaVersionMissing,
);
assert_diagnostic_field(&result.diagnostics, "artifacts.lawDiff.schemaVersion");
assert_diagnostic_field(&result.diagnostics, "artifacts.lawCoverage.schemaVersion");
assert_diagnostic_field(
&result.diagnostics,
"artifacts.lawCapabilities.schemaVersion",
);
assert_diagnostic_field(
&result.diagnostics,
"artifacts.contractBundleManifest.schemaVersion",
);
assert_diagnostic_field(&result.diagnostics, "artifacts.policy.schemaVersion");
}

#[test]
fn law_evidence_validator_reports_artifact_availability_size_and_read_errors() {
let bundle = valid_evidence_bundle();
Expand Down Expand Up @@ -505,10 +534,10 @@ fn valid_evidence_bundle() -> HolmesLawEvidenceBundle {
schema_version: "1.0.0".to_owned(),
bundle_id: "bundle-001".to_owned(),
artifacts: LawEvidenceArtifacts {
law_diff: ArtifactRef::new("evidence/law-diff.json"),
law_coverage: ArtifactRef::new("evidence/law-coverage.json"),
law_capabilities: ArtifactRef::new("evidence/law-capabilities.json"),
contract_bundle_manifest: ArtifactRef::new("evidence/bundle-manifest.json"),
law_diff: artifact_ref("evidence/law-diff.json"),
law_coverage: artifact_ref("evidence/law-coverage.json"),
law_capabilities: artifact_ref("evidence/law-capabilities.json"),
contract_bundle_manifest: artifact_ref("evidence/bundle-manifest.json"),
policy: None,
report: None,
witness: None,
Expand All @@ -523,6 +552,10 @@ fn valid_evidence_bundle() -> HolmesLawEvidenceBundle {
}
}

fn artifact_ref(path: &str) -> ArtifactRef {
ArtifactRef::new(path).with_schema_version("1.0.0")
}

fn assert_diagnostic(diagnostics: &[wesley_holmes::HolmesDiagnostic], code: HolmesDiagnosticCode) {
assert!(
diagnostics.iter().any(|diagnostic| diagnostic.code == code),
Expand Down
Loading