license switch agpl -> busl1, sprints work, new product advisories

This commit is contained in:
master
2026-01-20 15:32:20 +02:00
parent 4903395618
commit c32fff8f86
1835 changed files with 38630 additions and 4359 deletions

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraConnector.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System;

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraConnectorPlugin.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System;

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraTrustDefaults.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
namespace StellaOps.Concelier.Connector.Astra;

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraOptions.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System;

View File

@@ -307,4 +307,4 @@ sha256sum astra-linux-1.7-oval.xml
## License
Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
Copyright (c) Stella Operations. Licensed under BUSL-1.1.

View File

@@ -0,0 +1,694 @@
// -----------------------------------------------------------------------------
// ParsedSbom.cs
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
// Task: TASK-015-001 - Parsed SBOM model
// Description: Enriched SBOM extraction model for downstream consumers
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
namespace StellaOps.Concelier.SbomIntegration.Models;
/// <summary>
/// Enriched SBOM extraction result.
/// </summary>
public sealed record ParsedSbom
{
public required string Format { get; init; }
public required string SpecVersion { get; init; }
public required string SerialNumber { get; init; }
public ImmutableArray<ParsedComponent> Components { get; init; } = [];
public ImmutableArray<ParsedService> Services { get; init; } = [];
public ImmutableArray<ParsedDependency> Dependencies { get; init; } = [];
public ImmutableArray<ParsedComposition> Compositions { get; init; } = [];
public ImmutableArray<ParsedVulnerability> Vulnerabilities { get; init; } = [];
public ParsedFormulation? Formulation { get; init; }
public ParsedBuildInfo? BuildInfo { get; init; }
public ParsedDeclarations? Declarations { get; init; }
public ParsedDefinitions? Definitions { get; init; }
public ImmutableArray<ParsedAnnotation> Annotations { get; init; } = [];
public required ParsedSbomMetadata Metadata { get; init; }
}
/// <summary>
/// Metadata extracted from SBOM headers.
/// </summary>
public sealed record ParsedSbomMetadata
{
public string? Name { get; init; }
public string? Version { get; init; }
public DateTimeOffset? Timestamp { get; init; }
public ImmutableArray<string> Tools { get; init; } = [];
public ImmutableArray<string> Authors { get; init; } = [];
public string? Supplier { get; init; }
public string? Manufacturer { get; init; }
public ImmutableArray<string> Profiles { get; init; } = [];
public ImmutableArray<ParsedNamespaceMapEntry> NamespaceMap { get; init; } = [];
public ImmutableArray<string> Imports { get; init; } = [];
public string? RootComponentRef { get; init; }
}
public sealed record ParsedNamespaceMapEntry
{
public required string Prefix { get; init; }
public required string Namespace { get; init; }
}
/// <summary>
/// Software component extracted from an SBOM.
/// </summary>
public sealed record ParsedComponent
{
public required string BomRef { get; init; }
public string? Type { get; init; }
public required string Name { get; init; }
public string? Version { get; init; }
public string? Purl { get; init; }
public string? Cpe { get; init; }
public string? Group { get; init; }
public string? Publisher { get; init; }
public string? Description { get; init; }
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
public ParsedEvidence? Evidence { get; init; }
public ParsedPedigree? Pedigree { get; init; }
public ParsedCryptoProperties? CryptoProperties { get; init; }
public ParsedModelCard? ModelCard { get; init; }
public ParsedOrganization? Supplier { get; init; }
public ParsedOrganization? Manufacturer { get; init; }
public ComponentScope Scope { get; init; } = ComponentScope.Required;
public bool Modified { get; init; }
}
public enum ComponentScope
{
Required,
Optional,
Excluded,
Unknown
}
public sealed record ParsedHash
{
public required string Algorithm { get; init; }
public required string Value { get; init; }
}
public sealed record ParsedExternalRef
{
public string? Type { get; init; }
public string? Url { get; init; }
public string? Comment { get; init; }
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
}
public sealed record ParsedOrganization
{
public string? Name { get; init; }
public string? Url { get; init; }
public string? Contact { get; init; }
}
public sealed record ParsedEvidence
{
public ParsedEvidenceIdentity? Identity { get; init; }
public ImmutableArray<ParsedEvidenceOccurrence> Occurrences { get; init; } = [];
public ParsedEvidenceCallstack? Callstack { get; init; }
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
public ImmutableArray<string> Copyrights { get; init; } = [];
}
public sealed record ParsedEvidenceIdentity
{
public string? Field { get; init; }
public double? Confidence { get; init; }
public string? Value { get; init; }
}
public sealed record ParsedEvidenceOccurrence
{
public string? Location { get; init; }
public int? Line { get; init; }
public int? Offset { get; init; }
public string? Symbol { get; init; }
public string? AdditionalContext { get; init; }
}
public sealed record ParsedEvidenceCallstack
{
public ImmutableArray<ParsedCallstackFrame> Frames { get; init; } = [];
}
public sealed record ParsedCallstackFrame
{
public string? Package { get; init; }
public string? Module { get; init; }
public string? Function { get; init; }
public ImmutableArray<string> Parameters { get; init; } = [];
public int? Line { get; init; }
public int? Column { get; init; }
public string? FullFilename { get; init; }
}
public sealed record ParsedPedigree
{
public ImmutableArray<ParsedComponentReference> Ancestors { get; init; } = [];
public ImmutableArray<ParsedComponentReference> Variants { get; init; } = [];
public ImmutableArray<ParsedComponentReference> Commits { get; init; } = [];
public ImmutableArray<ParsedPatch> Patches { get; init; } = [];
public ImmutableArray<string> Notes { get; init; } = [];
}
public sealed record ParsedComponentReference
{
public string? BomRef { get; init; }
public string? Version { get; init; }
public string? Description { get; init; }
}
public sealed record ParsedPatch
{
public string? Type { get; init; }
public string? Diff { get; init; }
public string? Url { get; init; }
}
public sealed record ParsedService
{
public required string BomRef { get; init; }
public string? Provider { get; init; }
public string? Group { get; init; }
public required string Name { get; init; }
public string? Version { get; init; }
public string? Description { get; init; }
public ImmutableArray<string> Endpoints { get; init; } = [];
public bool Authenticated { get; init; }
public bool CrossesTrustBoundary { get; init; }
public ImmutableArray<ParsedDataFlow> Data { get; init; } = [];
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
public ImmutableArray<ParsedService> NestedServices { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedDataFlow
{
public DataFlowDirection Direction { get; init; }
public string? Classification { get; init; }
public string? SourceRef { get; init; }
public string? DestinationRef { get; init; }
}
public enum DataFlowDirection
{
Unknown,
Inbound,
Outbound,
Bidirectional
}
public sealed record ParsedDependency
{
public required string SourceRef { get; init; }
public ImmutableArray<string> DependsOn { get; init; } = [];
public DependencyScope Scope { get; init; } = DependencyScope.Runtime;
}
public enum DependencyScope
{
Runtime,
Development,
Optional,
Test,
Unknown
}
public sealed record ParsedComposition
{
public CompositionAggregate Aggregate { get; init; } = CompositionAggregate.Unknown;
public ImmutableArray<string> Assemblies { get; init; } = [];
public ImmutableArray<string> Dependencies { get; init; } = [];
public ImmutableArray<string> Vulnerabilities { get; init; } = [];
}
public enum CompositionAggregate
{
Complete,
Incomplete,
IncompleteFirstPartyProprietary,
IncompleteFirstPartyOpenSource,
IncompleteThirdPartyProprietary,
IncompleteThirdPartyOpenSource,
Unknown,
NotSpecified
}
public sealed record ParsedAnnotation
{
public string? BomRef { get; init; }
public ImmutableArray<string> Subjects { get; init; } = [];
public ParsedAnnotator? Annotator { get; init; }
public DateTimeOffset? Timestamp { get; init; }
public string? Text { get; init; }
}
public sealed record ParsedAnnotator
{
public string? Type { get; init; }
public string? Name { get; init; }
public string? Reference { get; init; }
}
public sealed record ParsedFormulation
{
public string? BomRef { get; init; }
public ImmutableArray<ParsedFormula> Components { get; init; } = [];
public ImmutableArray<ParsedWorkflow> Workflows { get; init; } = [];
public ImmutableArray<ParsedTask> Tasks { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedFormula
{
public string? BomRef { get; init; }
public ImmutableArray<string> ComponentRefs { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedWorkflow
{
public string? Name { get; init; }
public string? Description { get; init; }
public ImmutableArray<string> InputRefs { get; init; } = [];
public ImmutableArray<string> OutputRefs { get; init; } = [];
public ImmutableArray<ParsedTask> Tasks { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedTask
{
public string? Name { get; init; }
public string? Description { get; init; }
public ImmutableArray<string> InputRefs { get; init; } = [];
public ImmutableArray<string> OutputRefs { get; init; } = [];
public ImmutableDictionary<string, string> Parameters { get; init; } =
ImmutableDictionary<string, string>.Empty;
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedBuildInfo
{
public required string BuildId { get; init; }
public string? BuildType { get; init; }
public DateTimeOffset? BuildStartTime { get; init; }
public DateTimeOffset? BuildEndTime { get; init; }
public string? ConfigSourceEntrypoint { get; init; }
public string? ConfigSourceDigest { get; init; }
public string? ConfigSourceUri { get; init; }
public ImmutableDictionary<string, string> Environment { get; init; } =
ImmutableDictionary<string, string>.Empty;
public ImmutableDictionary<string, string> Parameters { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedCryptoProperties
{
public CryptoAssetType AssetType { get; init; }
public ParsedAlgorithmProperties? AlgorithmProperties { get; init; }
public ParsedCertificateProperties? CertificateProperties { get; init; }
public ParsedProtocolProperties? ProtocolProperties { get; init; }
public ParsedRelatedCryptoMaterial? RelatedCryptoMaterial { get; init; }
public string? Oid { get; init; }
}
public enum CryptoAssetType
{
Algorithm,
Certificate,
Protocol,
RelatedCryptoMaterial,
Unknown
}
public sealed record ParsedAlgorithmProperties
{
public CryptoPrimitive? Primitive { get; init; }
public string? ParameterSetIdentifier { get; init; }
public string? Curve { get; init; }
public CryptoExecutionEnvironment? ExecutionEnvironment { get; init; }
public string? ImplementationPlatform { get; init; }
public CertificationLevel? CertificationLevel { get; init; }
public CryptoMode? Mode { get; init; }
public CryptoPadding? Padding { get; init; }
public ImmutableArray<string> CryptoFunctions { get; init; } = [];
public int? ClassicalSecurityLevel { get; init; }
public int? NistQuantumSecurityLevel { get; init; }
public int? KeySize { get; init; }
}
public sealed record ParsedCertificateProperties
{
public string? SubjectName { get; init; }
public string? IssuerName { get; init; }
public DateTimeOffset? NotValidBefore { get; init; }
public DateTimeOffset? NotValidAfter { get; init; }
public string? SignatureAlgorithmRef { get; init; }
public string? SubjectPublicKeyRef { get; init; }
public string? CertificateFormat { get; init; }
public string? CertificateExtension { get; init; }
}
public sealed record ParsedProtocolProperties
{
public string? Type { get; init; }
public string? Version { get; init; }
public ImmutableArray<string> CipherSuites { get; init; } = [];
public ImmutableArray<string> IkeV2TransformTypes { get; init; } = [];
public ImmutableArray<string> CryptoRefArray { get; init; } = [];
}
public sealed record ParsedRelatedCryptoMaterial
{
public string? Type { get; init; }
public string? Reference { get; init; }
public ImmutableArray<string> MaterialRefs { get; init; } = [];
}
public enum CryptoPrimitive
{
Unknown,
Symmetric,
Asymmetric,
Hash,
Mac,
Kdf,
Rng
}
public enum CryptoMode
{
Unknown,
Ecb,
Cbc,
Ctr,
Gcm,
Xts
}
public enum CryptoPadding
{
Unknown,
None,
Pkcs1,
Pkcs7,
Oaep
}
public enum CryptoExecutionEnvironment
{
Unknown,
Hardware,
Software,
Hybrid
}
public enum CertificationLevel
{
Unknown,
Fips140_2,
Fips140_3,
CommonCriteria
}
public sealed record ParsedModelCard
{
public string? BomRef { get; init; }
public ParsedModelParameters? ModelParameters { get; init; }
public ParsedQuantitativeAnalysis? QuantitativeAnalysis { get; init; }
public ParsedConsiderations? Considerations { get; init; }
}
public sealed record ParsedModelParameters
{
public string? Task { get; init; }
public string? ArchitectureFamily { get; init; }
public string? ModelArchitecture { get; init; }
public ImmutableArray<ParsedDatasetRef> Datasets { get; init; } = [];
public ImmutableArray<ParsedInputOutput> Inputs { get; init; } = [];
public ImmutableArray<ParsedInputOutput> Outputs { get; init; } = [];
public string? AutonomyType { get; init; }
public string? Domain { get; init; }
public string? TypeOfModel { get; init; }
public string? EnergyConsumption { get; init; }
public ImmutableDictionary<string, string> Hyperparameters { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedDatasetRef
{
public string? Name { get; init; }
public string? Version { get; init; }
public string? Url { get; init; }
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
}
public sealed record ParsedInputOutput
{
public string? Format { get; init; }
public string? Description { get; init; }
}
public sealed record ParsedQuantitativeAnalysis
{
public ImmutableArray<ParsedPerformanceMetric> PerformanceMetrics { get; init; } = [];
public ImmutableArray<ParsedGraphic> Graphics { get; init; } = [];
}
public sealed record ParsedPerformanceMetric
{
public string? Type { get; init; }
public string? Value { get; init; }
public string? Slice { get; init; }
public string? ConfidenceIntervalLower { get; init; }
public string? ConfidenceIntervalUpper { get; init; }
}
public sealed record ParsedGraphic
{
public string? Name { get; init; }
public string? Image { get; init; }
public string? Description { get; init; }
}
public sealed record ParsedConsiderations
{
public ImmutableArray<string> Users { get; init; } = [];
public ImmutableArray<string> UseCases { get; init; } = [];
public ImmutableArray<string> TechnicalLimitations { get; init; } = [];
public ImmutableArray<ParsedRisk> EthicalConsiderations { get; init; } = [];
public ImmutableArray<ParsedFairnessAssessment> FairnessAssessments { get; init; } = [];
public ParsedEnvironmentalConsiderations? EnvironmentalConsiderations { get; init; }
}
public sealed record ParsedFairnessAssessment
{
public string? GroupAtRisk { get; init; }
public string? Benefits { get; init; }
public string? Harms { get; init; }
public string? MitigationStrategy { get; init; }
}
public sealed record ParsedRisk
{
public string? Name { get; init; }
public string? MitigationStrategy { get; init; }
}
public sealed record ParsedEnvironmentalConsiderations
{
public ImmutableArray<ParsedEnergyConsumption> EnergyConsumptions { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedEnergyConsumption
{
public string? Activity { get; init; }
public ImmutableArray<ParsedEnergyProvider> EnergyProviders { get; init; } = [];
public string? ActivityEnergyCost { get; init; }
public string? Co2CostEquivalent { get; init; }
public string? Co2CostOffset { get; init; }
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public sealed record ParsedEnergyProvider
{
public string? BomRef { get; init; }
public string? Description { get; init; }
public ParsedOrganization? Organization { get; init; }
public string? EnergySource { get; init; }
public string? EnergyProvided { get; init; }
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
}
public sealed record ParsedVulnerability
{
public required string Id { get; init; }
public string? Source { get; init; }
public string? Description { get; init; }
public string? Detail { get; init; }
public string? Recommendation { get; init; }
public ImmutableArray<string> Cwes { get; init; } = [];
public ImmutableArray<ParsedVulnRating> Ratings { get; init; } = [];
public ImmutableArray<ParsedVulnAffects> Affects { get; init; } = [];
public ParsedVulnAnalysis? Analysis { get; init; }
public DateTimeOffset? Published { get; init; }
public DateTimeOffset? Updated { get; init; }
}
public sealed record ParsedVulnRating
{
public string? Method { get; init; }
public string? Score { get; init; }
public string? Severity { get; init; }
public string? Vector { get; init; }
public string? Source { get; init; }
}
public sealed record ParsedVulnAffects
{
public string? Ref { get; init; }
public string? Version { get; init; }
public string? Status { get; init; }
}
public sealed record ParsedVulnAnalysis
{
public VexState State { get; init; }
public VexJustification? Justification { get; init; }
public ImmutableArray<string> Response { get; init; } = [];
public string? Detail { get; init; }
public DateTimeOffset? FirstIssued { get; init; }
public DateTimeOffset? LastUpdated { get; init; }
}
public enum VexState
{
Exploitable,
InTriage,
FalsePositive,
NotAffected,
Fixed,
UnderInvestigation,
Unknown
}
public enum VexJustification
{
ComponentNotPresent,
VulnerableCodeNotPresent,
VulnerableCodeNotInExecutePath,
InlineMitigationsAlreadyExist,
Other
}
public sealed record ParsedLicense
{
public string? SpdxId { get; init; }
public string? Name { get; init; }
public string? Url { get; init; }
public string? Text { get; init; }
public ParsedLicenseExpression? Expression { get; init; }
public ParsedLicenseTerms? Licensing { get; init; }
public ImmutableArray<string> Acknowledgements { get; init; } = [];
}
public sealed record ParsedLicenseTerms
{
public string? Licensor { get; init; }
public string? Licensee { get; init; }
public string? Purchaser { get; init; }
public string? PurchaseOrder { get; init; }
public ImmutableArray<string> LicenseTypes { get; init; } = [];
public DateTimeOffset? LastRenewal { get; init; }
public DateTimeOffset? Expiration { get; init; }
public ImmutableArray<string> AltIds { get; init; } = [];
public ImmutableDictionary<string, string> Properties { get; init; } =
ImmutableDictionary<string, string>.Empty;
}
public abstract record ParsedLicenseExpression;
public sealed record SimpleLicense(string Id) : ParsedLicenseExpression;
public sealed record WithException(ParsedLicenseExpression License, string Exception) : ParsedLicenseExpression;
public sealed record OrLater(string LicenseId) : ParsedLicenseExpression;
public sealed record ConjunctiveSet(ImmutableArray<ParsedLicenseExpression> Members) : ParsedLicenseExpression;
public sealed record DisjunctiveSet(ImmutableArray<ParsedLicenseExpression> Members) : ParsedLicenseExpression;
public enum LicenseCategory
{
Permissive,
WeakCopyleft,
StrongCopyleft,
Proprietary,
PublicDomain,
Unknown
}
public sealed record ParsedDeclarations
{
public ImmutableArray<ParsedAttestation> Attestations { get; init; } = [];
public ImmutableArray<ParsedAffirmation> Affirmations { get; init; } = [];
}
public sealed record ParsedAttestation
{
public ImmutableArray<string> Subjects { get; init; } = [];
public string? Predicate { get; init; }
public string? Evidence { get; init; }
public ParsedSignature? Signature { get; init; }
}
public sealed record ParsedAffirmation
{
public string? Statement { get; init; }
public ImmutableArray<string> Signatories { get; init; } = [];
}
public sealed record ParsedDefinitions
{
public ImmutableArray<ParsedStandard> Standards { get; init; } = [];
}
public sealed record ParsedStandard
{
public string? BomRef { get; init; }
public string? Name { get; init; }
public string? Version { get; init; }
public string? Description { get; init; }
public ParsedOrganization? Owner { get; init; }
public ImmutableArray<string> Requirements { get; init; } = [];
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
public ParsedSignature? Signature { get; init; }
}
public sealed record ParsedSignature
{
public string? Algorithm { get; init; }
public string? KeyId { get; init; }
public string? PublicKey { get; init; }
public ImmutableArray<string> CertificatePath { get; init; } = [];
public string? Value { get; init; }
}

View File

@@ -0,0 +1,27 @@
// -----------------------------------------------------------------------------
// IParsedSbomParser.cs
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM extraction
// Description: Interface for enriched SBOM parsing
// -----------------------------------------------------------------------------
using StellaOps.Concelier.SbomIntegration.Models;
namespace StellaOps.Concelier.SbomIntegration.Parsing;
/// <summary>
/// Service for parsing SBOM content into enriched ParsedSbom models.
/// </summary>
public interface IParsedSbomParser
{
/// <summary>
/// Parses SBOM content into a ParsedSbom model.
/// </summary>
/// <param name="content">SBOM content stream.</param>
/// <param name="format">SBOM format.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Parsed SBOM with enriched metadata.</returns>
Task<ParsedSbom> ParseAsync(
Stream content,
SbomFormat format,
CancellationToken cancellationToken = default);
}

View File

@@ -28,6 +28,7 @@ public static class ServiceCollectionExtensions
{
// Register parser
services.TryAddSingleton<ISbomParser, SbomParser>();
services.TryAddSingleton<IParsedSbomParser, ParsedSbomParser>();
// Register PURL index (requires Valkey connection)
services.TryAddSingleton<IPurlCanonicalIndex, ValkeyPurlCanonicalIndex>();
@@ -52,6 +53,7 @@ public static class ServiceCollectionExtensions
{
// Register parser
services.TryAddSingleton<ISbomParser, SbomParser>();
services.TryAddSingleton<IParsedSbomParser, ParsedSbomParser>();
// Register PURL index (requires Valkey connection)
services.TryAddSingleton<IPurlCanonicalIndex, ValkeyPurlCanonicalIndex>();

View File

@@ -1,10 +1,23 @@
# Concelier SbomIntegration Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`,
`docs/implplan/SPRINT_20260119_015_Concelier_sbom_full_extraction.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0237-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0237-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0237-A | TODO | Revalidated 2026-01-07 (open findings). |
| TASK-015-001 | DOING | ParsedSbom model scaffolding. |
| TASK-015-002 | DOING | ParsedService model scaffolding. |
| TASK-015-003 | DOING | ParsedCryptoProperties model scaffolding. |
| TASK-015-004 | DOING | ParsedModelCard model scaffolding. |
| TASK-015-005 | DOING | CycloneDX formulation parsing + tests added; SPDX build parsing added. |
| TASK-015-006 | DOING | ParsedVulnerability/VEX model scaffolding. |
| TASK-015-007 | DOING | ParsedLicense model scaffolding. |
| TASK-015-007a | DOING | CycloneDX license extraction expansion. |
| TASK-015-007b | DOING | SPDX licensing profile extraction expansion. |
| TASK-015-008 | DOING | CycloneDX extraction now covers formulation; tests updated. |
| TASK-015-009 | DOING | ParsedSbomParser SPDX 3.0.1 extraction baseline + build profile. |
| TASK-015-010 | DOING | ParsedSbom adapter + framework reference added; Artifact.Infrastructure build errors block tests. |

View File

@@ -1,5 +1,5 @@
// <copyright file="TemporalCacheTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_001_TEST_time_skew_idempotency
// Task: TSKW-010

View File

@@ -1,5 +1,5 @@
// <copyright file="ConcelierConfigDiffTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
// Task: CCUT-020

View File

@@ -1,5 +1,5 @@
// <copyright file="AstraConnectorTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System;

View File

@@ -0,0 +1,549 @@
// -----------------------------------------------------------------------------
// ParsedSbomParserTests.cs
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM parsing tests
// Description: Unit tests for enriched SBOM parsing
// -----------------------------------------------------------------------------
using System.Text;
using System.Linq;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using StellaOps.Concelier.SbomIntegration.Models;
using StellaOps.Concelier.SbomIntegration.Parsing;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.SbomIntegration.Tests;
public sealed class ParsedSbomParserTests
{
private readonly ParsedSbomParser _parser;
public ParsedSbomParserTests()
{
var loggerMock = new Mock<ILogger<ParsedSbomParser>>();
_parser = new ParsedSbomParser(loggerMock.Object);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParseAsync_CycloneDx_ExtractsMetadataComponentsAndServices()
{
var content = """
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:1234",
"metadata": {
"timestamp": "2026-01-20T00:00:00Z",
"component": {
"bom-ref": "app",
"name": "myapp",
"version": "1.0.0"
},
"tools": [
{ "name": "stella-scanner" }
],
"authors": [
{ "name": "dev@example.com" }
],
"supplier": { "name": "Acme" },
"manufacturer": { "name": "AcmeManu" }
},
"components": [
{
"bom-ref": "lib",
"name": "lib",
"version": "2.0.0",
"purl": "pkg:npm/lib@2.0.0",
"scope": "optional",
"modified": true,
"supplier": {
"name": "LibSupplier",
"url": "https://supplier.example.com",
"contact": [
{ "name": "Supplier Contact", "email": "contact@example.com" }
]
},
"manufacturer": {
"name": "LibManufacturer"
},
"evidence": {
"identity": {
"field": "purl",
"confidence": 0.9,
"value": "pkg:npm/lib@2.0.0"
},
"occurrences": [
{
"location": "src/lib.js",
"line": 10,
"offset": 4,
"symbol": "libfn",
"additionalContext": "ctx"
}
],
"callstack": {
"frames": [
{
"package": "pkg",
"module": "mod",
"function": "fn",
"parameters": ["a", "b"],
"line": 20,
"column": 2,
"fullFilename": "/src/lib.js"
}
]
},
"licenses": [
{ "expression": "MIT" }
],
"copyright": [
"Copyright 2026"
]
},
"pedigree": {
"ancestors": [
{ "bom-ref": "ancestor", "version": "0.1", "description": "base" }
],
"variants": [
{ "bom-ref": "variant", "version": "2.0" }
],
"commits": [
{ "uid": "abc123", "message": "fix" }
],
"patches": [
{ "type": "backport", "diff": { "text": "diff", "url": "https://example.com/diff" } }
],
"notes": ["note1", "note2"]
},
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "hash",
"parameterSetIdentifier": "ps1",
"curve": "P-256",
"executionEnvironment": "software",
"certificationLevel": "fips140-2",
"mode": "gcm",
"padding": "pkcs7",
"cryptoFunctions": ["digest"],
"classicalSecurityLevel": 128,
"nistQuantumSecurityLevel": 1,
"keySize": 256
},
"certificateProperties": {
"subjectName": "CN=Test",
"issuerName": "CA",
"notValidBefore": "2026-01-01T00:00:00Z",
"notValidAfter": "2027-01-01T00:00:00Z",
"signatureAlgorithmRef": "sha256",
"subjectPublicKeyRef": "key",
"certificateFormat": "x509",
"certificateExtension": "ext"
},
"protocolProperties": {
"type": "tls",
"version": "1.3",
"cipherSuites": ["TLS_AES_128_GCM_SHA256"],
"ikev2TransformTypes": ["aes"],
"cryptoRefArray": ["ref1"]
},
"relatedCryptoMaterialProperties": {
"type": "key",
"id": "key-1",
"algorithmRef": "alg-1",
"securedBy": ["sec1"],
"relatedCryptographicAssets": [
{ "type": "certificate", "ref": "cert-1" }
]
},
"oid": "1.2.3.4"
},
"modelCard": {
"bom-ref": "model-1",
"modelParameters": {
"task": "classification",
"architectureFamily": "cnn",
"modelArchitecture": "resnet",
"approach": { "type": "supervised" },
"datasets": [
{
"name": "dataset1",
"version": "1.0",
"url": "https://example.com/ds",
"hashes": [
{ "alg": "SHA-256", "content": "abcd" }
]
}
],
"inputs": [
{ "format": "image", "description": "jpg" }
],
"outputs": [
{ "format": "label", "description": "class" }
]
},
"quantitativeAnalysis": {
"performanceMetrics": [
{
"type": "accuracy",
"value": "0.9",
"slice": "overall",
"confidenceInterval": { "lowerBound": "0.88", "upperBound": "0.92" }
}
],
"graphics": {
"collection": [
{ "name": "roc", "image": "data:image/png;base64,aa", "description": "ROC" }
]
}
},
"considerations": {
"users": ["devs"],
"useCases": ["testing"],
"technicalLimitations": ["small dataset"],
"ethicalConsiderations": [
{ "name": "bias", "mitigationStrategy": "review" }
],
"fairnessAssessments": [
{ "groupAtRisk": "group1", "benefits": "benefit", "harms": "harm", "mitigationStrategy": "mit" }
],
"environmentalConsiderations": {
"energyConsumptions": [
{
"activity": "training",
"activityEnergyCost": "10kWh",
"co2CostEquivalent": "5kg",
"co2CostOffset": "1kg",
"properties": [
{ "name": "region", "value": "us" }
],
"energyProviders": [
{
"bom-ref": "prov",
"description": "provider",
"organization": { "name": "EnergyCo" },
"energySource": "solar",
"energyProvided": "5kWh",
"externalReferences": [
{ "type": "website", "url": "https://energy.example.com" }
]
}
]
}
],
"properties": [
{ "name": "note", "value": "env" }
]
}
}
},
"licenses": [
{
"license": {
"id": "MIT",
"name": "MIT License",
"url": "https://example.com/license",
"text": {
"content": "TUlU",
"encoding": "base64"
},
"licensing": {
"licensor": { "name": "Acme" },
"licensee": "Consumer",
"purchaseOrder": "PO-123"
}
}
},
{
"expression": "MIT AND (Apache-2.0 OR BSD-3-Clause)"
}
],
"externalReferences": [
{ "type": "website", "url": "https://example.com/lib", "comment": "home" }
]
}
],
"dependencies": [
{
"ref": "app",
"dependsOn": ["lib"]
}
],
"services": [
{
"bom-ref": "svc",
"name": "api",
"version": "1.0.0",
"authenticated": true,
"x-trust-boundary": true,
"endpoints": ["https://api.example.com"],
"licenses": [
{ "expression": "Apache-2.0" }
],
"externalReferences": [
{ "type": "documentation", "url": "https://example.com/api-docs" }
]
}
],
"formulation": [
{
"bom-ref": "form-1",
"components": [
"lib",
{
"ref": "app",
"properties": [
{ "name": "stage", "value": "build" }
]
}
],
"workflows": [
{
"name": "build",
"description": "build pipeline",
"inputs": ["src"],
"outputs": ["artifact"],
"tasks": [
{
"name": "compile",
"description": "compile sources",
"inputs": ["src"],
"outputs": ["bin"],
"parameters": [
{ "name": "opt", "value": "O2" }
],
"properties": [
{ "name": "runner", "value": "msbuild" }
]
}
],
"properties": [
{ "name": "workflow", "value": "ci" }
]
}
],
"tasks": [
{
"name": "package",
"description": "package app",
"inputs": ["bin"],
"outputs": ["artifact"],
"parameters": [
{ "name": "format", "value": "zip" }
]
}
],
"properties": [
{ "name": "formulation", "value": "v1" }
]
}
]
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
var result = await _parser.ParseAsync(stream, SbomFormat.CycloneDX);
result.Format.Should().Be("cyclonedx");
result.SpecVersion.Should().Be("1.7");
result.SerialNumber.Should().Be("urn:uuid:1234");
result.Metadata.Name.Should().Be("myapp");
result.Metadata.Version.Should().Be("1.0.0");
result.Metadata.Supplier.Should().Be("Acme");
result.Metadata.Manufacturer.Should().Be("AcmeManu");
result.Components.Should().Contain(c => c.BomRef == "app");
result.Components.Should().Contain(c => c.Purl == "pkg:npm/lib@2.0.0");
var lib = result.Components.Single(c => c.BomRef == "lib");
lib.ExternalReferences.Should().ContainSingle(r =>
r.Type == "website" && r.Url == "https://example.com/lib");
lib.Scope.Should().Be(ComponentScope.Optional);
lib.Modified.Should().BeTrue();
lib.Supplier.Should().NotBeNull();
lib.Supplier!.Name.Should().Be("LibSupplier");
lib.Supplier!.Url.Should().Be("https://supplier.example.com");
lib.Manufacturer.Should().NotBeNull();
lib.Manufacturer!.Name.Should().Be("LibManufacturer");
lib.Evidence.Should().NotBeNull();
lib.Evidence!.Identity.Should().NotBeNull();
lib.Evidence!.Identity!.Field.Should().Be("purl");
lib.Evidence!.Identity!.Confidence.Should().Be(0.9);
lib.Evidence!.Occurrences.Should().ContainSingle(o => o.Location == "src/lib.js");
lib.Evidence!.Callstack.Should().NotBeNull();
lib.Evidence!.Callstack!.Frames.Should().ContainSingle(f => f.Function == "fn");
lib.Evidence!.Licenses.Should().ContainSingle(l => l.Expression != null);
lib.Evidence!.Copyrights.Should().ContainSingle(c => c == "Copyright 2026");
lib.Pedigree.Should().NotBeNull();
lib.Pedigree!.Ancestors.Should().ContainSingle(a => a.BomRef == "ancestor");
lib.Pedigree!.Variants.Should().ContainSingle(v => v.BomRef == "variant");
lib.Pedigree!.Commits.Should().ContainSingle(c => c.BomRef == "abc123");
lib.Pedigree!.Patches.Should().ContainSingle(p => p.Type == "backport");
lib.Pedigree!.Notes.Should().Contain(n => n == "note1");
lib.CryptoProperties.Should().NotBeNull();
lib.CryptoProperties!.AssetType.Should().Be(CryptoAssetType.Algorithm);
lib.CryptoProperties!.AlgorithmProperties.Should().NotBeNull();
lib.CryptoProperties!.AlgorithmProperties!.Primitive.Should().Be(CryptoPrimitive.Hash);
lib.CryptoProperties!.CertificateProperties.Should().NotBeNull();
lib.CryptoProperties!.CertificateProperties!.SubjectName.Should().Be("CN=Test");
lib.CryptoProperties!.ProtocolProperties.Should().NotBeNull();
lib.CryptoProperties!.ProtocolProperties!.Type.Should().Be("tls");
lib.CryptoProperties!.RelatedCryptoMaterial.Should().NotBeNull();
lib.CryptoProperties!.RelatedCryptoMaterial!.Reference.Should().Be("key-1");
lib.CryptoProperties!.RelatedCryptoMaterial!.MaterialRefs.Should().Contain("cert-1");
lib.ModelCard.Should().NotBeNull();
lib.ModelCard!.BomRef.Should().Be("model-1");
lib.ModelCard!.ModelParameters.Should().NotBeNull();
lib.ModelCard!.ModelParameters!.Task.Should().Be("classification");
lib.ModelCard!.ModelParameters!.Datasets.Should().ContainSingle(d => d.Name == "dataset1");
lib.ModelCard!.QuantitativeAnalysis.Should().NotBeNull();
lib.ModelCard!.QuantitativeAnalysis!.PerformanceMetrics.Should().ContainSingle(m => m.Type == "accuracy");
lib.ModelCard!.Considerations.Should().NotBeNull();
lib.ModelCard!.Considerations!.Users.Should().ContainSingle(u => u == "devs");
lib.Licenses.Should().HaveCount(2);
lib.Licenses[0].SpdxId.Should().Be("MIT");
lib.Licenses[0].Name.Should().Be("MIT License");
lib.Licenses[0].Url.Should().Be("https://example.com/license");
lib.Licenses[0].Text.Should().Be("MIT");
lib.Licenses[0].Licensing.Should().NotBeNull();
lib.Licenses[0].Licensing!.Licensor.Should().Be("Acme");
lib.Licenses[0].Licensing!.Licensee.Should().Be("Consumer");
lib.Licenses[0].Licensing!.PurchaseOrder.Should().Be("PO-123");
lib.Licenses[1].Expression.Should().BeOfType<ConjunctiveSet>();
var andExpr = (ConjunctiveSet)lib.Licenses[1].Expression!;
andExpr.Members.Should().HaveCount(2);
andExpr.Members[0].Should().BeOfType<SimpleLicense>()
.Which.Id.Should().Be("MIT");
andExpr.Members[1].Should().BeOfType<DisjunctiveSet>();
var orExpr = (DisjunctiveSet)andExpr.Members[1];
orExpr.Members.OfType<SimpleLicense>()
.Should()
.ContainSingle(license => license.Id == "Apache-2.0");
result.Dependencies.Should().ContainSingle(d => d.SourceRef == "app");
result.Services.Should().ContainSingle(s => s.Name == "api");
result.Services[0].Authenticated.Should().BeTrue();
result.Services[0].CrossesTrustBoundary.Should().BeTrue();
result.Services[0].Licenses.Should().ContainSingle();
result.Services[0].ExternalReferences.Should().ContainSingle(r =>
r.Type == "documentation" && r.Url == "https://example.com/api-docs");
result.Formulation.Should().NotBeNull();
result.Formulation!.BomRef.Should().Be("form-1");
result.Formulation.Components.Should().HaveCount(2);
result.Formulation.Components.Should().ContainSingle(c => c.BomRef == "lib");
result.Formulation.Components.Should().ContainSingle(c =>
c.BomRef == "app" && c.Properties.ContainsKey("stage"));
result.Formulation.Workflows.Should().ContainSingle(w => w.Name == "build");
var workflow = result.Formulation.Workflows.Single(w => w.Name == "build");
workflow.InputRefs.Should().Contain("src");
workflow.OutputRefs.Should().Contain("artifact");
workflow.Tasks.Should().ContainSingle(t => t.Name == "compile");
workflow.Tasks[0].Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
workflow.Tasks[0].Properties.Should().ContainKey("runner").WhoseValue.Should().Be("msbuild");
result.Formulation.Tasks.Should().ContainSingle(t => t.Name == "package");
result.Formulation.Tasks[0].Parameters.Should().ContainKey("format").WhoseValue.Should().Be("zip");
result.Formulation.Properties.Should().ContainKey("formulation").WhoseValue.Should().Be("v1");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParseAsync_Spdx3_ExtractsDocumentAndPackageMetadata()
{
var content = """
{
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"@type": "SpdxDocument",
"spdxId": "urn:doc",
"name": "sbom-doc",
"creationInfo": {
"specVersion": "3.0.1",
"created": "2026-01-20T00:00:00Z",
"createdBy": ["org:Acme"],
"createdUsing": ["tool:stella"],
"profile": ["https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType/core"]
},
"rootElement": ["spdx:pkg:root"],
"namespaceMap": [
{ "prefix": "ex", "namespace": "https://example.com" }
],
"import": [
{ "externalSpdxId": "urn:ext" }
]
},
{
"@type": "build_Build",
"spdxId": "spdx:build:1",
"buildId": "build-123",
"buildType": "release",
"buildStartTime": "2026-01-20T01:00:00Z",
"buildEndTime": "2026-01-20T01:30:00Z",
"configSourceEntrypoint": "build.yaml",
"configSourceDigest": "sha256:abcd",
"configSourceUri": "https://example.com/build.yaml",
"environment": [
{ "name": "OS", "value": "linux" }
],
"parameters": {
"opt": "O2",
"threads": 8
}
},
{
"@type": "software_Package",
"spdxId": "spdx:pkg:root",
"name": "root",
"software_packageVersion": "1.0.0",
"packageUrl": "pkg:npm/root@1.0.0",
"simplelicensing_licenseExpression": "Apache-2.0 WITH LLVM-exception",
"verifiedUsing": [
{ "algorithm": "SHA256", "hashValue": "abc" }
],
"externalRef": [
{ "externalRefType": "purl", "locator": "pkg:npm/root@1.0.0", "comment": "source" }
],
"externalIdentifier": [
{ "externalIdentifierType": "cpe23", "identifier": "cpe:2.3:a:root" }
]
}
]
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
var result = await _parser.ParseAsync(stream, SbomFormat.SPDX);
result.Format.Should().Be("spdx");
result.SpecVersion.Should().Be("3.0.1");
result.SerialNumber.Should().Be("urn:doc");
result.Metadata.Name.Should().Be("sbom-doc");
result.Metadata.RootComponentRef.Should().Be("spdx:pkg:root");
result.Metadata.NamespaceMap.Should().ContainSingle(map => map.Prefix == "ex");
result.BuildInfo.Should().NotBeNull();
result.BuildInfo!.BuildId.Should().Be("build-123");
result.BuildInfo.BuildType.Should().Be("release");
result.BuildInfo.BuildStartTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:00:00Z"));
result.BuildInfo.BuildEndTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:30:00Z"));
result.BuildInfo.ConfigSourceEntrypoint.Should().Be("build.yaml");
result.BuildInfo.ConfigSourceDigest.Should().Be("sha256:abcd");
result.BuildInfo.ConfigSourceUri.Should().Be("https://example.com/build.yaml");
result.BuildInfo.Environment.Should().ContainKey("OS").WhoseValue.Should().Be("linux");
result.BuildInfo.Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
result.BuildInfo.Parameters.Should().ContainKey("threads").WhoseValue.Should().Be("8");
var root = result.Components.Single(c => c.Purl == "pkg:npm/root@1.0.0");
root.Cpe.Should().Be("cpe:2.3:a:root");
root.Hashes.Should().ContainSingle(h => h.Algorithm == "SHA256" && h.Value == "abc");
root.ExternalReferences.Should().ContainSingle(r =>
r.Type == "purl" && r.Url == "pkg:npm/root@1.0.0");
root.Licenses.Should().ContainSingle();
root.Licenses[0].Expression.Should().BeOfType<WithException>();
var withExpr = (WithException)root.Licenses[0].Expression!;
withExpr.Exception.Should().Be("LLVM-exception");
withExpr.License.Should().BeOfType<SimpleLicense>()
.Which.Id.Should().Be("Apache-2.0");
}
}

View File

@@ -1,5 +1,5 @@
// <copyright file="ConcelierSchemaEvolutionTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
// Task: CCUT-010