Files
compliance-scanner-agent/compliance-core/src/traits/evidence.rs
T
Sharang ParnerkarandClaude Fable 5 c6e82bc331 feat(onboarding): artifact ingest + classifier + suite-integration seams
Steps 3-4 of the onboarding plan, plus the sibling-product reconciliation seams.

Ingest (compliance-agent/src/ingest, #120):
- ingest_all / ingest_artifact normalize each artifact to a working path +
  metadata. Every blob is SHA-256 hashed (content-addressed store, dedup) —
  that digest is also the tramiton reconciliation key.
- git via GitOps reuse; zip archives + mobile packages extracted; firmware
  stored as blob; live URL / plaintext / container = metadata only.
- IngestContext decoupled from the full AgentConfig (testable in isolation).

Classify (compliance-agent/src/classify, #121):
- FirmwareDetector port + TramitonCli (shell out `tramiton detect --json`,
  parse a mirrored BuildPlan subset — no dependency on the proprietary crate)
  + a deterministic MockFirmwareDetector so CI never needs the binary.
- HeuristicClassifier: artifact-kind priors + source-marker fingerprinting
  (web/backend/mobile/desktop/PLC).
- classify_target merges + ranks verdicts into a Classification.

Suite-integration seams (compliance-core, #135/#136/#137):
- Model: ExternalRef/ExternalSystem (reconcile with tramiton/werkpilot/breakpilot),
  ComplianceProfile/ComplianceFramework + default_compliance_profile per type.
- Ports: EvidenceProvider (fetch external SBOM/VEX/lock/attestation) and
  ControlsProvider (built-in OSCAL vs breakpilot RAG).
- TargetType now derives Hash; AgentConfig gains artifact_store_base_path.

44 unit tests (23 core + 8 ingest + 13 classify). Passes fmt + clippy -D warnings
across agent, dashboard (server + web), and mcp. Additive; legacy paths untouched.

Refs #118, #120, #121, #135, #136, #137.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:41:46 +02:00

59 lines
2.4 KiB
Rust

//! The external-evidence provider port.
//!
//! A sibling product (tramiton, for firmware) may already hold authoritative
//! analysis for an artifact. An [`EvidenceProvider`] lets compliance-scanner
//! *reconcile* that evidence — a build plan, an SBOM, a VEX document, a
//! reproducible-build lock, an attestation — instead of recomputing it. The key
//! used to match is the artifact content digest (a firmware sha256, which equals
//! [`crate::models::Artifact::content_hash`]).
//!
//! Concrete providers live in the agent (a tramiton CLI shell-out today, a cloud
//! client later) plus a deterministic mock for tests, so nothing here depends on
//! an external binary.
use std::path::Path;
use crate::error::CoreError;
use crate::models::{Artifact, ExternalSystem};
/// A single reconcilable evidence document fetched from a sibling product.
#[derive(Debug, Clone)]
pub struct EvidenceDocument {
/// What the document is: `build_plan` | `sbom` | `vex` | `lock` | `attestation`.
pub kind: String,
/// The document's format (e.g. `cyclonedx-1.5`, `openvex-0.2.0`, `toml`, `json`).
pub format: String,
/// The raw document payload.
pub content: String,
}
/// The evidence a provider could return for a target's artifact.
#[derive(Debug, Clone, Default)]
pub struct ReconciledEvidence {
/// The sibling's project identifier, if resolved.
pub project_id: Option<String>,
/// The subject content digest the evidence pertains to.
pub subject_sha256: Option<String>,
/// The documents fetched (any of build plan / SBOM / VEX / lock / attestation).
pub documents: Vec<EvidenceDocument>,
}
/// A source of externally-held, reconcilable evidence for an artifact.
#[allow(async_fn_in_trait)]
pub trait EvidenceProvider: Send + Sync {
/// Which sibling product this provider integrates.
fn system(&self) -> ExternalSystem;
/// Whether this provider can handle the given artifact + working path
/// (e.g. tramiton handles firmware images / embedded source trees).
fn handles(&self, artifact: &Artifact, working_path: Option<&Path>) -> bool;
/// Reconcile existing evidence for the artifact, keyed by its content digest.
/// Returns `Ok(None)` when the provider has nothing for this artifact.
async fn reconcile(
&self,
artifact: &Artifact,
working_path: Option<&Path>,
) -> Result<Option<ReconciledEvidence>, CoreError>;
}