feat(onboarding): artifact ingest + classifier + native tramiton + suite seams (#138)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Failing after 5s
CI / Deploy Dashboard (push) Failing after 5s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Failing after 4s

This commit was merged in pull request #138.
This commit is contained in:
2026-07-10 16:00:51 +00:00
parent 675c4ef699
commit ef6ee3dcd1
18 changed files with 1661 additions and 6 deletions
+167 -1
View File
@@ -22,7 +22,7 @@ use super::scan::ScanType;
/// Targets look endlessly varied but fall into a small enumerable set classified
/// by where the analyzable signal lives. This drives the scan-applicability
/// matrix and the onboarding wizard's type selection.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TargetType {
/// Browser-facing web application (front end + server).
@@ -411,6 +411,134 @@ pub struct TargetScanConfig {
pub issue_tracker: Option<IssueTrackerConfig>,
}
/// A sibling product in the company suite that may already hold authoritative
/// data for a target. compliance-scanner reconciles with these rather than
/// recomputing what they already know.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExternalSystem {
/// Reproducible-build & firmware compliance engine (build plan, SBOM, VEX,
/// attestation).
Tramiton,
/// Code assistant (downstream remediation consumer).
Werkpilot,
/// Compliance-controls RAG (atomic controls derived from laws).
BreakpilotCompliance,
}
impl std::fmt::Display for ExternalSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tramiton => write!(f, "tramiton"),
Self::Werkpilot => write!(f, "werkpilot"),
Self::BreakpilotCompliance => write!(f, "breakpilot_compliance"),
}
}
}
/// A link from this target to a record in a sibling product, used to reconcile
/// existing evidence instead of recomputing it.
///
/// For tramiton, `project_id` is the shared cross-product key and
/// `subject_sha256` matches a firmware artifact's [`Artifact::content_hash`]
/// (which equals tramiton's `Artifact.sha256`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalRef {
/// Which sibling product this reference points at.
pub system: ExternalSystem,
/// The sibling product's project identifier, if known.
pub project_id: Option<String>,
/// Content digest of the subject artifact (firmware sha256), if known.
pub subject_sha256: Option<String>,
/// Reconciliation status: `linked` | `reconciled` | `unavailable`.
#[serde(default)]
pub status: String,
/// Opaque, offline-verifiable entitlement grant (e.g. tramiton's signed
/// `LicenseGrant`), if the tenant provided one.
pub license_grant: Option<String>,
/// When evidence was last reconciled from this system.
#[serde(default, with = "super::serde_helpers::opt_bson_datetime")]
pub last_reconciled_at: Option<DateTime<Utc>>,
}
impl ExternalRef {
/// A freshly linked (not yet reconciled) reference to a sibling system.
pub fn linked(system: ExternalSystem) -> Self {
Self {
system,
project_id: None,
subject_sha256: None,
status: "linked".to_string(),
license_grant: None,
last_reconciled_at: None,
}
}
}
/// A regulatory / standards framework a target must comply with. Drives which
/// controls the mapping engine pulls from the [`crate::traits::ControlsProvider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ComplianceFramework {
/// EU Cyber Resilience Act.
Cra,
/// IEC 62443 (industrial automation & control systems security).
Iec62443,
/// EU General Data Protection Regulation.
Gdpr,
/// SOC 2.
Soc2,
/// ISO/IEC 27001.
Iso27001,
/// EU Radio Equipment Directive (RED) cybersecurity articles.
RedDirective,
}
impl std::fmt::Display for ComplianceFramework {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cra => write!(f, "cra"),
Self::Iec62443 => write!(f, "iec_62443"),
Self::Gdpr => write!(f, "gdpr"),
Self::Soc2 => write!(f, "soc2"),
Self::Iso27001 => write!(f, "iso_27001"),
Self::RedDirective => write!(f, "red_directive"),
}
}
}
/// The compliance scope of a target: which frameworks apply and, optionally, the
/// jurisdiction. Captured at onboarding (with per-target-type defaults from
/// [`default_compliance_profile`]).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ComplianceProfile {
/// Applicable frameworks.
#[serde(default)]
pub frameworks: Vec<ComplianceFramework>,
/// Free-form jurisdiction (e.g. `eu`, `us`, `de`).
pub jurisdiction: Option<String>,
}
/// The sensible default compliance scope for a target type. Firmware / PLC /
/// embedded default to CRA + IEC 62443; software defaults to GDPR + SOC 2.
pub fn default_compliance_profile(target_type: TargetType) -> ComplianceProfile {
use ComplianceFramework::{Cra, Gdpr, Iec62443, Soc2};
let frameworks = match target_type {
TargetType::PlcSps
| TargetType::FirmwareBareMetal
| TargetType::FirmwareRtos
| TargetType::EmbeddedLinuxYocto => vec![Cra, Iec62443],
TargetType::WebApp | TargetType::BackendService => vec![Gdpr, Soc2],
TargetType::DesktopApp | TargetType::AndroidApp | TargetType::IosApp => {
vec![Gdpr, Cra]
}
};
ComplianceProfile {
frameworks,
jurisdiction: None,
}
}
/// A target onboarded for scanning: the unified replacement for the legacy
/// `TrackedRepository` (SAST) and `DastTarget` (DAST) records.
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -434,6 +562,12 @@ pub struct OnboardedTarget {
/// How this target should be scanned.
#[serde(default)]
pub scan_config: TargetScanConfig,
/// The compliance scope (applicable frameworks / jurisdiction).
#[serde(default)]
pub compliance_profile: ComplianceProfile,
/// Links to sibling products (tramiton, ...) holding reconcilable evidence.
#[serde(default)]
pub external_refs: Vec<ExternalRef>,
/// Cron schedule for recurring scans, if any.
pub scan_schedule: Option<String>,
/// Whether inbound webhooks are enabled for this target.
@@ -472,6 +606,8 @@ impl OnboardedTarget {
artifacts: Vec::new(),
classification: None,
scan_config: TargetScanConfig::default(),
compliance_profile: default_compliance_profile(target_type),
external_refs: Vec::new(),
scan_schedule: None,
webhook_enabled: false,
webhook_secret: Some(webhook_secret),
@@ -584,4 +720,34 @@ mod tests {
let b = Artifact::firmware_image("fw.bin");
assert_ne!(a.id, b.id);
}
#[test]
fn firmware_default_profile_is_cra_and_62443() {
let p = default_compliance_profile(TargetType::FirmwareBareMetal);
assert!(p.frameworks.contains(&ComplianceFramework::Cra));
assert!(p.frameworks.contains(&ComplianceFramework::Iec62443));
}
#[test]
fn webapp_default_profile_is_gdpr_and_soc2() {
let p = default_compliance_profile(TargetType::WebApp);
assert!(p.frameworks.contains(&ComplianceFramework::Gdpr));
assert!(p.frameworks.contains(&ComplianceFramework::Soc2));
}
#[test]
fn new_target_gets_default_profile_and_no_external_refs() {
let t = OnboardedTarget::new("fw".to_string(), TargetType::FirmwareRtos);
assert!(!t.compliance_profile.frameworks.is_empty());
assert!(t.external_refs.is_empty());
}
#[test]
fn external_ref_linked_defaults() {
let r = ExternalRef::linked(ExternalSystem::Tramiton);
assert_eq!(r.system, ExternalSystem::Tramiton);
assert_eq!(r.status, "linked");
assert!(r.project_id.is_none());
assert!(r.last_reconciled_at.is_none());
}
}