Implement VEX document verification system with issuer management and signature verification
- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation. - Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments. - Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats. - Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats. - Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction. - Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
This commit is contained in:
156
docs/modules/policy/design/policy-aoc-linting-rules.md
Normal file
156
docs/modules/policy/design/policy-aoc-linting-rules.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Policy AOC Linting Rules
|
||||
|
||||
**Document ID:** `DESIGN-POLICY-AOC-LINTING-001`
|
||||
**Version:** 1.0
|
||||
**Status:** Published
|
||||
**Last Updated:** 2025-12-06
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the linting and static analysis rules for Policy Engine and related library projects. These rules enforce determinism, nullability, async consistency, and JSON property ordering to ensure reproducible policy evaluation.
|
||||
|
||||
## Target Projects
|
||||
|
||||
| Project | Path | Notes |
|
||||
|---------|------|-------|
|
||||
| StellaOps.Policy.Engine | `src/Policy/StellaOps.Policy.Engine/` | Primary target |
|
||||
| StellaOps.Policy | `src/Policy/__Libraries/StellaOps.Policy/` | Core library |
|
||||
| StellaOps.PolicyDsl | `src/Policy/StellaOps.PolicyDsl/` | DSL compiler |
|
||||
| StellaOps.Policy.RiskProfile | `src/Policy/StellaOps.Policy.RiskProfile/` | Risk scoring |
|
||||
| StellaOps.Policy.Storage.Postgres | `src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/` | Storage layer |
|
||||
|
||||
### Excluded
|
||||
|
||||
- `**/obj/**` - Build artifacts
|
||||
- `**/bin/**` - Build outputs
|
||||
- `**/*.Tests/**` - Test projects (separate rules)
|
||||
- `**/Migrations/**` - Generated EF migrations
|
||||
|
||||
## Rule Categories
|
||||
|
||||
### 1. Determinism Rules (Error Severity)
|
||||
|
||||
Enforced by `ProhibitedPatternAnalyzer` at `src/Policy/StellaOps.Policy.Engine/DeterminismGuard/`.
|
||||
|
||||
| Rule ID | Pattern | Severity | Remediation |
|
||||
|---------|---------|----------|-------------|
|
||||
| DET-001 | `DateTime.Now` | Error | Use `TimeProvider.GetUtcNow()` |
|
||||
| DET-002 | `DateTime.UtcNow` | Error | Use `TimeProvider.GetUtcNow()` |
|
||||
| DET-003 | `DateTimeOffset.Now` | Error | Use `TimeProvider.GetUtcNow()` |
|
||||
| DET-004 | `DateTimeOffset.UtcNow` | Error | Use `TimeProvider.GetUtcNow()` |
|
||||
| DET-005 | `Guid.NewGuid()` | Error | Use `StableIdGenerator` or content hash |
|
||||
| DET-006 | `new Random()` | Error | Use seeded random or remove |
|
||||
| DET-007 | `RandomNumberGenerator` | Error | Remove from evaluation path |
|
||||
| DET-008 | `HttpClient` in eval | Critical | Remove network from eval path |
|
||||
| DET-009 | `File.Read*` in eval | Critical | Remove filesystem from eval path |
|
||||
| DET-010 | Dictionary iteration | Warning | Use `OrderBy` or `SortedDictionary` |
|
||||
| DET-011 | HashSet iteration | Warning | Use `OrderBy` or `SortedSet` |
|
||||
|
||||
### 2. Nullability Rules (Error Severity)
|
||||
|
||||
| Rule ID | Description | EditorConfig |
|
||||
|---------|-------------|--------------|
|
||||
| NUL-001 | Enable nullable reference types | `nullable = enable` |
|
||||
| NUL-002 | Nullable warnings as errors | `dotnet_diagnostic.CS8600-CS8609.severity = error` |
|
||||
| NUL-003 | Null parameter checks | `ArgumentNullException.ThrowIfNull()` |
|
||||
|
||||
### 3. Async/Sync Consistency Rules (Warning Severity)
|
||||
|
||||
| Rule ID | Description | EditorConfig |
|
||||
|---------|-------------|--------------|
|
||||
| ASY-001 | Async void methods | `dotnet_diagnostic.CA2012.severity = error` |
|
||||
| ASY-002 | Missing ConfigureAwait | `dotnet_diagnostic.CA2007.severity = warning` |
|
||||
| ASY-003 | Sync over async | `dotnet_diagnostic.MA0045.severity = warning` |
|
||||
| ASY-004 | Task.Result in async | `dotnet_diagnostic.MA0042.severity = error` |
|
||||
|
||||
### 4. JSON Property Ordering Rules
|
||||
|
||||
For deterministic JSON output, all DTOs must use explicit `[JsonPropertyOrder]` attributes.
|
||||
|
||||
| Rule ID | Description | Enforcement |
|
||||
|---------|-------------|-------------|
|
||||
| JSN-001 | Explicit property order | Code review + analyzer |
|
||||
| JSN-002 | Stable serialization | `JsonSerializerOptions.WriteIndented = false` |
|
||||
| JSN-003 | Key ordering | `JsonSerializerOptions.PropertyNamingPolicy` with stable order |
|
||||
|
||||
### 5. Code Style Rules
|
||||
|
||||
| Rule ID | Description | EditorConfig |
|
||||
|---------|-------------|--------------|
|
||||
| STY-001 | File-scoped namespaces | `csharp_style_namespace_declarations = file_scoped` |
|
||||
| STY-002 | Primary constructors | `csharp_style_prefer_primary_constructors = true` |
|
||||
| STY-003 | Collection expressions | `csharp_style_prefer_collection_expression = true` |
|
||||
| STY-004 | Implicit usings | `ImplicitUsings = enable` |
|
||||
|
||||
## Severity Levels
|
||||
|
||||
| Level | Behavior | CI Impact |
|
||||
|-------|----------|-----------|
|
||||
| Error | Build fails | Blocks merge |
|
||||
| Warning | Build succeeds, logged | Review required |
|
||||
| Info | Logged only | No action required |
|
||||
|
||||
## CI Integration
|
||||
|
||||
### Build-time Enforcement
|
||||
|
||||
Policy projects use `TreatWarningsAsErrors=true` in `.csproj`:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
### Static Analysis Pipeline
|
||||
|
||||
The `.gitea/workflows/policy-lint.yml` workflow runs:
|
||||
|
||||
1. **dotnet build** with analyzer packages
|
||||
2. **DeterminismGuard analysis** via CLI
|
||||
3. **Format check** via `dotnet format --verify-no-changes`
|
||||
|
||||
### Required Analyzer Packages
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.0-*" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.*" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
## Baseline Suppressions
|
||||
|
||||
Create `.globalconfig` for legacy code that cannot be immediately fixed:
|
||||
|
||||
```ini
|
||||
# Legacy suppressions - track issue for remediation
|
||||
[src/Policy/**/LegacyCode.cs]
|
||||
dotnet_diagnostic.DET-010.severity = suggestion
|
||||
```
|
||||
|
||||
## Runtime Enforcement
|
||||
|
||||
The `DeterminismGuardService` provides runtime monitoring:
|
||||
|
||||
```csharp
|
||||
using var scope = _determinismGuard.CreateScope(scopeId, timestamp);
|
||||
var result = await evaluation(scope);
|
||||
var analysis = scope.Complete();
|
||||
if (!analysis.Passed) { /* log/reject */ }
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. All Policy projects build with zero errors
|
||||
2. `dotnet format` reports no changes needed
|
||||
3. DeterminismGuard analysis passes
|
||||
4. New code has no nullable warnings
|
||||
5. Async methods use `ConfigureAwait(false)`
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Deterministic Evaluator Design](./deterministic-evaluator.md)
|
||||
- [Policy Engine Architecture](../architecture.md)
|
||||
- [CONTRACT-POLICY-STUDIO-007](../../contracts/policy-studio.md)
|
||||
203
docs/modules/policy/design/policy-determinism-tests.md
Normal file
203
docs/modules/policy/design/policy-determinism-tests.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Policy Determinism Test Design
|
||||
|
||||
**Document ID:** `DESIGN-POLICY-DETERMINISM-TESTS-001`
|
||||
**Version:** 1.0
|
||||
**Status:** Published
|
||||
**Last Updated:** 2025-12-06
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the test expectations for ensuring deterministic output from Policy Engine scoring and decision APIs. Determinism is critical for reproducible policy evaluation across environments.
|
||||
|
||||
## Determinism Requirements
|
||||
|
||||
### Output Ordering
|
||||
|
||||
All collections in API responses must have stable, deterministic ordering:
|
||||
|
||||
| Collection | Ordering Rule |
|
||||
|------------|---------------|
|
||||
| Findings | By `finding_id` alphabetically |
|
||||
| Decisions | By `decision_id` (timestamp prefix) |
|
||||
| Signals | By signal name alphabetically |
|
||||
| Severity counts | By canonical severity order: critical → high → medium → low → info |
|
||||
| Contributions | By signal name alphabetically |
|
||||
|
||||
### JSON Serialization
|
||||
|
||||
1. **Property Order**: Use `[JsonPropertyOrder]` or declare properties in stable order
|
||||
2. **No Random Elements**: No GUIDs, random IDs, or timestamps unless from context
|
||||
3. **Stable Key Order**: Dictionary keys must serialize in consistent order
|
||||
|
||||
### Deprecated Field Absence
|
||||
|
||||
After v2.0, responses must NOT include:
|
||||
- `normalized_score`
|
||||
- `top_severity_sources`
|
||||
- `source_rank`
|
||||
|
||||
See [Normalized Field Removal](./policy-normalized-field-removal.md).
|
||||
|
||||
## Test Categories
|
||||
|
||||
### 1. Snapshot Equality Tests
|
||||
|
||||
Verify that identical inputs produce byte-for-byte identical JSON outputs.
|
||||
|
||||
```csharp
|
||||
[Theory]
|
||||
[MemberData(nameof(DeterminismFixtures))]
|
||||
public void Scoring_ShouldProduceDeterministicOutput(string inputFile)
|
||||
{
|
||||
// Arrange
|
||||
var input = LoadFixture(inputFile);
|
||||
|
||||
// Act - Run twice with same input
|
||||
var result1 = _scoringService.Score(input);
|
||||
var result2 = _scoringService.Score(input);
|
||||
|
||||
// Assert - Byte-for-byte equality
|
||||
var json1 = JsonSerializer.Serialize(result1);
|
||||
var json2 = JsonSerializer.Serialize(result2);
|
||||
Assert.Equal(json1, json2);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Cross-Environment Tests
|
||||
|
||||
Verify output is identical across different environments (CI, local, prod).
|
||||
|
||||
```csharp
|
||||
[Theory]
|
||||
[InlineData("fixture-001")]
|
||||
public void Scoring_ShouldMatchGoldenFile(string fixtureId)
|
||||
{
|
||||
// Arrange
|
||||
var input = LoadFixture($"{fixtureId}-input.json");
|
||||
var expected = LoadFixture($"{fixtureId}-expected.json");
|
||||
|
||||
// Act
|
||||
var result = _scoringService.Score(input);
|
||||
|
||||
// Assert
|
||||
AssertJsonEqual(expected, result);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Ordering Verification Tests
|
||||
|
||||
Verify collections are always in expected order.
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void Decisions_ShouldBeOrderedByDecisionId()
|
||||
{
|
||||
// Arrange
|
||||
var input = CreateTestInput();
|
||||
|
||||
// Act
|
||||
var result = _decisionService.Evaluate(input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Decisions.SequenceEqual(
|
||||
result.Decisions.OrderBy(d => d.DecisionId)));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Deprecated Field Absence Tests
|
||||
|
||||
Verify deprecated fields are not serialized (v2.0+).
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void ScoringResult_ShouldNotIncludeNormalizedScore_InV2()
|
||||
{
|
||||
// Arrange
|
||||
var result = new RiskScoringResult(...);
|
||||
var options = new PolicyScoringOptions { IncludeLegacyNormalizedScore = false };
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(result, CreateJsonOptions(options));
|
||||
var doc = JsonDocument.Parse(json);
|
||||
|
||||
// Assert
|
||||
Assert.False(doc.RootElement.TryGetProperty("normalized_score", out _));
|
||||
}
|
||||
```
|
||||
|
||||
## Fixture Structure
|
||||
|
||||
### Input Fixtures
|
||||
|
||||
Located at `docs/modules/policy/samples/policy-determinism-fixtures*.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://stellaops.org/schemas/policy/determinism-fixture-v1.json",
|
||||
"fixture_id": "DET-001",
|
||||
"description": "Basic scoring determinism test",
|
||||
"input": {
|
||||
"finding_id": "CVE-2024-1234",
|
||||
"signals": {
|
||||
"cvss_base": 7.5,
|
||||
"exploitability": 2.8
|
||||
}
|
||||
},
|
||||
"expected_output": {
|
||||
"severity": "high",
|
||||
"signal_order": ["cvss_base", "exploitability"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Golden Files
|
||||
|
||||
Pre-computed expected outputs stored alongside inputs:
|
||||
- `policy-determinism-fixtures-input.json`
|
||||
- `policy-determinism-fixtures-expected.json`
|
||||
|
||||
## CI Integration
|
||||
|
||||
### Pipeline Steps
|
||||
|
||||
1. **Build**: Compile with analyzers
|
||||
2. **Unit Tests**: Run determinism unit tests
|
||||
3. **Snapshot Tests**: Compare against golden files
|
||||
4. **Diff Check**: Fail if any unexpected changes
|
||||
|
||||
### GitHub Action
|
||||
|
||||
```yaml
|
||||
- name: Run Determinism Tests
|
||||
run: |
|
||||
dotnet test --filter "Category=Determinism"
|
||||
|
||||
- name: Verify Snapshots
|
||||
run: |
|
||||
dotnet run --project tools/SnapshotVerifier -- \
|
||||
--fixtures docs/modules/policy/samples/policy-determinism-*.json
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Updating Golden Files
|
||||
|
||||
When intentionally changing output format:
|
||||
|
||||
1. Update design docs (this file, normalized-field-removal.md)
|
||||
2. Re-run tests with `UPDATE_GOLDEN=true`
|
||||
3. Review diffs
|
||||
4. Commit new golden files with explanation
|
||||
|
||||
### Adding New Fixtures
|
||||
|
||||
1. Create input fixture in `samples/`
|
||||
2. Run scoring to generate expected output
|
||||
3. Review for correctness
|
||||
4. Add to test data provider
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Policy AOC Linting Rules](./policy-aoc-linting-rules.md)
|
||||
- [Normalized Field Removal](./policy-normalized-field-removal.md)
|
||||
- [Deterministic Evaluator Design](./deterministic-evaluator.md)
|
||||
151
docs/modules/policy/design/policy-normalized-field-removal.md
Normal file
151
docs/modules/policy/design/policy-normalized-field-removal.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Policy Normalized Field Removal Design
|
||||
|
||||
**Document ID:** `DESIGN-POLICY-NORMALIZED-FIELD-REMOVAL-001`
|
||||
**Version:** 1.0
|
||||
**Status:** Draft
|
||||
**Last Updated:** 2025-12-06
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the migration plan for removing deprecated and legacy normalized fields from Policy Engine models. These fields were introduced for backwards compatibility but are now superseded by deterministic, canonical alternatives.
|
||||
|
||||
## Background
|
||||
|
||||
The Policy Engine currently includes several "normalized" fields that were designed for cross-source comparison but have been deprecated in favor of deterministic scoring:
|
||||
|
||||
1. **`normalized_score`** - Originally used for cross-vendor score comparison, now superseded by canonical severity scoring
|
||||
2. **`source_rank`** - Used for source prioritization, now handled by trust weighting service
|
||||
3. **Duplicated severity fields** - Multiple representations of the same severity data
|
||||
|
||||
## Fields Analysis
|
||||
|
||||
### Candidates for Removal
|
||||
|
||||
| Field | Location | Status | Migration Path |
|
||||
|-------|----------|--------|----------------|
|
||||
| `normalized_score` | `RiskScoringResult` | DEPRECATED | Use `severity` with canonical mapping |
|
||||
| `source_rank` in scoring | `PolicyDecisionSourceRank` | DEPRECATED | Use trust weighting service |
|
||||
| Legacy `severity_counts` | Multiple models | KEEP | Still used for aggregation |
|
||||
|
||||
### Fields to Retain
|
||||
|
||||
| Field | Location | Reason |
|
||||
|-------|----------|--------|
|
||||
| `severity` | All scoring models | Canonical severity (critical/high/medium/low/info) |
|
||||
| `profile_hash` | `RiskScoringResult` | Deterministic policy identification |
|
||||
| `trust_weight` | Decision models | Active trust weighting system |
|
||||
| `raw_score` | Scoring results | Needed for audit/debugging |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Deprecation (Current State)
|
||||
|
||||
Fields marked with `[Obsolete]` attribute to warn consumers:
|
||||
|
||||
```csharp
|
||||
[Obsolete("Use severity with canonical mapping. Scheduled for removal in v2.0")]
|
||||
[JsonPropertyName("normalized_score")]
|
||||
public double NormalizedScore { get; init; }
|
||||
```
|
||||
|
||||
### Phase 2: Soft Removal (v1.5)
|
||||
|
||||
- Remove fields from serialization by default
|
||||
- Add configuration flag to re-enable for backwards compatibility
|
||||
- Update API documentation
|
||||
|
||||
### Phase 3: Hard Removal (v2.0)
|
||||
|
||||
- Remove fields from models entirely
|
||||
- Remove backwards compatibility flags
|
||||
|
||||
## API Impact
|
||||
|
||||
### Before (Current)
|
||||
|
||||
```json
|
||||
{
|
||||
"finding_id": "CVE-2024-1234",
|
||||
"raw_score": 7.5,
|
||||
"normalized_score": 0.75,
|
||||
"severity": "high",
|
||||
"source_ranks": [
|
||||
{"source": "nvd", "rank": 1},
|
||||
{"source": "vendor", "rank": 2}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### After (Target)
|
||||
|
||||
```json
|
||||
{
|
||||
"finding_id": "CVE-2024-1234",
|
||||
"raw_score": 7.5,
|
||||
"severity": "high",
|
||||
"trust_weights": {
|
||||
"nvd": 1.0,
|
||||
"vendor": 0.8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Add Deprecation Markers
|
||||
|
||||
Add `[Obsolete]` to target fields with clear migration guidance.
|
||||
|
||||
### Step 2: Create Compatibility Layer
|
||||
|
||||
Add opt-in flag for legacy serialization:
|
||||
|
||||
```csharp
|
||||
public class PolicyScoringOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Include deprecated normalized_score field for backwards compatibility.
|
||||
/// </summary>
|
||||
public bool IncludeLegacyNormalizedScore { get; set; } = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Update Serialization
|
||||
|
||||
Use conditional serialization based on options:
|
||||
|
||||
```csharp
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public double? NormalizedScore =>
|
||||
_options.IncludeLegacyNormalizedScore ? _normalizedScore : null;
|
||||
```
|
||||
|
||||
### Step 4: Update Documentation
|
||||
|
||||
- Mark fields as deprecated in OpenAPI spec
|
||||
- Add migration guide to release notes
|
||||
|
||||
## Fixtures
|
||||
|
||||
Sample payloads are available in:
|
||||
- `docs/modules/policy/samples/policy-normalized-field-removal-before.json`
|
||||
- `docs/modules/policy/samples/policy-normalized-field-removal-after.json`
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
If issues arise during migration:
|
||||
1. Re-enable legacy fields via configuration
|
||||
2. No data loss - fields are computed, not stored
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Deprecated fields marked with `[Obsolete]`
|
||||
2. Configuration option for backwards compatibility
|
||||
3. API documentation updated
|
||||
4. Migration guide published
|
||||
5. Fixtures validate before/after behavior
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Policy AOC Linting Rules](./policy-aoc-linting-rules.md)
|
||||
- [CONTRACT-AUTHORITY-EFFECTIVE-WRITE-008](../../contracts/authority-effective-write.md)
|
||||
165
docs/modules/policy/samples/policy-determinism-fixtures.json
Normal file
165
docs/modules/policy/samples/policy-determinism-fixtures.json
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"$schema": "https://stellaops.org/schemas/policy/determinism-fixture-v1.json",
|
||||
"version": "1.0.0",
|
||||
"description": "Determinism fixtures for Policy Engine scoring and decision APIs",
|
||||
"fixtures": [
|
||||
{
|
||||
"fixture_id": "DET-001",
|
||||
"name": "Basic Scoring Determinism",
|
||||
"description": "Verify that scoring produces identical output for identical input",
|
||||
"input": {
|
||||
"finding_id": "CVE-2024-0001",
|
||||
"tenant_id": "default",
|
||||
"profile_id": "risk-profile-001",
|
||||
"signals": {
|
||||
"cvss_base": 7.5,
|
||||
"exploitability": 2.8,
|
||||
"impact": 5.9
|
||||
}
|
||||
},
|
||||
"expected_output": {
|
||||
"severity": "high",
|
||||
"raw_score": 7.5,
|
||||
"signal_order": ["cvss_base", "exploitability", "impact"],
|
||||
"assertions": [
|
||||
"signal_contributions keys are alphabetically ordered",
|
||||
"scored_at is from context, not wall clock"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-002",
|
||||
"name": "Multi-Finding Ordering",
|
||||
"description": "Verify that multiple findings are returned in stable order",
|
||||
"input": {
|
||||
"findings": [
|
||||
{"finding_id": "CVE-2024-0003", "cvss_base": 5.0},
|
||||
{"finding_id": "CVE-2024-0001", "cvss_base": 9.8},
|
||||
{"finding_id": "CVE-2024-0002", "cvss_base": 7.5}
|
||||
]
|
||||
},
|
||||
"expected_output": {
|
||||
"finding_order": ["CVE-2024-0001", "CVE-2024-0002", "CVE-2024-0003"],
|
||||
"assertions": [
|
||||
"findings sorted alphabetically by finding_id",
|
||||
"order is stable across multiple runs"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-003",
|
||||
"name": "Decision Summary Ordering",
|
||||
"description": "Verify severity counts are in canonical order",
|
||||
"input": {
|
||||
"decisions": [
|
||||
{"severity": "low", "count": 5},
|
||||
{"severity": "critical", "count": 1},
|
||||
{"severity": "medium", "count": 3},
|
||||
{"severity": "high", "count": 2}
|
||||
]
|
||||
},
|
||||
"expected_output": {
|
||||
"severity_order": ["critical", "high", "medium", "low", "info"],
|
||||
"assertions": [
|
||||
"severity_counts keys follow canonical order",
|
||||
"missing severities are either omitted or zero-filled consistently"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-004",
|
||||
"name": "Deprecated Field Absence (v2.0)",
|
||||
"description": "Verify deprecated fields are not present in v2.0 output",
|
||||
"input": {
|
||||
"finding_id": "CVE-2024-0001",
|
||||
"cvss_base": 7.5,
|
||||
"version": "2.0"
|
||||
},
|
||||
"expected_output": {
|
||||
"absent_fields": [
|
||||
"normalized_score",
|
||||
"top_severity_sources",
|
||||
"source_rank"
|
||||
],
|
||||
"present_fields": [
|
||||
"severity",
|
||||
"raw_score",
|
||||
"trust_weights"
|
||||
],
|
||||
"assertions": [
|
||||
"normalized_score is not serialized",
|
||||
"trust_weights replaces top_severity_sources"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-005",
|
||||
"name": "Legacy Compatibility Mode (v1.5)",
|
||||
"description": "Verify deprecated fields are present when legacy mode enabled",
|
||||
"input": {
|
||||
"finding_id": "CVE-2024-0001",
|
||||
"cvss_base": 7.5,
|
||||
"options": {
|
||||
"include_legacy_normalized_score": true
|
||||
}
|
||||
},
|
||||
"expected_output": {
|
||||
"present_fields": [
|
||||
"normalized_score",
|
||||
"severity",
|
||||
"raw_score"
|
||||
],
|
||||
"assertions": [
|
||||
"normalized_score is present for backwards compatibility",
|
||||
"severity is canonical (high, not HIGH)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-006",
|
||||
"name": "Signal Contribution Ordering",
|
||||
"description": "Verify signal contributions maintain stable key order",
|
||||
"input": {
|
||||
"signals": {
|
||||
"zeta_factor": 0.5,
|
||||
"alpha_score": 1.0,
|
||||
"beta_weight": 0.75
|
||||
}
|
||||
},
|
||||
"expected_output": {
|
||||
"contribution_order": ["alpha_score", "beta_weight", "zeta_factor"],
|
||||
"assertions": [
|
||||
"signal_contributions keys are alphabetically sorted",
|
||||
"contribution values are deterministic decimals"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "DET-007",
|
||||
"name": "Timestamp Determinism",
|
||||
"description": "Verify timestamps come from context, not wall clock",
|
||||
"input": {
|
||||
"finding_id": "CVE-2024-0001",
|
||||
"context": {
|
||||
"evaluation_time": "2025-12-06T10:00:00Z"
|
||||
}
|
||||
},
|
||||
"expected_output": {
|
||||
"scored_at": "2025-12-06T10:00:00Z",
|
||||
"assertions": [
|
||||
"scored_at matches context.evaluation_time exactly",
|
||||
"no random GUIDs in output"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"test_requirements": {
|
||||
"snapshot_equality": "Identical inputs must produce byte-for-byte identical JSON",
|
||||
"cross_environment": "Output must match across CI, local, and production",
|
||||
"ordering_stability": "Collection order must be deterministic and documented"
|
||||
},
|
||||
"migration_notes": {
|
||||
"v1.5": "Enable legacy mode with include_legacy_normalized_score for backwards compatibility",
|
||||
"v2.0": "Remove all deprecated fields, trust_weights replaces source ranking"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "https://stellaops.org/schemas/policy/scoring-result-v2.json",
|
||||
"description": "Sample scoring result AFTER normalized field removal (canonical format)",
|
||||
"scoring_result": {
|
||||
"finding_id": "CVE-2024-1234",
|
||||
"tenant_id": "default",
|
||||
"profile_id": "risk-profile-001",
|
||||
"profile_version": "1.2.0",
|
||||
"raw_score": 7.5,
|
||||
"severity": "high",
|
||||
"signal_values": {
|
||||
"cvss_base": 7.5,
|
||||
"exploitability": 2.8,
|
||||
"impact": 5.9
|
||||
},
|
||||
"scored_at": "2025-12-06T10:00:00Z",
|
||||
"profile_hash": "sha256:abc123def456..."
|
||||
},
|
||||
"decision_summary": {
|
||||
"total_decisions": 5,
|
||||
"total_conflicts": 1,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 3,
|
||||
"medium": 2,
|
||||
"low": 0
|
||||
},
|
||||
"trust_weights": {
|
||||
"nvd": 1.0,
|
||||
"vendor-advisory": 0.8
|
||||
}
|
||||
},
|
||||
"migration_notes": {
|
||||
"removed_fields": ["normalized_score", "top_severity_sources"],
|
||||
"added_fields": ["profile_hash", "trust_weights"],
|
||||
"canonical_severity_mapping": {
|
||||
"0.0-3.9": "low",
|
||||
"4.0-6.9": "medium",
|
||||
"7.0-8.9": "high",
|
||||
"9.0-10.0": "critical"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://stellaops.org/schemas/policy/scoring-result-v1.json",
|
||||
"description": "Sample scoring result BEFORE normalized field removal (legacy format)",
|
||||
"scoring_result": {
|
||||
"finding_id": "CVE-2024-1234",
|
||||
"tenant_id": "default",
|
||||
"profile_id": "risk-profile-001",
|
||||
"profile_version": "1.2.0",
|
||||
"raw_score": 7.5,
|
||||
"normalized_score": 0.75,
|
||||
"severity": "high",
|
||||
"signal_values": {
|
||||
"cvss_base": 7.5,
|
||||
"exploitability": 2.8,
|
||||
"impact": 5.9
|
||||
},
|
||||
"scored_at": "2025-12-06T10:00:00Z"
|
||||
},
|
||||
"decision_summary": {
|
||||
"total_decisions": 5,
|
||||
"total_conflicts": 1,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 3,
|
||||
"medium": 2,
|
||||
"low": 0
|
||||
},
|
||||
"top_severity_sources": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"total_weight": 1.0,
|
||||
"finding_count": 3
|
||||
},
|
||||
{
|
||||
"source": "vendor-advisory",
|
||||
"total_weight": 0.8,
|
||||
"finding_count": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user