audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -0,0 +1,220 @@
# ADR 0042: CGS Merkle Tree Implementation
## Status
ACCEPTED (2025-12-29)
## Context
The CGS (Canonical Graph Signature) system requires deterministic hash computation for verdicts. We need to decide whether to:
1. Reuse existing `StellaOps.Attestor.ProofChain` Merkle tree builder
2. Build a custom Merkle tree implementation in `VerdictBuilderService`
### Requirements
- **Determinism**: Same evidence must always produce identical CGS hash
- **Order Independence**: VEX document ordering should not affect hash (sorted internally)
- **Cross-Platform**: Identical hash on Windows, macOS, Linux (glibc), Linux (musl), BSD
- **Leaf Composition**: Specific ordering of evidence components (SBOM, VEX sorted, reachability, policy lock)
### Existing ProofChain Merkle Builder
Located at: `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/`
**Pros:**
- Already implements Merkle tree construction
- Tested and proven in production
- Handles parent/child attestation chains
**Cons:**
- Designed for attestation chains, not evidence hashing
- Includes attestation-specific metadata in hash
- Doesn't support custom leaf ordering required for CGS
- Would require modifications that might break existing attestation behavior
## Decision
**Build custom Merkle tree implementation in `VerdictBuilderService`.**
### Rationale
1. **Separation of Concerns**: CGS hash computation has different requirements than attestation chain verification
2. **Full Control Over Determinism**: Custom implementation allows:
- Explicit leaf ordering: SBOM → VEX (sorted) → Reachability → PolicyLock
- VEX document sorting by content hash (not insertion order)
- Culture-invariant string comparison (`StringComparer.Ordinal`)
3. **Simplicity**: ~50 lines of code vs modifying 500+ lines in ProofChain
4. **No Breaking Changes**: Doesn't affect existing attestation infrastructure
### Implementation
```csharp
// VerdictBuilderService.cs
private static string ComputeCgsHash(EvidencePack evidence, PolicyLock policyLock)
{
// Build Merkle tree from evidence components (sorted for determinism)
var leaves = new List<string>
{
ComputeHash(evidence.SbomCanonJson),
ComputeHash(evidence.FeedSnapshotDigest)
};
// Add VEX digests in sorted order (ORDER-CRITICAL for determinism!)
foreach (var vex in evidence.VexCanonJson.OrderBy(v => v, StringComparer.Ordinal))
{
leaves.Add(ComputeHash(vex));
}
// Add reachability if present
if (!string.IsNullOrEmpty(evidence.ReachabilityGraphJson))
{
leaves.Add(ComputeHash(evidence.ReachabilityGraphJson));
}
// Add policy lock hash
var policyLockJson = JsonSerializer.Serialize(policyLock, CanonicalJsonOptions);
leaves.Add(ComputeHash(policyLockJson));
// Build Merkle root
var merkleRoot = BuildMerkleRoot(leaves);
return $"cgs:sha256:{merkleRoot}";
}
private static string BuildMerkleRoot(List<string> leaves)
{
if (leaves.Count == 0)
return ComputeHash("");
if (leaves.Count == 1)
return leaves[0];
var level = leaves.ToList();
while (level.Count > 1)
{
var nextLevel = new List<string>();
for (int i = 0; i < level.Count; i += 2)
{
if (i + 1 < level.Count)
{
// Combine two hashes
var combined = level[i] + level[i + 1];
nextLevel.Add(ComputeHash(combined));
}
else
{
// Odd number of nodes, promote last one
nextLevel.Add(level[i]);
}
}
level = nextLevel;
}
return level[0];
}
private static string ComputeHash(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hashBytes = SHA256.HashData(bytes);
return Convert.ToHexString(hashBytes).ToLowerInvariant();
}
```
## Consequences
### Positive
- ✅ Full control over CGS hash computation logic
- ✅ No risk of breaking existing attestation chains
- ✅ Simple, testable implementation (~50 lines)
- ✅ Explicit ordering guarantees determinism
- ✅ Cross-platform verified (Windows, macOS, Linux, Alpine, Debian)
### Negative
- ⚠️ Code duplication with ProofChain (minimal - different use case)
- ⚠️ Need to maintain separate Merkle tree implementation (low maintenance burden)
### Neutral
- 📝 Custom implementation documented in tests (CgsDeterminismTests.cs)
- 📝 Future: Could extract shared Merkle tree primitives if needed
## Alternatives Considered
### Alternative 1: Modify ProofChain Builder
**Rejected because:**
- Would require adding configuration options to ProofChain
- Risk of breaking existing attestation behavior
- Increased complexity for both use cases
- Tight coupling between verdict and attestation systems
### Alternative 2: Use Third-Party Merkle Tree Library
**Rejected because:**
- External dependency for ~50 lines of code
- Less control over ordering and hash format
- Potential platform-specific issues
- Security review overhead
### Alternative 3: Single-Level Hash (No Merkle Tree)
**Rejected because:**
- Loses incremental verification capability
- Can't prove individual evidence components without full evidence pack
- Less efficient for large evidence packs (can't skip unchanged components)
## Verification
### Test Coverage
File: `src/__Tests/Determinism/CgsDeterminismTests.cs`
1. **Golden File Test**: Known evidence produces expected hash
2. **10-Iteration Stability**: Same input produces identical hash 10 times
3. **VEX Order Independence**: VEX document ordering doesn't affect hash
4. **Reachability Inclusion**: Reachability graph changes hash predictably
5. **Policy Lock Versioning**: Different policy versions produce different hashes
### Cross-Platform Verification
CI/CD Workflow: `.gitea/workflows/cross-platform-determinism.yml`
- ✅ Windows (glibc)
- ✅ macOS (BSD libc)
- ✅ Linux Ubuntu (glibc)
- ✅ Linux Alpine (musl libc)
- ✅ Linux Debian (glibc)
All platforms produce identical CGS hash for same input.
## Migration
No migration required - this is a new feature.
## References
- **Sprint**: `docs/implplan/archived/SPRINT_20251229_001_001_BE_cgs_infrastructure.md`
- **Implementation**: `src/__Libraries/StellaOps.Verdict/VerdictBuilderService.cs`
- **Tests**: `src/__Tests/Determinism/CgsDeterminismTests.cs`
- **ProofChain**: `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/`
## Decision Date
2025-12-29
## Decision Makers
- Backend Team
- Security Team
- Attestation Team (consulted)
## Review Date
2026-06-29 (6 months) - Evaluate if code duplication warrants shared library

View File

@@ -0,0 +1,441 @@
# ADR 0043: Fulcio Keyless Signing with Optional Parameter
## Status
ACCEPTED (2025-12-29)
## Context
StellaOps must support both **cloud-connected** and **air-gapped** deployments. Verdict signing requirements differ:
### Cloud-Connected Deployments
- Access to Fulcio (Sigstore) for keyless signing
- OIDC token available from identity provider
- Ephemeral keys generated per signature
- Transparency log (Rekor) accessible
### Air-Gapped Deployments
- No internet access (Fulcio/Rekor unreachable)
- No OIDC token available
- Must operate without external dependencies
- Long-lived keys managed internally (if signing required)
### Requirements
1. **Single Codebase**: Same `VerdictBuilderService` for both modes
2. **Runtime Configuration**: Deployment mode determined at startup
3. **No Breaking Changes**: Existing air-gap deployments must continue working
4. **Clear Separation**: Signing concerns separate from verdict building logic
## Decision
**Add optional `IDsseSigner? signer` parameter to `VerdictBuilderService` constructor.**
### Rationale
1. **Dependency Injection Friendly**: Signer is injected (or not) based on deployment config
2. **Explicit Air-Gap Mode**: `null` signer clearly indicates air-gapped operation
3. **Single Implementation**: No need for separate `VerdictBuilderService` classes
4. **Production Signing Pipeline**: Even with signer available, production verdicts go through `StellaOps.Signer` service for Proof-of-Entitlement (PoE) validation
### Implementation
```csharp
// VerdictBuilderService.cs
public sealed class VerdictBuilderService : IVerdictBuilder
{
private readonly ILogger<VerdictBuilderService> _logger;
private readonly IDsseSigner? _signer; // Null for air-gap mode
/// <summary>
/// Creates a VerdictBuilderService.
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="signer">Optional DSSE signer (e.g., KeylessDsseSigner for Fulcio).
/// Null for air-gapped deployments.</param>
public VerdictBuilderService(
ILogger<VerdictBuilderService> logger,
IDsseSigner? signer = null)
{
_logger = logger;
_signer = signer;
if (_signer == null)
{
_logger.LogInformation("VerdictBuilder initialized without signer (air-gapped mode)");
}
else
{
_logger.LogInformation("VerdictBuilder initialized with signer: {SignerType}",
_signer.GetType().Name);
}
}
private async ValueTask<DsseEnvelope> CreateDsseEnvelopeAsync(
VerdictPayload verdict,
string cgsHash,
CancellationToken ct)
{
var payloadJson = JsonSerializer.Serialize(verdict, CanonicalJsonOptions);
var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
if (_signer != null)
{
_logger.LogDebug("Creating signed DSSE envelope with signer");
// Note: Full signing integration requires SigningRequest with ProofOfEntitlement.
// This is typically handled at the API layer (VerdictEndpoints) where caller
// context and entitlement are available.
//
// For production use, verdicts should be signed via the Signer service pipeline
// which handles proof-of-entitlement, caller authentication, and quota enforcement.
}
// Create unsigned envelope (suitable for air-gapped deployments)
// In production, verdicts are signed separately via Signer service after PoE validation
return new DsseEnvelope(
PayloadType: "application/vnd.stellaops.verdict+json",
Payload: payloadBase64,
Signatures: new[]
{
new DsseSignature(
Keyid: $"cgs:{cgsHash}",
Sig: "unsigned:use-signer-service-for-production-signatures"
)
}
);
}
}
```
### Configuration Examples
#### Cloud-Connected Deployment (Fulcio)
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.Keyless;
options.FulcioUrl = "https://fulcio.sigstore.dev";
options.OidcIssuer = "https://oauth2.sigstore.dev/auth";
});
// Internal implementation:
services.AddSingleton<IDsseSigner, KeylessDsseSigner>();
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
#### Air-Gapped Deployment
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.AirGap;
// No signer configured
});
// Internal implementation:
// IDsseSigner not registered
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
#### Long-Lived Key Deployment (Future)
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.LongLivedKey;
options.KeyPath = "/etc/stellaops/signing-key.pem";
});
// Internal implementation:
services.AddSingleton<IDsseSigner, LongLivedKeySigner>();
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
## Consequences
### Positive
-**Single Codebase**: Same service for all deployment modes
-**Clear Intent**: `null` signer explicitly communicates air-gap mode
-**DI Friendly**: Standard dependency injection pattern
-**No Breaking Changes**: Existing air-gap deployments work without modification
-**Future Extensible**: Easy to add new signer implementations
### Negative
- ⚠️ **Runtime Validation**: Can't enforce signer requirement at compile time (must check at runtime)
- ⚠️ **Separation of Concerns**: Verdict building logic includes signing creation (even if unsigned)
- ⚠️ **Documentation Burden**: Developers must understand when to use signer vs null
### Neutral
- 📝 **Production Pipeline**: Even with signer, production signatures go through Signer service for PoE
- 📝 **Testing**: Tests create `VerdictBuilderService(logger, signer: null)` for simplicity
## Alternatives Considered
### Alternative 1: Separate Classes
```csharp
public class VerdictBuilderService : IVerdictBuilder
{
// Air-gap implementation
}
public class SignedVerdictBuilderService : IVerdictBuilder
{
private readonly IDsseSigner _signer;
// Keyless/signed implementation
}
```
**Rejected because:**
- Code duplication for verdict building logic
- Configuration complexity (which class to register?)
- Testing overhead (must test both classes)
- Tight coupling between signing and verdict building
### Alternative 2: Strategy Pattern
```csharp
public interface IVerdictSigningStrategy
{
Task<DsseEnvelope> SignAsync(VerdictPayload payload);
}
public class AirGapSigningStrategy : IVerdictSigningStrategy { }
public class KeylessSigningStrategy : IVerdictSigningStrategy { }
public class VerdictBuilderService
{
private readonly IVerdictSigningStrategy _signingStrategy;
}
```
**Rejected because:**
- Over-engineering for simple null check
- Additional abstraction layer for minimal benefit
- More difficult to understand for developers
### Alternative 3: Configuration Flag
```csharp
public class VerdictBuilderService
{
private readonly VerdictBuilderOptions _options;
public VerdictBuilderService(IOptions<VerdictBuilderOptions> options)
{
_options = options.Value;
if (_options.EnableSigning)
{
// Initialize signer
}
}
}
```
**Rejected because:**
- Hides signer dependency (not visible in constructor)
- Requires options even when not needed
- Less DI-friendly (can't inject mock signer for testing)
### Alternative 4: Builder Pattern
```csharp
var verdictBuilder = VerdictBuilderService
.Create()
.WithLogger(logger)
.WithKeylessSigning(fulcioClient)
.Build();
```
**Rejected because:**
- Not compatible with DI containers
- Verbose API for simple configuration
- Testing complexity (must build every time)
## Implementation Notes
### Signer Interface
```csharp
// StellaOps.Signer.Core.IDsseSigner
public interface IDsseSigner
{
/// <summary>
/// Signs a payload and returns DSSE envelope.
/// </summary>
Task<DsseEnvelope> SignAsync(
byte[] payload,
string payloadType,
SigningOptions options,
CancellationToken ct = default);
}
```
### Keyless Signer Implementation
Location: `src/Signer/__Libraries/StellaOps.Signer.Keyless/KeylessDsseSigner.cs`
**Dependencies:**
- `IFulcioClient` - Ephemeral certificate issuance
- `IAmbientOidcTokenProvider` - OIDC token acquisition
- `IRekorClient` - Transparency log submission (optional)
**Workflow:**
1. Generate ephemeral key pair (ECDSA P-256)
2. Acquire OIDC token from ambient provider
3. Request certificate from Fulcio (binds identity to public key)
4. Sign payload with ephemeral private key
5. Create DSSE envelope with signature + certificate
6. Submit to Rekor transparency log (if configured)
7. Discard ephemeral private key
### Air-Gap Mode
When `signer == null`:
1. Create DSSE envelope structure
2. Set `keyid` to `cgs:{hash}` (content-addressed identifier)
3. Set `sig` to `"unsigned:use-signer-service-for-production-signatures"`
4. Log warning if envelope is used in production context
## Security Considerations
### Proof-of-Entitlement (PoE)
**Critical**: Even when `IDsseSigner` is available, production verdict signing must go through the **Signer service pipeline** for:
1. **Caller Authentication**: Verify caller has permission to sign verdicts
2. **Proof-of-Entitlement**: Validate caller owns/operates the artifacts being assessed
3. **Quota Enforcement**: Rate-limit signing operations per tenant
4. **Audit Logging**: Record who signed what, when, and why
**VerdictBuilderService Role**: Creates unsigned envelopes (or test signatures)
**Signer Service Role**: Applies production signatures with PoE validation
### Separation of Concerns
```
┌─────────────────────────────────────────────────────────────┐
│ VerdictBuilderService │
│ - Computes CGS hash │
│ - Builds verdict payload │
│ - Creates unsigned DSSE envelope │
│ - Returns VerdictResult │
└──────────────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Signer Service (Production Pipeline) │
│ - Validates Proof-of-Entitlement (PoE) │
│ - Authenticates caller │
│ - Enforces quotas │
│ - Signs verdict with Fulcio/Long-lived key │
│ - Submits to Rekor transparency log │
│ - Returns signed DSSE envelope │
└─────────────────────────────────────────────────────────────┘
```
## Testing Strategy
### Unit Tests
```csharp
// Test with null signer (air-gap mode)
[Fact]
public async Task BuildAsync_WithoutSigner_CreatesUnsignedEnvelope()
{
var service = new VerdictBuilderService(logger, signer: null);
var result = await service.BuildAsync(evidence, policyLock, ct);
result.Dsse.Signatures[0].Sig.Should().StartWith("unsigned:");
}
// Test with mock signer (cloud mode)
[Fact]
public async Task BuildAsync_WithSigner_UsesSignerForProduction()
{
var mockSigner = new Mock<IDsseSigner>();
var service = new VerdictBuilderService(logger, mockSigner.Object);
// Note: Current implementation creates unsigned envelope
// Production signing happens via Signer service pipeline
}
```
### Integration Tests
```csharp
[Fact]
public async Task EndToEnd_VerdictSigning_WithFulcio()
{
// Arrange: Full production pipeline
var fulcioClient = new FulcioClient(fulcioUrl);
var oidcProvider = new AmbientOidcTokenProvider();
var signer = new KeylessDsseSigner(fulcioClient, oidcProvider);
var signerService = new SignerService(signer, poeValidator, quotaEnforcer);
var verdictBuilder = new VerdictBuilderService(logger, signer);
// Act: Build verdict → Sign with PoE → Verify
var verdict = await verdictBuilder.BuildAsync(evidence, policyLock, ct);
var signedEnvelope = await signerService.SignVerdictAsync(verdict, proofOfEntitlement, ct);
// Assert
signedEnvelope.Signatures.Should().HaveCount(1);
signedEnvelope.Signatures[0].Sig.Should().NotStartWith("unsigned:");
}
```
## Migration
No migration required - this is a new feature.
**Backward Compatibility**: Existing code that creates `VerdictBuilderService` without signer parameter will use default `signer: null` (air-gap mode).
## Monitoring
### Metrics
- `verdict_builder.signing_mode{mode="airgap"}` - Count of air-gap verdicts
- `verdict_builder.signing_mode{mode="keyless"}` - Count of keyless verdicts
- `verdict_builder.unsigned_envelopes_created` - Count of unsigned envelopes
### Alerts
- **Warning**: High volume of unsigned verdicts in production (should go through Signer service)
- **Error**: Signer initialization failed in cloud deployment
- **Critical**: OIDC token acquisition failed (blocks keyless signing)
## References
- **Sprint**: `docs/implplan/archived/SPRINT_20251229_001_001_BE_cgs_infrastructure.md`
- **Implementation**: `src/__Libraries/StellaOps.Verdict/VerdictBuilderService.cs`
- **Signer Interface**: `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/IDsseSigner.cs`
- **Keyless Implementation**: `src/Signer/__Libraries/StellaOps.Signer.Keyless/KeylessDsseSigner.cs`
- **ADR 0042**: CGS Merkle Tree Implementation
## Decision Date
2025-12-29
## Decision Makers
- Backend Team
- Security Team
- DevOps Team (air-gap deployment experts)
## Review Date
2026-06-29 (6 months) - Evaluate if PoE integration needs tighter coupling

View File

@@ -0,0 +1,205 @@
# ADR 0044: Binary Delta Signatures for Backport Detection
## Status
ACCEPTED (2026-01-03)
## Context
Vulnerability scanners today rely on version string comparison to determine if a package is vulnerable. However, Linux distributions (RHEL, Debian, Ubuntu, SUSE, Alpine) routinely **backport** security fixes into older versions without bumping the upstream version number.
### The Problem
**Example:** OpenSSL 1.0.1e on RHEL 6 has Heartbleed (CVE-2014-0160) patched, but upstream says `1.0.1e < 1.0.1g` (the fix version), so scanners flag it as vulnerable. This creates:
1. **False positives** - Patched systems flagged as vulnerable
2. **Alert fatigue** - Security teams waste time investigating non-issues
3. **Compliance failures** - Audit reports show phantom vulnerabilities
4. **Trust erosion** - Users distrust scanner results
### Current Mitigations
1. **Distro-specific advisory feeds** (DSA, RHSA, USN) - Incomplete coverage
2. **VEX statements from vendors** - Requires vendor participation, often delayed
3. **Manual triage** - Doesn't scale
4. **OVAL feeds** - OS packages only, not application binaries
### Requirements
- **Binary-level detection**: Examine compiled code, not version strings
- **Cryptographic proof**: Hash-based evidence that fix is present
- **Offline operation**: Work in air-gapped environments
- **Multi-architecture**: Support x86-64, ARM64, and other ISAs
- **Deterministic**: Same binary → same signature across platforms
- **LTO resilience**: Handle Link-Time Optimization changes
## Decision
**Implement binary delta signature matching using normalized code comparison.**
### Architecture
```
┌────────────────────────────────────────────────────────────────────────────┐
│ Delta Signature Pipeline │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ Binary Disassembly Normalization Signature │
│ ───────────► ───────────────► ──────────────► ─────────────► │
│ ELF/PE/MachO Iced (x86) or Zero addresses SHA-256 + │
│ B2R2 (ARM/MIPS) Canonicalize NOPs CFG hash + │
│ Normalize PLT/GOT Chunk hashes │
│ │
└────────────────────────────────────────────────────────────────────────────┘
```
### Disassembly Engine Selection
**Chosen: Plugin-based architecture with Iced (primary) + B2R2 (fallback)**
| Engine | Strengths | Weaknesses |
|--------|-----------|------------|
| **Iced** | Fastest x86/x86-64, MIT license, pure C# | x86 only |
| **B2R2** | Multi-arch (ARM, MIPS, RISC-V), IR lifting, MIT license | F# (requires wrapper) |
**Rationale:**
- Iced for performance-critical x86/x86-64 path (90%+ of scanned binaries)
- B2R2 for ARM64, MIPS, RISC-V when needed
- Plugin architecture allows adding engines without core changes
### Normalization Strategy
To compare binaries compiled by different toolchains/versions, we normalize:
1. **Zero absolute addresses** - Remove PC-relative and RIP-relative variance
2. **Canonicalize NOPs** - Collapse multi-byte NOPs (0x90, 0x0F1F, etc.) to single NOP
3. **Normalize PLT/GOT** - Replace dynamic linking stubs with symbolic tokens
4. **Zero relocations** - Remove relocation target variance
5. **Normalize jump tables** - Convert absolute offsets to relative
**Recipe versioning**: Every signature includes the normalization recipe ID and version. Changing normalization behavior requires a version bump.
### Signature Components
```json
{
"schema": "stellaops.deltasig.v1",
"cve": "CVE-2014-0160",
"package": { "name": "openssl", "soname": "libssl.so.1.0.0" },
"target": { "arch": "x86_64", "abi": "gnu" },
"normalization": { "recipeId": "stellaops.normalize.x64.v1", "version": "1.0.0" },
"signatureState": "patched",
"symbols": [
{
"name": "tls1_process_heartbeat",
"hashAlg": "sha256",
"hashHex": "abc123...",
"sizeBytes": 1234,
"cfgBbCount": 15,
"cfgEdgeHash": "def456...",
"chunks": [
{ "offset": 0, "size": 2048, "hashHex": "..." },
{ "offset": 2048, "size": 2048, "hashHex": "..." }
]
}
]
}
```
### Matching Strategy
1. **Exact match** - Full normalized hash matches patched or vulnerable signature
2. **Chunk match** - ≥70% of chunks match (handles LTO modifications)
3. **CFG match** - Control flow graph structure matches (catches recompilations)
### VEX Evidence Emission
When a binary is confirmed patched via delta signature:
```json
{
"result": "patched",
"cveIds": ["CVE-2014-0160"],
"confidence": 0.95,
"symbolMatches": [
{ "symbolName": "tls1_process_heartbeat", "state": "patched", "exactMatch": true }
],
"justification": "vulnerable_code_not_present",
"summary": "Binary confirmed PATCHED with 95% confidence. 1 symbol(s) matched patched signatures exactly."
}
```
This evidence feeds into VEX candidate generation with full audit trail.
## Alternatives Considered
### 1. Source Code Comparison
**Rejected**: Requires source access, doesn't work for closed-source binaries, compile options affect behavior.
### 2. Debug Symbol Matching
**Rejected**: Symbols often stripped in production, doesn't prove code content.
### 3. File Hash Matching
**Rejected**: Entire binary must match exactly; any rebuild invalidates signature.
### 4. YARA Rules
**Rejected**: Pattern-based, high false positive rate, doesn't provide cryptographic proof.
### 5. Single Disassembly Engine (B2R2 only)
**Rejected**: Performance critical; Iced is 3-5x faster for x86/x86-64 which is 90%+ of scanned binaries.
## Consequences
### Positive
1. **Eliminate false positives** for backported security fixes
2. **Cryptographic proof** of patch status (auditable, reproducible)
3. **Offline operation** with signature packs
4. **Multi-architecture** support for modern infrastructure
5. **VEX integration** for automated triage
### Negative
1. **Signature authoring required** - Must create signatures for each CVE/package
2. **Normalization limits** - Extreme compiler optimizations may defeat matching
3. **Storage overhead** - Signature database growth
4. **Compute cost** - Disassembly + normalization per binary
### Mitigations
- **Signature federation** - Share signatures across organizations
- **Chunk matching** - Resilient to LTO and PGO changes
- **Priority authoring** - Focus on high-severity CVEs first
- **Incremental scanning** - Cache analysis results
## Implementation
### Sprint: SPRINT_20260102_001_BE
| Component | Status | Notes |
|-----------|--------|-------|
| Disassembly.Abstractions | DONE | Plugin interface, models |
| Disassembly.Iced | DONE | x86/x86-64 support |
| Disassembly.B2R2 | DONE | Multi-arch support |
| Normalization | DONE | X64 + ARM64 pipelines |
| DeltaSig | DONE | Generator + matcher |
| Persistence | DONE | PostgreSQL schema |
| CLI | DONE | extract, author, sign, verify, match, pack, inspect |
| Scanner integration | DONE | DeltaSigAnalyzer, IBinaryVulnerabilityService |
| VEX emission | DONE | DeltaSignatureEvidence, DeltaSigVexEmitter |
### Test Coverage
- 74 unit tests for DeltaSig library
- 45 unit tests for Normalization
- 24 unit tests for Disassembly
- 11 property tests (FsCheck) for normalization idempotency
- 14 golden tests for known CVEs (Heartbleed, Log4Shell, POODLE)
- 25 unit tests for VEX evidence emission
## References
- [Binary Diff Signatures Advisory](../product-advisories/30-Dec-2025%20-%20Binary%20Diff%20Signatures%20for%20Patch%20Detection.md)
- [B2R2 GitHub](https://github.com/B2R2-org/B2R2)
- [Iced GitHub](https://github.com/icedland/iced)
- [OpenVEX Specification](https://github.com/openvex/spec)
- [CVE-2014-0160 (Heartbleed)](https://nvd.nist.gov/vuln/detail/CVE-2014-0160)

View File

@@ -0,0 +1,165 @@
# RFC: StellaOps.Authority.Plugin.Ldap
**Status:** Accepted Auth Guild, Security Guild, DevEx Docs sign-off (2025-11-03)
**Authors:** Plugin Team 4 (Auth Libraries & Identity Providers)
**Related initiatives:** PLG7 backlog, CORE5 event handlers, DOC4 developer guide
> Review log captured in `docs/notes/2025-11-03-authority-plugin-ldap-review.md`. Decisions below reflect consensus from Auth Guild, Security Guild, and DevEx Docs walkthroughs held on 2025-11-03.
## 0. Review Summary (2025-11-03)
- Confirmed plugin charter: deliver offline-friendly LDAP credential verification with deterministic caching and audit parity with the Standard plugin.
- Resolved open questions:
- **Client provisioning audit trail:** plugin always mirrors credential lifecycle metadata (actor, timestamp, hashed secret reference) into Authoritys Mongo store even when LDAP writes succeed; directory writes remain optional and secrets never persist locally.
- **Mutual TLS support:** LDAPS client certificates are required for regulated installations; configuration gains optional client certificate bindings with secret-provider integration and deterministic trust-store selection.
- **Group-to-role mapping:** mappings accept static DN dictionaries and deterministic `regex` matchers; regex captures project to canonical roles via substitution (documented contract for policy automation).
- Follow-up implementation issues filed in `StellaOps.Authority.Plugin.Standard/TASKS.md` (see Section 11) to track scaffolding, mutual TLS enablement, audit mirror, and mapping enhancements.
## 1. Problem Statement
Many on-prem StellaOps deployments rely on existing LDAP/Active Directory domains for workforce identity. The current Standard Mongo-backed plugin requires duplicating users and secrets, which increases operational overhead and violates corporate policy in some regulated environments. We need a sovereign, offline-friendly LDAP plugin that:
- Supports password grant and bootstrap provisioning flows without storing credentials in Mongo.
- Enforces StellaOps security policies (lockout, password policy hints, audit logging) while delegating credential validation to LDAP.
- Operates deterministically in offline or partially connected environments by caching directory metadata when necessary.
## 2. Goals
- Provide a first-party `StellaOps.Authority.Plugin.Ldap` plugin advertising `password` and optional `clientProvisioning` capabilities at launch.
- Support username/password authentication against LDAP bind operations with configurable DN templates.
- Allow optional bootstrap seeding of service accounts by writing into LDAP (guarded behind explicit configuration) or by mapping to pre-existing entries.
- Surface directory-derived claims (groups, attributes) for downstream authorization via `IClaimsEnricher`.
- Integrate with Authority lockout telemetry and structured logging without persisting secrets locally.
## 3. Non-Goals
- Implement multi-factor authentication out of the box (future enhancement once TOTP/WebAuthn strategy is finalised).
- Provide write-heavy directory management (e.g., user creation workflows) beyond optional bootstrap service account seeding.
- Replace the Standard plugin; both must remain supported and selectable per environment.
## 4. Key Constraints & Assumptions
- Offline-first posture: deployments may operate without outbound internet and with intermittent directory connectivity (e.g., read-only replicas). The plugin must tolerate transient LDAP connectivity failures and degrade gracefully.
- Deterministic behaviour: identical configuration and directory state must yield identical token issuance results. Cached metadata (e.g., group lookups) must have defined expiration.
- Security: No plaintext credential storage; TLS must be enforced for LDAP connections unless explicitly overridden for air-gapped lab environments.
## 5. High-Level Architecture
1. **Configuration binding** (`ldap.yaml`): defines server endpoints, bind strategy, claim mapping, and optional bootstrap overrides.
2. **Connection factory**: pooled LDAP connections using a resilient client (preferred dependency: `Novell.Directory.Ldap.NETStandard`).
3. **Credential validator** (`IUserCredentialStore`): performs bind-as-user flow with optional fallback bind using service account when directories disallow anonymous search.
4. **Claims enricher** (`IClaimsEnricher`): queries group membership/attributes and projects them into canonical roles/claims.
5. **Optional client provisioning / bootstrap** (`IClientProvisioningStore` + `IUserCredentialStore.UpsertUserAsync`): maintains machine/service principals either in Mongo (metadata) or via LDAP entries based on configuration. Capabilities are only advertised when the manifest requests them, configuration enables them, **and** the plug-in proves the bind identity can add/delete entries beneath the configured containers; otherwise the feature is automatically downgraded so read-only deployments remain safe.
6. **Health checks**: periodic LDAP `whoami` or `search` probes surfaced through `AuthorityPluginHealthResult`.
```
Authority Host
├── Plugin Manifest (ldap)
├── Registrar → registers ConnectionFactory, LdapCredentialStore, LdapClaimsEnricher
├── Password Grant Handler → CredentialStore.VerifyPasswordAsync → LDAP Bind
└── Claims Pipeline → ClaimsEnricher.EnrichAsync → LDAP group lookup
```
## 6. Configuration Schema (Draft)
```yaml
connection:
host: "ldaps://ldap.example.internal"
port: 636
useStartTls: false
validateCertificates: true
clientCertificate:
pfxPath: "file:/etc/stellaops/certs/ldap-client.pfx"
passwordSecret: "file:/etc/stellaops/secrets/ldap-client-pfx.txt"
sendChain: true
trustStore:
mode: "system" # system | bundle
bundlePath: "file:/etc/stellaops/trust/ldap-root.pem"
bindDn: "cn=stellaops-bind,ou=service,dc=example,dc=internal"
bindPasswordSecret: "file:/etc/stellaops/secrets/ldap-bind.txt"
searchBase: "dc=example,dc=internal"
usernameAttribute: "uid"
userDnFormat: "uid={username},ou=people,dc=example,dc=internal" # optional template
security:
requireTls: true
allowInsecureWithEnvToggle: false # requires STELLAOPS_LDAP_ALLOW_INSECURE=true
allowedCipherSuites: [] # optional allow-list
referralChasing: false
lockout:
useAuthorityPolicies: true # reuse Authority lockout counters
directoryLockoutAttribute: "pwdAccountLockedTime"
claims:
groupAttribute: "memberOf"
groupToRoleMap:
"cn=stellaops-admins,ou=groups,dc=example,dc=internal": "operators"
"cn=stellaops-read,ou=groups,dc=example,dc=internal": "auditors"
regexMappings:
- pattern: "^cn=stellaops-(?P<role>[a-z-]+),ou=groups,dc=example,dc=internal$"
roleFormat: "{role}" # yields operators/investigate/etc.
extraAttributes:
displayName: "displayName"
email: "mail"
clientProvisioning:
enabled: false
containerDn: "ou=service,dc=example,dc=internal"
secretAttribute: "userPassword"
auditMirror:
enabled: true
collectionName: "ldap_client_provisioning"
health:
probeIntervalSeconds: 60
timeoutSeconds: 5
```
## 7. Capability Mapping
| Capability | Implementation Notes |
|------------|---------------------|
| `password` | Bind-as-user validation with Authority lockout integration. Mandatory. Requires TLS and optionally client certificate binding per regulated install posture. |
| `clientProvisioning` | Optional; when enabled, creates/updates LDAP entries for machine clients **and** mirrors lifecycle metadata into Mongo for audit parity. |
| `bootstrap` | Exposed only when bootstrap manifest provides service account credentials AND directory write permissions are confirmed during startup; always records audit mirror entries. |
| `mfa` | Not supported in MVP. Future iteration may integrate TOTP attributes or external MFA providers. |
## 8. Operational Considerations
- **Offline cache:** provide optional Mongo cache for group membership to keep `/ready` responsive if LDAP is temporarily unreachable. Cache entries must include TTL and invalidation hooks.
- **Secrets management:** accept `file:` and environment variable references; integrate with existing `StellaOps.Configuration` secret providers.
- **Mutual TLS & trust anchors:** support client certificate authentication with deterministic trust-store selection (`system` vs bundled file) to satisfy regulated deployments; surface validation outcomes via health endpoints.
- **Audit mirror:** write deterministic Mongo records capturing provisioning operations (actor, LDAP DN, operation type, hashed secret reference) to align with Authority audit policy even when LDAP is authoritative.
- **Observability:** emit structured logs with event IDs (`LDAP_BIND_START`, `LDAP_BIND_FAILURE`, `LDAP_GROUP_LOOKUP`), counters for success/failure, and latency histograms.
- **Throttling:** reuse Authority rate-limiting middleware; add per-connection throttles to avoid saturating directory servers during brute-force attacks.
## 9. Security & Compliance
- Enforce TLS (`ldaps://` or STARTTLS) by default. Provide explicit `allowInsecure` flag gated behind environment variable for lab/testing only.
- Support optional mutual TLS (client cert authentication) with secret-backed PFX loader and deterministic trust bundle selection.
- Support password hash migration by detecting directory lockout attributes and surfacing `RequiresPasswordReset` when policies demand changes.
- Log distinguished names only at `Debug` level to avoid leaking sensitive structure in default logs.
- Coordinate with Security Guild for penetration testing before GA; incorporate audit log entries for bind attempts and provisioning changes.
## 10. Testing Strategy
- **Unit tests:** mock LDAP connections to validate DN formatting, error mapping, and capability negotiation.
- **Integration tests:** run against an ephemeral OpenLDAP container (seeded via LDIF fixtures) within CI. Include mutual TLS handshake verification (valid/expired certs) and offline cache regression (disconnect LDAP mid-test).
- **Determinism tests:** feed identical LDIF snapshots and configuration to ensure output tokens/claims remain stable across runs.
- **Smoke tests:** `dotnet test` harness plus manual `dotnet run` scenario verifying `/token` password grants, `/internal/users` bootstrap with LDAP-backed store, and Mongo audit mirror entries.
## 11. Implementation Plan
1. Scaffold `StellaOps.Authority.Plugin.Ldap` project + companion test project (net10.0, `<IsAuthorityPlugin>` true).
2. Implement configuration binding/validation, including secret-backed client certificate + trust-store options and `allowInsecureWithEnvToggle`.
3. Build connection factory + credential store with bind logic, TLS enforcement, and deterministic retry policies.
4. Implement claims enricher with regex mapping support and optional Mongo-backed cache layer.
5. Add client provisioning store with LDAP write toggles and Mongo audit mirror (`ldap_client_provisioning` collection).
6. Wire health checks, telemetry, and structured audit events (bind attempts, provisioning, cache fallbacks).
7. Deliver bootstrap validation that inspects directory permissions and logs deterministic capability summary.
8. Extend developer guide and samples with LDAP configuration guidance; include mutual TLS and regex mapping examples.
9. Update docs/TASKS trackers and release notes entry; ensure CI coverage (unit, integration with OpenLDAP, determinism, smoke tests).
## 12. Resolved Questions
- **Audit mirror:** Client provisioning always persists lifecycle metadata in Mongo for audit parity; LDAP remains the credential source of truth.
- **Mutual TLS:** Plugin must support optional client certificate authentication with secret-backed key material and deterministic trust-store selection.
- **Group mapping:** Provide deterministic regex mapping support to translate directory DNs into Authority roles/scopes without custom scripts.
## 13. Timeline (Tentative)
- **Week 1:** RFC review & sign-off.
- **Week 2-3:** Implementation & unit tests.
- **Week 4:** Integration tests + documentation updates.
- **Week 5:** Security review, release candidate packaging.
## 14. Approval
- **Auth Guild Lead:** ✅ Approved 2025-11-03 (see review log).
- **Security Guild Representative:** ✅ Approved 2025-11-03 (see review log).
- **DevEx Docs:** ✅ Approved 2025-11-03 (see review log).
---
Please add comments inline or via PR review. Once approved, track execution under PLG7.

View File

@@ -53,9 +53,9 @@ Each module directory bundles an ownership charter (`AGENTS.md`), current work (
| Advisory AI | [architecture.md](../../modules/advisory-ai/architecture.md) | [implementation_plan.md](../../modules/advisory-ai/implementation_plan.md) | - |
| Attestor | [architecture.md](../../modules/attestor/architecture.md) | [implementation_plan.md](../../modules/attestor/implementation_plan.md) | - |
| CLI | [architecture.md](../../modules/cli/architecture.md) | [implementation_plan.md](../../modules/cli/implementation_plan.md) | [operations/release-and-packaging.md](../../modules/cli/operations/release-and-packaging.md) |
| CI recipes | [architecture.md](../../modules/ci/architecture.md) | [implementation_plan.md](../../modules/ci/implementation_plan.md) | [recipes.md](../../modules/ci/recipes.md) |
| CI recipes | [architecture.md](../cicd/ci-architecture.md) | - | [recipes.md](../cicd/ci-recipes.md) |
| Concelier | [architecture.md](../../modules/concelier/architecture.md) | [implementation_plan.md](../../modules/concelier/implementation_plan.md) | [operations/](../../modules/concelier/operations/) |
| DevOps / release | [architecture.md](../../modules/devops/architecture.md) | [implementation_plan.md](../../modules/devops/implementation_plan.md) | [runbooks/](../../modules/devops/runbooks/) |
| DevOps / release | [architecture.md](../../operations/devops/architecture.md) | - | [runbooks/](../../operations/devops/runbooks/) |
| Excititor | [architecture.md](../../modules/excititor/architecture.md) | [implementation_plan.md](../../modules/excititor/implementation_plan.md) | [mirrors.md](../../modules/excititor/mirrors.md) |
| Export Center | [architecture.md](../../modules/export-center/architecture.md) | [implementation_plan.md](../../modules/export-center/implementation_plan.md) | [operations/runbook.md](../../modules/export-center/operations/runbook.md) |
| Graph | [architecture.md](../../modules/graph/architecture.md) | [implementation_plan.md](../../modules/graph/implementation_plan.md) | - |

View File

@@ -38,5 +38,5 @@ Artifact store for SBOMs, evidence bundles, and replayable outputs. The exact bu
Alternative messaging transport for environments that require persistent streams or specific operational characteristics. NATS must be explicitly configured and must not be required for core workflows.
## Deployment references
- Compose profiles: `deploy/compose/README.md`
- Compose profiles: `devops/compose/README.md`
- Deployment bundles overview: `deploy/README.md`

View File

@@ -156,4 +156,4 @@ SUPPORTING
## Notes
- Module dossiers live under `docs/modules/<module>/architecture.md`.
- Deployment defaults (ports, profile overlays, pinned digests) live under `deploy/` (`deploy/compose/`, `deploy/helm/`, `deploy/releases/`).
- Deployment defaults (ports, profile overlays, pinned digests) live under `deploy/` (`devops/compose/`, `devops/helm/`, `deploy/releases/`).

View File

@@ -0,0 +1,329 @@
# CI/CD Infrastructure Overview
> **Sprint:** CI/CD Enhancement - Documentation
> **Last Updated:** 2025-12-28
> **Workflow Count:** 100 workflows
## Quick Links
- [Workflow Triggers & Dependencies](./workflow-triggers.md)
- [Release Pipelines](./release-pipelines.md)
- [Security Scanning](./security-scanning.md)
- [Test Strategy](./test-strategy.md)
- [Troubleshooting Guide](../.gitea/docs/troubleshooting.md)
---
## Architecture Overview
The StellaOps CI/CD infrastructure uses **Gitea Actions** (GitHub Actions compatible) with a sophisticated multi-tier triggering strategy designed for:
- **Determinism & Reproducibility** - Identical builds across runs
- **Offline-First Operation** - Air-gap compatible pipelines
- **Supply Chain Security** - SLSA Level 2-3 compliance
- **Developer Velocity** - Fast PR feedback with comprehensive nightly testing
### Pipeline Tiers
```
┌─────────────────────────────────────────────────────────────────────────┐
│ TRIGGER HIERARCHY │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ TIER 1: PR GATING (Every Pull Request) │
│ ├── test-matrix.yml (Unit, Architecture, Contract, Integration, │
│ │ Security, Golden) │
│ ├── build-test-deploy.yml (Build verification) │
│ ├── policy-lint.yml (Policy file validation) │
│ ├── sast-scan.yml (Static security analysis) │
│ └── docs.yml (Documentation validation) │
│ │
│ TIER 2: MAIN BRANCH (Post-Merge) │
│ ├── All Tier 1 workflows │
│ ├── build-test-deploy.yml → Deploy stage (staging environment) │
│ ├── integration-tests-gate.yml → Extended coverage │
│ └── coverage-report (Full coverage analysis) │
│ │
│ TIER 3: SCHEDULED (Nightly/Weekly) │
│ ├── nightly-regression.yml (2:00 AM UTC daily) │
│ ├── test-matrix.yml → Extended tests (5:00 AM UTC daily) │
│ ├── dependency-security-scan.yml (2:00 AM UTC Sunday) │
│ ├── renovate.yml (3:00 AM & 3:00 PM UTC daily) │
│ ├── sast-scan.yml (3:30 AM UTC Monday) │
│ └── migration-test.yml (4:30 AM UTC daily) │
│ │
│ TIER 4: RELEASE (Tag-Triggered) │
│ ├── release-suite.yml (suite-YYYY.MM tags) │
│ ├── release.yml (v* tags) │
│ └── module-publish.yml (module-*-v* tags) │
│ │
│ TIER 5: MANUAL (On-Demand) │
│ ├── cli-build.yml, scanner-determinism.yml │
│ ├── rollback.yml, promote.yml │
│ └── 20+ specialized test/debug workflows │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Workflow Categories
### 1. Core Build & Test (12 workflows)
| Workflow | Purpose | Triggers |
|----------|---------|----------|
| `build-test-deploy.yml` | Main build pipeline | PR, main push, daily, manual |
| `test-matrix.yml` | Unified test execution | PR, main push, daily, manual |
| `integration-tests-gate.yml` | Extended integration testing | PR, main push, manual |
| `nightly-regression.yml` | Comprehensive nightly suite | Daily 2 AM UTC |
| `migration-test.yml` | Database migration validation | PR (migrations), daily |
### 2. Release Automation (8 workflows)
| Workflow | Purpose | Triggers |
|----------|---------|----------|
| `release-suite.yml` | Ubuntu-style suite releases | `suite-*` tags, manual |
| `release.yml` | Version bundle releases | `v*` tags, manual |
| `module-publish.yml` | Per-module publishing | `module-*-v*` tags, manual |
| `cli-build.yml` | Multi-platform CLI builds | Manual only |
| `promote.yml` | Environment promotion | Manual only |
| `rollback.yml` | Emergency rollback | Manual only |
### 3. Security Scanning (6 workflows)
| Workflow | Purpose | Triggers |
|----------|---------|----------|
| `sast-scan.yml` | Static code analysis | PR, main push, weekly |
| `secrets-scan.yml` | Credential detection | PR, main push |
| `container-scan.yml` | Image vulnerability scanning | Dockerfile changes, daily |
| `dependency-security-scan.yml` | NuGet/npm vulnerability audit | Weekly, PR (deps) |
| `dependency-license-gate.yml` | License compliance | PR (deps) |
### 4. Quality Assurance (15 workflows)
| Workflow | Purpose | Triggers |
|----------|---------|----------|
| `policy-lint.yml` | Policy file validation | PR, main push |
| `docs.yml` | Documentation linting | docs/** changes |
| `scanner-determinism.yml` | Output reproducibility | Manual only |
| `determinism-gate.yml` | Build determinism | Manual only |
| `cross-platform-determinism.yml` | Multi-OS verification | Manual only |
### 5. Module-Specific (30+ workflows)
Specialized workflows for individual modules (Scanner, Concelier, Authority, etc.)
---
## Trigger Quick Reference
### Branch Patterns
| Pattern | Example | Workflows Triggered |
|---------|---------|---------------------|
| Push to `main` | Direct commit or merge | All Tier 1 + Tier 2 |
| Push to `develop` | Feature integration | Selected gating workflows |
| Pull Request | Any PR to main/develop | All Tier 1 (gating) |
| Push to `feature/*` | Feature branches | None (PR required) |
| Push to `release/*` | Release prep branches | Selected validation |
### Tag Patterns
| Pattern | Example | Workflow |
|---------|---------|----------|
| `v*` | `v2025.12.1` | `release.yml` |
| `suite-*` | `suite-2026.04` | `release-suite.yml` |
| `module-*-v*` | `module-authority-v1.2.3` | `module-publish.yml` |
### Schedule Summary
| Time (UTC) | Frequency | Workflow |
|------------|-----------|----------|
| 2:00 AM | Daily | `nightly-regression.yml` |
| 2:00 AM | Sunday | `dependency-security-scan.yml` |
| 3:00 AM | Daily | `renovate.yml` |
| 3:30 AM | Monday | `sast-scan.yml` |
| 4:30 AM | Daily | `migration-test.yml` |
| 5:00 AM | Daily | `build-test-deploy.yml`, `test-matrix.yml` |
| 3:00 PM | Daily | `renovate.yml` |
---
## Environment Flow
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PR │───▶│ Staging │───▶│ Stable │───▶│ LTS │
│ (Preview)│ │ (Edge) │ │ (Tested) │ │(Long-Term)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
PR tests Auto-deploy promote.yml promote.yml
(gating) on main merge (manual) (manual)
```
### Environment Matrix
| Environment | Branch/Tag | Auto-Deploy | Rollback |
|-------------|------------|-------------|----------|
| Preview | PR | Yes (ephemeral) | N/A |
| Staging (Edge) | `main` | Yes | `rollback.yml` |
| Stable | `v*` tags | Manual | `rollback.yml` |
| LTS | `suite-*` tags | Manual | `rollback.yml` |
---
## Key Features
### 1. PR-Gating Tests
Required tests that must pass before merge:
- **Unit Tests** - Fast, isolated tests
- **Architecture Tests** - Dependency rule enforcement
- **Contract Tests** - API compatibility
- **Integration Tests** - PostgreSQL integration
- **Security Tests** - Security-focused assertions
- **Golden Tests** - Corpus-based validation
### 2. Determinism Verification
All builds produce identical outputs:
- Binary checksums compared across runs
- UTC timezone enforcement (`TZ: UTC`)
- Stable JSON serialization
- Reproducible SBOM generation
### 3. Supply Chain Security
- **SBOM Generation** - Syft for CycloneDX/SPDX
- **Artifact Signing** - Cosign/Sigstore integration
- **Provenance** - in-toto/DSSE attestations
- **Dependency Scanning** - Automated vulnerability detection
### 4. Rollback Automation
Emergency rollback via `rollback.yml`:
- Target: < 5 minute SLA
- Helm-based deployment rollback
- Health check verification
- Notification integration
---
## Directory Structure
```
.gitea/
├── workflows/ # 100 workflow files
│ ├── build-test-deploy.yml
│ ├── test-matrix.yml
│ ├── release-suite.yml
│ └── ...
├── scripts/ # CI/CD scripts
│ ├── build/ # Build orchestration
│ ├── test/ # Test execution
│ ├── release/ # Release automation
│ ├── sign/ # Signing operations
│ └── validate/ # Validation scripts
└── docs/ # CI-specific docs
├── architecture.md
├── scripts.md
└── troubleshooting.md
devops/
├── scripts/
│ └── lib/ # Shared bash libraries
│ ├── logging.sh
│ ├── exit-codes.sh
│ ├── git-utils.sh
│ ├── path-utils.sh
│ └── hash-utils.sh
├── compose/ # Docker Compose profiles
├── helm/ # Helm charts
└── docker/ # Dockerfiles
```
---
## Getting Started
### Running Workflows Locally
```bash
# Run test matrix locally
./devops/scripts/test-local.sh
# Validate compose files
./devops/scripts/validate-compose.sh
# Run a specific test category
./.gitea/scripts/test/run-test-category.sh Unit
```
### Triggering Manual Workflows
```bash
# Via Gitea UI: Actions → Workflow → Run workflow
# Or via API:
curl -X POST \
-H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/owner/repo/actions/workflows/rollback.yml/dispatches" \
-d '{"ref":"main","inputs":{"environment":"staging","version":"v2025.12.0"}}'
```
### Creating a Release
1. **Module Release:**
```bash
git tag module-authority-v1.2.3
git push origin module-authority-v1.2.3
```
2. **Suite Release:**
```bash
git tag suite-2026.04
git push origin suite-2026.04
```
3. **Bundle Release:**
```bash
git tag v2025.12.1
git push origin v2025.12.1
```
---
## Related Documentation
- [Workflow Triggers Deep Dive](./workflow-triggers.md)
- [Release Pipeline Details](./release-pipelines.md)
- [Security Scanning Guide](./security-scanning.md)
- [Test Strategy](./test-strategy.md)
- [CI Quality Gates](../testing/ci-quality-gates.md)
- [Troubleshooting](../.gitea/docs/troubleshooting.md)
- [Script Reference](../.gitea/docs/scripts.md)
---
## Metrics & Monitoring
### Key Metrics Tracked
| Metric | Target | Measurement |
|--------|--------|-------------|
| PR Build Time | < 15 min | Workflow duration |
| Main Build Time | < 20 min | Workflow duration |
| Test Flakiness | < 1% | Flaky test detection |
| Security Scan Coverage | 100% | SAST/DAST coverage |
| Rollback SLA | < 5 min | Rollback workflow duration |
### Dashboard Links
- [Workflow Runs](../../.gitea/workflows/) (Gitea Actions UI)
- [Test Results](./test-results/) (TRX/JUnit artifacts)
- [Coverage Reports](./coverage/) (Generated nightly)

View File

@@ -0,0 +1,34 @@
# CI Recipes agent guide
## Mission
CI module collects reproducible pipeline recipes for builds, tests, and release promotion across supported platforms.
## Key docs
- [Module README](./README.md)
- [Architecture](./architecture.md)
- [Implementation plan](./implementation_plan.md)
- [Task board](./TASKS.md)
## How to get started
1. Open sprint file `/docs/implplan/SPRINT_*.md` and locate the stories referencing this module.
2. Review ./TASKS.md for local follow-ups and confirm status transitions (TODO → DOING → DONE/BLOCKED).
3. Read the architecture and README for domain context before editing code or docs.
4. Coordinate cross-module changes in the main /AGENTS.md description and through the sprint plan.
## Guardrails
- Honour the Aggregation-Only Contract where applicable (see ../../aoc/aggregation-only-contract.md).
- Preserve determinism: sort outputs, normalise timestamps (UTC ISO-8601), and avoid machine-specific artefacts.
- Keep Offline Kit parity in mind—document air-gapped workflows for any new feature.
- Update runbooks/observability assets when operational characteristics change.
## Required Reading
- `docs/modules/ci/README.md`
- `docs/modules/ci/architecture.md`
- `docs/modules/ci/implementation_plan.md`
- `docs/modules/platform/architecture-overview.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both correspoding sprint file `/docs/implplan/SPRINT_*.md` and the local `TASKS.md` when you start or finish work.
- 2. Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
- 4. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
- 5. Revert to `TODO` if you pause the task without shipping changes; leave notes in commit/PR descriptions for context.

View File

@@ -0,0 +1,30 @@
# CI Recipes architecture
## Scope & responsibilities
- Curate deterministic CI pipeline templates for ingestion, scanning, policy evaluation, export, and notifications.
- Capture provenance for each recipe (inputs, pinned tool versions, checksum manifests) and keep offline/air-gap parity.
- Provide reusable fragments (YAML/JSON) plus guardrails (AOC checks, DSSE attestation hooks, Rekor/Transparency toggles).
## Components
- **Recipe catalogue (`recipes.md`)** — Source of truth for pipeline snippets; sorted deterministically and annotated with required secrets/scopes.
- **Guardrail hooks** — Inline steps for schema validation, SBOM/VEX signing, and attestation verification; reuse Authority/Signer/Export Center helpers.
- **Observability shim** — Optional steps to emit structured logs/metrics to Telemetry Core when allowed; defaults to no-op in sealed/offline mode.
- **Offline bundle path** — Scripts/guides to package recipes and pinned tool archives for air-gapped runners; hashes recorded in release notes.
## Data & determinism
- All generated artifacts (templates, manifests, example outputs) must sort keys and lists, emit UTC ISO-8601 timestamps, and avoid host-specific paths.
- DSSE/attestation helpers should target the platform trust roots defined in Authority/Sigstore docs; prefer BLAKE3 hashing where compatible.
- Keep retry/backoff logic deterministic for reproducible CI runs; avoid time-based jitter unless explicitly documented.
## Integration points
- Authority/Signer for DSSE + Rekor publication; Export Center for bundle assembly; Notify for preview hooks; Telemetry Core for optional metrics.
- Recipes must remain compatible with CLI/SDK surface referenced in `docs/modules/cli/guides/` and devportal snippets.
## Testing lanes and catalog
- CI lane filters are defined by `docs/technical/testing/TEST_CATALOG.yml` and aligned with `docs/technical/testing/testing-strategy-models.md`.
- Standard categories: Unit, Contract, Integration, Security, Performance, Live (opt-in only).
- Any new test gate or lane must update `docs/technical/testing/TEST_SUITE_OVERVIEW.md` and `docs/technical/testing/ci-quality-gates.md`.
## Change process
- Track active work in `docs/implplan/SPRINT_0315_0001_0001_docs_modules_ci.md` and mirror statuses in `./TASKS.md`.
- When adding new recipes, include offline notes, determinism checks, and minimal test harness references in `docs/benchmarks` or `tests/**` as applicable.

353
docs/technical/cicd/ci-recipes.md Executable file
View File

@@ -0,0 +1,353 @@
# StellaOps CI Recipes  (20250804)
## 0·Key variables (export these once)
| Variable | Meaning | Typical value |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `STELLA_URL` | Host that: ① stores the **CLI** & **SBOMbuilder** images under `/registry` **and** ② receives API calls at `https://$STELLA_URL` | `stella-ops.ci.acme.example` |
| `DOCKER_HOST` | How containers reach your Docker daemon (because we no longer mount `/var/run/docker.sock`) | `tcp://docker:2375` |
| `WORKSPACE` | Directory where the pipeline stores artefacts (SBOM file) | `$(pwd)` |
| `IMAGE` | The image you are building & scanning | `acme/backend:sha-${COMMIT_SHA}` |
| `SBOM_FILE` | Immutable SBOM name `<image-ref>YYYYMMDDThhmmssZ.sbom.json` | `acme_backend_shaabc12320250804T153050Z.sbom.json` |
> **Authority graph scopes note (2025-10-27):** CI stages that spin up the Authority compose profile now rely on the checked-in `etc/authority.yaml`. Before running integration smoke jobs, inject real secrets for every `etc/secrets/*.secret` file (Cartographer, Graph API, Policy Engine, Concelier, Excititor). The repository defaults contain `*-change-me` placeholders and Authority will reject tokens if those secrets are not overridden. Reissue CI tokens that previously used `policy:write`/`policy:submit`/`policy:edit` scopes—new bundles must request `policy:read`, `policy:author`, `policy:review`, `policy:simulate`, and (`policy:approve`/`policy:operate`/`policy:activate` when pipelines promote policies).
```bash
export STELLA_URL="stella-ops.ci.acme.example"
export DOCKER_HOST="tcp://docker:2375" # Jenkins/Circle often expose it like this
export WORKSPACE="$(pwd)"
export IMAGE="acme/backend:sha-${COMMIT_SHA}"
export SBOM_FILE="$(echo "${IMAGE}" | tr '/:+' '__')-$(date -u +%Y%m%dT%H%M%SZ).sbom.json"
```
---
## 1·SBOM creation strategies
### Option A **Buildx attested SBOM** (preferred if you can use BuildKit)
You pass **two build args** so the Dockerfile can run the builder and copy the result out of the build context.
```bash
docker buildx build \
--build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \
--provenance=true --sbom=true \
--build-arg SBOM_FILE="$SBOM_FILE" \
-t "$IMAGE" .
```
**If you **cannot** use Buildx, use Option B below.** The older “run a builder stage inside the Dockerfile” pattern is unreliable for producing an SBOM of the final image.
```Dockerfile
ARG STELLA_SBOM_BUILDER
ARG SBOM_FILE
FROM $STELLA_SBOM_BUILDER as sbom
ARG IMAGE
ARG SBOM_FILE
RUN $STELLA_SBOM_BUILDER build --image $IMAGE --output /out/$SBOM_FILE
# ---- actual build stages … ----
FROM alpine:3.20
COPY --from=sbom /out/$SBOM_FILE / # (optional) keep or discard
# (rest of your Dockerfile)
```
### Option B **External builder step** (works everywhere; recommended baseline if Buildx isnt available)
*(keep this block if your pipeline already has an imagebuild step that you cant modify)*
```bash
docker run --rm \
-e DOCKER_HOST="$DOCKER_HOST" \ # let builder reach the daemon remotely
-v "$WORKSPACE:/workspace" \ # place SBOM beside the source code
"$STELLA_URL/registry/stella-sbom-builder:latest" \
build --image "$IMAGE" --output "/workspace/${SBOM_FILE}"
```
---
## 2·Scan the image & upload results
```bash
docker run --rm \
-e DOCKER_HOST="$DOCKER_HOST" \ # remotedaemon pointer
-v "$WORKSPACE/${SBOM_FILE}:/${SBOM_FILE}:ro" \ # mount SBOM under same name at container root
-e STELLA_OPS_URL="https://${STELLA_URL}" \ # where the CLI posts findings
"$STELLA_URL/registry/stella-cli:latest" \
scan --sbom "/${SBOM_FILE}" "$IMAGE"
```
The CLI returns **exit 0** if policies pass, **>0** if blocked — perfect for failing the job.
---
## 3·CI templates
Below are minimal, cutandpaste snippets.
**Feel free to deleteOption B** if you adopt Option A.
### 3.1 Jenkins (Declarative Pipeline)
```groovy
pipeline {
agent { docker { image 'docker:25' args '--privileged' } } // gives us /usr/bin/docker
environment {
STELLA_URL = 'stella-ops.ci.acme.example'
DOCKER_HOST = 'tcp://docker:2375'
IMAGE = "acme/backend:${env.BUILD_NUMBER}"
SBOM_FILE = "acme_backend_${env.BUILD_NUMBER}-${new Date().format('yyyyMMdd\'T\'HHmmss\'Z\'', TimeZone.getTimeZone('UTC'))}.sbom.json"
}
stages {
stage('Build image + SBOM (Option A)') {
steps {
sh '''
docker build \
--build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \
--build-arg SBOM_FILE="$SBOM_FILE" \
-t "$IMAGE" .
'''
}
}
/* ---------- Option B fallback (when you must keep the existing build step asis) ----------
stage('SBOM builder (Option B)') {
steps {
sh '''
docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
-v "$WORKSPACE:/workspace" \
"$STELLA_URL/registry/stella-sbom-builder:latest" \
build --image "$IMAGE" --output "/workspace/${SBOM_FILE}"
'''
}
}
------------------------------------------------------------------------------------------ */
stage('Scan & upload') {
steps {
sh '''
docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
-v "$WORKSPACE/${SBOM_FILE}:/${SBOM_FILE}:ro" \
-e STELLA_OPS_URL="https://$STELLA_URL" \
"$STELLA_URL/registry/stella-cli:latest" \
scan --sbom "/${SBOM_FILE}" "$IMAGE"
'''
}
}
}
}
```
---
### 3.2 CircleCI `.circleci/config.yml`
```yaml
version: 2.1
jobs:
stella_scan:
docker:
- image: cimg/base:stable # baremetal image with Docker CLI
environment:
STELLA_URL: stella-ops.ci.acme.example
DOCKER_HOST: tcp://docker:2375 # Circles “remote Docker” socket
steps:
- checkout
- run:
name: Compute vars
command: |
echo 'export IMAGE="acme/backend:${CIRCLE_SHA1}"' >> $BASH_ENV
echo 'export SBOM_FILE="$(echo acme/backend:${CIRCLE_SHA1} | tr "/:+" "__")-$(date -u +%Y%m%dT%H%M%SZ).sbom.json"' >> $BASH_ENV
- run:
name: Build image + SBOM (Option A)
command: |
docker build \
--build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \
--build-arg SBOM_FILE="$SBOM_FILE" \
-t "$IMAGE" .
# --- Option B fallback (when you must keep the existing build step asis) ---
#- run:
# name: SBOM builder (Option B)
# command: |
# docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
# -v "$PWD:/workspace" \
# "$STELLA_URL/registry/stella-sbom-builder:latest" \
# build --image "$IMAGE" --output "/workspace/${SBOM_FILE}"
- run:
name: Scan
command: |
docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
-v "$PWD/${SBOM_FILE}:/${SBOM_FILE}:ro" \
-e STELLA_OPS_URL="https://$STELLA_URL" \
"$STELLA_URL/registry/stella-cli:latest" \
scan --sbom "/${SBOM_FILE}" "$IMAGE"
workflows:
stella:
jobs: [stella_scan]
```
---
### 3.3 Gitea Actions `.gitea/workflows/stella.yml`
*(Gitea 1.22+ ships native Actions compatible with GitHub syntax)*
```yaml
name: Stella Scan
on: [push]
jobs:
stella:
runs-on: ubuntu-latest
env:
STELLA_URL: ${{ secrets.STELLA_URL }}
DOCKER_HOST: tcp://docker:2375 # provided by the docker:dind service
services:
docker:
image: docker:dind
options: >-
--privileged
steps:
- uses: actions/checkout@v4
- name: Compute vars
id: vars
run: |
echo "IMAGE=ghcr.io/${{ gitea.repository }}:${{ gitea.sha }}" >> $GITEA_OUTPUT
echo "SBOM_FILE=$(echo ghcr.io/${{ gitea.repository }}:${{ gitea.sha }} | tr '/:+' '__')-$(date -u +%Y%m%dT%H%M%SZ).sbom.json" >> $GITEA_OUTPUT
- name: Build image + SBOM (Option A)
run: |
docker build \
--build-arg STELLA_SBOM_BUILDER="${STELLA_URL}/registry/stella-sbom-builder:latest" \
--build-arg SBOM_FILE="${{ steps.vars.outputs.SBOM_FILE }}" \
-t "${{ steps.vars.outputs.IMAGE }}" .
# --- Option B fallback (when you must keep the existing build step asis) ---
#- name: SBOM builder (Option B)
# run: |
# docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
# -v "$(pwd):/workspace" \
# "${STELLA_URL}/registry/stella-sbom-builder:latest" \
# build --image "${{ steps.vars.outputs.IMAGE }}" --output "/workspace/${{ steps.vars.outputs.SBOM_FILE }}"
- name: Scan
run: |
docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \
-v "$(pwd)/${{ steps.vars.outputs.SBOM_FILE }}:/${{ steps.vars.outputs.SBOM_FILE }}:ro" \
-e STELLA_OPS_URL="https://${STELLA_URL}" \
"${STELLA_URL}/registry/stella-cli:latest" \
scan --sbom "/${{ steps.vars.outputs.SBOM_FILE }}" "${{ steps.vars.outputs.IMAGE }}"
```
---
## 4·Docs CI (Gitea Actions & Offline Mirror)
StellaOps ships a dedicated Docs workflow at `.gitea/workflows/docs.yml`. When mirroring the pipeline offline or running it locally, install the same toolchain so markdown linting, schema validation, and HTML preview stay deterministic.
### 4.1 Toolchain bootstrap
```bash
# Node.js 20.x is required; install once per runner
npm install --no-save \
markdown-link-check \
remark-cli \
remark-preset-lint-recommended \
ajv \
ajv-cli \
ajv-formats
# Python 3.11+ powers the preview renderer
python -m pip install --upgrade pip
python -m pip install markdown pygments
```
> **No `pip` available?** Some hardened Python builds (including the repos `tmp/docenv`
> interpreter) ship without `pip`/`ensurepip`. In that case download the purePython
> sdists (e.g. `Markdown-3.x.tar.gz`, `pygments-2.x.tar.gz`) and extract their
> packages directly into the virtualenvs `lib/python*/site-packages/` folder.
> This keeps the renderer working even when package managers are disabled.
**Offline tip.** Add the packages above to your artifact mirror (for example `ops/devops/offline-kit.json`) so runners can install them via `npm --offline` / `pip --no-index`.
### 4.2 Schema validation step
Ajv compiles every event schema to guard against syntax or format regressions. The workflow uses `ajv-formats` for UUID/date-time support.
```bash
for schema in docs/modules/signals/events/*.json; do
npx ajv compile -c ajv-formats -s "$schema"
done
```
Run this loop before committing schema changes. For new references, append `-r additional-file.json` so CI and local runs stay aligned.
### 4.3 Preview build
```bash
python scripts/render_docs.py --source docs --output artifacts/docs-preview --clean
```
Host the resulting bundle via any static file server for review (for example `python -m http.server`).
### 4.4 Publishing checklist
- [ ] Toolchain installs succeed without hitting the public internet (mirror or cached tarballs).
- [ ] Ajv validation passes for `scanner.report.ready@1`, `scheduler.rescan.delta@1`, `attestor.logged@1`.
- [ ] Markdown link check (`npx markdown-link-check`) reports no broken references.
- [ ] Preview bundle archived (or attached) for stakeholders.
### 4.5 Policy DSL lint stage
Policy Engine v2 pipelines now fail fast if policy documents are malformed. After checkout and dotnet restore, run:
```bash
dotnet run \
--project src/Tools/PolicyDslValidator/PolicyDslValidator.csproj \
-- \
--strict docs/modules/policy/samples/*.yaml
```
- `--strict` treats warnings as errors so missing metadata doesnt slip through.
- The validator accepts globs, so you can point it at tenant policy directories later (`policies/**/*.yaml`).
- Exit codes follow UNIX conventions: `0` success, `1` parse/errors, `2` warnings when `--strict` is set, `64` usage mistakes.
Capture the validator output as part of your build logs; Support uses it when triaging policy rollout issues.
### 4.6 Policy simulation smoke
Catch unexpected policy regressions by exercising a small set of golden SBOM findings via the simulation smoke tool:
```bash
dotnet run \
--project src/Tools/PolicySimulationSmoke/PolicySimulationSmoke.csproj \
-- \
--scenario-root samples/policy/simulations \
--output artifacts/policy-simulations
```
- The tool loads each `scenario.json` under `samples/policy/simulations`, evaluates the referenced policy, and fails the build if projected verdicts change.
- In CI the command runs twice (to `run1/` and `run2/`) and `diff -u` compares the summaries—any mismatch signals a determinism regression.
- Artifacts land in `artifacts/policy-simulations/policy-simulation-summary.json`; upload them for later inspection (see CI workflow).
- Expand scenarios by copying real-world findings into the samples directory—ensure expected statuses are recorded so regressions trip the pipeline.
---
## 5·Troubleshooting cheatsheet
| Symptom | Root cause | First things to try |
| ------------------------------------- | --------------------------- | --------------------------------------------------------------- |
| `no such host $STELLA_URL` | DNS typo or VPN outage | `ping $STELLA_URL` from runner |
| `connection refused` when CLI uploads | Port 443 blocked | open firewall / check ingress |
| `failed to stat /<sbom>.json` | SBOM wasnt produced | Did Option A actually run builder? If not, enable Option B |
| `registry unauthorized` | Runner lacks registry creds | `docker login $STELLA_URL/registry` (store creds in CI secrets) |
| Nonzero scan exit | Blocking vuln/licence | Open project in Ops UI → triage or waive |
---
### Change log
* **20251018** Documented Docs CI toolchain (Ajv validation, static preview) and offline checklist.
* **20250804** Variable cleanup, removed Dockersocket & cache mounts, added Jenkins / CircleCI / Gitea examples, clarified Option B comment.

View File

@@ -0,0 +1,283 @@
# Build-Time DSSE Attestation Walkthrough
> **Status:** Complete — implements the November 2025 advisory "Embed in-toto attestations (DSSE-wrapped) into .NET 10/C# builds." Updated 2025-11-27 with CLI verification commands (`DSSE-CLI-401-021`).
> **Owners:** Attestor Guild · DevOps Guild · Docs Guild.
This guide shows how to emit signed, in-toto compliant DSSE envelopes for every container build step (scan → package → push) using StellaOps Authority keys. The same primitives power our Signer/Attestor services, but this walkthrough targets developer pipelines (GitHub/GitLab, dotnet builds, container scanners).
---
## 1. Concepts refresher
| Term | Meaning |
|------|---------|
| **In-toto Statement** | JSON document describing what happened (predicate) to which artifact (subject). |
| **DSSE** | Dead Simple Signing Envelope: wraps the statement, base64 payload, and signatures. |
| **Authority Signer** | StellaOps client that signs data via file-based keys, HSM/KMS, or keyless Fulcio certs. |
| **PAE** | Pre-Authentication Encoding: canonical “DSSEv1 <len> <payloadType> <len> <payload>” byte layout that is signed. |
Requirements:
1. .NET 10 SDK (preview) for C# helper code.
2. Authority key material (dev: file-based Ed25519; prod: Authority/KMS signer).
3. Artifact digest (e.g., `pkg:docker/registry/app@sha256:...`) per step.
---
## 2. Helpers (drop-in library)
Create `src/StellaOps.Attestation` with:
```csharp
public sealed record InTotoStatement(
string _type,
IReadOnlyList<Subject> subject,
string predicateType,
object predicate);
public sealed record Subject(string name, IReadOnlyDictionary<string,string> digest);
public sealed record DsseEnvelope(
string payloadType,
string payload,
IReadOnlyList<Signature> signatures);
public sealed record Signature(string keyid, string sig);
public interface IAuthoritySigner
{
Task<string> GetKeyIdAsync(CancellationToken ct = default);
Task<byte[]> SignAsync(ReadOnlyMemory<byte> pae, CancellationToken ct = default);
}
```
DSSE helper:
```csharp
public static class DsseHelper
{
public static async Task<DsseEnvelope> WrapAsync(
InTotoStatement statement,
IAuthoritySigner signer,
string payloadType = "application/vnd.in-toto+json",
CancellationToken ct = default)
{
var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
var pae = PreAuthEncode(payloadType, payloadBytes);
var signature = await signer.SignAsync(pae, ct).ConfigureAwait(false);
var keyId = await signer.GetKeyIdAsync(ct).ConfigureAwait(false);
return new DsseEnvelope(
payloadType,
Convert.ToBase64String(payloadBytes),
new[] { new Signature(keyId, Convert.ToBase64String(signature)) });
}
private static byte[] PreAuthEncode(string payloadType, byte[] payload)
{
static byte[] Cat(params byte[][] parts)
{
var len = parts.Sum(p => p.Length);
var buf = new byte[len];
var offset = 0;
foreach (var part in parts)
{
Buffer.BlockCopy(part, 0, buf, offset, part.Length);
offset += part.Length;
}
return buf;
}
var header = Encoding.UTF8.GetBytes("DSSEv1");
var pt = Encoding.UTF8.GetBytes(payloadType);
var lenPt = Encoding.UTF8.GetBytes(pt.Length.ToString(CultureInfo.InvariantCulture));
var lenPayload = Encoding.UTF8.GetBytes(payload.Length.ToString(CultureInfo.InvariantCulture));
var space = Encoding.UTF8.GetBytes(" ");
return Cat(header, space, lenPt, space, pt, space, lenPayload, space, payload);
}
}
```
Authority signer examples:
/dev (file Ed25519):
```csharp
public sealed class FileEd25519Signer : IAuthoritySigner, IDisposable
{
private readonly Ed25519 _ed;
private readonly string _keyId;
public FileEd25519Signer(byte[] privateKeySeed, string keyId)
{
_ed = new Ed25519(privateKeySeed);
_keyId = keyId;
}
public Task<string> GetKeyIdAsync(CancellationToken ct) => Task.FromResult(_keyId);
public Task<byte[]> SignAsync(ReadOnlyMemory<byte> pae, CancellationToken ct)
=> Task.FromResult(_ed.Sign(pae.Span.ToArray()));
public void Dispose() => _ed.Dispose();
}
```
Prod (Authority KMS):
Reuse the existing `StellaOps.Signer.KmsSigner` adapter—wrap it behind `IAuthoritySigner`.
---
## 3. Emitting attestations per step
Subject helper:
```csharp
static Subject ImageSubject(string imageDigest) => new(
name: imageDigest,
digest: new Dictionary<string,string>{{"sha256", imageDigest.Replace("sha256:", "", StringComparison.Ordinal)}});
```
### 3.1 Scan
```csharp
var scanStmt = new InTotoStatement(
_type: "https://in-toto.io/Statement/v1",
subject: new[]{ ImageSubject(imageDigest) },
predicateType: "https://stella.ops/predicates/scanner-evidence/v1",
predicate: new {
scanner = "StellaOps.Scanner 0.9.0",
findingsSha256 = scanResultsHash,
startedAt = startedIso,
finishedAt = finishedIso,
rulePack = "lattice:default@2025-11-01"
});
var scanEnvelope = await DsseHelper.WrapAsync(scanStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-scan.dsse.json", JsonSerializer.Serialize(scanEnvelope));
```
### 3.2 Package (SLSA provenance)
```csharp
var pkgStmt = new InTotoStatement(
"https://in-toto.io/Statement/v1",
new[]{ ImageSubject(imageDigest) },
"https://slsa.dev/provenance/v1",
new {
builder = new { id = "stella://builder/dockerfile" },
buildType = "dockerfile/v1",
invocation = new { configSource = repoUrl, entryPoint = dockerfilePath },
materials = new[] { new { uri = repoUrl, digest = new { git = gitSha } } }
});
var pkgEnvelope = await DsseHelper.WrapAsync(pkgStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-package.dsse.json", JsonSerializer.Serialize(pkgEnvelope));
```
### 3.3 Push
```csharp
var pushStmt = new InTotoStatement(
"https://in-toto.io/Statement/v1",
new[]{ ImageSubject(imageDigest) },
"https://stella.ops/predicates/push/v1",
new { registry = registryUrl, repository = repoName, tags, pushedAt = DateTimeOffset.UtcNow });
var pushEnvelope = await DsseHelper.WrapAsync(pushStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-push.dsse.json", JsonSerializer.Serialize(pushEnvelope));
```
---
## 4. CI integration
### 4.1 GitLab example
```yaml
.attest-template: &attest
image: mcr.microsoft.com/dotnet/sdk:10.0-preview
before_script:
- dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj
variables:
AUTHORITY_KEY_FILE: "$CI_PROJECT_DIR/secrets/ed25519.key"
IMAGE_DIGEST: "$CI_REGISTRY_IMAGE@${CI_COMMIT_SHA}"
attest:scan:
stage: scan
script:
- dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "$IMAGE_DIGEST" --out artifacts/attest-scan.dsse.json
artifacts:
paths: [artifacts/attest-scan.dsse.json]
attest:package:
stage: package
script:
- dotnet run --project tools/StellaOps.Attestor.Tool -- step package --subject "$IMAGE_DIGEST" --out artifacts/attest-package.dsse.json
attest:push:
stage: push
script:
- dotnet run --project tools/StellaOps.Attestor.Tool -- step push --subject "$IMAGE_DIGEST" --registry "$CI_REGISTRY" --tags "$CI_COMMIT_REF_NAME"
```
### 4.2 GitHub Actions snippet
```yaml
jobs:
attest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '10.0.x' }
- name: Build attestor helpers
run: dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj
- name: Emit scan attestation
run: dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "${{ env.IMAGE_DIGEST }}" --out artifacts/attest-scan.dsse.json
env:
AUTHORITY_KEY_REF: ${{ secrets.AUTHORITY_KEY_REF }}
```
---
## 5. Verification
* `stella attest verify --envelope artifacts/attest-scan.dsse.json` — offline verification using the CLI.
* Additional verification options:
* `--policy policy.json` — apply custom verification policy
* `--root keys/root.pem` — specify trusted root certificate
* `--transparency-checkpoint checkpoint.json` — verify against Rekor checkpoint
* Manual validation:
1. Base64 decode payload → ensure `_type` = `https://in-toto.io/Statement/v1`, `subject[].digest.sha256` matches artifact.
2. Recompute PAE and verify signature with the Authority public key.
3. Attach envelope to Rekor (optional) via existing Attestor API.
---
## 6. Storage conventions
Store DSSE files next to build outputs:
```
artifacts/
attest-scan.dsse.json
attest-package.dsse.json
attest-push.dsse.json
```
Include the SHA-256 digest of each envelope in promotion manifests (`docs/release/promotion-attestations.md`) so downstream verifiers can trace chain of custody.
---
## 7. References
- [In-toto Statement v1](https://in-toto.io/spec/v1)
- [DSSE specification](https://github.com/secure-systems-lab/dsse)
- `docs/modules/signer/architecture.md`
- `docs/modules/attestor/architecture.md`
- `docs/release/promotion-attestations.md`
This file was updated as part of `DSSE-LIB-401-020` and `DSSE-CLI-401-021` (completed 2025-11-27). See `docs/modules/cli/guides/attest.md` for CI/CD workflow snippets.

View File

@@ -0,0 +1,414 @@
# Path Filters Reference
> Complete reference for path filter patterns used in CI/CD workflows.
---
## Overview
Path filters determine which workflows run based on changed files. This ensures:
- **Efficiency**: Only relevant tests run for each change
- **Speed**: Module-specific changes don't trigger full builds
- **Cascading**: Shared library changes trigger dependent module tests
---
## Configuration Location
Centralized path filter definitions are maintained in:
```
.gitea/config/path-filters.yml
```
This file serves as the source of truth for all path filter patterns.
---
## Path Filter Categories
### 1. Infrastructure Files (Trigger FULL CI)
Changes to these files trigger all tests and full build validation:
```yaml
infrastructure:
- 'Directory.Build.props' # Root MSBuild properties
- 'Directory.Build.rsp' # MSBuild response file
- 'Directory.Packages.props' # Central package versions
- 'src/Directory.Build.props' # Source directory properties
- 'src/Directory.Packages.props'
- 'nuget.config' # NuGet feed configuration
- 'StellaOps.sln' # Solution file
- '.gitea/workflows/**' # CI/CD workflow changes
```
**When to use:** All PR-gating and integration workflows should include these paths.
### 2. Documentation Paths (Skip CI)
These paths should use `paths-ignore` to skip builds:
```yaml
docs_ignore:
- 'docs/**' # All documentation
- '*.md' # Root markdown files
- 'etc/**' # Configuration samples
- 'LICENSE' # License file
- '.gitignore' # Git ignore
- '.editorconfig' # Editor configuration
```
**Exceptions:** These markdown files SHOULD trigger CI:
- `CLAUDE.md` - Agent instructions (affects behavior)
- `AGENTS.md` - Module-specific guidance
### 3. Shared Library Paths (Trigger Cascading)
Changes to shared libraries trigger tests in dependent modules:
#### Cryptography (CRITICAL - affects security)
```yaml
cryptography:
paths:
- 'src/__Libraries/StellaOps.Cryptography*/**'
- 'src/Cryptography/**'
cascades_to:
- Scanner tests
- Attestor tests
- Authority tests
- EvidenceLocker tests
- Signer tests
- AirGap tests
- Security test suite
- Offline E2E tests
```
#### Evidence & Provenance
```yaml
evidence:
paths:
- 'src/__Libraries/StellaOps.Evidence*/**'
- 'src/__Libraries/StellaOps.Provenance/**'
cascades_to:
- Scanner tests
- Attestor tests
- EvidenceLocker tests
- ExportCenter tests
- SbomService tests
```
#### Infrastructure & Database
```yaml
infrastructure:
paths:
- 'src/__Libraries/StellaOps.Infrastructure*/**'
- 'src/__Libraries/StellaOps.DependencyInjection/**'
cascades_to:
- ALL integration tests
```
#### Replay & Determinism
```yaml
replay:
paths:
- 'src/__Libraries/StellaOps.Replay*/**'
- 'src/__Libraries/StellaOps.Testing.Determinism/**'
cascades_to:
- Scanner determinism tests
- Determinism gate
- Replay module tests
```
#### Verdict & Policy Primitives
```yaml
verdict:
paths:
- 'src/__Libraries/StellaOps.Verdict/**'
- 'src/__Libraries/StellaOps.DeltaVerdict/**'
cascades_to:
- Policy engine tests
- RiskEngine tests
- ReachGraph tests
```
#### Plugin Framework
```yaml
plugin:
paths:
- 'src/__Libraries/StellaOps.Plugin/**'
cascades_to:
- Authority tests (plugin-based auth)
- Scanner tests (analyzer plugins)
- Concelier tests (connector plugins)
```
---
## Module-Specific Paths
Each module has defined source and test paths:
### Core Platform
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Authority | `src/Authority/**` | `src/Authority/__Tests/**` |
| Gateway | `src/Gateway/**` | `src/Gateway/__Tests/**` |
| Router | `src/Router/**` | `src/Router/__Tests/**` |
### Scanning & Analysis
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Scanner | `src/Scanner/**`, `src/BinaryIndex/**` | `src/Scanner/__Tests/**`, `src/BinaryIndex/__Tests/**` |
| AdvisoryAI | `src/AdvisoryAI/**` | `src/AdvisoryAI/__Tests/**` |
| ReachGraph | `src/ReachGraph/**` | `src/ReachGraph/__Tests/**` |
### Data Ingestion
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Concelier | `src/Concelier/**` | `src/Concelier/__Tests/**` |
| Excititor | `src/Excititor/**` | `src/Excititor/__Tests/**` |
| VexLens | `src/VexLens/**` | `src/VexLens/__Tests/**` |
| VexHub | `src/VexHub/**` | `src/VexHub/__Tests/**` |
### Artifacts & Evidence
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Attestor | `src/Attestor/**` | `src/Attestor/__Tests/**` |
| SbomService | `src/SbomService/**` | `src/SbomService/__Tests/**` |
| EvidenceLocker | `src/EvidenceLocker/**` | `src/EvidenceLocker/__Tests/**` |
| ExportCenter | `src/ExportCenter/**` | `src/ExportCenter/__Tests/**` |
| Findings | `src/Findings/**` | `src/Findings/__Tests/**` |
### Policy & Risk
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Policy | `src/Policy/**` | `src/Policy/__Tests/**` |
| RiskEngine | `src/RiskEngine/**` | `src/RiskEngine/__Tests/**` |
### Operations
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Notify | `src/Notify/**`, `src/Notifier/**` | `src/Notify/__Tests/**` |
| Orchestrator | `src/Orchestrator/**` | `src/Orchestrator/__Tests/**` |
| Scheduler | `src/Scheduler/**` | `src/Scheduler/__Tests/**` |
| PacksRegistry | `src/PacksRegistry/**` | `src/PacksRegistry/__Tests/**` |
| Replay | `src/Replay/**` | `src/Replay/__Tests/**` |
### Infrastructure
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| Cryptography | `src/Cryptography/**` | `src/__Libraries/__Tests/StellaOps.Cryptography*/**` |
| Telemetry | `src/Telemetry/**` | `src/Telemetry/__Tests/**` |
| Signals | `src/Signals/**` | `src/Signals/__Tests/**` |
| AirGap | `src/AirGap/**` | `src/AirGap/__Tests/**` |
| AOC | `src/Aoc/**` | `src/Aoc/__Tests/**` |
### Integration
| Module | Source Paths | Test Paths |
|--------|--------------|------------|
| CLI | `src/Cli/**` | `src/Cli/__Tests/**` |
| Web | `src/Web/**` | `src/Web/**/*.spec.ts` |
---
## DevOps & CI/CD Paths
### Docker & Containers
```yaml
docker:
- 'devops/docker/**'
- '**/Dockerfile'
- '**/Dockerfile.*'
```
### Compose Profiles
```yaml
compose:
- 'devops/compose/**'
- 'docker-compose*.yml'
```
### Helm Charts
```yaml
helm:
- 'devops/helm/**'
- 'devops/helm/stellaops/**'
```
### Database
```yaml
database:
- 'devops/database/**'
- 'devops/database/postgres/**'
```
### CI/CD Scripts
```yaml
scripts:
- '.gitea/scripts/**'
- 'devops/scripts/**'
```
---
## Test Infrastructure Paths
### Global Test Suites
```yaml
global_tests:
- 'src/__Tests/**'
- 'src/__Tests/Integration/**'
- 'src/__Tests/architecture/**'
- 'src/__Tests/security/**'
- 'src/__Tests/chaos/**'
- 'src/__Tests/e2e/**'
```
### Shared Test Libraries
```yaml
test_libraries:
- 'src/__Tests/__Libraries/**'
- 'src/__Tests/__Libraries/StellaOps.TestKit/**'
- 'src/__Tests/__Libraries/StellaOps.Infrastructure.Postgres.Testing/**'
```
### Test Datasets
```yaml
datasets:
- 'src/__Tests/__Datasets/**'
- 'src/__Tests/__Benchmarks/**'
```
---
## Example Workflow Configurations
### PR-Gating Workflow (Skip Docs)
```yaml
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
- 'etc/**'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- 'etc/**'
```
### Module-Specific Workflow (With Cascading)
```yaml
on:
push:
branches: [main]
paths:
# Direct module paths
- 'src/Scanner/**'
- 'src/BinaryIndex/**'
# Shared library cascades
- 'src/__Libraries/StellaOps.Evidence*/**'
- 'src/__Libraries/StellaOps.Cryptography*/**'
- 'src/__Libraries/StellaOps.Replay*/**'
- 'src/__Libraries/StellaOps.Provenance/**'
# Infrastructure cascades
- 'Directory.Build.props'
- 'Directory.Packages.props'
# Self-reference
- '.gitea/workflows/scanner-*.yml'
pull_request:
paths:
- 'src/Scanner/**'
- 'src/BinaryIndex/**'
- 'src/__Libraries/StellaOps.Evidence*/**'
- 'src/__Libraries/StellaOps.Cryptography*/**'
```
### Documentation-Only Workflow
```yaml
on:
push:
paths:
- 'docs/**'
- '*.md'
- 'scripts/render_docs.py'
pull_request:
paths:
- 'docs/**'
- '*.md'
```
### Docker/Container Workflow
```yaml
on:
push:
paths:
- '**/Dockerfile'
- '**/Dockerfile.*'
- 'devops/docker/**'
schedule:
- cron: '0 4 * * *' # Also run daily for vulnerability updates
```
---
## Validation Checklist
When adding or modifying path filters:
- [ ] Does the workflow skip docs-only changes? (Use `paths-ignore`)
- [ ] Does the workflow include dependent shared library paths? (Cascading)
- [ ] Does the workflow include infrastructure files for full builds?
- [ ] Are glob patterns correct? (`**` for recursive, `*` for single level)
- [ ] Is the workflow self-referenced? (e.g., `.gitea/workflows/module-*.yml`)
---
## Glob Pattern Reference
| Pattern | Matches |
|---------|---------|
| `src/**` | All files under src/ recursively |
| `src/*` | Direct children of src/ only |
| `**/*.cs` | All .cs files anywhere |
| `*.md` | Markdown files in root only |
| `src/**/*.csproj` | All .csproj files under src/ |
| `!src/**/*.md` | Exclude markdown in src/ |
| `**/Dockerfile*` | Dockerfile, Dockerfile.prod, etc. |
---
## Related Documentation
- [Workflow Triggers](./workflow-triggers.md) - Complete trigger reference
- [Test Strategy](./test-strategy.md) - Test categories and execution
- [CI/CD Overview](./README.md) - Architecture overview

View File

@@ -0,0 +1,509 @@
# Release Pipelines
> Complete guide to StellaOps release automation including suite releases, module publishing, and promotion workflows.
---
## Release Strategy Overview
StellaOps uses a **dual-versioning strategy**:
1. **Suite Releases** - Ubuntu-style `YYYY.MM` versioning with codenames
2. **Module Releases** - Semantic versioning `MAJOR.MINOR.PATCH` per module
### Release Channels
| Channel | Purpose | Stability | Update Frequency |
|---------|---------|-----------|------------------|
| **Edge** | Latest features, early adopters | Beta | Every merge to main |
| **Stable** | Production-ready, tested | Production | Bi-weekly |
| **LTS** | Long-term support, enterprise | Enterprise | Quarterly |
---
## Suite Release Pipeline
### Trigger
```bash
# Tag-based trigger
git tag suite-2026.04
git push origin suite-2026.04
# Or manual trigger via Gitea Actions UI
# Workflow: release-suite.yml
# Inputs: version, codename, channel, skip_tests, dry_run
```
### Workflow: `release-suite.yml`
```
┌─────────────────────────────────────────────────────────────────┐
│ SUITE RELEASE PIPELINE │
│ │
│ ┌──────────────┐ │
│ │ parse-tag │ (if triggered by tag push) │
│ │ or validate │ (if triggered manually) │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ test-gate │ (optional, skipped with skip_tests=true) │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────────────────────────────────────────┐ │
│ │ BUILD PHASE │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ build-modules │ │ build-containers│ │ │
│ │ │ (9 in parallel)│ │ (9 in parallel)│ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ build-cli │ │ build-helm │ │ │
│ │ │ (5 platforms) │ │ │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────┬────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ release-manifest │ │
│ │ - Binary manifest with SHA256 checksums │ │
│ │ - SBOM generation (CycloneDX, SPDX) │ │
│ │ - Provenance attestation (in-toto/DSSE) │ │
│ └───────────────────────┬────────────────────────┘ │
│ │ │
│ ┌─────────────────────┴─────────────────────────┐ │
│ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ changelog │ │ suite-docs │ │ compose │ │
│ │ generation │ │ generation │ │ generation │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ create-release │ │
│ │ - Upload artifacts to Gitea Releases │ │
│ │ - Sign with Cosign (keyless Sigstore) │ │
│ │ - Publish to container registry │ │
│ └───────────────────────┬────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ commit-docs │ │
│ │ - Update docs/releases/ │ │
│ │ - Update devops/compose/ │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Suite Versioning
| Component | Format | Example |
|-----------|--------|---------|
| Suite Version | `YYYY.MM` | `2026.04` |
| Codename | Alpha name | `Nova`, `Orion`, `Phoenix` |
| Full Tag | `suite-YYYY.MM` | `suite-2026.04` |
| Docker Tag | `YYYY.MM-channel` | `2026.04-stable` |
### Modules Built
| Module | NuGet Package | Container Image |
|--------|---------------|-----------------|
| Authority | `StellaOps.Authority` | `stellaops/authority` |
| Scanner | `StellaOps.Scanner` | `stellaops/scanner` |
| Concelier | `StellaOps.Concelier` | `stellaops/concelier` |
| Excititor | `StellaOps.Excititor` | `stellaops/excititor` |
| SbomService | `StellaOps.SbomService` | `stellaops/sbom-service` |
| EvidenceLocker | `StellaOps.EvidenceLocker` | `stellaops/evidence-locker` |
| Policy | `StellaOps.Policy` | `stellaops/policy` |
| Attestor | `StellaOps.Attestor` | `stellaops/attestor` |
| VexLens | `StellaOps.VexLens` | `stellaops/vexlens` |
### CLI Platforms
| Runtime ID | OS | Architecture | Binary Name |
|------------|-----|--------------|-------------|
| `linux-x64` | Linux | x86_64 | `stellaops-linux-x64` |
| `linux-arm64` | Linux | ARM64 | `stellaops-linux-arm64` |
| `win-x64` | Windows | x86_64 | `stellaops-win-x64.exe` |
| `osx-x64` | macOS | Intel | `stellaops-osx-x64` |
| `osx-arm64` | macOS | Apple Silicon | `stellaops-osx-arm64` |
---
## Module Release Pipeline
### Trigger
```bash
# Tag-based trigger
git tag module-authority-v1.2.3
git push origin module-authority-v1.2.3
# Or manual trigger via Gitea Actions UI
# Workflow: module-publish.yml
# Inputs: module, version, publish_nuget, publish_container, prerelease
```
### Workflow: `module-publish.yml`
```
┌─────────────────────────────────────────────────────────────────┐
│ MODULE PUBLISH PIPELINE │
│ │
│ ┌──────────────┐ │
│ │ parse-tag │ Extract module name and version from tag │
│ │ or validate │ Normalize manual inputs │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │publish-nuget │ (if flag set) │publish-cont. │ │
│ │ │ │ (if flag set)│ │
│ │ - Pack │ │ - Build │ │
│ │ - Sign │ │ - Scan │ │
│ │ - Push │ │ - Sign │ │
│ └──────────────┘ │ - Push │ │
│ └──────────────┘ │
│ │ │
│ OR (if module=CLI) │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ publish-cli │ │
│ │ - Build for 5 platforms │ │
│ │ - Native AOT compilation │ │
│ │ - Code sign binaries │ │
│ │ - Generate checksums │ │
│ │ - Upload to release │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ summary │ │
│ │ - Release notes │ │
│ │ - Artifact links │ │
│ │ - SBOM references │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Module Tag Format
```
module-<name>-v<semver>
Examples:
module-authority-v1.2.3
module-scanner-v2.0.0
module-cli-v3.1.0-beta.1
```
### Available Modules
| Module Name | NuGet | Container | CLI |
|-------------|-------|-----------|-----|
| `authority` | Yes | Yes | No |
| `scanner` | Yes | Yes | No |
| `concelier` | Yes | Yes | No |
| `excititor` | Yes | Yes | No |
| `sbomservice` | Yes | Yes | No |
| `evidencelocker` | Yes | Yes | No |
| `policy` | Yes | Yes | No |
| `attestor` | Yes | Yes | No |
| `vexlens` | Yes | Yes | No |
| `cli` | No | No | Yes (multi-platform) |
---
## Bundle Release Pipeline
### Trigger
```bash
# Tag-based trigger
git tag v2025.12.1
git push origin v2025.12.1
# Channel-specific tags
git tag v2025.12.0-edge
git tag v2025.12.0-stable
git tag v2025.12.0-lts
```
### Workflow: `release.yml`
Creates deterministic release bundles with:
- Signed container images
- SBOM generation
- Provenance attestations
- CLI parity verification
---
## Rollback Pipeline
### Trigger
```bash
# Manual trigger only via Gitea Actions UI
# Workflow: rollback.yml
# Inputs: environment, service, version, reason
```
### Workflow: `rollback.yml`
```
┌─────────────────────────────────────────────────────────────────┐
│ ROLLBACK PIPELINE │
│ (SLA Target: < 5 min) │
│ │
│ ┌──────────────┐ │
│ │ validate │ Verify inputs and permissions │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ fetch-prev │ Download previous version artifacts │
│ │ version │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ execute │ Run rollback via Helm/kubectl │
│ │ rollback │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │health-check │ Verify service health post-rollback │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ notify │ Send notification (Slack/Teams/Webhook) │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Rollback Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `environment` | choice | `staging`, `production` |
| `service` | choice | Service to rollback (or `all`) |
| `version` | string | Target version to rollback to |
| `reason` | string | Reason for rollback (audit log) |
| `dry_run` | boolean | Simulate without executing |
---
## Promotion Pipeline
### Trigger
```bash
# Manual trigger only via Gitea Actions UI
# Workflow: promote.yml
# Inputs: from_environment, to_environment, version
```
### Promotion Flow
```
┌─────────────┐
│ Edge │ (Automatic on main merge)
└──────┬──────┘
│ promote.yml (manual)
┌─────────────┐
│ Stable │ (After testing period)
└──────┬──────┘
│ promote.yml (manual)
┌─────────────┐
│ LTS │ (After extended validation)
└─────────────┘
```
### Promotion Checklist (Automated)
1. **Pre-Flight Checks**
- All tests passing in source environment
- No critical vulnerabilities
- Performance SLOs met
- Documentation complete
2. **Promotion Steps**
- Re-tag containers with new channel
- Update Helm chart values
- Deploy to target environment
- Run smoke tests
3. **Post-Promotion**
- Health check verification
- Update release documentation
- Notify stakeholders
---
## Artifact Signing
### Cosign Integration
All release artifacts are signed using Cosign with Sigstore keyless signing:
```bash
# Verify container signature
cosign verify \
--certificate-identity-regexp=".*github.com/stellaops.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/stellaops/scanner:2026.04
# Verify SBOM
cosign verify-attestation \
--type spdxjson \
--certificate-identity-regexp=".*github.com/stellaops.*" \
ghcr.io/stellaops/scanner:2026.04
```
### Signature Artifacts
| Artifact Type | Signature Location |
|---------------|-------------------|
| Container Image | OCI registry (same repo) |
| CLI Binary | `.sig` file alongside binary |
| SBOM | Attestation on OCI image |
| Provenance | Attestation on OCI image |
---
## Release Artifacts
### Per-Release Artifacts
| Artifact | Format | Location |
|----------|--------|----------|
| Release Notes | Markdown | Gitea Release |
| Changelog | `CHANGELOG.md` | Gitea Release, `docs/releases/` |
| Binary Checksums | `SHA256SUMS.txt` | Gitea Release |
| SBOM (CycloneDX) | JSON | Gitea Release, OCI attestation |
| SBOM (SPDX) | JSON | Gitea Release |
| Provenance | in-toto/DSSE | OCI attestation |
| Docker Compose | YAML | `devops/compose/` |
| Helm Chart | TGZ | OCI registry |
### Artifact Retention
| Environment | Retention Period |
|-------------|------------------|
| PR/Preview | 7 days |
| Edge | 30 days |
| Stable | 1 year |
| LTS | 3 years |
---
## Creating a Release
### Suite Release
```bash
# 1. Ensure main is stable
git checkout main
git pull
# 2. Create and push tag
git tag suite-2026.04
git push origin suite-2026.04
# 3. Monitor release pipeline
# Gitea Actions → release-suite.yml
# 4. Verify artifacts
# - Check Gitea Releases page
# - Verify container images pushed
# - Validate SBOM and signatures
```
### Module Release
```bash
# 1. Update module version
# Edit src/<Module>/version.txt or .csproj
# 2. Create and push tag
git tag module-authority-v1.2.3
git push origin module-authority-v1.2.3
# 3. Monitor release pipeline
# Gitea Actions → module-publish.yml
```
### Hotfix Release
```bash
# 1. Create hotfix branch from release tag
git checkout -b hotfix/v2025.12.1 v2025.12.0
# 2. Apply fix
# ... make changes ...
git commit -m "Fix: critical security issue"
# 3. Create hotfix tag
git tag v2025.12.1
git push origin hotfix/v2025.12.1 v2025.12.1
# 4. Fast-track through pipeline
# Workflow will run with reduced test scope
```
---
## Troubleshooting Releases
### Release Pipeline Failed
1. **Check build logs** - Gitea Actions → failed job
2. **Verify tag format** - Must match expected pattern
3. **Check secrets** - Registry credentials, signing keys
4. **Review test failures** - May need to skip with `skip_tests=true`
### Container Not Published
1. **Check registry authentication** - `REGISTRY_TOKEN` secret
2. **Verify image name** - Check for typos in workflow
3. **Check rate limits** - May need to wait and retry
4. **Review scan results** - Image may be blocked by vulnerability scan
### Signature Verification Failed
1. **Check Sigstore availability** - May have temporary outage
2. **Verify certificate identity** - Workflow must match expected pattern
3. **Check OIDC issuer** - Must be GitHub/Gitea Actions
### Rollback Failed
1. **Verify target version exists** - Check artifact storage
2. **Check Helm/kubectl access** - Cluster credentials
3. **Review health check** - Service may need manual intervention
4. **Check resource constraints** - May need to scale down first
---
## Related Documentation
- [README - CI/CD Overview](./README.md)
- [Workflow Triggers](./workflow-triggers.md)
- [Versioning Guide](../releases/VERSIONING.md)
- [Container Registry Guide](../operations/container-registry.md)
- [Helm Deployment Guide](../operations/helm-deployment.md)

View File

@@ -0,0 +1,250 @@
# SARIF Integration Guide
**Sprint:** SPRINT_3500_0004_0001
**Task:** SDIFF-BIN-032 - Documentation for SARIF integration
## Overview
StellaOps Scanner supports SARIF (Static Analysis Results Interchange Format) 2.1.0 output for seamless integration with CI/CD platforms including GitHub, GitLab, and Azure DevOps.
## Supported Platforms
| Platform | Integration Method | Native Support |
|----------|-------------------|----------------|
| GitHub Actions | Code Scanning API | ✅ Yes |
| GitLab CI | SAST Reports | ✅ Yes |
| Azure DevOps | SARIF Viewer Extension | ✅ Yes |
| Jenkins | SARIF Plugin | ✅ Yes |
| Other | File upload | ✅ Yes |
## Quick Start
### API Endpoint
```bash
# Get SARIF output for a scan
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/smart-diff/scans/{scanId}/sarif"
# With pretty printing
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/smart-diff/scans/{scanId}/sarif?pretty=true"
```
### CLI Usage
```bash
# Scan with SARIF output
stellaops scan image:tag --output-format sarif > results.sarif
# Smart-diff with SARIF output
stellaops smart-diff --base image:v1 --target image:v2 --output-format sarif
```
## SARIF Rule Definitions
StellaOps emits the following rule categories in SARIF output:
| Rule ID | Name | Description |
|---------|------|-------------|
| SDIFF001 | ReachabilityChange | Vulnerability reachability status changed |
| SDIFF002 | VexStatusFlip | VEX status changed (affected/not_affected/fixed) |
| SDIFF003 | HardeningRegression | Binary hardening flag regressed |
| SDIFF004 | IntelligenceSignal | EPSS/KEV status changed |
## GitHub Actions Integration
```yaml
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run StellaOps Scanner
run: |
stellaops scan ${{ github.repository }} \
--output-format sarif \
--output results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
category: stellaops
```
## GitLab CI Integration
```yaml
security_scan:
stage: test
image: stellaops/cli:latest
script:
- stellaops scan $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --output-format sarif > gl-sast-report.sarif
artifacts:
reports:
sast: gl-sast-report.sarif
```
## Azure DevOps Integration
```yaml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
displayName: 'Run StellaOps Scanner'
inputs:
targetType: 'inline'
script: |
stellaops scan $(containerImage) --output-format sarif > $(Build.ArtifactStagingDirectory)/results.sarif
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/results.sarif'
artifactName: 'security-results'
```
## SARIF Schema Details
### Result Levels
| SARIF Level | StellaOps Severity | Description |
|-------------|-------------------|-------------|
| `error` | Critical, High | Requires immediate attention |
| `warning` | Medium | Should be reviewed |
| `note` | Low, Info | For awareness |
### Result Kinds
| Kind | Meaning |
|------|---------|
| `fail` | Finding indicates a problem |
| `pass` | Check passed (for VEX suppressed) |
| `notApplicable` | Finding does not apply |
| `informational` | Advisory information |
### Location Information
SARIF results include:
- **Physical location**: File path and line numbers (when available)
- **Logical location**: Component PURL, function name
- **URI**: OCI artifact digest or SBOM reference
## Example SARIF Output
```json
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "StellaOps Scanner",
"version": "1.0.0",
"informationUri": "https://stellaops.io",
"rules": [
{
"id": "SDIFF001",
"name": "ReachabilityChange",
"shortDescription": {
"text": "Vulnerability reachability changed"
},
"defaultConfiguration": {
"level": "warning"
}
}
]
}
},
"results": [
{
"ruleId": "SDIFF001",
"level": "warning",
"message": {
"text": "CVE-2024-1234 became reachable in pkg:npm/lodash@4.17.20"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "package-lock.json"
}
},
"logicalLocations": [
{
"name": "pkg:npm/lodash@4.17.20",
"kind": "package"
}
]
}
],
"properties": {
"vulnerability": "CVE-2024-1234",
"tier": "executed",
"direction": "increased"
}
}
]
}
]
}
```
## Filtering Results
### By Tier
```bash
# Only tainted_sink findings
stellaops scan image:tag --output-format sarif --tier tainted_sink
# Executed and tainted_sink
stellaops scan image:tag --output-format sarif --tier executed,tainted_sink
```
### By Priority
```bash
# Only high priority changes
stellaops smart-diff --output-format sarif --min-priority 0.7
```
## Troubleshooting
### SARIF Validation Errors
If your CI platform rejects the SARIF output:
1. Validate against schema:
```bash
stellaops validate-sarif results.sarif
```
2. Check for required fields:
- `$schema` must be present
- `version` must be `"2.1.0"`
- Each result must have `ruleId` and `message`
### Empty Results
If SARIF contains no results:
- Check scan completed successfully
- Verify image has vulnerability data
- Ensure feed snapshots are current
## Related Documentation
- [Smart-Diff Detection Rules](../modules/scanner/smart-diff-rules.md)
- [Scanner API Reference](../api/scanner-api.md)
- [CLI Reference](../API_CLI_REFERENCE.md)
- [Scoring Configuration](./scoring-configuration.md)

View File

@@ -0,0 +1,292 @@
# Smart-Diff Scoring Configuration Guide
**Sprint:** SPRINT_3500_0004_0001
**Task:** SDIFF-BIN-031 - Documentation for scoring configuration
## Overview
Smart-Diff uses configurable scoring weights to prioritize material risk changes. This guide explains how to customize scoring for your organization's risk appetite.
## Configuration Location
Smart-Diff scoring can be configured via:
1. **PolicyScoringConfig** - Integrated with policy engine
2. **SmartDiffScoringConfig** - Standalone configuration
3. **Environment variables** - Runtime overrides
4. **API** - Dynamic configuration
## Default Configuration
```json
{
"name": "default",
"version": "1.0",
"reachabilityFlipUpWeight": 1.0,
"reachabilityFlipDownWeight": 0.8,
"vexFlipToAffectedWeight": 0.9,
"vexFlipToNotAffectedWeight": 0.7,
"vexFlipToFixedWeight": 0.6,
"vexFlipToUnderInvestigationWeight": 0.3,
"rangeEntryWeight": 0.8,
"rangeExitWeight": 0.6,
"kevAddedWeight": 1.0,
"epssThreshold": 0.1,
"epssThresholdCrossWeight": 0.5,
"hardeningRegressionWeight": 0.7,
"hardeningImprovementWeight": 0.3,
"hardeningRegressionThreshold": 0.1
}
```
## Weight Categories
### Reachability Weights (R1)
Controls scoring for reachability status changes.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `reachabilityFlipUpWeight` | 1.0 | Unreachable → Reachable (risk increase) |
| `reachabilityFlipDownWeight` | 0.8 | Reachable → Unreachable (risk decrease) |
| `useLatticeConfidence` | true | Factor in reachability confidence |
**Example scenarios:**
- Vulnerability becomes reachable after code refactoring → weight = 1.0
- Dependency removed, vulnerability no longer reachable → weight = 0.8
### VEX Status Weights (R2)
Controls scoring for VEX statement changes.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `vexFlipToAffectedWeight` | 0.9 | Status changed to "affected" |
| `vexFlipToNotAffectedWeight` | 0.7 | Status changed to "not_affected" |
| `vexFlipToFixedWeight` | 0.6 | Status changed to "fixed" |
| `vexFlipToUnderInvestigationWeight` | 0.3 | Status changed to "under_investigation" |
**Rationale:**
- "affected" is highest weight as it confirms exploitability
- "fixed" is lower as it indicates remediation
- "under_investigation" is lowest as status is uncertain
### Version Range Weights (R3)
Controls scoring for affected version range changes.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `rangeEntryWeight` | 0.8 | Version entered affected range |
| `rangeExitWeight` | 0.6 | Version exited affected range |
### Intelligence Signal Weights (R4)
Controls scoring for external intelligence changes.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `kevAddedWeight` | 1.0 | Vulnerability added to CISA KEV |
| `epssThreshold` | 0.1 | EPSS score threshold for significance |
| `epssThresholdCrossWeight` | 0.5 | Weight when EPSS crosses threshold |
### Binary Hardening Weights (R5)
Controls scoring for binary hardening flag changes.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `hardeningRegressionWeight` | 0.7 | Security flag disabled (e.g., NX removed) |
| `hardeningImprovementWeight` | 0.3 | Security flag enabled (e.g., PIE added) |
| `hardeningRegressionThreshold` | 0.1 | Minimum score drop to flag regression |
## Presets
### Default Preset
Balanced configuration suitable for most organizations.
```csharp
SmartDiffScoringConfig.Default
```
### Strict Preset
Higher weights for regressions, recommended for security-critical applications.
```csharp
SmartDiffScoringConfig.Strict
```
Configuration:
```json
{
"name": "strict",
"reachabilityFlipUpWeight": 1.2,
"vexFlipToAffectedWeight": 1.1,
"kevAddedWeight": 1.5,
"hardeningRegressionWeight": 1.0,
"hardeningRegressionThreshold": 0.05
}
```
### Lenient Preset
Lower weights for alerts, suitable for development/staging environments.
```json
{
"name": "lenient",
"reachabilityFlipUpWeight": 0.7,
"vexFlipToAffectedWeight": 0.6,
"kevAddedWeight": 0.8,
"hardeningRegressionWeight": 0.4,
"epssThreshold": 0.2
}
```
## Policy Integration
Smart-Diff scoring integrates with `PolicyScoringConfig`:
```csharp
var config = new PolicyScoringConfig(
Version: "1.0",
SeverityWeights: severityWeights,
QuietPenalty: 0.1,
WarnPenalty: 0.5,
IgnorePenalty: 0.0,
TrustOverrides: trustOverrides,
ReachabilityBuckets: reachabilityBuckets,
UnknownConfidence: unknownConfig,
SmartDiff: new SmartDiffPolicyScoringConfig(
ReachabilityFlipUpWeight: 1.0,
VexFlipToAffectedWeight: 0.9,
KevAddedWeight: 1.2
)
);
```
## Environment Variable Overrides
```bash
# Override reachability weights
export STELLAOPS_SMARTDIFF_REACHABILITY_FLIP_UP_WEIGHT=1.2
export STELLAOPS_SMARTDIFF_REACHABILITY_FLIP_DOWN_WEIGHT=0.7
# Override KEV weight
export STELLAOPS_SMARTDIFF_KEV_ADDED_WEIGHT=1.5
# Override hardening threshold
export STELLAOPS_SMARTDIFF_HARDENING_REGRESSION_THRESHOLD=0.05
```
## API Configuration
### Get Current Configuration
```bash
GET /api/v1/config/smart-diff/scoring
Response:
{
"name": "default",
"version": "1.0",
"weights": { ... }
}
```
### Update Configuration
```bash
PUT /api/v1/config/smart-diff/scoring
Content-Type: application/json
{
"reachabilityFlipUpWeight": 1.2,
"kevAddedWeight": 1.5
}
```
## Score Calculation Formula
The final priority score is calculated as:
```
priority_score = base_severity × Σ(change_weight × rule_match)
```
Where:
- `base_severity` is the CVSS/severity normalized to 0-1
- `change_weight` is the configured weight for the change type
- `rule_match` is 1 if the rule triggered, 0 otherwise
### Example Calculation
Given:
- CVE-2024-1234 with CVSS 7.5 (base_severity = 0.75)
- Became reachable (reachabilityFlipUpWeight = 1.0)
- Added to KEV (kevAddedWeight = 1.0)
```
priority_score = 0.75 × (1.0 + 1.0) = 1.5 → capped at 1.0
```
## Tuning Recommendations
### For CI/CD Pipelines
```json
{
"kevAddedWeight": 1.5,
"hardeningRegressionWeight": 1.2,
"epssThreshold": 0.05
}
```
Focus on blocking builds for known exploited vulnerabilities and hardening regressions.
### For Alert Fatigue Reduction
```json
{
"reachabilityFlipDownWeight": 0.3,
"vexFlipToNotAffectedWeight": 0.2,
"rangeExitWeight": 0.2
}
```
Lower weights for positive changes to reduce noise.
### For Compliance Focus
```json
{
"kevAddedWeight": 2.0,
"vexFlipToAffectedWeight": 1.2,
"hardeningRegressionThreshold": 0.02
}
```
Higher weights for regulatory-relevant changes.
## Monitoring and Metrics
Track scoring effectiveness with:
```sql
-- Average priority score by rule type
SELECT
change_type,
AVG(priority_score) as avg_score,
COUNT(*) as count
FROM smart_diff_changes
WHERE created_at > now() - interval '30 days'
GROUP BY change_type
ORDER BY avg_score DESC;
```
## Related Documentation
- [Smart-Diff Detection Rules](../modules/scanner/smart-diff-rules.md)
- [Policy Engine Configuration](../modules/policy/architecture.md)
- [SARIF Integration](./sarif-integration.md)

View File

@@ -0,0 +1,508 @@
# Security Scanning
> Complete guide to security scanning workflows in the StellaOps CI/CD pipeline.
---
## Security Scanning Overview
StellaOps implements a **defense-in-depth** security scanning strategy:
```
┌─────────────────────────────────────────────────────────────────┐
│ SECURITY SCANNING LAYERS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: PRE-COMMIT │
│ └── Secrets scanning (pre-commit hook) │
│ │
│ Layer 2: PULL REQUEST │
│ ├── SAST (Static Application Security Testing) │
│ ├── Secrets scanning │
│ ├── Dependency vulnerability audit │
│ └── License compliance check │
│ │
│ Layer 3: MAIN BRANCH │
│ ├── All Layer 2 scans │
│ ├── Container image scanning │
│ └── Extended SAST analysis │
│ │
│ Layer 4: SCHEDULED │
│ ├── Weekly deep SAST scan (Monday) │
│ ├── Weekly dependency audit (Sunday) │
│ ├── Daily container scanning │
│ └── Nightly regression security tests │
│ │
│ Layer 5: RELEASE │
│ ├── Final vulnerability gate │
│ ├── SBOM generation and signing │
│ ├── Provenance attestation │
│ └── Container signing │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Scanning Workflows
### 1. SAST Scanning (`sast-scan.yml`)
**Purpose:** Detect security vulnerabilities in source code through static analysis.
**Triggers:**
- Pull requests (source code changes)
- Push to main/develop
- Weekly Monday 3:30 AM UTC
- Manual dispatch
**Scanned Languages:**
- C# / .NET
- JavaScript / TypeScript
- Python
- YAML
- Dockerfile
**Checks Performed:**
| Check | Tool | Scope |
|-------|------|-------|
| Code vulnerabilities | Semgrep/CodeQL (placeholder) | All source |
| .NET security analyzers | Built-in Roslyn | C# code |
| Dependency vulnerabilities | `dotnet list --vulnerable` | NuGet packages |
| Dockerfile best practices | Hadolint | Dockerfiles |
**Configuration:**
```yaml
# sast-scan.yml inputs
workflow_dispatch:
inputs:
scan_level:
type: choice
options:
- quick # Fast scan, critical issues only
- standard # Default, balanced coverage
- comprehensive # Full scan, all rules
fail_on_findings:
type: boolean
default: true # Block on findings
```
**.NET Security Analyzer Rules:**
The workflow enforces these security-critical CA rules as errors:
| Category | Rules | Description |
|----------|-------|-------------|
| SQL Injection | CA2100 | Review SQL queries for vulnerabilities |
| Cryptography | CA5350-5403 | Weak crypto, insecure algorithms |
| Deserialization | CA2300-2362 | Unsafe deserialization |
| XML Security | CA3001-3012 | XXE, XPath injection |
| Web Security | CA3061, CA5358-5398 | XSS, CSRF, CORS |
---
### 2. Secrets Scanning (`secrets-scan.yml`)
**Purpose:** Detect hardcoded credentials, API keys, and secrets in code.
**Triggers:**
- Pull requests
- Push to main/develop
- Manual dispatch
**Detection Patterns:**
| Secret Type | Example Pattern |
|-------------|-----------------|
| API Keys | `sk_live_[a-zA-Z0-9]+` |
| AWS Keys | `AKIA[0-9A-Z]{16}` |
| Private Keys | `-----BEGIN RSA PRIVATE KEY-----` |
| Connection Strings | `Password=.*;User ID=.*` |
| JWT Tokens | `eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+` |
| GitHub Tokens | `gh[ps]_[A-Za-z0-9]{36}` |
**Tool Options (Placeholder):**
```yaml
# Choose one by uncommenting in sast-scan.yml:
# Option 1: TruffleHog (recommended for open source)
# - name: TruffleHog Scan
# uses: trufflesecurity/trufflehog@main
# with:
# extra_args: --only-verified
# Option 2: Gitleaks
# - name: Gitleaks Scan
# uses: gitleaks/gitleaks-action@v2
# env:
# GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
# Option 3: Semgrep
# - name: Semgrep Secrets
# uses: returntocorp/semgrep-action@v1
# with:
# config: p/secrets
```
**Allowlist Configuration:**
Create `.gitleaksignore` or `.secretsignore` for false positives:
```
# Ignore test fixtures
src/__Tests/**/*
docs/modules/**/samples/**/*
# Ignore specific files
path/to/test-credentials.json
# Ignore by rule ID
[allowlist]
regexes = ["test_api_key_[a-z]+"]
```
---
### 3. Container Scanning (`container-scan.yml`)
**Purpose:** Scan container images for OS and application vulnerabilities.
**Triggers:**
- Dockerfile changes
- Daily schedule (4 AM UTC)
- Manual dispatch
**Scan Targets:**
| Image | Built From | Scanned Components |
|-------|------------|-------------------|
| `stellaops/authority` | `src/Authority/Dockerfile` | OS packages, .NET runtime |
| `stellaops/scanner` | `src/Scanner/Dockerfile` | OS packages, .NET runtime, analyzers |
| `stellaops/concelier` | `src/Concelier/Dockerfile` | OS packages, .NET runtime |
| (9 total images) | ... | ... |
**Vulnerability Severity Levels:**
| Severity | Action | Example |
|----------|--------|---------|
| CRITICAL | Block release | Remote code execution |
| HIGH | Block release (configurable) | Privilege escalation |
| MEDIUM | Warning | Information disclosure |
| LOW | Log only | Minor issues |
| UNKNOWN | Log only | Unclassified |
**Tool Options (Placeholder):**
```yaml
# Choose one by uncommenting in container-scan.yml:
# Option 1: Trivy (recommended)
# - name: Trivy Scan
# uses: aquasecurity/trivy-action@master
# with:
# image-ref: ${{ steps.build.outputs.image }}
# format: sarif
# output: trivy-results.sarif
# severity: CRITICAL,HIGH
# Option 2: Grype
# - name: Grype Scan
# uses: anchore/scan-action@v3
# with:
# image: ${{ steps.build.outputs.image }}
# fail-build: true
# severity-cutoff: high
# Option 3: Snyk Container
# - name: Snyk Container
# uses: snyk/actions/docker@master
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
```
---
### 4. Dependency Security Scanning (`dependency-security-scan.yml`)
**Purpose:** Audit NuGet and npm packages for known vulnerabilities.
**Triggers:**
- Weekly Sunday 2 AM UTC
- Pull requests (dependency file changes)
- Manual dispatch
**Scanned Files:**
| Ecosystem | Files |
|-----------|-------|
| NuGet | `src/Directory.Packages.props`, `**/*.csproj` |
| npm | `**/package.json`, `**/package-lock.json` |
**Vulnerability Sources:**
- GitHub Advisory Database
- NVD (National Vulnerability Database)
- OSV (Open Source Vulnerabilities)
- Vendor security advisories
**Scan Process:**
```
┌─────────────────────────────────────────────────────────────────┐
│ DEPENDENCY SECURITY SCAN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ scan-nuget │ dotnet list package --vulnerable │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ scan-npm │ npm audit --json │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ summary │ Aggregate results, generate report │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
**Example Output:**
```
## Dependency Vulnerability Audit
### NuGet Packages
| Package | Installed | Vulnerable | Severity | Advisory |
|---------|-----------|------------|----------|----------|
| Newtonsoft.Json | 12.0.1 | < 13.0.1 | HIGH | GHSA-xxxx |
### npm Packages
| Package | Installed | Vulnerable | Severity | Advisory |
|---------|-----------|------------|----------|----------|
| lodash | 4.17.15 | < 4.17.21 | CRITICAL | npm:lodash:1 |
```
---
### 5. License Compliance (`dependency-license-gate.yml`)
**Purpose:** Ensure all dependencies use approved licenses.
**Approved Licenses:**
| License | SPDX ID | Status |
|---------|---------|--------|
| MIT | MIT | Approved |
| Apache 2.0 | Apache-2.0 | Approved |
| BSD 2-Clause | BSD-2-Clause | Approved |
| BSD 3-Clause | BSD-3-Clause | Approved |
| ISC | ISC | Approved |
| MPL 2.0 | MPL-2.0 | Review Required |
| LGPL 2.1+ | LGPL-2.1-or-later | Review Required |
| GPL 2.0+ | GPL-2.0-or-later | Blocked (copyleft) |
| AGPL 3.0 | AGPL-3.0 | Blocked (copyleft) |
**Blocked on Violation:**
- GPL-licensed runtime dependencies
- Unknown/proprietary licenses without explicit approval
---
## Scan Results & Reporting
### GitHub Step Summary
All security scans generate GitHub Step Summary reports:
```markdown
## SAST Scan Summary
| Check | Status |
|-------|--------|
| SAST Analysis | ✅ Pass |
| .NET Security | ⚠️ 3 warnings |
| Dependency Check | ✅ Pass |
| Dockerfile Lint | ✅ Pass |
### .NET Security Warnings
- CA5350: Weak cryptographic algorithm (src/Crypto/Legacy.cs:42)
- CA2100: SQL injection risk (src/Data/Query.cs:78)
```
### SARIF Integration
Scan results are uploaded in SARIF format for IDE integration:
```yaml
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: scan-results.sarif
```
### Artifact Retention
| Artifact | Retention |
|----------|-----------|
| SARIF files | 30 days |
| Vulnerability reports | 90 days |
| License audit logs | 1 year |
---
## Security Gates
### PR Merge Requirements
| Gate | Threshold | Block Merge? |
|------|-----------|--------------|
| SAST Critical | 0 | Yes |
| SAST High | 0 | Configurable |
| Secrets Found | 0 | Yes |
| Vulnerable Dependencies (Critical) | 0 | Yes |
| Vulnerable Dependencies (High) | 5 | Warning |
| License Violations | 0 | Yes |
### Release Requirements
| Gate | Threshold | Block Release? |
|------|-----------|----------------|
| Container Scan (Critical) | 0 | Yes |
| Container Scan (High) | 0 | Yes |
| SBOM Generation | Success | Yes |
| Signature Verification | Valid | Yes |
---
## Remediation Workflows
### Dependency Vulnerability Fix
1. **Renovate Auto-Fix:**
```yaml
# renovate.json
{
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"automerge": false
}
}
```
2. **Manual Override:**
```bash
# Update specific package
dotnet add package Newtonsoft.Json --version 13.0.3
# Audit and fix npm
npm audit fix
```
### False Positive Suppression
**.NET Analyzer Suppression:**
```csharp
// Suppress specific instance
#pragma warning disable CA2100 // Review SQL queries for vulnerability
var query = $"SELECT * FROM {tableName}";
#pragma warning restore CA2100
// Or in .editorconfig
[*.cs]
dotnet_diagnostic.CA2100.severity = none # NOT RECOMMENDED
```
**Semgrep/SAST Suppression:**
```csharp
// nosemgrep: sql-injection
var query = $"SELECT * FROM {tableName}";
```
**Container Scan Ignore:**
```yaml
# .trivyignore
CVE-2021-44228 # Log4j - not applicable (no Java)
CVE-2022-12345 # Accepted risk with mitigation
```
---
## Configuration Files
### Location
| File | Purpose | Location |
|------|---------|----------|
| `.gitleaksignore` | Secrets scan allowlist | Repository root |
| `.trivyignore` | Container scan ignore list | Repository root |
| `.semgrepignore` | SAST ignore patterns | Repository root |
| `renovate.json` | Dependency update config | Repository root |
| `.editorconfig` | Analyzer severity | Repository root |
### Example `.trivyignore`
```
# Ignore by CVE ID
CVE-2021-44228
# Ignore by package
pkg:npm/lodash@4.17.15
# Ignore with expiration
CVE-2022-12345 exp:2025-06-01
```
---
## Scheduled Scan Summary
| Day | Time (UTC) | Workflow | Focus |
|-----|------------|----------|-------|
| Daily | 2:00 AM | `nightly-regression.yml` | Security tests |
| Daily | 4:00 AM | `container-scan.yml` | Image vulnerabilities |
| Sunday | 2:00 AM | `dependency-security-scan.yml` | Package audit |
| Monday | 3:30 AM | `sast-scan.yml` | Deep code analysis |
---
## Monitoring & Alerts
### Notification Channels
Configure notifications for security findings:
```yaml
# In workflow
- name: Notify on Critical
if: steps.scan.outputs.critical_count > 0
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-d '{"text":"🚨 Critical security finding in '${{ github.repository }}'"}'
```
### Dashboard Integration
Security scan results can be exported to:
- Grafana dashboards (via OTLP metrics)
- Security Information and Event Management (SIEM)
- Vulnerability management platforms
---
## Related Documentation
- [README - CI/CD Overview](./README.md)
- [Workflow Triggers](./workflow-triggers.md)
- [Release Pipelines](./release-pipelines.md)
- [Dependency Management](../operations/dependency-management.md)
- [SBOM Guide](../sbom/guide.md)

View File

@@ -0,0 +1,461 @@
# Test Strategy
> Complete guide to the StellaOps testing strategy and CI/CD integration.
---
## Test Category Overview
StellaOps uses a **tiered testing strategy** with 13 test categories:
### PR-Gating Tests (Required for Merge)
| Category | Purpose | Timeout | Parallelism |
|----------|---------|---------|-------------|
| **Unit** | Isolated component tests | 20 min | High |
| **Architecture** | Dependency rule enforcement | 15 min | High |
| **Contract** | API compatibility | 15 min | High |
| **Integration** | Database/service integration | 45 min | Medium |
| **Security** | Security-focused assertions | 25 min | High |
| **Golden** | Corpus-based validation | 25 min | High |
### Extended Tests (Scheduled/On-Demand)
| Category | Purpose | Timeout | Trigger |
|----------|---------|---------|---------|
| **Performance** | Latency/throughput benchmarks | 45 min | Daily, Manual |
| **Benchmark** | BenchmarkDotNet profiling | 60 min | Daily, Manual |
| **AirGap** | Offline operation validation | 45 min | Manual |
| **Chaos** | Resilience testing | 45 min | Manual |
| **Determinism** | Reproducibility verification | 45 min | Manual |
| **Resilience** | Failure recovery testing | 45 min | Manual |
| **Observability** | Telemetry validation | 30 min | Manual |
---
## Test Discovery
### Automatic Discovery
The `test-matrix.yml` workflow automatically discovers all test projects:
```bash
# Discovery pattern
find src \( \
-name "*.Tests.csproj" \
-o -name "*UnitTests.csproj" \
-o -name "*SmokeTests.csproj" \
-o -name "*FixtureTests.csproj" \
-o -name "*IntegrationTests.csproj" \
\) -type f \
! -path "*/node_modules/*" \
! -path "*/bin/*" \
! -path "*/obj/*" \
! -name "StellaOps.TestKit.csproj" \
! -name "*Testing.csproj"
```
### Test Category Trait
Tests are categorized using xUnit traits:
```csharp
[Trait("Category", "Unit")]
public class MyUnitTests
{
[Fact]
public void Should_Do_Something()
{
// ...
}
}
[Trait("Category", "Integration")]
public class MyIntegrationTests
{
[Fact]
public void Should_Connect_To_Database()
{
// ...
}
}
```
### Running Specific Categories
```bash
# Run Unit tests only
dotnet test --filter "Category=Unit"
# Run multiple categories
dotnet test --filter "Category=Unit|Category=Integration"
# Run excluding a category
dotnet test --filter "Category!=Performance"
```
---
## Test Infrastructure
### Shared Libraries
| Library | Purpose | Location |
|---------|---------|----------|
| `StellaOps.TestKit` | Common test utilities | `src/__Tests/__Libraries/` |
| `StellaOps.Infrastructure.Postgres.Testing` | PostgreSQL fixtures | `src/__Tests/__Libraries/` |
| `StellaOps.Concelier.Testing` | Concelier test fixtures | `src/Concelier/__Tests/` |
### Testcontainers
Integration tests use Testcontainers for isolated dependencies:
```csharp
public class PostgresFixture : IAsyncLifetime
{
private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
.WithImage("postgres:16")
.WithDatabase("test_db")
.Build();
public string ConnectionString => _container.GetConnectionString();
public Task InitializeAsync() => _container.StartAsync();
public Task DisposeAsync() => _container.DisposeAsync().AsTask();
}
```
### Ground Truth Corpus
Golden tests use a corpus of known-good outputs:
```
src/__Tests/__Datasets/
├── scanner/
│ ├── golden/
│ │ ├── npm-package.expected.json
│ │ ├── dotnet-project.expected.json
│ │ └── container-image.expected.json
│ └── fixtures/
│ ├── npm-package/
│ ├── dotnet-project/
│ └── container-image/
└── concelier/
├── golden/
└── fixtures/
```
---
## CI/CD Integration
### Test Matrix Workflow
```yaml
# Simplified test-matrix.yml structure
jobs:
discover:
# Find all test projects
outputs:
test-projects: ${{ steps.find.outputs.projects }}
pr-gating-tests:
strategy:
matrix:
include:
- category: Unit
timeout: 20
- category: Architecture
timeout: 15
- category: Contract
timeout: 15
- category: Security
timeout: 25
- category: Golden
timeout: 25
steps:
- run: .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}"
integration:
services:
postgres:
image: postgres:16
steps:
- run: .gitea/scripts/test/run-test-category.sh Integration
summary:
needs: [discover, pr-gating-tests, integration]
```
### Test Results
All tests produce TRX (Visual Studio Test Results) files:
```bash
# Output structure
TestResults/
├── Unit/
│ ├── src_Scanner___Tests_StellaOps.Scanner.Tests-unit.trx
│ └── src_Authority___Tests_StellaOps.Authority.Tests-unit.trx
├── Integration/
│ └── ...
└── Combined/
└── test-results-combined.trx
```
### Coverage Collection
```yaml
# Collect coverage for Unit tests
- run: |
.gitea/scripts/test/run-test-category.sh Unit --collect-coverage
```
Coverage reports are generated in Cobertura format and converted to HTML.
---
## Test Categories Deep Dive
### Unit Tests
**Purpose:** Test isolated components without external dependencies.
**Characteristics:**
- No I/O (database, network, file system)
- No async waits or delays
- Fast execution (< 100ms per test)
- High parallelism
**Example:**
```csharp
[Trait("Category", "Unit")]
public class VexPolicyBinderTests
{
[Fact]
public void Bind_WithValidPolicy_ReturnsSuccess()
{
var binder = new VexPolicyBinder();
var policy = new VexPolicy { /* ... */ };
var result = binder.Bind(policy);
Assert.True(result.IsSuccess);
}
}
```
### Architecture Tests
**Purpose:** Enforce architectural rules and dependency constraints.
**Rules Enforced:**
- Layer dependencies (UI Application Domain Infrastructure)
- Namespace conventions
- Circular dependency prevention
- Interface segregation
**Example:**
```csharp
[Trait("Category", "Architecture")]
public class DependencyTests
{
[Fact]
public void Domain_Should_Not_Depend_On_Infrastructure()
{
var result = Types.InAssembly(typeof(DomainMarker).Assembly)
.That().ResideInNamespace("StellaOps.Domain")
.ShouldNot().HaveDependencyOn("StellaOps.Infrastructure")
.GetResult();
Assert.True(result.IsSuccessful);
}
}
```
### Contract Tests
**Purpose:** Validate API contracts are maintained.
**Checks:**
- Request/response schemas
- OpenAPI specification compliance
- Backward compatibility
**Example:**
```csharp
[Trait("Category", "Contract")]
public class VulnerabilityApiContractTests
{
[Fact]
public async Task GetVulnerability_ReturnsExpectedSchema()
{
var response = await _client.GetAsync("/api/v1/vulnerabilities/CVE-2024-1234");
await Verify(response)
.UseDirectory("Snapshots")
.UseMethodName("GetVulnerability");
}
}
```
### Integration Tests
**Purpose:** Test component integration with real dependencies.
**Dependencies:**
- PostgreSQL (via Testcontainers)
- Valkey/Redis (via Testcontainers)
- File system
**Example:**
```csharp
[Trait("Category", "Integration")]
public class VulnerabilityRepositoryTests : IClassFixture<PostgresFixture>
{
private readonly PostgresFixture _fixture;
[Fact]
public async Task Save_AndRetrieve_Vulnerability()
{
var repo = new VulnerabilityRepository(_fixture.ConnectionString);
var vuln = new Vulnerability { Id = "CVE-2024-1234" };
await repo.SaveAsync(vuln);
var retrieved = await repo.GetAsync("CVE-2024-1234");
Assert.Equal(vuln.Id, retrieved.Id);
}
}
```
### Security Tests
**Purpose:** Validate security controls and assertions.
**Checks:**
- Input validation
- Authorization enforcement
- Cryptographic operations
- Secrets handling
**Example:**
```csharp
[Trait("Category", "Security")]
public class AuthorizationTests
{
[Fact]
public async Task Unauthorized_User_Cannot_Access_Admin_Endpoint()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = null;
var response = await client.GetAsync("/api/admin/settings");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
}
```
### Golden Tests
**Purpose:** Verify output matches known-good corpus.
**Example:**
```csharp
[Trait("Category", "Golden")]
public class ScannerGoldenTests
{
[Theory]
[InlineData("npm-package")]
[InlineData("dotnet-project")]
public async Task Scan_MatchesGoldenOutput(string fixture)
{
var scanner = new ContainerScanner();
var result = await scanner.ScanAsync($"fixtures/{fixture}");
await Verify(result)
.UseDirectory("golden")
.UseFileName(fixture);
}
}
```
---
## Performance Testing
### BenchmarkDotNet
```csharp
[Trait("Category", "Benchmark")]
[MemoryDiagnoser]
public class ScannerBenchmarks
{
[Benchmark]
public async Task ScanSmallImage()
{
await _scanner.ScanAsync(_smallImage);
}
[Benchmark]
public async Task ScanLargeImage()
{
await _scanner.ScanAsync(_largeImage);
}
}
```
### Performance SLOs
| Metric | Target | Action on Breach |
|--------|--------|------------------|
| Unit test P95 | < 100ms | Warning |
| Integration test P95 | < 5s | Warning |
| Scan time P95 | < 5 min | Block |
| Memory peak | < 2GB | Block |
---
## Troubleshooting
### Tests Fail in CI but Pass Locally
1. **Check timezone** - CI uses `TZ=UTC`
2. **Check parallelism** - CI runs tests in parallel
3. **Check container availability** - Testcontainers requires Docker
4. **Check file paths** - Case sensitivity on Linux
### Flaky Tests
1. **Add retry logic** for network operations
2. **Use proper async/await** - no `Task.Run` for async operations
3. **Isolate shared state** - use fresh fixtures per test
4. **Increase timeouts** for integration tests
### Missing Test Category
Ensure your test class has the correct trait:
```csharp
[Trait("Category", "Unit")] // Add this!
public class MyTests
{
// ...
}
```
---
## Related Documentation
- [README - CI/CD Overview](./README.md)
- [Workflow Triggers](./workflow-triggers.md)
- [CI Quality Gates](../testing/ci-quality-gates.md)
- [Test Catalog](../testing/TEST_CATALOG.yml)

View File

@@ -0,0 +1,719 @@
# Workflow Triggers & Dependencies
> Complete reference for CI/CD workflow triggering rules and dependency chains.
---
## Trigger Types Overview
### 1. Push Triggers
Workflows triggered by commits pushed to branches.
```yaml
on:
push:
branches: [main, develop] # Branch filter
paths: # Path filter (optional)
- 'src/**'
paths-ignore: # Exclude paths (optional)
- 'docs/**'
tags: # Tag filter (for releases)
- 'v*'
```
### 2. Pull Request Triggers
Workflows triggered by PR events.
```yaml
on:
pull_request:
branches: [main, develop] # Target branch filter
types: [opened, synchronize, reopened] # Event types
paths:
- 'src/**'
```
### 3. Schedule Triggers
Cron-based scheduled execution.
```yaml
on:
schedule:
- cron: '0 5 * * *' # Daily at 5 AM UTC
- cron: '0 2 * * 0' # Weekly Sunday at 2 AM UTC
```
### 4. Manual Triggers
On-demand workflow execution with inputs.
```yaml
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
dry_run:
type: boolean
default: false
```
### 5. Workflow Call (Reusable)
Called by other workflows.
```yaml
on:
workflow_call:
inputs:
category:
type: string
required: true
```
---
## Complete Trigger Matrix
### PR-Gating Workflows (Always Run on PR)
| Workflow | Branches | Path Filters | Purpose |
|----------|----------|--------------|---------|
| `test-matrix.yml` | main | `!docs/**`, `!*.md` | Unit, Architecture, Contract, Integration, Security, Golden tests |
| `build-test-deploy.yml` | main, develop | `src/**`, `docs/**`, `scripts/**` | Build verification |
| `policy-lint.yml` | main | `docs/policy/**`, `src/Cli/**` | Policy file validation |
| `sast-scan.yml` | main, develop | `src/**`, `*.cs`, `*.ts`, `Dockerfile*` | Static security analysis |
| `docs.yml` | - | `docs/**`, `scripts/render_docs.py` | Documentation validation |
| `integration-tests-gate.yml` | main, develop | `src/**`, `src/__Tests/**` | Extended integration |
### Main Branch Only Workflows
| Workflow | Trigger Condition | Purpose |
|----------|-------------------|---------|
| `build-test-deploy.yml` → deploy | `github.ref == 'refs/heads/main'` | Deploy to staging |
| `integration-tests-gate.yml` → corpus-validation | `github.ref == 'refs/heads/main'` | Ground truth validation |
| `coverage-report` | After integration tests on main | Full coverage analysis |
### Tag-Triggered Workflows
| Workflow | Tag Pattern | Example | Purpose |
|----------|-------------|---------|---------|
| `release-suite.yml` | `suite-*` | `suite-2026.04` | Ubuntu-style suite release |
| `release.yml` | `v*` | `v2025.12.1`, `v2025.12.0-edge` | Version bundle release |
| `module-publish.yml` | `module-*-v*` | `module-authority-v1.2.3` | Per-module publishing |
### Scheduled Workflows
| Workflow | Schedule (UTC) | Frequency | Purpose |
|----------|----------------|-----------|---------|
| `nightly-regression.yml` | `0 2 * * *` | Daily 2 AM | Full regression suite |
| `dependency-security-scan.yml` | `0 2 * * 0` | Sunday 2 AM | Vulnerability audit |
| `renovate.yml` | `0 3,15 * * *` | Daily 3 AM & 3 PM | Dependency updates |
| `sast-scan.yml` | `30 3 * * 1` | Monday 3:30 AM | Weekly deep scan |
| `migration-test.yml` | `30 4 * * *` | Daily 4:30 AM | Migration validation |
| `build-test-deploy.yml` | `0 5 * * *` | Daily 5 AM | Extended build tests |
| `test-matrix.yml` | `0 5 * * *` | Daily 5 AM | Extended test categories |
### Manual-Only Workflows
| Workflow | Inputs | Purpose |
|----------|--------|---------|
| `cli-build.yml` | rids, config, sign | Multi-platform CLI builds |
| `scanner-determinism.yml` | - | Verify scanner reproducibility |
| `cross-platform-determinism.yml` | - | Cross-OS build verification |
| `rollback.yml` | environment, service, version | Emergency rollback |
| `promote.yml` | from_env, to_env, version | Environment promotion |
---
## Dependency Chains
### Build → Test → Deploy Pipeline
```
┌─────────────────────────────────────────────────────────────────┐
│ build-test-deploy.yml │
├─────────────────────────────────────────────────────────────────┤
│ │
│ profile-validation ─────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ build-test ─────────────────────────────────────────┤ │
│ │ (CLI, Concelier, Authority, Scanner, etc.) │ │
│ │ │ │
│ ▼ │ │
│ quality-gates ──────────────────────────────────────┤ │
│ │ (Reachability, TTFS, Performance SLOs) │ │
│ │ │ │
│ ▼ │ │
│ security-testing (PR label or schedule) ────────────┤ │
│ │ │ │
│ ▼ │ │
│ sealed-mode-ci ─────────────────────────────────────┤ │
│ │ │ │
│ ▼ │ │
│ docs ───────────────────────────────────────────────┤ │
│ │ │ │
│ ▼ │ │
│ scanner-perf ───────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ deploy (main branch only OR workflow_dispatch) │
│ │ │
│ ▼ │
│ summary │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Test Matrix Pipeline
```
┌─────────────────────────────────────────────────────────────────┐
│ test-matrix.yml │
├─────────────────────────────────────────────────────────────────┤
│ │
│ discover ─────────────────────────────────────────────────┐ │
│ │ (Find all *.Tests.csproj files) │ │
│ │ │ │
│ ├───▶ pr-gating-tests (Matrix: 5 categories) │ │
│ │ ├── Unit │ │
│ │ ├── Architecture │ │
│ │ ├── Contract │ │
│ │ ├── Security │ │
│ │ └── Golden │ │
│ │ │ │
│ ├───▶ integration (PostgreSQL service) │ │
│ │ │ │
│ └───▶ extended-tests (schedule or manual) │ │
│ ├── Performance │ │
│ ├── Benchmark │ │
│ ├── AirGap │ │
│ ├── Chaos │ │
│ ├── Determinism │ │
│ ├── Resilience │ │
│ └── Observability │ │
│ │ │ │
│ ◀────────────────────────────────────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ summary ◀─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Suite Release Pipeline
```
┌─────────────────────────────────────────────────────────────────┐
│ release-suite.yml │
│ (suite-* tag OR manual) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ parse-tag (if push event) ────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ validate ─────────────────────────────────────────────────┤ │
│ │ │ │
│ ▼ │ │
│ test-gate (optional, skip with skip_tests=true) ──────────┤ │
│ │ │ │
│ ├───▶ build-modules (Matrix: 9 modules) │ │
│ │ ├── Authority │ │
│ │ ├── Scanner │ │
│ │ ├── Concelier │ │
│ │ ├── Excititor │ │
│ │ ├── SbomService │ │
│ │ ├── EvidenceLocker │ │
│ │ ├── Policy │ │
│ │ ├── Attestor │ │
│ │ └── VexLens │ │
│ │ │ │
│ ├───▶ build-containers (Matrix: 9 images) │ │
│ │ │ │
│ ├───▶ build-cli (Matrix: 5 runtimes) │ │
│ │ ├── linux-x64 │ │
│ │ ├── linux-arm64 │ │
│ │ ├── win-x64 │ │
│ │ ├── osx-x64 │ │
│ │ └── osx-arm64 │ │
│ │ │ │
│ └───▶ build-helm │ │
│ │ │
│ ◀────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ release-manifest ─────────────────────────────────────────┐ │
│ │ │ │
│ ├───▶ generate-changelog │ │
│ ├───▶ generate-suite-docs │ │
│ └───▶ generate-compose │ │
│ │ │
│ ◀────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ create-release ───────────────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ commit-docs ──────────────────────────────────────────────┤ │
│ │ │ │
│ ▼ │ │
│ summary ◀─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Conditional Execution Patterns
### Branch-Based Conditions
```yaml
# Deploy only on main branch
deploy:
if: github.ref == 'refs/heads/main'
# Run on main or develop
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
# Skip on release branches
if: "!startsWith(github.ref, 'refs/heads/release/')"
```
### Event-Based Conditions
```yaml
# Different behavior based on trigger
steps:
- name: Full scan (schedule)
if: github.event_name == 'schedule'
run: ./scan.sh --full
- name: Quick scan (PR)
if: github.event_name == 'pull_request'
run: ./scan.sh --quick
```
### Input-Based Conditions
```yaml
# Run extended tests if requested
extended-tests:
if: >-
github.event_name == 'schedule' ||
github.event.inputs.include_performance == 'true' ||
github.event.inputs.include_benchmark == 'true'
```
### Failure-Based Conditions
```yaml
# Run cleanup on failure
cleanup:
if: failure()
# Run notification always
notify:
if: always()
# Run only on success
deploy:
if: success()
```
### Complex Conditions
```yaml
# Deploy gate: multiple conditions
deploy:
if: >-
needs.build-test.result == 'success' &&
needs.docs.result == 'success' &&
needs.scanner-perf.result == 'success' &&
((github.event_name == 'push' && github.ref == 'refs/heads/main') ||
github.event_name == 'workflow_dispatch')
```
---
## Path Filters Reference
### Common Path Patterns
| Pattern | Matches | Example Files |
|---------|---------|---------------|
| `src/**` | All source code | `src/Scanner/Program.cs` |
| `docs/**` | All documentation | `docs/api/openapi.yaml` |
| `*.md` | Root markdown | `README.md`, `CHANGELOG.md` |
| `**/*.csproj` | All project files | `src/Cli/StellaOps.Cli.csproj` |
| `devops/**` | DevOps config | `devops/helm/values.yaml` |
| `.gitea/workflows/**` | Workflow files | `.gitea/workflows/test-matrix.yml` |
### Path Filter Examples
```yaml
# Source code changes only
paths:
- 'src/**'
- '!src/**/*.md' # Exclude markdown in src
# Documentation only
paths:
- 'docs/**'
- '*.md'
- 'scripts/render_docs.py'
# Security-relevant files
paths:
- 'src/**/*.cs'
- 'src/**/*.csproj'
- '**/Dockerfile*'
- '.gitea/workflows/sast-scan.yml'
# Dependency files
paths:
- 'src/Directory.Packages.props'
- '**/package.json'
- '**/package-lock.json'
- '**/*.csproj'
```
---
## Tag Patterns Reference
### Semantic Version Tags
```yaml
# Standard releases
tags:
- 'v*' # v1.0.0, v2025.12.1, v2025.12.0-edge
# Channel-specific
tags:
- 'v*-edge' # v2025.12.0-edge
- 'v*-stable' # v2025.12.0-stable
- 'v*-lts' # v2025.12.0-lts
```
### Suite Tags
```yaml
tags:
- 'suite-*' # suite-2026.04, suite-2026.10
```
### Module Tags
```yaml
tags:
- 'module-*-v*' # module-authority-v1.2.3
# module-scanner-v2.0.0
# module-cli-v3.1.0
```
---
## Workflow Inputs Reference
### Common Input Types
```yaml
workflow_dispatch:
inputs:
# String input
version:
description: 'Version to release'
required: true
type: string
# Choice input
environment:
description: 'Target environment'
type: choice
options:
- staging
- production
default: staging
# Boolean input
dry_run:
description: 'Run without making changes'
type: boolean
default: false
# Multi-select (via string)
rids:
description: 'Runtime identifiers (comma-separated)'
type: string
default: 'linux-x64,linux-arm64,win-x64'
```
### Accessing Inputs
```yaml
steps:
- name: Use input
run: |
echo "Version: ${{ github.event.inputs.version }}"
echo "Environment: ${{ inputs.environment }}"
if [[ "${{ inputs.dry_run }}" == "true" ]]; then
echo "Dry run mode"
fi
```
---
## Best Practices
### 1. Minimize PR Workflow Duration
```yaml
# Use path filters to skip irrelevant runs
paths-ignore:
- 'docs/**'
- '*.md'
- 'LICENSE'
# Use concurrency to cancel outdated runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
```
### 2. Fail Fast for Critical Issues
```yaml
strategy:
fail-fast: true # Stop all jobs if one fails
matrix:
category: [Unit, Integration, Security]
```
### 3. Use Matrix for Parallel Execution
```yaml
strategy:
matrix:
include:
- category: Unit
timeout: 20
- category: Integration
timeout: 45
```
### 4. Preserve Artifacts
```yaml
- uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.category }}
path: ./TestResults
retention-days: 14 # PR artifacts
# retention-days: 90 # Release artifacts
```
### 5. Use Conditional Steps
```yaml
- name: Deploy (main only)
if: github.ref == 'refs/heads/main'
run: ./deploy.sh
- name: Notify on failure
if: failure()
run: ./notify-slack.sh
```
---
## Troubleshooting Triggers
### Workflow Not Running
1. **Check branch protection rules** - Ensure workflow runs are allowed
2. **Verify path filters** - File changes must match `paths` patterns
3. **Check `if` conditions** - Job may be skipped by condition
4. **Review concurrency settings** - May be cancelled by concurrent run
### Workflow Running Unexpectedly
1. **Check `paths-ignore`** - May need to exclude more paths
2. **Review schedule** - Cron schedule may overlap with events
3. **Check tag patterns** - Tag may match unexpected pattern
### Schedule Not Triggering
1. **Verify cron syntax** - Use [crontab.guru](https://crontab.guru/)
2. **Check workflow file location** - Must be on default branch
3. **Review repository activity** - Inactive repos may have schedules disabled
---
## Trigger Decision Tree
Use this decision tree to determine which workflows run for each event:
```
On PUSH to branch:
├── Is branch main/develop?
│ ├── YES → Run Category A (PR-Gating) + B (Main-Only) + affected C (Module)
│ └── NO (feature branch) → Skip CI (rely on PR workflow)
On PULL REQUEST:
├── Check changed paths
│ ├── docs/** only → Skip all (or run docs.yml only)
│ ├── src/** changed → Run Category A + affected C modules
│ └── *.csproj or *.props changed → Run Category A + B (full infrastructure)
On TAG push:
├── Match tag pattern
│ ├── suite-* → release-suite.yml
│ ├── module-*-v* → module-publish.yml
│ ├── service-*-v* → service-release.yml
│ ├── v*.*.* → containers-multiarch.yml + cli-build.yml
│ └── Other → Ignore
On SCHEDULE:
└── Run Category E pipelines per cron schedule
```
---
## Smart Dependency Cascading
When shared libraries change, dependent module tests must also run:
### Dependency Graph
```
SHARED LIBRARY TRIGGERS TESTS FOR
─────────────────────────────────────────────────────────────────
StellaOps.Cryptography* → ALL modules (security-critical)
- Scanner, Attestor, Authority
- EvidenceLocker, Signer
- AirGap, Offline tests
- Security test suite
StellaOps.Evidence* → Scanner, Attestor, EvidenceLocker
StellaOps.Provenance → ExportCenter, SbomService
StellaOps.Infrastructure.* → ALL integration tests
StellaOps.Postgres* (database-dependent modules)
StellaOps.Replay* → Scanner, Determinism tests
StellaOps.Determinism Replay module tests
StellaOps.Verdict → Policy, RiskEngine, ReachGraph
StellaOps.DeltaVerdict
StellaOps.Plugin → Authority, Scanner, Concelier
(plugin-based modules)
Directory.Build.props → ALL modules (build config)
Directory.Packages.props ALL tests
nuget.config
```
### Cascade Implementation
Each workflow includes paths from its dependencies:
```yaml
# Example: scanner-ci.yml with cascading
name: Scanner CI
on:
push:
branches: [main]
paths:
# Direct module paths
- 'src/Scanner/**'
- 'src/BinaryIndex/**'
# Shared library dependencies (cascading)
- 'src/__Libraries/StellaOps.Evidence*/**'
- 'src/__Libraries/StellaOps.Cryptography*/**'
- 'src/__Libraries/StellaOps.Replay*/**'
- 'src/__Libraries/StellaOps.Provenance/**'
# Infrastructure (triggers full test)
- 'Directory.Build.props'
- 'Directory.Packages.props'
```
### Cascade Matrix Quick Reference
| When This Changes | Run These Tests |
|-------------------|-----------------|
| `src/__Libraries/StellaOps.Cryptography*/**` | Scanner, Attestor, Authority, Evidence, Signer, AirGap, Security |
| `src/__Libraries/StellaOps.Evidence*/**` | Scanner, Attestor, EvidenceLocker, Export |
| `src/__Libraries/StellaOps.Infrastructure*/**` | ALL integration tests |
| `src/__Libraries/StellaOps.Replay*/**` | Scanner, Determinism, Replay |
| `src/__Libraries/StellaOps.Verdict/**` | Policy, RiskEngine, ReachGraph |
| `src/__Libraries/StellaOps.Plugin/**` | Authority, Scanner, Concelier |
| `Directory.Build.props` | ALL modules |
---
## Master Trigger Configuration
### Complete Workflow Trigger Table
| Workflow | Feature Branch | PR | Main Push | Tag | Schedule |
|----------|:--------------:|:--:|:---------:|:---:|:--------:|
| **Category A: PR-Gating** |||||
| build-test-deploy.yml | ❌ | ✅ | ✅ | ❌ | ✅ Daily |
| test-matrix.yml | ❌ | ✅ | ✅ | ❌ | ✅ Daily |
| determinism-gate.yml | ❌ | ✅ | ✅ | ❌ | ❌ |
| policy-lint.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| sast-scan.yml | ❌ | ✅ | ✅ | ❌ | ✅ Weekly |
| secrets-scan.yml | ❌ | ✅ | ✅ | ❌ | ❌ |
| dependency-license-gate.yml | ❌ | ✅* | ❌ | ❌ | ❌ |
| **Category B: Main-Only** |||||
| container-scan.yml | ❌ | ❌ | ✅* | ❌ | ✅ Daily |
| integration-tests-gate.yml | ❌ | ❌ | ✅ | ❌ | ❌ |
| api-governance.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| aoc-guard.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| provenance-check.yml | ❌ | ❌ | ✅ | ❌ | ❌ |
| **Category C: Module-Specific** |||||
| scanner-*.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| concelier-*.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| authority-*.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| findings-ledger-ci.yml | ❌ | ✅* | ✅* | ❌ | ❌ |
| evidence-locker.yml | ❌ | ❌ | ❌ | ❌ | ❌ (manual) |
| [all module-*.yml] | ❌ | ✅* | ✅* | ❌ | ❌ |
| **Category D: Release** |||||
| release-suite.yml | ❌ | ❌ | ❌ | ✅ suite-* | ❌ |
| module-publish.yml | ❌ | ❌ | ❌ | ✅ module-*-v* | ❌ |
| service-release.yml | ❌ | ❌ | ❌ | ✅ service-*-v* | ❌ |
| release.yml | ❌ | ❌ | ❌ | ✅ v* | ❌ |
| cli-build.yml | ❌ | ❌ | ❌ | ❌ | ❌ (manual) |
| containers-multiarch.yml | ❌ | ❌ | ❌ | ❌ | ❌ (manual) |
| rollback.yml | ❌ | ❌ | ❌ | ❌ | ❌ (manual) |
| promote.yml | ❌ | ❌ | ❌ | ❌ | ❌ (manual) |
| **Category E: Scheduled** |||||
| nightly-regression.yml | ❌ | ❌ | ❌ | ❌ | ✅ 2AM |
| dependency-security-scan.yml | ❌ | ✅* | ❌ | ❌ | ✅ Sun 2AM |
| container-scan.yml | ❌ | ❌ | ✅* | ❌ | ✅ 4AM |
| renovate.yml | ❌ | ❌ | ❌ | ❌ | ✅ 3AM/3PM |
| migration-test.yml | ❌ | ❌ | ❌ | ❌ | ✅ 4:30AM |
*Legend: ✅* = with path filter, ✅ = always, ❌ = never*
---
## Related Documentation
- [README - CI/CD Overview](./README.md)
- [Release Pipelines](./release-pipelines.md)
- [Test Strategy](./test-strategy.md)
- [Path Filters Reference](./path-filters.md)
- [Troubleshooting](../../.gitea/docs/troubleshooting.md)

View File

@@ -0,0 +1,51 @@
# Snapshot
**Status:** Design/Planning
**Source:** N/A (cross-cutting concept)
**Owner:** Platform Team
## Purpose
Snapshot defines the knowledge snapshot model for deterministic, point-in-time captures of StellaOps data. Enables offline operation, merge preview, replay, and air-gap export with cryptographic integrity.
## Components
**Concept Documentation:**
- `merge-preview.md` - Merge preview specification
- `replay-yaml.md` - Replay YAML format and semantics
**Snapshot Types:**
- Advisory snapshots (Concelier ingestion state)
- VEX snapshots (VexHub distribution state)
- SBOM snapshots (SbomService repository state)
- Policy snapshots (Policy Engine rule state)
- Task pack snapshots (PacksRegistry versions)
## Implementation Locations
Snapshot functionality is implemented across multiple modules:
- **AirGap** - Snapshot export/import orchestration
- **ExportCenter** - Snapshot bundle creation
- **Replay** - Deterministic replay from snapshots
- **Concelier** - Advisory snapshot merge preview
- All data modules (snapshot sources)
## Dependencies
- AirGap (snapshot orchestration)
- ExportCenter (bundle creation)
- Replay (snapshot replay)
- All data modules (snapshot sources)
## Related Documentation
- Merge Preview: `./merge-preview.md`
- Replay YAML: `./replay-yaml.md`
- AirGap: `../airgap/`
- ExportCenter: `../export-center/`
- Replay: `../replay/` (if exists)
- Offline Kit: `../../OFFLINE_KIT.md`
## Current Status
Snapshot concepts documented in merge-preview.md and replay-yaml.md. Implementation distributed across AirGap (export/import), ExportCenter (packaging), and Replay (playback) modules. Used for offline/air-gap operation.

View File

@@ -0,0 +1,250 @@
# Policy Merge Preview
## Overview
The **Policy Merge Preview** shows how VEX statements from different sources combine using lattice logic. This visualization helps analysts understand:
1. What each source contributes
2. How conflicts are resolved
3. What evidence is missing
4. The final merged status
## Merge Semantics
StellaOps uses a three-layer merge model:
```
vendor ⊕ distro ⊕ internal = final
```
Where ⊕ represents the lattice join operation.
### Layer Hierarchy
| Layer | Description | Typical Trust |
|-------|-------------|---------------|
| **Vendor** | Software vendor's VEX statements | 0.95-1.0 |
| **Distro** | Distribution maintainer's assessments | 0.85-0.95 |
| **Internal** | Organization's own assessments | 0.70-0.90 |
### Lattice Operations
Using K4 (Kleene's 4-valued logic):
| Status | Lattice Position |
|--------|-----------------|
| Affected | Top (Both) |
| UnderInvestigation | Unknown (Neither) |
| Fixed | True |
| NotAffected | False |
Join table (⊕):
```
| Affected | Under... | Fixed | NotAffected
---------+----------+----------+-------+------------
Affected | A | A | A | A
Under... | A | U | U | U
Fixed | A | U | F | F
NotAff | A | U | F | N
```
## API Endpoint
### GET /policy/merge-preview/{cveId}
**Parameters:**
- `cveId`: CVE identifier
- `artifact`: Artifact digest (query param)
**Response:**
```json
{
"cveId": "CVE-2024-1234",
"artifactDigest": "sha256:abc...",
"contributions": [
{
"layer": "vendor",
"sources": ["lodash-security"],
"status": "NotAffected",
"trustScore": 0.95,
"statements": [...],
"mergeTrace": {
"explanation": "Vendor states not affected due to version"
}
},
{
"layer": "distro",
"sources": ["redhat-csaf"],
"status": "Affected",
"trustScore": 0.90,
"statements": [...],
"mergeTrace": {
"explanation": "Distro states affected without context"
}
},
{
"layer": "internal",
"sources": [],
"status": null,
"trustScore": 0,
"statements": [],
"mergeTrace": null
}
],
"finalStatus": "NotAffected",
"finalConfidence": 0.925,
"missingEvidence": [
{
"type": "internal-assessment",
"description": "Add internal security assessment",
"priority": "medium"
}
],
"latticeType": "K4",
"generatedAt": "2024-12-22T12:00:00Z"
}
```
## Conflict Resolution
When sources disagree, resolution follows:
1. **Trust Weight**: Higher trust wins
2. **Lattice Position**: If trust equal, higher lattice position wins
3. **Freshness**: If still tied, more recent statement wins
4. **Tie**: First statement wins
### Resolution Trace
Each merge includes explanation:
```json
{
"leftSource": "vendor:lodash",
"rightSource": "distro:redhat",
"leftStatus": "NotAffected",
"rightStatus": "Affected",
"leftTrust": 0.95,
"rightTrust": 0.90,
"resultStatus": "NotAffected",
"explanation": "'vendor:lodash' has higher trust weight than 'distro:redhat'"
}
```
## UI Components
### Layer Cards
Each layer shows:
- Source names
- Current status badge
- Trust score bar
- Statement count
### Merge Flow Diagram
Visual representation:
```
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐
│ Vendor │ ──⊕─│ Distro │ ──⊕─│ Internal │ ──=─│ Final │
│ NotAff. │ │ Affected│ │ — │ │ NotAff. │
│ (0.95) │ │ (0.90) │ │ │ │ (0.925) │
└─────────┘ └─────────┘ └──────────┘ └─────────┘
```
### Missing Evidence CTA
Prominent call-to-action for missing evidence:
```
┌─────────────────────────────────────────┐
│ ⚠ Improve Confidence │
│ │
│ [+] Add internal security assessment │
│ Increases confidence by ~5% │
│ │
│ [+] Add reachability analysis │
│ Determines if code is called │
└─────────────────────────────────────────┘
```
### Merge Traces (Expandable)
Detailed explanation of each merge:
```
▼ Merge Details
Vendor layer:
Sources: lodash-security
Status: NotAffected (trust: 95%)
Distro layer:
Sources: redhat-csaf
Status: Affected (trust: 90%)
Merge: 'lodash-security' has higher trust weight
Internal layer:
No statements
Final: NotAffected (confidence: 92.5%)
```
## Configuration
### Trust Weights
Configure in `trust.yaml`:
```yaml
sources:
vendor: 1.0
distro: 0.9
nvd: 0.8
internal: 0.85
community: 0.5
unknown: 0.3
```
### Lattice Type
Configure lattice in `policy.yaml`:
```yaml
lattice:
type: K4 # or Boolean, 8-state
joinStrategy: trust-weighted
```
## Use Cases
### 1. Understanding Disagreement
When vendor says "not affected" but NVD says "affected":
- View merge preview
- See trust scores
- Understand why vendor wins
- Decide if internal assessment needed
### 2. Adding Internal Context
When external sources lack context:
- View missing evidence
- Click "Add evidence"
- Submit internal VEX statement
- See confidence increase
### 3. Audit Documentation
For compliance:
- Export merge preview
- Include in audit report
- Document decision rationale
## Best Practices
1. **Review Conflicts**: When layers disagree, investigate why
2. **Add Internal Context**: Your reachability data often resolves conflicts
3. **Trust Calibration**: Adjust trust weights based on source accuracy
4. **Document Decisions**: Use merge preview in exception justifications
## Related Documentation
- [VEX Trust Scoring](../excititor/scoring.md)
- [Lattice Configuration](../policy/implementation_plan.md)
- [REPLAY.yaml Specification](./replay-yaml.md)

View File

@@ -0,0 +1,295 @@
# REPLAY.yaml Manifest Specification
## Overview
The **REPLAY.yaml** manifest defines the complete set of inputs required to reproduce a StellaOps evaluation. It is the root document in a `.stella-replay.tgz` bundle.
## File Location
```
.stella-replay.tgz
├── REPLAY.yaml # This manifest
├── sboms/
├── vex/
├── reach/
├── exceptions/
├── policies/
├── feeds/
├── config/
└── SIGNATURE.sig # Optional DSSE signature
```
## Schema Version
Current schema version: `1.0.0`
```yaml
version: "1.0.0"
```
## Complete Example
```yaml
version: "1.0.0"
snapshot:
id: "snap-20241222-abc123"
createdAt: "2024-12-22T12:00:00Z"
artifact: "sha256:abc123..."
previousId: "snap-20241221-xyz789"
inputs:
sboms:
- path: "sboms/cyclonedx.json"
format: "cyclonedx-1.6"
digest: "sha256:def456..."
- path: "sboms/spdx.json"
format: "spdx-3.0.1"
digest: "sha256:ghi789..."
vex:
- path: "vex/vendor-lodash.json"
source: "vendor:lodash"
format: "openvex"
digest: "sha256:jkl012..."
trustScore: 0.95
- path: "vex/redhat-csaf.json"
source: "distro:redhat"
format: "csaf"
digest: "sha256:mno345..."
trustScore: 0.90
reachability:
- path: "reach/api-handler.json"
entryPoint: "/api/handler"
digest: "sha256:pqr678..."
nodeCount: 42
edgeCount: 57
exceptions:
- path: "exceptions/exc-001.json"
exceptionId: "exc-001"
digest: "sha256:stu901..."
policies:
bundlePath: "policies/bundle.tar.gz"
digest: "sha256:vwx234..."
version: "2.1.0"
rulesHash: "sha256:yza567..."
feeds:
- feedId: "nvd"
name: "National Vulnerability Database"
version: "2024-12-22T00:00:00Z"
digest: "sha256:bcd890..."
fetchedAt: "2024-12-22T06:00:00Z"
- feedId: "ghsa"
name: "GitHub Security Advisories"
version: "2024-12-22T01:00:00Z"
digest: "sha256:efg123..."
fetchedAt: "2024-12-22T06:15:00Z"
lattice:
type: "K4"
configDigest: "sha256:hij456..."
trust:
configDigest: "sha256:klm789..."
defaultWeight: 0.5
outputs:
verdictPath: "verdict.json"
verdictDigest: "sha256:nop012..."
findingsPath: "findings.ndjson"
findingsDigest: "sha256:qrs345..."
seeds:
rng: 12345678
sampling: 87654321
environment:
STELLAOPS_POLICY_VERSION: "2.1.0"
STELLAOPS_LATTICE_TYPE: "K4"
signature:
algorithm: "ecdsa-p256"
keyId: "signing-key-prod-2024"
value: "MEUCIQDx..."
```
## Field Reference
### snapshot
Metadata about the snapshot itself.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | string | Yes | Unique snapshot identifier |
| createdAt | datetime | Yes | ISO 8601 timestamp |
| artifact | string | Yes | Artifact digest being evaluated |
| previousId | string | No | Previous snapshot for diff |
### inputs.sboms
SBOM documents included in bundle.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| path | string | Yes | Path within bundle |
| format | string | Yes | `cyclonedx-1.6` or `spdx-3.0.1` |
| digest | string | Yes | Content digest |
### inputs.vex
VEX documents from various sources.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| path | string | Yes | Path within bundle |
| source | string | Yes | Source identifier (vendor:*, distro:*, etc.) |
| format | string | Yes | `openvex`, `csaf`, `cyclonedx-vex` |
| digest | string | Yes | Content digest |
| trustScore | number | Yes | Trust weight (0.0-1.0) |
### inputs.reachability
Reachability subgraph data.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| path | string | Yes | Path within bundle |
| entryPoint | string | Yes | Entry point identifier |
| digest | string | Yes | Content digest |
| nodeCount | integer | No | Number of nodes |
| edgeCount | integer | No | Number of edges |
### inputs.exceptions
Active exceptions at snapshot time.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| path | string | Yes | Path within bundle |
| exceptionId | string | Yes | Exception identifier |
| digest | string | Yes | Content digest |
### inputs.policies
Policy bundle reference.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| bundlePath | string | Yes | Path to policy bundle |
| digest | string | Yes | Bundle digest |
| version | string | No | Policy version |
| rulesHash | string | Yes | Hash of compiled rules |
### inputs.feeds
Advisory feed versions at snapshot time.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| feedId | string | Yes | Feed identifier |
| name | string | No | Human-readable name |
| version | string | Yes | Feed version/timestamp |
| digest | string | Yes | Feed content digest |
| fetchedAt | datetime | Yes | When feed was fetched |
### inputs.lattice
Lattice configuration for merge semantics.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| type | string | Yes | `K4`, `Boolean`, `8-state` |
| configDigest | string | Yes | Configuration hash |
### inputs.trust
Trust weight configuration.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| configDigest | string | Yes | Configuration hash |
| defaultWeight | number | No | Default trust weight |
### outputs
Evaluation outputs for verification.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| verdictPath | string | Yes | Path to verdict file |
| verdictDigest | string | Yes | Verdict content digest |
| findingsPath | string | No | Path to findings file |
| findingsDigest | string | No | Findings content digest |
### seeds
Random seeds for deterministic evaluation.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| rng | integer | No | Random number generator seed |
| sampling | integer | No | Sampling algorithm seed |
### environment
Environment variables captured (non-sensitive).
Key-value pairs of environment configuration.
### signature
DSSE signature over manifest.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| algorithm | string | Yes | Signing algorithm |
| keyId | string | Yes | Signing key identifier |
| value | string | Yes | Base64-encoded signature |
## Digest Format
All digests use the format:
```
sha256:<64-char-hex>
```
Example:
```
sha256:a1b2c3d4e5f6...
```
## Validation
Bundle validation checks:
1. REPLAY.yaml exists at bundle root
2. All referenced files exist
3. All digests match content
4. Schema validates against JSON Schema
5. Signature verifies (if present)
## CLI Usage
```bash
# Create bundle
stella snapshot export --output snapshot.stella-replay.tgz
# Verify bundle
stella snapshot verify snapshot.stella-replay.tgz
# Replay from bundle
stella replay --bundle snapshot.stella-replay.tgz
# View manifest
stella snapshot manifest snapshot.stella-replay.tgz
```
## Related Documentation
- [Knowledge Snapshot Model](./knowledge-snapshot.md)
- [Merge Preview](./merge-preview.md)
- [Replay Engine](../../modules/policy/implementation_plan.md)

View File

@@ -26,4 +26,4 @@ Guides for capturing metrics, logs, traces, and delivering notifications.
- Scanner analyzers dashboard: [../../modules/scanner/operations/analyzers-grafana-dashboard.json](../../modules/scanner/operations/analyzers-grafana-dashboard.json).
- Scheduler worker dashboards & alert rules: [../../modules/scheduler/operations/worker-grafana-dashboard.json](../../modules/scheduler/operations/worker-grafana-dashboard.json), [../../modules/scheduler/operations/worker-prometheus-rules.yaml](../../modules/scheduler/operations/worker-prometheus-rules.yaml).
- Authority monitoring: [../../modules/authority/operations/monitoring.md](../../modules/authority/operations/monitoring.md).
- DevOps observability tasks: see [../../modules/devops/architecture.md](../../modules/devops/architecture.md) and runbooks.
- DevOps observability tasks: see [../../operations/devops/architecture.md](../../operations/devops/architecture.md) and runbooks.

View File

@@ -37,7 +37,7 @@ Deployment, runtime operations, and air-gap playbooks for running StellaOps i
## DevOps & Release Automation
- [../devops/policy-schema-export.md](../../devops/policy-schema-export.md) policy schema export automation.
- [../modules/devops/runbooks/launch-readiness.md](../../modules/devops/runbooks/launch-readiness.md), [../modules/devops/runbooks/launch-cutover.md](../../modules/devops/runbooks/launch-cutover.md), [../modules/devops/runbooks/deployment-upgrade.md](../../modules/devops/runbooks/deployment-upgrade.md), [../modules/devops/runbooks/nuget-preview-bootstrap.md](../../modules/devops/runbooks/nuget-preview-bootstrap.md).
- [../operations/devops/runbooks/launch-readiness.md](../../operations/devops/runbooks/launch-readiness.md), [../operations/devops/runbooks/launch-cutover.md](../../operations/devops/runbooks/launch-cutover.md), [../operations/devops/runbooks/deployment-upgrade.md](../../operations/devops/runbooks/deployment-upgrade.md), [../operations/devops/runbooks/nuget-preview-bootstrap.md](../../operations/devops/runbooks/nuget-preview-bootstrap.md).
- [../modules/registry/operations/token-service.md](../../modules/registry/operations/token-service.md) registry token runbook.
- [../modules/concelier/operations/mirror.md](../../modules/concelier/operations/mirror.md) mirror operations.
- [../modules/concelier/operations/connectors/](../../modules/concelier/operations/connectors/) connector-specific procedures (ACSC, CCCS, CERT-Bund, etc.).

View File

@@ -5,7 +5,7 @@ This document defines the reproducibility guarantees, verdict identity computati
## Overview
StellaOps provides **deterministic, reproducible outputs** for all security artifacts:
- SBOM generation (CycloneDX 1.6, SPDX 3.0.1)
- SBOM generation (CycloneDX 1.7, SPDX 2.2/2.3; SPDX 3.0.1 planned)
- VEX statements (OpenVEX)
- Policy verdicts
- Delta computations

View File

@@ -358,5 +358,5 @@ If CI determinism gate fails:
- [Testing Strategy Models](testing-strategy-models.md) - Overview of testing models
- [Canonical JSON Specification](../DATA_SCHEMAS.md#canonical-json) - JSON serialization rules
- [CI/CD Workflows](../modules/devops/architecture.md) - CI pipeline details
- [CI/CD Workflows](../../operations/devops/architecture.md) - CI pipeline details
- [Evidence Bundle Schema](../modules/evidence-locker/architecture.md) - Bundle format reference

View File

@@ -0,0 +1,409 @@
# Testing Enhancements Architecture
**Version:** 1.0.0
**Last Updated:** 2026-01-05
**Status:** In Development
## Overview
This document describes the architecture of StellaOps testing enhancements derived from the product advisory "New Testing Enhancements for Stella Ops" (05-Dec-2026). The enhancements address gaps in temporal correctness, policy drift control, replayability, and competitive awareness.
## Problem Statement
> "The next gains for StellaOps testing are no longer about coverage—they're about temporal correctness, policy drift control, replayability, and competitive awareness. Systems that fail now do so quietly, over time, and under sequence pressure."
### Key Gaps Identified
| Gap | Impact | Current State |
|-----|--------|---------------|
| **Temporal Edge Cases** | Silent failures under clock drift, leap seconds, TTL boundaries | TimeProvider exists but no edge case tests |
| **Failure Choreography** | Cascading failures untested | Single-point chaos tests only |
| **Trace Replay** | Assumptions vs. reality mismatch | Replay module underutilized |
| **Policy Drift** | Silent behavior changes | Determinism tests exist but no diff testing |
| **Decision Opacity** | Audit/debug difficulty | Verdicts without explanations |
| **Evidence Gaps** | Test runs not audit-grade | TRX files not in EvidenceLocker |
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Testing Enhancements Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Time-Skew │ │ Trace Replay │ │ Failure │ │
│ │ & Idempotency │ │ & Evidence │ │ Choreography │ │
│ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ StellaOps.Testing.* Libraries │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │
│ │ │ Temporal │ │ Replay │ │ Chaos │ │ Evidence │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │
│ │ │ Policy │ │Explainability│ │ Coverage │ │ConfigDiff│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Existing Infrastructure │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │
│ │ │ TestKit │ │Determinism │ │ Postgres │ │ AirGap │ │ │
│ │ │ │ │ Testing │ │ Testing │ │ Testing │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Component Architecture
### 1. Temporal Testing (`StellaOps.Testing.Temporal`)
**Purpose:** Simulate temporal edge conditions and verify idempotency.
```
┌─────────────────────────────────────────────────────────────┐
│ Temporal Testing │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ SimulatedTimeProvider│ │ IdempotencyVerifier │ │
│ │ - Advance() │ │ - VerifyAsync() │ │
│ │ - JumpTo() │ │ - VerifyWithRetries│ │
│ │ - SetDrift() │ └─────────────────────┘ │
│ │ - JumpBackward() │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │LeapSecondTimeProvider│ │TtlBoundaryTimeProvider│ │
│ │ - AdvanceThrough │ │ - PositionAtExpiry │ │
│ │ LeapSecond() │ │ - GenerateBoundary │ │
│ └─────────────────────┘ │ TestCases() │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ClockSkewAssertions │ │
│ │ - AssertHandlesClockJumpForward() │ │
│ │ - AssertHandlesClockJumpBackward() │ │
│ │ - AssertHandlesClockDrift() │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Key Interfaces:**
- `SimulatedTimeProvider` - Time progression with drift
- `IdempotencyVerifier<T>` - Retry idempotency verification
- `ClockSkewAssertions` - Clock anomaly assertions
### 2. Trace Replay & Evidence (`StellaOps.Testing.Replay`, `StellaOps.Testing.Evidence`)
**Purpose:** Replay production traces and link test runs to EvidenceLocker.
```
┌─────────────────────────────────────────────────────────────┐
│ Trace Replay & Evidence │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │TraceAnonymizer │ │ TestEvidenceService │ │
│ │ - AnonymizeAsync│ │ - BeginSessionAsync │ │
│ │ - ValidateAnon │ │ - RecordTestResult │ │
│ └────────┬────────┘ │ - FinalizeSession │ │
│ │ └──────────┬──────────┘ │
│ ▼ │ │
│ ┌─────────────────┐ ▼ │
│ │TraceCorpusManager│ ┌─────────────────────┐ │
│ │ - ImportAsync │ │ EvidenceLocker │ │
│ │ - QueryAsync │ │ (immutable storage)│ │
│ └────────┬─────────┘ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ReplayIntegrationTestBase │ │
│ │ - ReplayAndVerifyAsync() │ │
│ │ - ReplayBatchAsync() │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Data Flow:**
```
Production Traces → Anonymization → Corpus → Replay Tests → Evidence Bundle
```
### 3. Failure Choreography (`StellaOps.Testing.Chaos`)
**Purpose:** Orchestrate sequenced, cascading failure scenarios.
```
┌─────────────────────────────────────────────────────────────┐
│ Failure Choreography │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ FailureChoreographer │ │
│ │ - InjectFailure(componentId, failureType) │ │
│ │ - RecoverComponent(componentId) │ │
│ │ - ExecuteOperation(name, action) │ │
│ │ - AssertCondition(name, condition) │ │
│ │ - ExecuteAsync() → ChoreographyResult │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────────┐ ┌────────────────┐ │
│ │DatabaseFailure │ │HttpClient │ │ CacheFailure │ │
│ │ Injector │ │ Injector │ │ Injector │ │
│ └────────────────┘ └────────────┘ └────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ConvergenceTracker │ │
│ │ - CaptureSnapshotAsync() │ │
│ │ - WaitForConvergenceAsync() │ │
│ │ - VerifyConvergenceAsync() │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────────┐ ┌────────────────┐ │
│ │ DatabaseState │ │ Metrics │ │ QueueState │ │
│ │ Probe │ │ Probe │ │ Probe │ │
│ └────────────────┘ └────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Failure Types:**
- `Unavailable` - Component completely down
- `Timeout` - Slow responses
- `Intermittent` - Random failures
- `PartialFailure` - Some operations fail
- `Degraded` - Reduced capacity
- `Flapping` - Alternating up/down
### 4. Policy & Explainability (`StellaOps.Core.Explainability`, `StellaOps.Testing.Policy`)
**Purpose:** Explain automated decisions and test policy changes.
```
┌─────────────────────────────────────────────────────────────┐
│ Policy & Explainability │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ DecisionExplanation │ │
│ │ - DecisionId, DecisionType, DecidedAt │ │
│ │ - Outcome (value, confidence, summary) │ │
│ │ - Factors[] (type, weight, contribution) │ │
│ │ - AppliedRules[] (id, triggered, impact) │ │
│ │ - Metadata (engine version, input hashes) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │IExplainableDecision│ │ ExplainabilityAssertions│ │
│ │ <TInput, TOutput> │ │ - AssertHasExplanation │ │
│ │ - EvaluateWith │ │ - AssertExplanation │ │
│ │ ExplanationAsync│ │ Reproducible │ │
│ └─────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ PolicyDiffEngine │ │
│ │ - ComputeDiffAsync(baseline, new, inputs) │ │
│ │ → PolicyDiffResult (changed behaviors, deltas) │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ PolicyRegressionTestBase │ │
│ │ - Policy_Change_Produces_Expected_Diff() │ │
│ │ - Policy_Change_No_Unexpected_Regressions() │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Explainable Services:**
- `ExplainableVexConsensusService`
- `ExplainableRiskScoringService`
- `ExplainablePolicyEngine`
### 5. Cross-Cutting Standards (`StellaOps.Testing.*`)
**Purpose:** Enforce standards across all testing.
```
┌─────────────────────────────────────────────────────────────┐
│ Cross-Cutting Standards │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ BlastRadius Annotations │ │
│ │ - Auth, Scanning, Evidence, Compliance │ │
│ │ - Advisories, RiskPolicy, Crypto │ │
│ │ - Integrations, Persistence, Api │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ SchemaEvolutionTestBase │ │
│ │ - TestAgainstPreviousSchemaAsync() │ │
│ │ - TestReadBackwardCompatibilityAsync() │ │
│ │ - TestWriteForwardCompatibilityAsync() │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ BranchCoverageEnforcer │ │
│ │ - Validate() → dead paths │ │
│ │ - GenerateDeadPathReport() │ │
│ │ - Exemption mechanism │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ ConfigDiffTestBase │ │
│ │ - TestConfigBehavioralDeltaAsync() │ │
│ │ - TestConfigIsolationAsync() │ │
│ └───────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Library Structure
```
src/__Tests/__Libraries/
├── StellaOps.Testing.Temporal/
│ ├── SimulatedTimeProvider.cs
│ ├── LeapSecondTimeProvider.cs
│ ├── TtlBoundaryTimeProvider.cs
│ ├── IdempotencyVerifier.cs
│ └── ClockSkewAssertions.cs
├── StellaOps.Testing.Replay/
│ ├── ReplayIntegrationTestBase.cs
│ └── IReplayOrchestrator.cs
├── StellaOps.Testing.Evidence/
│ ├── ITestEvidenceService.cs
│ ├── TestEvidenceService.cs
│ └── XunitEvidenceReporter.cs
├── StellaOps.Testing.Chaos/
│ ├── FailureChoreographer.cs
│ ├── ConvergenceTracker.cs
│ ├── Injectors/
│ │ ├── IFailureInjector.cs
│ │ ├── DatabaseFailureInjector.cs
│ │ ├── HttpClientFailureInjector.cs
│ │ └── CacheFailureInjector.cs
│ └── Probes/
│ ├── IStateProbe.cs
│ ├── DatabaseStateProbe.cs
│ └── MetricsStateProbe.cs
├── StellaOps.Testing.Policy/
│ ├── PolicyDiffEngine.cs
│ ├── PolicyRegressionTestBase.cs
│ └── PolicyVersionControl.cs
├── StellaOps.Testing.Explainability/
│ └── ExplainabilityAssertions.cs
├── StellaOps.Testing.SchemaEvolution/
│ └── SchemaEvolutionTestBase.cs
├── StellaOps.Testing.Coverage/
│ └── BranchCoverageEnforcer.cs
└── StellaOps.Testing.ConfigDiff/
└── ConfigDiffTestBase.cs
```
## CI/CD Integration
### Pipeline Structure
```
┌─────────────────────────────────────────────────────────────┐
│ CI/CD Pipelines │
├─────────────────────────────────────────────────────────────┤
│ │
│ PR-Gating: │
│ ├── test-blast-radius.yml (validate annotations) │
│ ├── policy-diff.yml (policy change validation) │
│ ├── dead-path-detection.yml (coverage enforcement) │
│ └── test-evidence.yml (evidence capture) │
│ │
│ Scheduled: │
│ ├── schema-evolution.yml (backward compat tests) │
│ ├── chaos-choreography.yml (failure choreography) │
│ └── trace-replay.yml (production trace replay) │
│ │
│ On-Demand: │
│ └── rollback-lag.yml (rollback timing measurement) │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Workflow Triggers
| Workflow | Trigger | Purpose |
|----------|---------|---------|
| test-blast-radius | PR (test files) | Validate annotations |
| policy-diff | PR (policy files) | Validate policy changes |
| dead-path-detection | Push/PR | Prevent untested code |
| test-evidence | Push (main) | Store test evidence |
| schema-evolution | Daily | Backward compatibility |
| chaos-choreography | Weekly | Cascading failure tests |
| trace-replay | Weekly | Production trace validation |
| rollback-lag | Manual | Measure rollback timing |
## Implementation Roadmap
### Sprint Schedule
| Sprint | Focus | Duration | Key Deliverables |
|--------|-------|----------|------------------|
| 002_001 | Time-Skew & Idempotency | 3 weeks | Temporal libraries, module tests |
| 002_002 | Trace Replay & Evidence | 3 weeks | Anonymization, evidence linking |
| 002_003 | Failure Choreography | 3 weeks | Choreographer, cascade tests |
| 002_004 | Policy & Explainability | 3 weeks | Explanation schema, diff testing |
| 002_005 | Cross-Cutting Standards | 3 weeks | Annotations, CI enforcement |
### Dependencies
```
002_001 (Temporal) ────┐
002_002 (Replay) ──────┼──→ 002_003 (Choreography) ──→ 002_005 (Cross-Cutting)
│ ↑
002_004 (Policy) ──────┘────────────────────────────────────┘
```
## Success Metrics
| Metric | Baseline | Target | Sprint |
|--------|----------|--------|--------|
| Temporal edge case coverage | ~5% | 80%+ | 002_001 |
| Idempotency test coverage | ~10% | 90%+ | 002_001 |
| Replay test coverage | 0% | 50%+ | 002_002 |
| Test evidence capture | 0% | 100% | 002_002 |
| Choreographed failure scenarios | 0 | 15+ | 002_003 |
| Decisions with explanations | 0% | 100% | 002_004 |
| Policy changes with diff tests | 0% | 100% | 002_004 |
| Tests with blast-radius | ~10% | 100% | 002_005 |
| Dead paths (non-exempt) | Unknown | <50 | 002_005 |
## References
- **Sprint Files:**
- `docs/implplan/SPRINT_20260105_002_001_TEST_time_skew_idempotency.md`
- `docs/implplan/SPRINT_20260105_002_002_TEST_trace_replay_evidence.md`
- `docs/implplan/SPRINT_20260105_002_003_TEST_failure_choreography.md`
- `docs/implplan/SPRINT_20260105_002_004_TEST_policy_explainability.md`
- `docs/implplan/SPRINT_20260105_002_005_TEST_cross_cutting.md`
- **Advisory:** `docs/product-advisories/05-Dec-2026 - New Testing Enhancements for Stella Ops.md`
- **Test Infrastructure:** `src/__Tests/AGENTS.md`