themed the bulk of advisories

This commit is contained in:
StellaOps Bot
2025-12-14 19:58:38 +02:00
parent 00c41790f4
commit 9202cd7da8
63 changed files with 6377 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
# CVSS and Competitive Analysis Technical Reference
**Source Advisories**:
- 29-Nov-2025 - CVSS v4.0 Momentum in Vulnerability Management
- 30-Nov-2025 - Comparative Evidence Patterns for Stella Ops
- 03-Dec-2025 - NextGen Scanner Differentiators and Evidence Moat
**Last Updated**: 2025-12-14
---
## 1. CVSS V4.0 INTEGRATION
### 1.1 Requirements
- Vendors (NVD, GitHub, Microsoft, Snyk) shipping CVSS v4 signals
- Awareness needed for receipt schemas, reporting, UI alignment
### 1.2 Determinism & Offline
- Keep CVSS vector parsing deterministic
- Pin scoring library versions in receipts
- Avoid live API dependency
- Rely on mirrored NVD feeds or frozen samples
### 1.3 Schema Mapping
- Map impacts to receipt schemas
- Identify UI/reporting deltas for transparency
- Note in sprint Decisions & Risks for CVSS receipts
## 2. SCANNER DISCREPANCIES ANALYSIS
### 2.1 Trivy vs Grype Comparative Study (927 images)
**Findings**:
- Tools disagreed on total vulnerability counts and specific CVE IDs
- Grype: ~603,259 vulns; Trivy: ~473,661 vulns
- Exact match in only 9.2% of cases (80 out of 865 vulnerable images)
- Even with same counts, specific vulnerability IDs differed
**Root Causes**:
- Divergent vulnerability databases
- Differing matching logic
- Incomplete visibility
### 2.2 VEX Tools Consistency Study (2025)
**Tools Tested**:
- Trivy
- Grype
- OWASP DepScan
- Docker Scout
- Snyk CLI
- OSV-Scanner
- Vexy
**Results**:
- Low consistency/similarity across container scanners
- DepScan: 18,680 vulns; Vexy: 191 vulns (2 orders of magnitude difference)
- Pairwise Jaccard indices very low (near 0)
- 4 most consistent tools shared only ~18% common vulnerabilities
### 2.3 Implications for StellaOps
**Moats Needed**:
- Golden-fixture benchmarks (container images with known, audited vulnerabilities)
- Deterministic, replayable scans
- Cryptographic integrity
- VEX/SBOM proofs
**Metrics**:
- **Closure rate**: Time from flagged to confirmed exploitable
- **Proof coverage**: % of dependencies with valid SBOM/VEX proofs
- **Differential-closure**: Impact of database updates or policy changes on prior scan results
## 3. RUNTIME REACHABILITY APPROACHES
### 3.1 Runtime-Aware Vulnerability Prioritization
**Approach**:
- Monitor container workloads at runtime to determine which vulnerable components are actually used
- Use eBPF-based monitors, dynamic tracers, or built-in profiling
- Construct runtime call graph or dependency graph
- Map vulnerabilities to code entities (functions/modules)
- If execution trace covers entity, vulnerability is "reachable"
**Findings**: ~85% of critical vulns in containers are in inactive code (Sysdig)
### 3.2 Reachability Analysis Techniques
**Static**:
- Call-graph analysis (Snyk reachability, CodeQL)
- All possible paths
**Dynamic**:
- Runtime observation (loaded modules, invoked functions)
- Actual runtime paths
**Granularity Levels**:
- Function-level (precise, limited languages: Java, .NET)
- Package/module-level (broader, coarse)
**Hybrid Approach**: Combine static (all possible paths) + dynamic (actual runtime paths)
## 4. CONTAINER PROVENANCE & SUPPLY CHAIN
### 4.1 In-Toto/DSSE Framework (NDSS 2024)
**Purpose**:
- Track chain of custody in software builds
- Signed metadata (attestations) for each step
- DSSE: Dead Simple Signing Envelope for standardized signing
### 4.2 Scudo System
**Features**:
- Combines in-toto with Uptane
- Verifies build process and final image
- Full verification on client inefficient; verify upstream and trust summary
- Client checks final signature + hash only
### 4.3 Supply Chain Verification
**Signers**:
- Developer key signs code commit
- CI key signs build attestation
- Scanner key signs vulnerability attestation
- Release key signs container image
**Verification Optimization**: Repository verifies in-toto attestations; client verifies final metadata only
## 5. VENDOR EVIDENCE PATTERNS
### 5.1 Snyk
**Evidence Handling**:
- Runtime insights integration (Nov 2025)
- Evolution from static-scan noise to prioritized workflow
- Deployment context awareness
**VEX Support**:
- CycloneDX VEX format
- Reachability-aware suppression
### 5.2 GitHub Advanced Security
**Features**:
- CodeQL for static analysis
- Dependency graph
- Dependabot alerts
- Security advisories
**Evidence**:
- SARIF output
- SBOM generation (SPDX)
### 5.3 Aqua Security
**Approach**:
- Runtime protection
- Image scanning
- Kubernetes security
**Evidence**:
- Dynamic runtime traces
- Network policy violations
### 5.4 Anchore/Grype
**Features**:
- Open-source scanner
- Policy-based compliance
- SBOM generation
**Evidence**:
- CycloneDX/SPDX SBOM
- Vulnerability reports (JSON)
### 5.5 Prisma Cloud
**Features**:
- Cloud-native security
- Runtime defense
- Compliance monitoring
**Evidence**:
- Multi-cloud attestations
- Compliance reports
## 6. STELLAOPS DIFFERENTIATORS
### 6.1 Reachability-with-Evidence
**Why it Matters**:
- Snyk Container integrating runtime insights as "signal" (Nov 2025)
- Evolution from static-scan noise to prioritized, actionable workflow
- Deployment context: what's running, what's reachable, what's exploitable
**Implication**: Container security triage relies on runtime/context signals
### 6.2 Proof-First Architecture
**Advantages**:
- Every claim backed by DSSE-signed attestations
- Cryptographic integrity
- Audit trail
- Offline verification
### 6.3 Deterministic Scanning
**Advantages**:
- Reproducible results
- Bit-identical outputs given same inputs
- Replay manifests
- Golden fixture benchmarks
### 6.4 VEX-First Decisioning
**Advantages**:
- Exploitability modeled in OpenVEX
- Lattice logic for stable outcomes
- Evidence-linked justifications
### 6.5 Offline/Air-Gap First
**Advantages**:
- No hidden network dependencies
- Bundled feeds, keys, Rekor snapshots
- Verifiable without internet access
## 7. COMPETITIVE POSITIONING
### 7.1 Market Segments
| Vendor | Strength | Weakness vs StellaOps |
|--------|----------|----------------------|
| Snyk | Developer experience | Less deterministic, SaaS-only |
| Aqua | Runtime protection | Less reachability precision |
| Anchore | Open-source, SBOM | Less proof infrastructure |
| Prisma Cloud | Cloud-native breadth | Less offline/air-gap support |
| GitHub | Integration with dev workflow | Less cryptographic proof chain |
### 7.2 StellaOps Unique Value
1. **Deterministic + Provable**: Bit-identical scans with cryptographic proofs
2. **Reachability + Runtime**: Hybrid static/dynamic analysis
3. **Offline/Sovereign**: Air-gap operation with regional crypto (FIPS/GOST/eIDAS/SM)
4. **VEX-First**: Evidence-backed decisioning, not just alerting
5. **AGPL-3.0**: Self-hostable, no vendor lock-in
## 8. MOAT METRICS
### 8.1 Proof Coverage
```
proof_coverage = findings_with_valid_receipts / total_findings
Target: ≥95%
```
### 8.2 Closure Rate
```
closure_rate = time_from_flagged_to_confirmed_exploitable
Target: P95 < 24 hours
```
### 8.3 Differential-Closure Impact
```
differential_impact = findings_changed_after_db_update / total_findings
Target: <5% (non-code changes)
```
### 8.4 False Positive Reduction
```
fp_reduction = (baseline_fp_rate - stella_fp_rate) / baseline_fp_rate
Target: ≥50% vs baseline scanner
```
### 8.5 Reachability Accuracy
```
reachability_accuracy = correct_r0_r1_r2_r3_classifications / total_classifications
Target: ≥90%
```
## 9. COMPETITIVE INTELLIGENCE TRACKING
### 9.1 Feature Parity Matrix
| Feature | Snyk | Aqua | Anchore | Prisma | StellaOps |
|---------|------|------|---------|--------|-----------|
| SBOM Generation | ✓ | ✓ | ✓ | ✓ | ✓ |
| VEX Support | ✓ | ✗ | Partial | ✗ | ✓ |
| Reachability Analysis | ✓ | ✗ | ✗ | ✗ | ✓ |
| Runtime Evidence | ✓ | ✓ | ✗ | ✓ | ✓ |
| Cryptographic Proofs | ✗ | ✗ | ✗ | ✗ | ✓ |
| Deterministic Scans | ✗ | ✗ | ✗ | ✗ | ✓ |
| Offline/Air-Gap | ✗ | Partial | ✗ | ✗ | ✓ |
| Regional Crypto | ✗ | ✗ | ✗ | ✗ | ✓ |
### 9.2 Monitoring Strategy
- Track vendor release notes
- Monitor GitHub repos for feature announcements
- Participate in security conferences
- Engage with customer feedback
- Update competitive matrix quarterly
## 10. MESSAGING FRAMEWORK
### 10.1 Core Message
"StellaOps provides deterministic, proof-backed vulnerability management with reachability analysis for offline/air-gapped environments."
### 10.2 Key Differentiators (Elevator Pitch)
1. **Deterministic**: Same inputs → same outputs, every time
2. **Provable**: Cryptographic proof chains for every decision
3. **Reachable**: Static + runtime analysis, not just presence
4. **Sovereign**: Offline operation, regional crypto compliance
5. **Open**: AGPL-3.0, self-hostable, no lock-in
### 10.3 Target Personas
- **Security Engineers**: Need proof-backed decisions for audits
- **DevOps Teams**: Need deterministic scans in CI/CD
- **Compliance Officers**: Need offline/air-gap for regulated environments
- **Platform Engineers**: Need self-hostable, sovereign solution
## 11. BENCHMARKING PROTOCOL
### 11.1 Comparative Test Suite
**Images**:
- 50 representative production images
- Known vulnerabilities labeled
- Reachability ground truth established
**Metrics**:
- Precision (1 - FP rate)
- Recall (TP / (TP + FN))
- F1 score
- Scan time (P50, P95)
- Determinism (identical outputs over 10 runs)
### 11.2 Test Execution
```bash
# Run StellaOps scan
stellaops scan --image test-image:v1 --output stella-results.json
# Run competitor scans
trivy image --format json test-image:v1 > trivy-results.json
grype test-image:v1 -o json > grype-results.json
snyk container test test-image:v1 --json > snyk-results.json
# Compare results
stellaops benchmark compare \
--ground-truth ground-truth.json \
--stella stella-results.json \
--trivy trivy-results.json \
--grype grype-results.json \
--snyk snyk-results.json
```
### 11.3 Results Publication
- Publish benchmarks quarterly
- Open-source test images and ground truth
- Invite community contributions
- Document methodology transparently
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,640 @@
# Determinism and Reproducibility Technical Reference
**Source Advisories**:
- 07-Dec-2025 - Designing Deterministic Vulnerability Scores
- 12-Dec-2025 - Designing a Deterministic Vulnerability Scoring Matrix
- 12-Dec-2025 - Replay Fidelity as a Proof Metric
- 01-Dec-2025 - Benchmarks for a Testable Security Moat
- 02-Dec-2025 - Benchmarking a Testable Security Moat
**Last Updated**: 2025-12-14
---
## 1. SCORE FORMULA (BASIS POINTS)
**Total Score:**
```
riskScore = (wB*B + wR*R + wE*E + wP*P) / 10000
```
**Default Weights (basis points, sum = 10000):**
- `wB=1000` (10%) - Base Severity
- `wR=4500` (45%) - Reachability
- `wE=3000` (30%) - Evidence
- `wP=1500` (15%) - Provenance
## 2. SUBSCORE DEFINITIONS (0-100 integers)
### 2.1 BaseSeverity (B)
```
B = round(CVSS * 10) // CVSS 0.0-10.0 → 0-100
```
### 2.2 Reachability (R)
Hop Buckets:
```
0-2 hops: 100
3 hops: 85
4 hops: 70
5 hops: 55
6 hops: 45
7 hops: 35
8+ hops: 20
unreachable: 0
```
Gate Multipliers (in basis points):
```
behind feature flag: ×7000
auth required: ×8000
admin only: ×8500
non-default config: ×7500
```
Final R:
```
R = bucketScore * gateMultiplier / 10000
```
### 2.3 Evidence (E)
Points:
```
runtime trace: +60
DAST/integration test: +30
SAST precise sink: +20
SCA presence only: +10
```
Freshness Multiplier (basis points):
```
≤ 7 days: ×10000
≤ 30 days: ×9000
≤ 90 days: ×7500
≤ 180 days: ×6000
≤ 365 days: ×4000
> 365 days: ×2000
```
Final E:
```
E = min(100, sum(points)) * freshness / 10000
```
### 2.4 Provenance (P)
```
unsigned/unknown: 0
signed image: 30
signed + SBOM hash-linked: 60
signed + SBOM + DSSE attestations: 80
above + reproducible build match: 100
```
## 3. SCORE POLICY YAML SCHEMA
```yaml
policyVersion: score.v1
weightsBps:
baseSeverity: 1000
reachability: 4500
evidence: 3000
provenance: 1500
reachability:
hopBuckets:
- { maxHops: 2, score: 100 }
- { maxHops: 3, score: 85 }
- { maxHops: 4, score: 70 }
- { maxHops: 5, score: 55 }
- { maxHops: 6, score: 45 }
- { maxHops: 7, score: 35 }
- { maxHops: 9999, score: 20 }
unreachableScore: 0
gateMultipliersBps:
featureFlag: 7000
authRequired: 8000
adminOnly: 8500
nonDefaultConfig: 7500
evidence:
points:
runtime: 60
dast: 30
sast: 20
sca: 10
freshnessBuckets:
- { maxAgeDays: 7, multiplierBps: 10000 }
- { maxAgeDays: 30, multiplierBps: 9000 }
- { maxAgeDays: 90, multiplierBps: 7500 }
- { maxAgeDays: 180, multiplierBps: 6000 }
- { maxAgeDays: 365, multiplierBps: 4000 }
- { maxAgeDays: 99999, multiplierBps: 2000 }
provenance:
levels:
unsigned: 0
signed: 30
signedWithSbom: 60
signedWithSbomAndAttestations: 80
reproducible: 100
overrides:
- name: knownExploitedAndReachable
when:
flags:
knownExploited: true
minReachability: 70
setScore: 95
- name: unreachableAndOnlySca
when:
maxReachability: 0
maxEvidence: 10
clampMaxScore: 25
```
## 4. SCORE DATA CONTRACTS
### 4.1 ScoreInput
```json
{
"asOf": "2025-12-14T10:20:30Z",
"policyVersion": "score.v1",
"reachabilityDigest": "sha256:...",
"evidenceDigest": "sha256:...",
"provenanceDigest": "sha256:...",
"baseSeverityDigest": "sha256:..."
}
```
### 4.2 ScoreResult
```json
{
"scoreId": "score_...",
"riskScore": 73,
"subscores": {
"baseSeverity": 75,
"reachability": 85,
"evidence": 60,
"provenance": 60
},
"cvss": {
"v": "3.1",
"base": 7.5,
"environmental": 5.3,
"vector": "CVSS:3.1/AV:N/AC:L/..."
},
"inputsRef": ["evidence_sha256:...", "env_sha256:..."],
"policyVersion": "score.v1",
"policyDigest": "sha256:...",
"engineVersion": "stella-scorer@1.8.2",
"computedAt": "2025-12-09T10:20:30Z",
"resultDigest": "sha256:...",
"explain": [
{"factor": "reachability", "value": 85, "reason": "3 hops from HTTP endpoint"},
{"factor": "evidence", "value": 60, "reason": "Runtime trace (60pts), 20 days old (×90%)"}
]
}
```
### 4.3 ReachabilityReport
```json
{
"artifactDigest": "sha256:...",
"graphDigest": "sha256:...",
"vulnId": "CVE-2024-1234",
"vulnerableSymbol": "org.example.VulnClass.vulnMethod",
"entrypoints": ["POST /api/upload"],
"shortestPath": {
"hops": 3,
"nodes": [
{"symbol": "UploadController.handleUpload", "file": "Controller.cs", "line": 42},
{"symbol": "ProcessorService.process", "file": "Service.cs", "line": 18},
{"symbol": "org.example.VulnClass.vulnMethod", "file": null, "line": null}
]
},
"gates": [
{"type": "authRequired", "detail": "Requires JWT token"},
{"type": "featureFlag", "detail": "FEATURE_UPLOAD_V2=true"}
],
"computedAt": "2025-12-14T10:15:30Z",
"toolVersion": "reachability-analyzer@2.1.0"
}
```
### 4.4 EvidenceBundle
```json
{
"evidenceId": "sha256:...",
"artifactDigest": "sha256:...",
"vulnId": "CVE-2024-1234",
"type": "RUNTIME",
"tool": "runtime-tracer@1.0.0",
"timestamp": "2025-12-10T14:30:00Z",
"confidence": 95,
"subject": "org.example.VulnClass.vulnMethod",
"payloadDigest": "sha256:..."
}
```
### 4.5 ProvenanceReport
```json
{
"artifactDigest": "sha256:...",
"signatureChecks": [
{"signer": "CI-KEY-1", "algorithm": "ECDSA-P256", "result": "VALID"}
],
"sbomDigest": "sha256:...",
"sbomType": "cyclonedx-1.6",
"attestations": ["sha256:...", "sha256:..."],
"transparencyLogRefs": ["rekor://..."],
"reproducibleMatch": true,
"computedAt": "2025-12-14T10:15:30Z",
"toolVersion": "provenance-verifier@1.0.0"
}
```
## 5. DETERMINISM CONSTRAINTS
### 5.1 Fixed-Point Math
- Use integer basis points (100% = 10,000 bps)
- No floating point in scoring math
- Round only at final display
### 5.2 Canonical Serialization
- RFC-style canonical JSON (JCS)
- Sort keys and arrays deterministically
- Stable ordering for explanation lists by `(factorId, contributingObjectDigest)`
### 5.3 Time Handling
- No implicit time
- `asOf` is explicit input
- Freshness = `asOf - evidence.timestamp`
- Use monotonic time internally
## 6. FIDELITY METRICS
### 6.1 Bitwise Fidelity (BF)
```
BF = identical_outputs / total_replays
Target: ≥ 0.98
```
### 6.2 Semantic Fidelity (SF)
- Normalized object comparison (same packages, versions, CVEs, severities, verdicts)
- Allows formatting differences
### 6.3 Policy Fidelity (PF)
- Final policy decision (pass/fail + reason codes) matches
## 7. SCAN MANIFEST SCHEMA
```json
{
"manifest_version": "1.0",
"scan_id": "scan_123",
"created_at": "2025-12-12T10:15:30Z",
"input": {
"type": "oci_image",
"image_ref": "registry/app@sha256:...",
"layers": ["sha256:...", "sha256:..."],
"source_provenance": {"repo_sha": "abc123", "build_id": "ci-999"}
},
"scanner": {
"engine": "stella",
"scanner_image_digest": "sha256:...",
"scanner_version": "2025.12.0",
"config_digest": "sha256:...",
"flags": ["--deep", "--vex"]
},
"feeds": {
"vuln_feed_bundle_digest": "sha256:...",
"license_db_digest": "sha256:..."
},
"policy": {
"policy_bundle_digest": "sha256:...",
"policy_set": "prod-default"
},
"environment": {
"arch": "amd64",
"os": "linux",
"tz": "UTC",
"locale": "C",
"network": "disabled",
"clock_mode": "frozen",
"clock_value": "2025-12-12T10:15:30Z"
},
"normalization": {
"canonicalizer_version": "1.2.0",
"sbom_schema": "cyclonedx-1.6",
"vex_schema": "cyclonedx-vex-1.0"
}
}
```
## 8. MISMATCH CLASSIFICATION TAXONOMY
```
- Feed drift
- Policy drift
- Runtime drift
- Scanner drift
- Nondeterminism (ordering, concurrency, RNG, time-based logic)
- External IO
```
## 9. POSTGRESQL SCHEMA
```sql
CREATE TABLE scan_manifest (
manifest_id UUID PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
artifact_digest TEXT NOT NULL,
feeds_merkle_root TEXT NOT NULL,
engine_build_hash TEXT NOT NULL,
policy_lattice_hash TEXT NOT NULL,
ruleset_hash TEXT NOT NULL,
config_flags JSONB NOT NULL,
environment_fingerprint JSONB NOT NULL,
raw_manifest JSONB NOT NULL,
raw_manifest_sha256 TEXT NOT NULL
);
CREATE TABLE scan_execution (
execution_id UUID PRIMARY KEY,
manifest_id UUID NOT NULL REFERENCES scan_manifest(manifest_id) ON DELETE CASCADE,
started_at TIMESTAMPTZ NOT NULL,
finished_at TIMESTAMPTZ NOT NULL,
t_ingest_ms INT NOT NULL,
t_analyze_ms INT NOT NULL,
t_reachability_ms INT NOT NULL,
t_vex_ms INT NOT NULL,
t_sign_ms INT NOT NULL,
t_publish_ms INT NOT NULL,
proof_bundle_sha256 TEXT NOT NULL,
findings_sha256 TEXT NOT NULL,
vex_bundle_sha256 TEXT NOT NULL,
replay_mode BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE classification_history (
id BIGSERIAL PRIMARY KEY,
artifact_digest TEXT NOT NULL,
manifest_id UUID NOT NULL REFERENCES scan_manifest(manifest_id) ON DELETE CASCADE,
execution_id UUID NOT NULL REFERENCES scan_execution(execution_id) ON DELETE CASCADE,
previous_status TEXT NOT NULL,
new_status TEXT NOT NULL,
cause TEXT NOT NULL,
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE VIEW scan_tte AS
SELECT
execution_id,
manifest_id,
(finished_at - started_at) AS tte_interval
FROM scan_execution;
CREATE MATERIALIZED VIEW fn_drift_stats AS
SELECT
date_trunc('day', changed_at) AS day_bucket,
COUNT(*) FILTER (WHERE new_status = 'affected') AS affected_count,
COUNT(*) AS total_reclassified,
ROUND(
(COUNT(*) FILTER (WHERE new_status = 'affected')::numeric /
NULLIF(COUNT(*), 0)) * 100, 4
) AS drift_percent
FROM classification_history
GROUP BY 1;
```
## 10. C# CANONICAL DATA STRUCTURES
```csharp
public sealed record CanonicalScanManifest
{
public required string ArtifactDigest { get; init; }
public required string FeedsMerkleRoot { get; init; }
public required string EngineBuildHash { get; init; }
public required string PolicyLatticeHash { get; init; }
public required string RulesetHash { get; init; }
public required IReadOnlyDictionary<string, string> ConfigFlags { get; init; }
public required EnvironmentFingerprint Environment { get; init; }
}
public sealed record EnvironmentFingerprint
{
public required string CpuModel { get; init; }
public required string RuntimeVersion { get; init; }
public required string Os { get; init; }
public required IReadOnlyDictionary<string, string> Extra { get; init; }
}
public sealed record ScanExecutionMetrics
{
public required int IngestMs { get; init; }
public required int AnalyzeMs { get; init; }
public required int ReachabilityMs { get; init; }
public required int VexMs { get; init; }
public required int SignMs { get; init; }
public required int PublishMs { get; init; }
}
```
## 11. CANONICALIZATION IMPLEMENTATION
```csharp
internal static class CanonicalJson
{
private static readonly JsonSerializerOptions Options = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static string Serialize(object obj)
{
using var stream = new MemoryStream();
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Indented = false,
SkipValidation = false
}))
{
JsonSerializer.Serialize(writer, obj, obj.GetType(), Options);
}
var bytes = stream.ToArray();
var canonical = JsonCanonicalizer.Canonicalize(bytes);
return canonical;
}
}
```
## 12. REPLAY RUNNER
```csharp
public static class ReplayRunner
{
public static ReplayResult Replay(Guid manifestId, IScannerEngine engine)
{
var manifest = ManifestRepository.Load(manifestId);
var canonical = CanonicalJson.Serialize(manifest.RawObject);
var canonicalHash = Sha256(canonical);
if (canonicalHash != manifest.RawManifestSHA256)
throw new InvalidOperationException("Manifest integrity violation.");
using var feeds = FeedSnapshotResolver.Open(manifest.FeedsMerkleRoot);
var exec = engine.Scan(new ScanRequest
{
ArtifactDigest = manifest.ArtifactDigest,
Feeds = feeds,
LatticeHash = manifest.PolicyLatticeHash,
EngineBuildHash = manifest.EngineBuildHash,
CanonicalManifest = canonical
});
return new ReplayResult(
exec.FindingsHash == manifest.FindingsSHA256,
exec.VexBundleHash == manifest.VexBundleSHA256,
exec.ProofBundleHash == manifest.ProofBundleSHA256,
exec
);
}
}
```
## 13. BENCHMARK METRICS
### 13.1 Time-to-Evidence (TTE)
**Definition:**
```
TTE = t(proof_ready) t(artifact_ingested)
```
**Targets:**
- P50 < 2m for typical containers (≤ 500 MB)
- P95 < 5m including cold-start/offline-bundle mode
**Stage Breakdown:**
- t_ingest_ms
- t_analyze_ms
- t_reachability_ms
- t_vex_ms
- t_sign_ms
- t_publish_ms
### 13.2 False-Negative Drift Rate (FN-DRIFT)
**Definition (rolling 30d window):**
```
FN-Drift = (# artifacts re-classified from {unaffected/unknown} → affected) / (total artifacts re-evaluated)
```
**Stratification:**
- feed delta
- rule delta
- lattice/policy delta
- reachability delta
**Targets:**
- Engine-caused FN-Drift 0
- Feed-caused FN-Drift: faster is better
### 13.3 Deterministic Reproducibility
**Proof Object:**
```json
{
"artifact_digest": "sha256:...",
"scan_manifest_hash": "sha256:...",
"feeds_merkle_root": "sha256:...",
"engine_build_hash": "sha256:...",
"policy_lattice_hash": "sha256:...",
"findings_sha256": "sha256:...",
"vex_bundle_sha256": "sha256:...",
"proof_bundle_sha256": "sha256:..."
}
```
**Metric:**
```
Repro rate = identical_outputs / total_replays
Target: 100%
```
### 13.4 Detection Metrics
```
true_positive_count (TP)
false_positive_count (FP)
false_negative_count (FN)
precision = TP / (TP + FP)
recall = TP / (TP + FN)
fp_reduction = (baseline_fp_rate - stella_fp_rate) / baseline_fp_rate
```
### 13.5 Proof Coverage
```
proof_coverage_all = findings_with_valid_receipts / total_findings
proof_coverage_vex = vex_items_with_valid_receipts / total_vex_items
proof_coverage_reachable = reachable_findings_with_proofs / total_reachable_findings
```
## 14. SLO THRESHOLDS
**Fidelity:**
- BF 0.98 (general)
- BF 0.95 (regulated projects)
- PF 1.0 (unless policy changed intentionally)
**Alerts:**
- BF drops 2% week-over-week warn
- BF < 0.90 overall page/block release
- Regulated BF < 0.95 page/block release
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL 16, Angular v17

View File

@@ -0,0 +1,399 @@
# Developer Onboarding Technical Reference
**Source Advisories**:
- 01-Dec-2025 - Common Developers guides
- 29-Nov-2025 - StellaOps Mid-Level .NET Onboarding (Quick Start)
- 30-Nov-2025 - Implementor Guidelines for Stella Ops
- 30-Nov-2025 - Standup Sprint Kickstarters
**Last Updated**: 2025-12-14
---
## 1. CORE ENGINEERING PRINCIPLES
- **SOLID First**: Interface and dependency inversion required
- **100-line File Rule**: Files >100 lines must be split/refactored
- **Contracts vs Runtime**: Public DTOs/interfaces in `*.Contracts` projects
- **Single Composition Root**: DI wiring in `StellaOps.Web/Program.cs` and plugin `IoCConfigurator`
- **No Service Locator**: Constructor injection only
- **Fail-fast Startup**: Validate configuration before web host starts
- **Hot-load Compatibility**: Avoid static singletons that survive plugin unload
## 2. REPOSITORY LAYOUT RULES
- No "Module" folders or nested solution hierarchies
- Tests mirror `src/` structure 1:1
- No test code in production projects
- Feature folder layout: `Scan/ScanService.cs`, `Scan/ScanController.cs`
## 3. NAMING & STYLE CONVENTIONS
### 3.1 Namespaces & Files
- **Namespaces**: File-scoped, `StellaOps.*`
- **Classes/records**: PascalCase
- **Interfaces**: `I` prefix (`IScannerRunner`)
- **Private fields**: `camelCase` (no leading `_`)
- **Constants**: `SCREAMING_SNAKE_CASE`
- **Async methods**: End with `Async`
### 3.2 Usings
- Outside namespace
- Sorted
- No wildcards
## 4. C# FEATURE USAGE
- Nullable reference types enabled
- Use `record` for immutable DTOs
- Prefer pattern matching over long `switch` cascades
- `Span`/`Memory` only when measured as necessary
- Use `await foreach` instead of manual iterator loops
## 5. DI POLICY
### 5.1 Composition Root
- **One composition root** per process
- Plugins contribute via `[ServiceBinding]` or `IoCConfigurator : IDependencyInjectionRoutine`
- Default lifetime: **scoped**
- Singletons only for stateless, thread-safe helpers
- Never use service locator or manually build nested service providers
### 5.2 Service Binding Attributes
```csharp
[ServiceBinding(typeof(IMyContract), ServiceLifetime.Scoped)]
public class MyService : IMyContract
{
// Implementation
}
```
### 5.3 Advanced DI Configuration
```csharp
public class MyPluginIoCConfigurator : IDependencyInjectionRoutine
{
public void Configure(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IMyContract, MyService>();
services.Configure<MyOptions>(config.GetSection("MyPlugin"));
}
}
```
## 6. ASYNC & THREADING
- All I/O is async; avoid `.Result` / `.Wait()`
- Library code uses `ConfigureAwait(false)`
- Control concurrency with channels or `Parallel.ForEachAsync`
## 7. TEST LAYERS
- **Unit**: xUnit
- **Property-based**: FsCheck
- **Integration**: API with Testcontainers, DB/merge with Mongo + Redis
- **Contracts**: gRPC breakage checks with Buf
- **Frontend**: Jest (unit), Playwright (e2e), Lighthouse (performance/a11y)
- **Non-functional**: k6 (load), Docker (chaos), dependency/license scanning, SBOM reproducibility
## 8. QUALITY GATES
- API unit test coverage ≥ ~85%
- API P95 latency ≤ ~120ms
- Δ-SBOM warm scan P95 ≤ ~5s
- Lighthouse perf ≥ ~90, a11y ≥ ~95
## 9. PLUGIN SYSTEM
### 9.1 Plugin Templates
```bash
dotnet new stellaops-plugin-schedule -n MyPlugin.Schedule
```
### 9.2 Plugin Publishing
- Publish signed artifacts to `src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/`
- Backend verifies Cosign signature
- Enforces `[StellaPluginVersion]` compatibility
- Loads plugins in isolated `AssemblyLoadContext`s
### 9.3 Plugin Signing
```bash
dotnet publish -c Release -p:PublishSingleFile=true -o out
cosign sign --key $COSIGN_KEY out/MyPlugin.Schedule.dll
```
## 10. POLICY DSL (stella-dsl@1)
### 10.1 Goals
- Deterministic
- Declarative
- Explainable
- Offline-friendly
- Reachability-aware
### 10.2 Structure
- One `policy` block per `.stella` file
- Contains: `metadata`, `profile` blocks, `rule` blocks, optional `settings`
### 10.3 Context Namespaces
- `sbom`
- `advisory`
- `vex`
- `env`
- `telemetry`
- `secret`
- `profile.*`
### 10.4 Helpers
- `normalize_cvss`
- `risk_score`
- `vex.any`
- `vex.latest`
- `sbom.any_component`
- `exists`
- `coalesce`
### 10.5 Rules
- Always include clear `because` when changing `status` or `severity`
- Avoid catch-all suppressions (`when true` + `status := "suppressed"`)
- Use `stella policy lint/compile/simulate` in CI
- Test in sealed (offline) mode
## 11. PR CHECKLIST
1. Use **Conventional Commit** prefixes (`feat:`, `fix:`, `docs:`)
2. Run `dotnet format` and `dotnet test` (both must be green)
3. Keep files within 100-line guideline
4. Update XML-doc comments for new public API
5. Update docs and JSON schema for contract changes
6. Ensure analyzers and CI jobs pass
## 12. ONBOARDING DETERMINISM REQUIREMENTS
- Use fixed seeds and pinned toolchain versions
- Avoid live network calls; prefer cached feeds/mirrors
- Note mirror paths in examples
## 13. SPRINT READINESS CHECKLIST
- Scanner regressions verification
- Postgres slice validation
- DSSE/Rekor sweep complete
- Pin tool versions in scripts
## 14. MODULE-SPECIFIC GUIDANCE
### 14.1 Scanner Module
- Reachability algorithms only in Scanner.WebService
- Cache lazy and keyed by deterministic inputs
- Output includes explicit evidence pointers
- UI endpoints expose reachability state in structured form
### 14.2 Authority Module
- Trust roots: pinned via out-of-band distribution
- Key rotation: maintain version history in trust store
- Revocation: maintain revoked_keys list in trust anchors
### 14.3 Excititor (VEX) Module
- VEX schema includes pointers to all upstream artifacts
- No duplication of SBOM/scan content inside VEX
- DSSE used as standard envelope type
### 14.4 Policy Module
- Facts and policies serialized separately
- Lattice code in allowed services only
- Merge strategies named and versioned
- Artifacts record which lattice algorithm used
## 15. COMMON PITFALLS & SOLUTIONS
### 15.1 Avoid
- ❌ Service Locator pattern
- ❌ Static mutable state
- ❌ Async void (except event handlers)
- ❌ Blocking on async code (.Result, .Wait())
- ❌ Non-deterministic ordering
- ❌ Hard-coded timestamps
- ❌ Environment variables in core algorithms
### 15.2 Prefer
- ✅ Constructor injection
- ✅ Immutable data structures
- ✅ async Task
- ✅ await or Task.Run for CPU-bound work
- ✅ Stable sorting with explicit comparers
- ✅ Explicit `asOf` parameters
- ✅ Configuration objects passed as parameters
## 16. DEBUGGING WORKFLOW
### 16.1 Local Development
```bash
# Run all services
docker-compose up -d
# Run specific service
dotnet run --project src/Scanner/StellaOps.Scanner.WebService
# Attach debugger
# Use VS Code launch.json or Visual Studio F5
```
### 16.2 Log Correlation
```csharp
using var activity = Activity.Current;
activity?.SetTag("scan.id", scanId);
_logger.LogInformation("Processing scan {ScanId}", scanId);
```
### 16.3 OpenTelemetry
```csharp
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddNpgsql()
.AddOtlpExporter());
```
## 17. PERFORMANCE OPTIMIZATION
### 17.1 Database
- Use indexes for hot queries
- Batch inserts/updates
- Use `COPY` for bulk data
- Avoid N+1 queries
### 17.2 Memory
- Use `Span<T>` for hot paths
- Pool large objects
- Dispose `IDisposable` promptly
- Profile with dotMemory
### 17.3 Caching
- Cache deterministically (keyed by input hashes)
- Use distributed cache (Valkey/Redis) for shared state
- TTL appropriate to data volatility
## 18. SECURITY GUIDELINES
### 18.1 Input Validation
- Validate all user inputs
- Use allowlists, not denylists
- Sanitize for SQL, XSS, path traversal
### 18.2 Authentication & Authorization
- Never roll your own crypto
- Use standard protocols (OAuth2, OIDC)
- Implement principle of least privilege
### 18.3 Secrets Management
- Never commit secrets
- Use environment variables or KMS
- Rotate credentials regularly
## 19. DOCUMENTATION STANDARDS
### 19.1 XML Documentation
```csharp
/// <summary>
/// Scans the specified artifact for vulnerabilities.
/// </summary>
/// <param name="artifactId">The artifact identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Scan results with reachability analysis.</returns>
/// <exception cref="ArgumentNullException">If artifactId is null.</exception>
public Task<ScanResult> ScanAsync(string artifactId, CancellationToken ct);
```
### 19.2 Architecture Decision Records (ADRs)
```markdown
# ADR-XXX: Title
## Status
Proposed | Accepted | Deprecated | Superseded
## Context
What is the issue?
## Decision
What did we decide?
## Consequences
What are the implications?
```
## 20. CI/CD INTEGRATION
### 20.1 Build Pipeline
```yaml
stages:
- restore
- build
- test
- analyze
- package
- deploy
```
### 20.2 Required Checks
- Unit tests pass
- Integration tests pass
- Code coverage ≥85%
- No high/critical vulnerabilities
- SBOM generated
- Determinism tests pass
## 21. MIGRATION GUIDE
### 21.1 From .NET 8 to .NET 10
- Update `<TargetFramework>net10.0</TargetFramework>`
- Review breaking changes
- Update NuGet packages
- Test thoroughly
### 21.2 Database Migrations
```bash
# Create migration
dotnet ef migrations add MigrationName -p src/Module -s src/WebService
# Apply migration
dotnet ef database update -p src/Module -s src/WebService
```
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,379 @@
# Offline and Air-Gap Technical Reference
**Source Advisories**:
- 01-Dec-2025 - DSSESigned Offline Scanner Updates
- 07-Dec-2025 - Reliable AirGap Verification Workflows
**Last Updated**: 2025-12-14
---
## 1. OFFLINE UPDATE BUNDLE STRUCTURE
### 1.1 Directory Layout
```
/bundle-2025-12-14/
manifest.json # version, created_at, entries[], sha256s
payload.tar.zst # actual DB/indices/feeds
payload.tar.zst.sha256
statement.dsse.json # DSSE-wrapped statement over payload hash
rekor-receipt.json # Rekor v2 inclusion/verification material
```
### 1.2 Manifest Schema
**manifest.json**:
```json
{
"version": "string",
"created_at": "UTC ISO-8601",
"entries": [{"name": "string", "sha256": "string", "size": int}],
"payload_sha256": "string"
}
```
### 1.3 DSSE Predicate Schema
**statement.dsse.json payload**:
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"subject": {
"name": "stella-ops-offline-kit-<DATE>.tgz",
"digest": {"sha256": "string"}
},
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
"predicate": {
"offline_manifest_sha256": "string",
"feeds": [{"name": "string", "snapshot_date": "UTC ISO-8601", "archive_digest": "string"}],
"builder": "string",
"created_at": "UTC ISO-8601",
"oukit_channel": "edge|stable|fips-profile"
}
}
}
```
### 1.4 Rekor Receipt Schema
**rekor-receipt.json**:
```json
{
"uuid": "string",
"logIndex": int,
"rootHash": "string",
"hashes": ["string"],
"checkpoint": "string"
}
```
## 2. VERIFICATION SEQUENCE
### 2.1 Offline Kit Import Steps
```
1. Validate Cosign signature of tarball
2. Validate offline-manifest.json with JWS signature
3. Verify file digests for all entries (including /attestations/*)
4. Verify DSSE:
- Call StellaOps.Attestor.Verify with:
- offline-update.dsse.json
- offline-update.rekor.json
- local Rekor log snapshot/segment
- Ensure payload digest matches kit tarball + manifest digests
5. Only after all checks pass:
- Swap Scanner's feed pointer to new snapshot
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
```
### 2.2 Activation Acceptance Rules
- Trust root: pinned publisher public keys (out-of-band rotation)
- Monotonicity: only activate if `manifest.version > current.version`
- Atomic switch: unpack → validate → symlink flip (`db/staging/``db/active/`)
- Quarantine on failure: move to `updates/quarantine/` with reason code
## 3. OFFLINE DIRECTORY LAYOUT
```
/evidence/
keys/
roots/ # root/intermediate certs, PGP pubkeys
identities/ # per-vendor public keys
tlog-root/ # hashed/pinned tlog root(s)
policy/
verify-policy.yaml
lattice-rules.yaml
sboms/ # *.cdx.json, *.spdx.json
attestations/ # *.intoto.jsonl.dsig (DSSE)
tlog/
checkpoint.sig # signed tree head
entries/ # *.jsonl (Merkle leaves) + proofs
tools/
cosign-<ver> (sha256)
oras-<ver> (sha256)
jq-<ver> (sha256)
scanner-<ver> (sha256)
```
## 4. OFFLINE VERIFICATION POLICY SCHEMA
```yaml
keys:
- ./evidence/keys/identities/vendor_A.pub
- ./evidence/keys/identities/your_authority.pub
tlog:
mode: "offline"
checkpoint: "./evidence/tlog/checkpoint.sig"
entry_pack: "./evidence/tlog/entries"
attestations:
required:
- type: slsa-provenance
- type: cyclonedx-sbom
optional:
- type: vex
constraints:
subjects:
alg: "sha256"
certs:
allowed_issuers:
- "https://fulcio.offline"
allow_expired_if_timepinned: true
```
## 5. DETERMINISTIC EVIDENCE RECONCILIATION ALGORITHM
```
1. Index artifacts by immutable digest
2. For each artifact digest:
- Collect SBOM nodes from canonical SBOM files
- Collect attestations (provenance, VEX, SLSA, signatures)
- Validate each attestation (sig + tlog inclusion proof)
3. Normalize all docs (stable sort, strip non-essential timestamps, lowercase URIs)
4. Apply lattice rules (precedence: vendor > maintainer > 3rd-party)
5. Emit `evidence-graph.json` (stable node/edge order) + `evidence-graph.sha256` + DSSE signature
```
## 6. OFFLINE FLOW OPERATIONAL STEPS
```
1. Import bundle (mount WORM media read-only)
2. Verify tools (hash + signature) before execution
3. Verify tlog checkpoint
4. Verify each inclusion proof
5. Verify attestations (keyring + policy)
6. Ingest SBOMs (canonicalize + hash)
7. Reconcile (apply lattice rules → evidence graph)
8. Record run: write `run.manifest` with input/policy/tool/output hashes; DSSE-sign with Authority key
```
## 7. SCANNER CONFIG SURFACE
### 7.1 Offline Kit Configuration
```yaml
scanner:
offlineKit:
requireDsse: true # fail import if DSSE/Rekor verification fails
rekorOfflineMode: true # use local snapshots only
attestationVerifier: https://attestor.internal
trustAnchors:
- anchorId: "UUID"
purlPattern: "pkg:npm/*"
allowedKeyids: ["key1", "key2"]
```
### 7.2 DSSE/Rekor Failure Handling
**DSSE/Rekor fail, Cosign + manifest OK**:
- Keep old feeds active
- Mark import as failed; surface ProblemDetails error via API/UI
- Log structured fields: `rekorUuid`, `attestationDigest`, `offlineKitHash`, `failureReason`
**Config flag to soften during rollout**:
- When `requireDsse=false`: treat DSSE/Rekor failure as warning; allow import with alerts
## 8. SBOM INGESTION DETERMINISTIC FLOW
```bash
# 1. Normalize SBOMs to canonical form
jq -S . sboms/app.cdx.json > sboms/_canon/app.cdx.json
# 2. Validate schemas (vendored validators)
# 3. Hash-pin canonical files and record in manifest.lock
sha256sum sboms/_canon/*.json > manifest.lock
# 4. Import to DB with idempotent keys: (artifactDigest, sbomHash)
```
## 9. OFFLINE REKOR MIRROR VERIFICATION
### 9.1 File-Ledger Pattern
- Keep `tlog/checkpoint.sig` (signed tree head) + `tlog/entries/*.jsonl` (leaves + proofs)
### 9.2 Verification Steps
```
1. Recompute Merkle root from entries
2. Check matches `checkpoint.sig` (after verifying signature with tlog root key)
3. For each attestation:
- Verify UUID/digest appears in entry pack
- Verify inclusion proof resolves
```
## 10. METRICS & OBSERVABILITY
### 10.1 Offline Kit Metrics (Prometheus)
```
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
offlinekit_attestation_verify_latency_seconds (histogram)
attestor_rekor_success_total
attestor_rekor_retry_total
rekor_inclusion_latency
```
### 10.2 Structured Logging Fields
```
rekorUuid
attestationDigest
offlineKitHash
failureReason
kitFilename
tarballDigest
dsseStatementDigest
rekorLogIndex
```
## 11. ERROR HANDLING
### 11.1 Import Failure Modes
| Failure Type | Action | Audit Event |
|--------------|--------|-------------|
| Cosign signature invalid | Reject, quarantine | `IMPORT_FAILED_COSIGN` |
| Manifest signature invalid | Reject, quarantine | `IMPORT_FAILED_MANIFEST` |
| DSSE verification failed | Reject (if requireDsse=true) | `IMPORT_FAILED_DSSE` |
| Rekor inclusion failed | Reject (if requireDsse=true) | `IMPORT_FAILED_REKOR` |
| Digest mismatch | Reject, quarantine | `IMPORT_FAILED_DIGEST` |
| Version not monotonic | Reject | `IMPORT_FAILED_VERSION` |
### 11.2 Quarantine Structure
```
/updates/quarantine/<timestamp>-<reason>/
bundle.tar.zst
manifest.json
verification.log
failure-reason.txt
```
## 12. CLI COMMANDS
### 12.1 Offline Kit Import
```bash
stellaops offline import \
--bundle ./bundle-2025-12-14.tar.zst \
--verify-dsse \
--verify-rekor \
--trust-root /evidence/keys/roots/stella-root.pub
```
### 12.2 Offline Kit Status
```bash
stellaops offline status
# Output:
# Active kit: bundle-2025-12-14
# Kit digest: sha256:abc123...
# Activated at: 2025-12-14T10:00:00Z
# DSSE verified: true
# Rekor verified: true
```
### 12.3 Offline Verification
```bash
stellaops verify offline \
--evidence-dir /evidence \
--artifact sha256:def456... \
--policy verify-policy.yaml
```
## 13. AUDIT TRAIL
### 13.1 Audit Event Schema
```json
{
"eventId": "uuid",
"eventType": "OFFLINE_KIT_IMPORTED",
"timestamp": "2025-12-14T10:00:00Z",
"actor": "system",
"details": {
"kitFilename": "bundle-2025-12-14.tar.zst",
"tarballDigest": "sha256:...",
"dsseStatementDigest": "sha256:...",
"rekorUuid": "...",
"rekorLogIndex": 12345,
"previousKitVersion": "bundle-2025-12-07",
"newKitVersion": "bundle-2025-12-14"
},
"result": "success"
}
```
### 13.2 Audit Log Storage
```sql
CREATE TABLE offline_kit_audit (
event_id UUID PRIMARY KEY,
event_type TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
actor TEXT NOT NULL,
details JSONB NOT NULL,
result TEXT NOT NULL
);
CREATE INDEX idx_offline_kit_audit_ts ON offline_kit_audit(timestamp DESC);
CREATE INDEX idx_offline_kit_audit_type ON offline_kit_audit(event_type);
```
## 14. SECURITY CONSIDERATIONS
### 14.1 Key Management
- Trust roots: pinned via out-of-band distribution
- Key rotation: maintain version history in trust store
- Revocation: maintain revoked_keys list in trust anchors
### 14.2 Integrity Guarantees
- All bundles content-addressed
- Manifest integrity via signature
- DSSE envelope integrity via signature
- Rekor inclusion proof integrity via Merkle tree
### 14.3 Air-Gap Boundaries
**Allowed**:
- Local file system reads (read-only mount)
- Local tool execution (verified binaries)
- Local database writes (staged)
**Forbidden**:
- Network egress
- DNS lookups
- NTP synchronization (use frozen clock)
- External API calls
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,503 @@
# PostgreSQL Patterns Technical Reference
**Source Advisories**:
- 01-Dec-2025 - PostgreSQL Patterns for Each StellaOps Module
- 14-Dec-2025 - Evaluate PostgreSQL vs MongoDB for StellaOps
**Last Updated**: 2025-12-14
---
## 1. MODULE-SCHEMA MAPPING
| Module | Schema | Primary Tables |
|--------|--------|----------------|
| Authority | `authority` | `user`, `role`, `grant`, `oauth_client`, `oauth_token`, `audit_log` |
| Routing | `routing` | `feature_flag`, `instance`, `rate_limit_config` |
| VEX | `vex` | `vuln_fact`, `package`, `vex_decision`, `mv_triage_queue` |
| Unknowns | `unknowns` | `unknown` (bitemporal) |
| Artifact | `artifact` | `artifact`, `signature`, `tag` |
| Core | `core` | `outbox` |
## 2. CORE POSTGRESQL CONVENTIONS
### 2.1 Required Columns (All Tables)
```sql
id uuid primary key default gen_random_uuid()
tenant_id uuid not null
created_at timestamptz not null default now()
updated_at timestamptz not null default now()
```
### 2.2 Multi-Tenancy RLS Pattern
```sql
alter table <table> enable row level security;
create policy p_<table>_tenant on <table>
for all using (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.3 Session Configuration (Set Per Request)
```sql
select set_config('app.user_id', '<uuid>', false);
select set_config('app.tenant_id', '<uuid>', false);
select set_config('app.roles', 'role1,role2', false);
```
## 3. TABLE TAXONOMY AND PATTERNS
### 3.1 Source-of-Truth (SOR) Tables
```sql
create table <module>.<entity> (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
external_id uuid,
content_hash bytea not null,
doc jsonb not null,
schema_version int not null,
created_at timestamptz not null default now(),
supersedes_id bigint null
);
create unique index on <entity>(tenant_id, content_hash);
```
### 3.2 JSONB Facts + Relational Decisions
**Facts (Immutable)**:
```sql
create table vex.vuln_fact (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
source text not null,
external_id text,
payload jsonb not null,
schema_version int not null,
received_at timestamptz not null default now()
);
```
**Decisions (Relational)**:
```sql
create table vex.vex_decision (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
package_id uuid not null,
vuln_id text not null,
status text check (status in ('not_affected','affected','fixed','under_investigation')),
rationale text,
proof_ref text,
decided_by uuid,
decided_at timestamptz not null default now(),
unique (tenant_id, package_id, vuln_id)
);
```
### 3.3 Queue Pattern (SKIP LOCKED)
```sql
create table job_queue (
id bigserial primary key,
tenant_id uuid,
kind text not null,
payload jsonb not null,
run_after timestamptz default now(),
attempts int default 0,
locked_at timestamptz,
locked_by text
);
create index ix_job_ready
on job_queue(kind, run_after, id)
where locked_at is null;
-- Claim job
with cte as (
select id from job_queue
where kind = $1
and run_after <= now()
and locked_at is null
order by id
for update skip locked
limit 1
)
update job_queue j
set locked_at = now(), locked_by = $2
from cte
where j.id = cte.id
returning j.*;
```
### 3.4 Temporal Pattern (Unknowns)
```sql
create table unknowns.unknown (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
subject_hash text not null,
kind text not null,
context jsonb not null,
valid_from timestamptz not null default now(),
valid_to timestamptz,
sys_from timestamptz not null default now(),
sys_to timestamptz,
created_at timestamptz not null default now()
);
create unique index unknown_one_open_per_subject
on unknowns.unknown (tenant_id, subject_hash, kind)
where valid_to is null;
create view unknowns.current as
select * from unknowns.unknown
where valid_to is null;
```
### 3.5 Audit Log Pattern
```sql
create table authority.audit_log (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
actor_id uuid,
action text not null,
entity_type text not null,
entity_id uuid,
at timestamptz not null default now(),
diff jsonb not null
);
```
### 3.6 Outbox Pattern (Exactly-Once Side Effects)
```sql
create table core.outbox (
id uuid primary key default gen_random_uuid(),
tenant_id uuid,
aggregate_type text not null,
aggregate_id uuid,
topic text not null,
payload jsonb not null,
created_at timestamptz not null default now(),
dispatched_at timestamptz,
dispatch_attempts int not null default 0,
error text
);
```
## 4. JSONB WITH GENERATED COLUMNS
```sql
create table sbom_document (
id bigserial primary key,
tenant_id uuid not null,
artifact_purl text not null,
content_hash bytea not null,
doc jsonb not null,
created_at timestamptz not null default now(),
-- hot keys as generated columns
bom_format text generated always as ((doc->>'bomFormat')) stored,
spec_version text generated always as ((doc->>'specVersion')) stored
);
create unique index ux_sbom_doc_hash on sbom_document(tenant_id, content_hash);
create index ix_sbom_doc_tenant_artifact on sbom_document(tenant_id, artifact_purl, created_at desc);
create index ix_sbom_doc_json_gin on sbom_document using gin (doc jsonb_path_ops);
create index ix_sbom_doc_bomformat on sbom_document(tenant_id, bom_format);
```
## 5. MATERIALIZED VIEWS FOR HOT READS
```sql
create materialized view mv_artifact_risk as
select tenant_id, artifact_purl, max(score) as risk_score
from open_findings
group by tenant_id, artifact_purl;
create unique index ux_mv_artifact_risk
on mv_artifact_risk(tenant_id, artifact_purl);
-- Refresh
refresh materialized view concurrently mv_artifact_risk;
```
## 6. PARTITIONING (TIME-BASED EVENTS)
```sql
create table scan_run_event (
tenant_id uuid not null,
scan_run_id bigint not null,
occurred_at timestamptz not null,
event_type text not null,
payload jsonb not null
) partition by range (occurred_at);
create index brin_scan_events_time
on scan_run_event using brin (occurred_at);
```
## 7. INDEX PATTERNS
| Use Case | Index Pattern |
|----------|---------------|
| Tenant-scoped queries | `INDEX(tenant_id, ...)` |
| Latest version lookup | `INDEX(tenant_id, artifact_purl, created_at DESC)` |
| Queue readiness | `INDEX(kind, run_after, id) WHERE locked_at IS NULL` |
| JSONB containment | `INDEX USING GIN (doc jsonb_path_ops)` |
| JSONB key lookup | `INDEX((doc->>'key'))` |
| Time-series scan | `INDEX USING BRIN (occurred_at)` |
## 8. PERFORMANCE REQUIREMENTS
### 8.1 Query Performance Standards
**Required per PR**:
- Provide SQL query + intended parameters
- Provide `EXPLAIN (ANALYZE, BUFFERS)` from staging-sized dataset
- Identify serving index(es)
- Confirm row estimates not wildly wrong
- Confirm tenant-scoped and uses tenant-leading index
### 8.2 Index Performance Standards
| Pattern | Requirement |
|---------|-------------|
| Tenant queries | `INDEX(tenant_id, ...)` leading column |
| Sort ordering | Index must end with `ORDER BY` column + direction |
| Queue claims | Partial index `WHERE locked_at IS NULL` |
| Time-series | BRIN index on timestamp columns for partitioned tables |
| JSONB containment | GIN `jsonb_path_ops` for `@>` queries |
### 8.3 General Performance Rules
- Every hot query must have an index story
- Write path stays simple: prefer append-only versioning
- Multi-tenant explicit: all core tables include `tenant_id`
- Derived data modeled as projection tables or materialized views
- Idempotency enforced in DB: unique keys for imports/jobs/results
## 9. FEATURE FLAG SCHEMA
```sql
create table routing.feature_flag (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
key text not null,
rules jsonb not null,
version int not null default 1,
is_enabled boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, key)
);
create table routing.feature_flag_history (
id uuid primary key default gen_random_uuid(),
feature_flag_id uuid not null,
tenant_id uuid not null,
key text not null,
rules jsonb not null,
version int not null,
changed_at timestamptz not null default now(),
changed_by uuid
);
```
**Redis Cache Pattern**:
```
SETEX flag:{key}:{version} <ttl> <json>
```
## 10. RATE LIMIT CONFIGURATION
```sql
create table routing.rate_limit_config (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
key text not null,
limit_per_interval int not null,
interval_seconds int not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, key)
);
```
**Redis Counter Pattern**:
```
INCR rl:{bucket}:{window}
```
## 11. INSTANCE REGISTRY
```sql
create table routing.instance (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null,
instance_key text not null,
domain text not null,
last_heartbeat timestamptz not null default now(),
status text not null check (status in ('active','draining','offline')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, instance_key),
unique (tenant_id, domain)
);
```
## 12. MIGRATION PATTERNS
### 12.1 Schema Versioning
```sql
create table core.schema_version (
module text primary key,
version int not null,
applied_at timestamptz not null default now(),
migration_hash text not null
);
```
### 12.2 Migration Script Template
```sql
-- Migration: <module>_v<version>_<description>
-- Dependencies: <module>_v<previous_version>
begin;
-- Schema changes
create table if not exists <module>.<table> (...);
-- Data migrations (if needed)
-- Update version
insert into core.schema_version (module, version, migration_hash)
values ('<module>', <version>, '<hash>')
on conflict (module) do update
set version = excluded.version,
applied_at = now(),
migration_hash = excluded.migration_hash;
commit;
```
## 13. CONNECTION POOLING
### 13.1 Recommended Settings
```yaml
database:
host: postgres.local
port: 5432
database: stellaops
username: stellaops_app
password: <from secrets>
pool:
min_size: 5
max_size: 20
connection_timeout: 5000 # ms
idle_timeout: 600000 # ms (10 min)
max_lifetime: 1800000 # ms (30 min)
```
### 13.2 .NET Configuration
```csharp
services.AddNpgsqlDataSource(connectionString, builder =>
{
builder.MaxConnections = 20;
builder.MinConnections = 5;
builder.ConnectionIdleLifetime = TimeSpan.FromMinutes(10);
builder.ConnectionLifetime = TimeSpan.FromMinutes(30);
});
```
## 14. MONITORING & OBSERVABILITY
### 14.1 Essential Metrics
```
postgres_connections_active
postgres_connections_idle
postgres_transaction_duration_seconds
postgres_query_duration_seconds
postgres_cache_hit_ratio
postgres_table_size_bytes
postgres_index_size_bytes
postgres_slow_queries_total
```
### 14.2 Query Performance Monitoring
```sql
-- Enable pg_stat_statements
create extension if not exists pg_stat_statements;
-- Top 10 slowest queries
select
substring(query, 1, 100) as query_snippet,
calls,
total_exec_time / 1000 as total_time_sec,
mean_exec_time as mean_time_ms,
max_exec_time as max_time_ms
from pg_stat_statements
order by total_exec_time desc
limit 10;
```
## 15. BACKUP & RECOVERY
### 15.1 Backup Strategy
- **Point-in-time recovery (PITR)**: Enabled via WAL archiving
- **Daily full backups**: Automated via `pg_basebackup`
- **Retention**: 30 days for compliance
- **Testing**: Monthly restore drills
### 15.2 Backup Commands
```bash
# Full backup
pg_basebackup -h postgres.local -D /backup/$(date +%Y%m%d) -Ft -z -P
# WAL archiving (postgresql.conf)
# archive_mode = on
# archive_command = 'cp %p /archive/%f'
```
## 16. SECURITY BEST PRACTICES
### 16.1 Access Control
- Use RLS for multi-tenancy isolation
- Grant minimal privileges per role
- Separate read-only and read-write users
- Use connection pooler with separate credentials
### 16.2 Encryption
- TLS for connections: `sslmode=require`
- Transparent data encryption (TDE) for data at rest
- Encrypted backups
### 16.3 Audit Logging
```sql
-- Enable audit logging
create extension if not exists pgaudit;
-- Configure audit (postgresql.conf)
-- pgaudit.log = 'write, ddl'
-- pgaudit.log_catalog = off
```
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,971 @@
# Proof and Evidence Chain Technical Reference
**Source Advisories**:
- 29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint
- 01-Dec-2025 - Turning SBOM Data Into Verifiable Proofs
- 01-Dec-2025 - Proof-Linked VEX User Interface
- 02-Dec-2025 - Converting SBOM Data into Proof Chains
- 06-Dec-2025 - How to Build a Verifiable SBOM→VEX Chain
- 08-Dec-2025 - Defining Stella Ops' ProofLinked Advantage
- 08-Dec-2025 - Designing UX for Signed Evidence Trails
- 03-Dec-2025 - Comparing ProofLinked VEX UX Across Tools
**Last Updated**: 2025-12-14
---
## 1. CORE IDENTIFIERS & DATA MODEL
### 1.1 Canonical IDs
```
ArtifactID = sha256:<digest>
SBOMEntryID = <sbomDigest>:<purl>[@<version>]
EvidenceID = hash(canonical_evidence_json)
ReasoningID = hash(canonical_reasoning_json)
VEXVerdictID = hash(canonical_vex_json)
ProofBundleID = merkle_root(SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID)
TrustAnchorID = per-dependency anchor (public key + policy)
```
### 1.2 Component Identifiers (bom-ref)
```
Format: pkg:<ecosystem>/<name>@<version>?sha256=<digest>
Examples:
pkg:maven/org.apache.commons/commons-lang3@3.14.0?sha256=<digest>
pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e
pkg:oci/<repo>@sha256:<manifestDigest>
pkg:npm/lodash@4.17.21
Rules:
- Must be stable across regenerations for identical content
- Independent of local paths, build numbers
- Derived from canonical bytes
- Used as CycloneDX bom-ref
```
### 1.3 Subject Schema
```csharp
public sealed record ProofSubject(
string Name, // PURL or canonical URI
IReadOnlyDictionary<string,string> Digest // {"sha256": "...", "sha512": "..."}
);
```
### 1.4 SBOM Identity
```
sbomId = sha256(canonical_sbom_bytes)
```
## 2. DSSE ENVELOPE STRUCTURES
### 2.1 Evidence Statement
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "evidence.stella/v1",
"predicate": {
"source": "scanner/feed name",
"sourceVersion": "tool version",
"collectionTime": "2025-12-14T00:00:00Z",
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"rawFinding": "<pointer or data>",
"evidenceId": "<EvidenceID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
```
Signer: Scanner/Ingestor key
### 2.2 Reasoning Statement
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "reasoning.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<EvidenceID>", ...],
"policyVersion": "v2.3.1",
"inputs": {
"currentEvaluationTime": "2025-12-14T00:00:00Z",
"severityThresholds": {...},
"latticeRules": {...}
},
"intermediateFindings": {},
"reasoningId": "<ReasoningID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
```
Signer: Policy/Authority key
### 2.3 VEX Verdict Statement
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "cdx-vex.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"status": "not_affected|affected|fixed|under_investigation",
"justification": "vulnerable_code_not_in_execute_path",
"policyVersion": "v2.3.1",
"reasoningId": "<ReasoningID>",
"vexVerdictId": "<VEXVerdictID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
```
Signer: VEXer key or vendor key
### 2.4 Proof Spine Statement
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "proofspine.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<ID1>", "<ID2>"],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"policyVersion": "v2.3.1",
"proofBundleId": "<ProofBundleID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
```
Signer: Authority key
### 2.5 SBOM Linkage Statement
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{"name": "pkg:npm/lodash@4.17.21", "digest": {"sha256": "...", "sha512": "..."}}
],
"predicateType": "https://stella-ops.org/predicates/sbom-linkage/v1",
"predicate": {
"sbom": {
"id": "<sbomId>",
"format": "CycloneDX",
"specVersion": "1.6",
"mediaType": "application/vnd.cyclonedx+json",
"sha256": "<sha256>",
"location": "oci://... or file://..."
},
"generator": {
"name": "StellaOps.Sbomer",
"version": "x.y.z"
},
"generatedAt": "2025-12-14T00:00:00Z",
"incompleteSubjects": [],
"tags": {
"tenantId": "...",
"projectId": "...",
"pipelineRunId": "..."
}
}
}
```
## 3. CYCLONEDX VEX STRUCTURE
```json
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"vulnerabilities": [
{
"id": "CVE-2024-12345",
"source": {"name": "NVD"},
"analysis": {
"state": "not_affected",
"justification": "vulnerable_code_not_present",
"response": ["will_not_fix"],
"detail": "Linked OpenSSL feature set excludes the vulnerable cipher."
},
"affects": [
{"ref": "pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e"}
],
"properties": [
{"name": "evidence.sbomDigest", "value": "sha256:91f2...9a"},
{"name": "evidence.rekorLogID", "value": "425c1d1e..."},
{"name": "reachability.report", "value": "sha256:reacha..."},
{"name": "policy.decision", "value": "TrustGate#R-17.2"}
]
}
]
}
```
### VEX Status Values
```
not_affected
affected
fixed
under_investigation
```
### VEX Justification Values
```
vulnerable_code_not_present
vulnerable_code_not_in_execute_path
vulnerable_code_not_configured
vulnerable_code_cannot_be_controlled_by_adversary
component_not_present
inline_mitigations_exist
```
## 4. STORAGE SCHEMA
### 4.1 PostgreSQL Tables
```sql
CREATE TABLE sbom_entries (
entry_id UUID PRIMARY KEY,
bom_digest VARCHAR(64) NOT NULL,
purl TEXT NOT NULL,
version TEXT,
artifact_digest VARCHAR(64),
trust_anchor_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE dsse_envelopes (
env_id UUID PRIMARY KEY,
entry_id UUID REFERENCES sbom_entries(entry_id),
predicate_type TEXT NOT NULL,
signer_keyid TEXT NOT NULL,
body_hash VARCHAR(64) NOT NULL,
envelope_blob_ref TEXT NOT NULL,
signed_at TIMESTAMPTZ NOT NULL,
INDEX idx_entry_predicate (entry_id, predicate_type)
);
CREATE TABLE spines (
entry_id UUID PRIMARY KEY REFERENCES sbom_entries(entry_id),
bundle_id VARCHAR(64) NOT NULL,
evidence_ids TEXT[] NOT NULL,
reasoning_id VARCHAR(64) NOT NULL,
vex_id VARCHAR(64) NOT NULL,
anchor_id UUID,
policy_version TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE trust_anchors (
anchor_id UUID PRIMARY KEY,
purl_pattern TEXT NOT NULL,
allowed_keyids TEXT[] NOT NULL,
policy_ref TEXT,
revoked_keys TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE rekor_entries (
dsse_sha256 VARCHAR(64) PRIMARY KEY,
log_index BIGINT NOT NULL,
log_id TEXT NOT NULL,
integrated_time BIGINT NOT NULL,
inclusion_proof JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### 4.2 Proof Graph Nodes
```
Node Types:
- Artifact (container image, binary, Helm chart)
- SbomDocument (by sbomId)
- InTotoStatement (by statement hash)
- DsseEnvelope (by envelope hash)
- RekorEntry (by log index/UUID)
- VexStatement (by vex hash)
- Subject (component from SBOM)
Edge Types:
- DESCRIBED_BY: Artifact → SbomDocument
- ATTESTED_BY: SbomDocument → InTotoStatement
- WRAPPED_BY: InTotoStatement → DsseEnvelope
- LOGGED_IN: DsseEnvelope → RekorEntry
- HAS_VEX: Artifact/Subject → VexStatement
- CONTAINS_SUBJECT: InTotoStatement → Subject
- PRODUCES: Build → SBOM
- AFFECTS: VEX → Component
- SIGNED_BY: Envelope → Key
- RECORDED_AT: Envelope → Rekor
```
## 5. API CONTRACTS
### 5.1 Proof Spine API
```
POST /proofs/:entry/spine
Body: {
"evidenceIds": ["<ID1>", ...],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"policyVersion": "v2.3.1"
}
Response: 201 Created, {"proofBundleId": "..."}
GET /proofs/:entry/receipt
Response: {
"proofBundleId": "...",
"verifiedAt": "2025-12-14T00:00:00Z",
"verifierVersion": "1.0.0",
"anchorId": "...",
"result": "pass|fail",
"details": {...}
}
GET /proofs/:entry/vex
Response: <VEX JSON body>
GET /anchors/:anchor
Response: {
"anchorId": "...",
"purlPattern": "pkg:npm/*",
"allowedKeyids": ["key1", "key2"],
"policyRef": "...",
"revokedKeys": []
}
```
### 5.2 Verification API
```
POST /verify
Body: {
"artifactDigest": "sha256:...",
"sbom": <SBOM JSON or reference>,
"vex": <VEX JSON or reference>,
"signatures": [...],
"logs": [...]
}
Response: {
"artifact": "pkg:oci/...",
"sbomVerified": true,
"vexVerified": true,
"components": [
{
"bomRef": "pkg:...",
"vulnerabilities": [
{
"id": "CVE-...",
"state": "not_affected",
"justification": "..."
}
]
}
]
}
```
## 6. CANONICALIZATION RULES
### 6.1 JSON Canonicalization
```
1. UTF-8 encoding
2. Sorted keys (lexicographic)
3. No insignificant whitespace
4. No volatile fields beyond semantic need
5. Version schema: evidence.stella/v1, reasoning.stella/v1
6. Deterministic array ordering where semantically unordered
```
### 6.2 SBOM Canonicalization
```csharp
public interface ISbomCanonicalizer
{
byte[] Canonicalize(ReadOnlySpan<byte> rawSbom, string mediaType);
}
public interface IBlobHasher
{
string ComputeSha256Hex(ReadOnlySpan<byte> data);
}
```
Rules:
- Remove insignificant whitespace
- Sort object keys lexicographically
- Sort arrays deterministically (by bom-ref or purl)
- Strip volatile fields: generation timestamps, tool build IDs, non-deterministic UUIDs
- Convert to internal JSON, then canonicalize
### 6.3 Subject Extraction
```csharp
IEnumerable<Subject> ToSubjects(CycloneDxSbom sbom)
{
foreach (var c in sbom.Metadata.Components)
{
if (c.Hashes == null || c.Hashes.Count == 0) continue;
var name = $"pkg:{c.Type}/{c.Name}@{c.Version}";
var dig = c.Hashes
.OrderBy(h => h.Algorithm)
.ToDictionary(
h => h.Algorithm.ToLowerInvariant(),
h => h.Value.ToLowerInvariant()
);
yield return new Subject(name, dig);
}
}
```
Digest requirements:
- Must have at least sha256 or sha512
- Normalize algorithm keys to lowercase
- Sort subjects by: 1) Name ascending, 2) Algorithm:value pairs
## 7. REKOR INTEGRATION
### 7.1 Rekor Entry Structure
```json
{
"dsseSha256": "sha256:...",
"rekor": {
"uuid": "...",
"logIndex": 12345,
"logId": "...",
"integratedTime": 1733736000,
"inclusionProof": {
"rootHash": "...",
"hashes": ["...", "..."],
"checkpoint": "..."
}
}
}
```
### 7.2 Offline Update Bundle Structure
```
/bundle-2025-12-14/
manifest.json # version, created_at, entries[], sha256s
payload.tar.zst # actual DB/indices/feeds
payload.tar.zst.sha256
statement.dsse.json # DSSE-wrapped statement over payload hash
rekor-receipt.json # Rekor v2 inclusion/verification material
```
### 7.3 Offline Update DSSE Predicate
```json
{
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
"predicate": {
"offline_manifest_sha256": "sha256:...",
"feeds": [
{
"name": "nvd",
"snapshot_date": "2025-12-14",
"archive_digest": "sha256:..."
}
],
"builder": "ci-workflow-id / git-commit / job-id",
"created_at": "2025-12-14T00:00:00Z",
"oukit_channel": "stable|edge|fips-profile"
}
}
```
### 7.4 Verification Sequence (Offline Kit)
```
1. Validate Cosign signature of tarball
2. Validate offline-manifest.json with JWS signature
3. Verify file digests for all entries (including /attestations/*)
4. Verify DSSE:
- Call StellaOps.Attestor.Verify with:
- offline-update.dsse.json
- offline-update.rekor.json
- local Rekor log snapshot/segment
- Ensure payload digest matches kit tarball + manifest digests
5. Only after all checks pass:
- Swap Scanner's feed pointer to new snapshot
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
```
## 8. CRYPTOGRAPHIC SPECIFICATIONS
### 8.1 Signing Keys & Profiles
```
Default Profile:
Hash: SHA-256
Signature: Ed25519 or ECDSA P-256
Future Profiles:
GOST R 34.10-2012
eIDAS-compliant algorithms
FIPS 140-2/140-3
SM2/SM3 (Chinese standards)
PQC: Dilithium/Falcon for long-term archives
Key Storage:
KMS/HSM (production)
PKCS#11 for air-gap
Per-environment keysets: dev, staging, prod
Per-role keysets: Authority, VEXer, Evidence Ingestor
```
### 8.2 Key Rotation
```
Rotation Process:
1. Add new allowed_keyids to TrustAnchor
2. Never mutate old DSSE envelopes
3. Publish key material via attestation feed or Rekor-mirror
4. Record all changes in audit log
5. Maintain key version history
```
### 8.3 Trust Anchor Structure
```json
{
"trustAnchorId": "UUID",
"purlPattern": "pkg:npm/*",
"allowedKeyids": ["keyid1", "keyid2"],
"allowedPredicateTypes": [
"evidence.stella/v1",
"reasoning.stella/v1",
"cdx-vex.stella/v1"
],
"policyVersion": "v2.3.1",
"revokedKeys": []
}
```
## 9. VERIFICATION PIPELINE
### 9.1 Verification Algorithm
```
Input: SBOMEntryID or ProofBundleID
Steps:
1. Resolve SBOMEntryID → TrustAnchorID
2. Fetch spine and trust anchor
3. Verify spine DSSE signature against TrustAnchor.allowedKeyids
4. Verify VEX DSSE signature
5. Verify reasoning DSSE signature
6. Verify evidence DSSE signatures
7. Recompute EvidenceIDs from stored canonical evidence
8. Recompute ReasoningID from reasoning
9. Recompute VEXVerdictID from VEX body
10. Recompute ProofBundleID (merkle root) from above
11. Compare all computed IDs to stored IDs
12. If using Rekor:
- Verify log inclusion proof
- Verify payload hashes match local files
13. Emit Receipt
Output: Receipt {
proofBundleId,
verifiedAt,
verifierVersion,
anchorId,
result: "pass|fail",
details: [...]
}
```
### 9.2 Receipt Structure
```json
{
"proofBundleId": "sha256:...",
"verifiedAt": "2025-12-14T00:00:00Z",
"verifierVersion": "1.0.0",
"anchorId": "UUID",
"result": "pass",
"checks": [
{
"check": "spine_signature",
"status": "pass",
"keyid": "..."
},
{
"check": "evidence_id_recompute",
"status": "pass",
"expected": "sha256:...",
"actual": "sha256:..."
},
{
"check": "rekor_inclusion",
"status": "pass",
"logIndex": 12345
}
],
"toolDigests": {
"verifier": "sha256:...",
"canonicalizer": "sha256:..."
}
}
```
## 10. DETERMINISM CONSTRAINTS
### 10.1 Non-Negotiable Invariants
```
1. Immutability of Signed Facts
- DSSE envelopes are append-only
- Never edit or delete content inside signed envelope
- Corrections via superseding (new statement pointing to old)
2. Determinism
- Same {SBOMEntryID, Evidence set, policyVersion} ⇒ same {ReasoningID, VEXVerdictID, ProofBundleID}
- No non-deterministic inputs in ID computation
- No current time, random IDs in verdict logic
3. Traceability
- Every VEX verdict → SBOM entry, evidence blobs, policy snapshot, trust anchor
4. Least Trust/Least Privilege
- Trust always explicit via TrustAnchors + signature verification
- Never "because it's in our DB"
5. Backwards Compatibility
- New code verifies old proofs
- New policies generate new spines, old spines intact
```
### 10.2 Temporal Handling
```
UTC ISO-8601 only
No local time
Timestamps only when semantically required
Derivation from content preferred over wall-clock time
Record evaluation time as explicit input if policy needs it
```
### 10.3 Ordering Requirements
```
Subjects: sorted by Name ascending, then digest keys
Evidence IDs: sorted lexicographically
Keys in JSON: sorted lexicographically
Array elements: stable sort by semantic key (bom-ref, purl)
```
## 11. IMPLEMENTATION INTERFACES
### 11.1 .NET 10 Core Interfaces
```csharp
// DSSE Signing
public interface IDsseSigner
{
Task<DsseEnvelope> SignAsync(
ReadOnlyMemory<byte> payload,
string payloadType,
string keyProfile,
CancellationToken ct = default
);
}
// Verification
public record DsseEnvelope(
string PayloadType,
byte[] Payload,
Signature[] Signatures
);
public record Signature(
string Keyid,
string Sig,
string? Cert
);
// Subject Extraction
public sealed record ProofSubject(
string Name,
IReadOnlyDictionary<string,string> Digest
);
// Predicate Models
public record SbomLinkagePredicate(
SbomDescriptor Sbom,
GeneratorDescriptor Generator,
DateTimeOffset GeneratedAt,
IReadOnlyList<IncompleteSubject>? IncompleteSubjects,
IReadOnlyDictionary<string,string>? Tags
);
public record EvidencePredicate(
string Source,
string SourceVersion,
DateTimeOffset CollectionTime,
string SbomEntryId,
string? VulnerabilityId,
object RawFinding,
string EvidenceId
);
public record ReasoningPredicate(
string SbomEntryId,
string[] EvidenceIds,
string PolicyVersion,
Dictionary<string,object> Inputs,
Dictionary<string,object>? IntermediateFindings,
string ReasoningId
);
public record VexPredicate(
string SbomEntryId,
string VulnerabilityId,
string Status,
string Justification,
string PolicyVersion,
string ReasoningId,
string VexVerdictId
);
public record ProofSpinePredicate(
string SbomEntryId,
string[] EvidenceIds,
string ReasoningId,
string VexVerdictId,
string PolicyVersion,
string ProofBundleId
);
```
### 11.2 Rekor Client Interface
```csharp
public interface IRekorClient
{
Task<RekorEntry> SubmitDsseAsync(
DsseEnvelope envelope,
CancellationToken ct = default
);
Task<bool> VerifyInclusionAsync(
RekorEntry entry,
byte[] payloadDigest,
byte[] rekorPublicKey,
CancellationToken ct = default
);
}
public record RekorEntry(
string Uuid,
long LogIndex,
string LogId,
long IntegratedTime,
InclusionProof Proof
);
public record InclusionProof(
string RootHash,
string[] Hashes,
string Checkpoint
);
```
## 12. CONFIGURATION SCHEMA
### 12.1 Scanner Offline Kit Config
```yaml
scanner:
offlineKit:
requireDsse: true
rekorOfflineMode: true
attestationVerifier: https://attestor.internal
trustAnchors:
- anchorId: "UUID"
purlPattern: "pkg:npm/*"
allowedKeyids: ["key1", "key2"]
```
### 12.2 Signer Config
```yaml
signer:
profiles:
default:
algorithm: "SHA256-ED25519"
keyStore: "kms://..."
fips:
algorithm: "SHA256-ECDSA-P256"
keyStore: "hsm://..."
pqc:
algorithm: "SHA256-DILITHIUM3"
keyStore: "kms://..."
```
### 12.3 Authority Config
```yaml
authority:
trustRoots:
- id: "root-ca-1"
publicKey: "..."
validFrom: "2025-01-01T00:00:00Z"
validUntil: "2030-01-01T00:00:00Z"
keystores:
- type: "kms"
url: "aws-kms://..."
region: "us-east-1"
```
## 13. ERROR HANDLING
### 13.1 Ingestion Failures
```
If SBOM invalid:
- Reject SBOM
- Record DSSE failure attestation:
{
"error": "schema_validation_failed",
"file": "sbom.json",
"system_version": "1.0.0"
}
- Maintain proof trail for "we tried and it failed"
```
### 13.2 Missing Digests
```
If component lacks sha256/sha512:
- Do NOT use as primary subject in proof chain
- Log in "incompleteSubjects" block in predicate
- Expose in UI as "unverifiable component"
```
### 13.3 Rekor Failures
```
If Rekor unavailable:
- Store DSSE envelope locally
- Queue for retry
- Mark proof chain as "rekorStatus: pending"
- Internal-only until Rekor sync succeeds
- Flag in verification results
```
## 14. METRICS & OBSERVABILITY
### 14.1 Pipeline Metrics
```
sboms_ingested_total
sbom_ingest_errors_total{reason}
evidence_statements_created_total
reasoning_statements_created_total
vex_statements_created_total
proof_spines_created_total
proof_verifications_total{result}
*_duration_seconds (latency histograms per stage)
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
offlinekit_attestation_verify_latency_seconds
attestor_rekor_success_total
attestor_rekor_retry_total
rekor_inclusion_latency
```
### 14.2 Structured Logging Fields
```
sbomEntryId
proofBundleId
anchorId
policyVersion
requestId / traceId
rekorUuid
attestationDigest
offlineKitHash
failureReason
```
## 15. CI/CD INTEGRATION
### 15.1 Pipeline Hooks
```
On SBOM ingest:
- Create/refresh SBOMEntry rows
- Attach TrustAnchor
On scan completion:
- Produce Evidence Statements (DSSE) immediately
On policy evaluation:
- Produce Reasoning + VEX
- Assemble Spine
Release gates:
- Require: GET /proofs/:entry/receipt == PASS
```
### 15.2 CLI Exit Codes
```
0 = no policy violation
1 = policy violation
2 = scanner/system error (distinguish from "found vulns")
```
### 15.3 CLI Output Modes
```
Default: Human-readable summary (3-5 lines)
--output json: Machine-readable with:
- Web UI run page link
- Proof bundle ID
- Rekor/ledger reference
-v / -vv: Verbose details
```
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,992 @@
# Reachability Analysis Technical Reference
**Source Advisories**:
- 05-Dec-2025 - Building a Deterministic, ReachabilityFirst Architecture
- 13-Dec-2025 - Designing the CallStack Reachability Engine
- 03-Dec-2025 - Reachability Benchmarks and Moat Metrics
- 09-Dec-2025 - Caching Reachability the Smart Way
- 04-Dec-2025 - Ranking Unknowns in Reachability Graphs
- 02-Dec-2025 - Designing Deterministic Reachability UX
- 05-Dec-2025 - Design Notes on SmartDiff and CallStack Analysis
**Last Updated**: 2025-12-14
---
## 1. ARCHITECTURE PATTERNS
### 1.1 Core System Architecture
**Module Responsibilities**
- `StellaOps.Scanner.WebService`: Authoritative for reachability pipeline and lattice computation
- Language workers: Stateless compute producing `CallGraph.v1.json`
- Runtime collectors: Agent/sidecar emitting evidence events only
- Concelier/Excititor: Provide pruned sources; never compute reachability
- Authority: Manages replay manifests, crypto profiles
- Scheduler: Executes rescan/escalation policies
**Architectural Rules**
- Scanner = origin of truth for reachability
- Concelier/Vexer = prune-preservers only
- Authority = replay manifest owner
- Scheduler = executor of policies
- Postgres = System of Record (SoR)
- Valkey = ephemeral only (dedupe, hot cache, rate limits)
### 1.2 Evidence Graph Structure
**Node Types**
- `Artifact`, `Component`, `Vulnerability`, `Attestation`, `Build`, `Deployment`, `RuntimeSignal`
**Edge Types**
- `DESCRIBES`, `AFFECTS`, `NOT_AFFECTED_BY`, `FIXED_IN`, `DERIVED_FROM`, `DEPLOYS`, `OBSERVED_AT_RUNTIME`
**Edge Signing**
- Sign edges, not just nodes (edge = claim)
### 1.3 Determinism Requirements
**Input Manifest Structure**
```jsonc
{
"scannerVersion": "1.3.0",
"rulesetId": "stella-default-2025.11",
"feeds": {
"nvdDigest": "sha256:...",
"osvDigest": "sha256:..."
},
"sbomDigest": "sha256:...",
"policyDigest": "sha256:..."
}
```
**Canonicalization Rules**
- Sort arrays by stable keys
- Normalize paths (POSIX style)
- Line endings (LF)
- Encodings (UTF-8)
- No environment variables in core algorithms
- No machine-local files
- No system clock inside algorithms
## 2. DATA CONTRACTS
### 2.1 CallGraph.v1.json Schema
```json
{
"schema": "stella.callgraph.v1",
"scanKey": "uuid",
"language": "dotnet|java|node|python|go|rust|binary",
"artifacts": [{
"artifactKey": "…",
"kind": "assembly|jar|module|binary",
"sha256": "…"
}],
"nodes": [{
"nodeId": "…",
"artifactKey": "…",
"symbolKey": "Namespace.Type::Method(…)",
"visibility": "public|internal|private|unknown",
"isEntrypointCandidate": false
}],
"edges": [{
"from": "nodeId",
"to": "nodeId",
"kind": "static|heuristic",
"reason": "direct_call|virtual_call|reflection_string|di_binding|dynamic_import|unknown",
"weight": 1.0
}],
"entrypoints": [{
"nodeId": "…",
"kind": "http|grpc|cli|job|event|unknown",
"route": "/api/orders/{id}",
"framework": "aspnetcore|minimalapi|spring|express|unknown"
}]
}
```
### 2.2 RuntimeEvidence.v1.json Schema
```json
{
"schema": "stella.runtimeevidence.v1",
"scanKey": "uuid",
"collectedAt": "2025-12-14T10:00:00Z",
"environment": {
"os": "linux|windows",
"k8s": {"namespace": "…", "pod": "…", "container": "…"},
"imageDigest": "sha256:…",
"buildId": "…"
},
"samples": [{
"timestamp": "…",
"pid": 1234,
"threadId": 77,
"frames": ["nodeId","nodeId","nodeId"],
"sampleWeight": 1.0
}],
"loadedArtifacts": [{
"artifactKey": "…",
"evidence": "loaded_module|mapped_file|jar_loaded"
}]
}
```
### 2.3 ReplayManifest.json Schema
```json
{
"schema": "stella.replaymanifest.v1",
"scanId": "uuid",
"inputs": {
"sbomDigest": "sha256:…",
"callGraphs": [{"language":"dotnet","digest":"sha256:…"}],
"runtimeEvidence": [{"digest":"sha256:…"}],
"concelierSnapshot": "sha256:…",
"excititorSnapshot": "sha256:…",
"policyDigest": "sha256:…"
}
}
```
### 2.4 ProofSpine Data Model
```csharp
public sealed record ProofSpine(
string SpineId,
string ArtifactId,
string VulnerabilityId,
string PolicyProfileId,
IReadOnlyList<ProofSegment> Segments,
string Verdict,
string VerdictReason,
string RootHash,
string ScanRunId,
DateTimeOffset CreatedAt,
string? SupersededBySpineId
);
public sealed record ProofSegment(
string SegmentId,
string SegmentType,
int Index,
string InputHash,
string ResultHash,
string? PrevSegmentHash,
DsseEnvelope Envelope,
string ToolId,
string ToolVersion,
string Status
);
```
**Segment Types**
- `SBOM_SLICE`: Component relevance
- `MATCH`: SBOM-to-vuln mapping
- `REACHABILITY`: Symbol reachability
- `GUARD_ANALYSIS`: Config/feature flag gates
- `RUNTIME_OBSERVATION`: Runtime evidence
- `POLICY_EVAL`: Lattice decision
## 3. DATABASE SCHEMAS
### 3.1 Core Reachability Tables (Postgres)
```sql
-- Scan tracking
CREATE TABLE scan (
scan_id uuid PRIMARY KEY,
created_at timestamptz,
repo_uri text,
commit_sha text,
sbom_digest text,
policy_digest text,
status text
);
CREATE INDEX idx_scan_cache ON scan(commit_sha, sbom_digest);
-- Artifacts
CREATE TABLE artifact (
artifact_id uuid PRIMARY KEY,
scan_id uuid REFERENCES scan,
artifact_key text,
kind text,
sha256 text,
build_id text,
purl text,
UNIQUE(scan_id, artifact_key)
);
-- Call graph nodes
CREATE TABLE cg_node (
scan_id uuid,
node_id text,
artifact_key text,
symbol_key text,
visibility text,
flags int,
PRIMARY KEY(scan_id, node_id)
);
-- Call graph edges
CREATE TABLE cg_edge (
scan_id uuid,
from_node_id text,
to_node_id text,
kind smallint,
reason smallint,
weight real,
PRIMARY KEY(scan_id, from_node_id, to_node_id, kind, reason)
);
CREATE INDEX idx_cg_edge_from ON cg_edge(scan_id, from_node_id);
CREATE INDEX idx_cg_edge_to ON cg_edge(scan_id, to_node_id);
-- Entrypoints
CREATE TABLE entrypoint (
scan_id uuid,
node_id text,
kind text,
framework text,
route text,
PRIMARY KEY(scan_id, node_id, kind, framework, route)
);
-- Runtime samples
CREATE TABLE runtime_sample (
scan_id uuid,
collected_at timestamptz,
env_hash text,
sample_id bigserial PRIMARY KEY,
timestamp timestamptz,
pid int,
thread_id int,
frames text[],
weight real
);
-- Symbol-to-component mapping
CREATE TABLE symbol_component_map (
scan_id uuid,
node_id text,
purl text,
mapping_kind text,
confidence real,
PRIMARY KEY(scan_id, node_id, purl)
);
-- Reachability results
CREATE TABLE reachability_component (
scan_id uuid,
purl text,
status smallint,
confidence real,
why jsonb,
evidence jsonb,
PRIMARY KEY(scan_id, purl)
);
CREATE TABLE reachability_finding (
scan_id uuid,
cve_id text,
purl text,
status smallint,
confidence real,
why jsonb,
evidence jsonb,
PRIMARY KEY(scan_id, cve_id, purl)
);
```
### 3.2 Unknowns Ranking Tables
```sql
CREATE TABLE unknowns (
unknown_id uuid PRIMARY KEY,
pkg_id text,
pkg_version text,
digest_anchor bytea,
unknown_flags jsonb,
popularity_p float,
potential_e float,
uncertainty_u float,
centrality_c float,
staleness_s float,
score float,
band text CHECK(band IN ('HOT','WARM','COLD')),
graph_slice_hash bytea,
evidence_set_hash bytea,
normalization_trace jsonb,
callgraph_attempt_hash bytea,
created_at timestamptz,
updated_at timestamptz
);
CREATE TABLE deploy_refs (
pkg_id text,
image_id text,
env text,
first_seen timestamptz,
last_seen timestamptz
);
CREATE TABLE graph_metrics (
pkg_id text PRIMARY KEY,
degree_c float,
betweenness_c float,
last_calc_at timestamptz
);
```
### 3.3 Proof Spine Tables
```sql
CREATE TABLE proof_spines (
spine_id uuid PRIMARY KEY,
artifact_id text,
vuln_id text,
policy_profile_id text,
verdict text,
verdict_reason text,
root_hash text,
scan_run_id uuid,
created_at timestamptz,
superseded_by_spine_id uuid,
segment_count int
);
CREATE INDEX idx_spine_lookup ON proof_spines(artifact_id, vuln_id, policy_profile_id);
CREATE TABLE proof_segments (
segment_id uuid PRIMARY KEY,
spine_id uuid REFERENCES proof_spines,
idx int,
segment_type text,
input_hash text,
result_hash text,
prev_segment_hash text,
envelope bytea,
tool_id text,
tool_version text,
status text,
created_at timestamptz
);
```
## 4. ALGORITHMS
### 4.1 Reachability Status Classification
**Status Values**
- `UNREACHABLE`: No path from entrypoints
- `POSSIBLY_REACHABLE`: Graph incomplete/dynamic behavior
- `REACHABLE_STATIC`: Static path exists
- `REACHABLE_PROVEN`: Runtime evidence confirms
**Confidence Scoring (Deterministic)**
Base scores:
```
UNREACHABLE → 0.05
POSSIBLY_REACHABLE → 0.35
REACHABLE_STATIC → 0.70
REACHABLE_PROVEN → 0.95
```
Modifiers:
```
+0.10 if path uses only static edges
-0.15 if path includes reflection_string|dynamic_import
+0.10 if runtime evidence hits affected component
-0.10 if NO_ENTRYPOINTS_DISCOVERED
Clamp to [0, 1]
```
### 4.2 Reachability Computation Algorithm
```
Inputs:
- Call graph nodes/edges + entrypoints
- Runtime evidence (optional)
- SBOM with purls
- Vulnerability facts (CVE ↔ purl/version)
- VEX statements
Steps:
1. Build adjacency list for cg_edge.kind in (static, heuristic)
2. Optional: Compress SCCs (Tarjan/Kosaraju)
3. Seed from entrypoints; if empty → mark POSSIBLY_REACHABLE with NO_ENTRYPOINTS_DISCOVERED
4. Traverse reachable nodes; track:
- firstSeenFromEntrypoint[node]
- pathWitness[node]
5. Map reachable nodes to purls via symbol_component_map:
Priority order:
a. Exact binary symbol → package metadata
b. Assembly/jar/module to SBOM component (hash/purl)
c. Heuristics: namespace prefixes, import paths, jar manifest
6. Runtime evidence upgrade:
- Mark frame nodes as "executed"
- Mint runtime edges: consecutive frames → runtime_minted
- Upgrade to REACHABLE_PROVEN if executed node maps to affected purl
7. Compute confidence using deterministic formula
```
### 4.3 Unknowns Ranking Algorithm
**Score Formula**
```
Score = clamp01(
wP·P + # Popularity impact
wE·E + # Exploit consequence potential
wU·U + # Uncertainty density
wC·C + # Graph centrality
wS·S # Evidence staleness
)
```
**Default Weights**
```
wP = 0.25 (deployment impact)
wE = 0.25 (potential consequence)
wU = 0.25 (uncertainty density)
wC = 0.15 (graph centrality)
wS = 0.10 (evidence staleness)
```
**Heuristics**
```
P = min(1, log10(1 + deployments)/log10(1 + 100))
U = sum of flags, capped at 1.0:
+0.30 if no provenance anchor
+0.25 if version_range
+0.20 if conflicting_feeds
+0.15 if missing_vector
+0.10 if unreachable source advisory
S = min(1, age_days / 14)
```
**Band Assignment**
```
Score ≥ 0.70 → HOT (immediate rescan + VEX escalation)
0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h)
Score < 0.40 → COLD (weekly batch)
```
### 4.4 Node ID Computation (.NET)
**Primary (IL-based)**
```
nodeId = SHA256(MVID + ":" + metadataToken + ":" + arity + ":" + signatureShape)
```
**Fallback (source-only)**
```
nodeId = SHA256(projectPath + ":" + file + ":" + span + ":" + symbolDisplayString)
```
**External nodes**
```
nodeId = SHA256("ext:" + artifactKey + ":" + symbolKey)
```
### 4.5 Canonical Symbol Key Format
```
{Namespace}.{TypeName}[`Arity][+Nested]::{MethodName}[`Arity]({ParamType1},{ParamType2},...)
```
Rules:
- Use `System.*` full names for BCL types
- Use `+` for nested types (metadata style)
- Use backtick arity for generic type/method definitions
- Arrays: `System.String[]`
- Byref: `System.String&`
### 4.6 Reachability Cache Algorithm
```csharp
public readonly record struct ReachKey(
string AlgoSig, // e.g., "RTA@sha256:…"
string InputsHash, // SBOM slice + policy + versions
string CallPathHash // normalized query graph
);
public sealed class ReachCache {
private readonly ConcurrentDictionary<ReachKey, Lazy<Task<ReachResult>>> _memo = new();
public Task<ReachResult> GetOrComputeAsync(
ReachKey key,
Func<Task<ReachResult>> compute,
CancellationToken ct)
{
var lazy = _memo.GetOrAdd(key, _ => new Lazy<Task<ReachResult>>(
() => compute(), LazyThreadSafetyMode.ExecutionAndPublication));
return lazy.Value.ContinueWith(t => {
if (t.IsCompletedSuccessfully) return t.Result;
_memo.TryRemove(key, out _);
throw t.Exception ?? new Exception("reachability failed");
}, ct);
}
}
```
**Operational rules**
- Canonical everything: sort nodes/edges, normalize paths
- Cache scope: per-scan, per-workspace, or per-feed version
- TTL: 15-60 minutes or evict after pipeline completes
- Max-entries cap
- Concurrency: `Lazy<Task<>>` coalesces duplicate in-flight calls
### 4.7 Proof Spine Construction
```
1. Sbomer produces SBOM_SLICE segment, DSSE-signs it
2. Scanner produces MATCH segment
3. Reachability produces REACHABILITY segment
4. Guard Analyzer produces GUARD_ANALYSIS segment
5. Vexer evaluates lattice, produces POLICY_EVAL segment
6. ProofSpineBuilder:
- Sorts segments by predetermined order
- Chains PrevSegmentHash
- Computes RootHash = hash(concat of segment hashes)
- Stores ProofSpine with deterministic SpineId
```
## 5. API CONTRACTS
### 5.1 Scanner Ingestion Endpoints
```
POST /api/scans
Returns: scanId
POST /api/scans/{scanId}/callgraphs
Body: CallGraph.v1.json
Header: Content-Digest (for idempotency)
POST /api/scans/{scanId}/runtimeevidence
Body: RuntimeEvidence.v1.json
POST /api/scans/{scanId}/sbom
Body: CycloneDX/SPDX
POST /api/scans/{scanId}/compute-reachability
Idempotent trigger
```
### 5.2 Query Endpoints
```
GET /api/scans/{scanId}/reachability/components?purl=...
GET /api/scans/{scanId}/reachability/findings?cve=...
GET /api/scans/{scanId}/reachability/explain?cve=...&purl=...
Returns: why[], path witness, sample refs
```
### 5.3 Export Endpoints
```
GET /api/scans/{scanId}/exports/sarif
GET /api/scans/{scanId}/exports/cdxr # CycloneDX reachability extension
GET /api/scans/{scanId}/exports/openvex
```
### 5.4 Proof Spine Endpoints
```
GET /graph/runtime/{podId}/lineage
GET /graph/image/{digest}/vex
GET /spines/{spineId}
Returns: ProofSpine with all segments
```
## 6. .NET IMPLEMENTATION PATTERNS
### 6.1 .NET Worker (Roslyn + IL) Required Features
**Call Graph Extraction**
- Direct invocation edges: `InvocationExpressionSyntax`
- Object creation edges: constructors
- Delegate invocation: record heuristic edge when target unresolved
- Virtual/interface dispatch: record `virtual_call` edge to declared method
- Async/await: connect logical caller → awaited method
**Entrypoint Discovery**
- `Program.Main` (classic)
- ASP.NET Core:
- Controllers: `[ApiController]`, route attributes, action methods
- Minimal APIs: `MapGet/MapPost/MapMethods` patterns
- gRPC: `MapGrpcService<T>()` and service methods
- Hosted services: `IHostedService`, `BackgroundService.ExecuteAsync`
**Reflection and DI Heuristics**
- `Type.GetType("…")`, `Assembly.GetType`, `GetMethod("…")`, `Invoke`
- `services.AddTransient<IFoo,Foo>()` / `AddScoped` / `AddSingleton`
- `Activator.CreateInstance`, `ServiceProvider.GetService`
- Produce heuristic edges with `reason = di_binding` or `reflection_string`
### 6.2 NuGet Dependencies
```xml
<!-- Roslyn / MSBuild -->
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Build.Locator" Version="*" />
<!-- IL + metadata -->
<!-- Use System.Reflection.Metadata (BCL) -->
<!-- Optional: Mono.Cecil for faster IL traversal -->
<!-- CLI + JSON -->
<PackageReference Include="System.CommandLine" Version="*" />
<PackageReference Include="System.Text.Json" Version="*" />
```
### 6.3 Roslyn IL Opcodes for Call Detection
```
Recognize as "calls":
- call
- callvirt
- newobj
- jmp
- ldftn
- ldvirtftn
```
### 6.4 MSBuild Workspace Loading Pattern
```csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
var ws = MSBuildWorkspace.Create();
var sln = await ws.OpenSolutionAsync(@"path\to.sln");
foreach (var proj in sln.Projects)
foreach (var doc in proj.Documents)
{
var model = await doc.GetSemanticModelAsync();
var root = await doc.GetSyntaxRootAsync();
foreach (var node in root.DescendantNodes()
.OfType<InvocationExpressionSyntax>())
{
var sym = model.GetSymbolInfo(node).Symbol as IMethodSymbol;
if (sym != null)
{
// record edge: caller -> sym
}
}
}
```
## 7. CONFIGURATION PATTERNS
### 7.1 Crypto Profiles
```csharp
public interface ICryptoProfile
{
string ProfileId { get; }
byte[] Sign(byte[] data, string keyId);
bool Verify(byte[] data, byte[] signature, string keyId);
}
// Implementations: FipsProfile, GostProfile, EidasProfile, SmProfile, PqcProfile
```
**Profile Metadata**
- Algorithm
- Key ID
- Profile name
**Key Rotation**
- Keys have validity intervals
- Spines keep KeyId in each DSSE signature
- Authority maintains trust table: which keys trusted for which SegmentType and time window
### 7.2 Offline Bundle Format
**Required Contents**
- SBOM + feeds + policy bundle + key material
- Manifest with hashes of all contents
- Replay manifest for deterministic rerun
**Format**
- Zip/tar + manifest
- Each entry content-addressed
### 7.3 Cache Configuration (appsettings)
```json
{
"Scanner": {
"Reach": {
"Cache": {
"MaxEntries": 10000,
"TtlMinutes": 60,
"EvictOnPipelineComplete": true
}
}
}
}
```
## 8. METRICS AND THRESHOLDS
### 8.1 Performance SLOs (v1)
```
Medium service (100k LOC .NET) static graph: < 2 minutes
Reachability compute: < 30 seconds
Query GET finding: < 200ms p95
```
### 8.2 Quality Metrics
**Proof verification**
- % verified proofs
- Proof verification failures
- Proof age since last verification
- % entries with valid inclusion proof (Rekor)
**Unknowns triage**
- Hot/Warm/Cold distribution
- Rescan success rate after N attempts
- Evidence staleness distribution
**Drift detection**
- Runtime edges not in static graph (above threshold → emit COVERAGE_DRIFT warning)
### 8.3 Benchmark Metrics (Moat)
| Metric | Target |
|--------|--------|
| Time-to-evidence: SBOM → signed call-graph | < 5 minutes for 100k LOC service |
| SBOM-diff false positive rate under dependency churn | < 5% change in reachability status for non-code changes |
| Deterministic priority scoring under air-gap replay | Bit-identical results given same inputs |
| Proof verification time | < 200ms p95 |
## 9. TESTING PATTERNS
### 9.1 Golden Corpus Requirements
**Mandatory Test Cases**
1. Minimal ASP.NET controller with reachable endpoint vulnerable lib call
2. Vulnerable lib present but never called unreachable
3. Reflection-based activation "possible" unless runtime proves
4. BackgroundService job case
5. Version range ambiguity
6. Mismatched epoch/backport
7. Missing CVSS vector
8. Conflicting severity vendor/NVD
9. Unanchored filesystem library
**Assertions per Test**
- Reachability status
- At least one `why[]` reason
- Deterministic confidence within ±0.01
- Segments with expected status (verified/partial/invalid)
### 9.2 Replay Manifest Tests
Given manifest containing:
- feed hashes
- rules version
- normalization logic
- lattice rules
Assert: ranking/reachability recomputes identically (byte-for-byte)
### 9.3 Signature Tampering Tests
- Flip byte in DSSE payload UI must show `invalid`
- Mark key untrusted segments show `untrusted-key`
- Entire spine marked as compromised
## 10. SPECIFICATION COMPLIANCE
### 10.1 SBOM Standards
**CycloneDX**
- Version: 1.6 (ECMA-424) and 1.7 (current)
- Media type: include version parameter
- Ingest: validate against ECMA-424/1.7 schemas
**SPDX**
- Version: 3.0.1
- Validate against canonical spec
**Ingestion Rules**
- Accept only `*.cdx.json` and `*.spdx.json`
- Hard fail others
- Store: raw bytes + parsed form + normalized canonical form
### 10.2 VEX Standards
**OpenVEX**
- Minimal, SBOM-agnostic
- Predicate type: `https://openvex.dev/ns/v0.2.0`
**CSAF VEX**
- Alternative format for interoperability
**Required VEX Fields**
- status: `not_affected|affected|fixed|under_investigation`
- justification
- timestamp
- author
- link to supporting evidence
### 10.3 Attestation Standards
**DSSE (Dead Simple Signing Envelope)**
- Use for all signed artifacts
- Payload: canonical JSON
- Envelope: signature + key metadata + profile
**in-toto**
- Statement structure
- Predicate types:
- SBOM: `https://spdx.dev/Document`
- VEX: OpenVEX predicate URI
- Custom: reachability predicates
**Cosign Integration**
```bash
cosign attest --yes --type https://spdx.dev/Document \
--predicate sbom.spdx.json \
--key cosign.key \
"${IMAGE_DIGEST}"
```
### 10.4 Rekor Integration
**CLI Verification**
```bash
rekor-cli verify --rekor_server https://rekor.sigstore.dev \
--signature artifact.sig \
--public-key cosign.pub \
--artifact artifact.bin
```
**Persistence per Entry**
- Rekor UUID
- Log index
- Integrated time
- Inclusion proof data
## 11. CLI COMMANDS
### 11.1 Worker CLI
```bash
# Artifacts-first scan
stella-worker-dotnet scan \
--scanKey 00000000-0000-0000-0000-000000000000 \
--assemblies ./artifacts/bin/Release \
--out ./callgraph.json
# Build-and-scan
stella-worker-dotnet scan \
--scanKey ... \
--sln ./src/MySolution.sln \
--configuration Release \
--tfm net10.0 \
--buildMode build \
--out ./callgraph.json
# Upload to scanner.webservice
stella-worker-dotnet scan \
--scanKey ... \
--assemblies ./artifacts/bin/Release \
--upload https://scanner/api/scans/{scanId}/callgraphs \
--apiKey $STELLA_API_KEY
```
### 11.2 Replay CLI
```bash
stellaops scan \
--replay-manifest <id-or-file> \
--artifact <image-digest> \
--vuln <cve> \
--explain
```
### 11.3 Reachability Query CLI
```bash
stella scan graph --lang dotnet --sln path.sln --out graph.scc.json
stella scan runtime --target pod/myservice --duration 30s --out stacks.json
stella reachability join \
--graph graph.scc.json \
--runtime stacks.json \
--sbom bom.cdx.json \
--out reach.cdxr.json
```
## 12. DEVELOPER CHECKLIST
### 12.1 Per-Feature Definition of Done
For any feature touching scans, VEX, or evidence:
- [ ] Deterministic: input manifest defined, canonicalization applied, golden fixture(s) added
- [ ] Evidence: outputs DSSE-wrapped and linked
- [ ] Reachability/Lattice: runs only in allowed services, records algorithm IDs
- [ ] Crypto: calls through profile abstraction, tests for 2 profiles
- [ ] Graph: lineage edges added, node/edge IDs stable and queryable
- [ ] UX/API: API to retrieve structured evidence
- [ ] Tests: unit + golden + integration test with full SBOM scan VEX chain
### 12.2 Determinism Checklist
- [ ] Input manifest type defined and versioned
- [ ] Canonicalization applied before hashing/signing
- [ ] Output stored with `inputsDigest` and `algoDigest`
- [ ] At least one golden fixture proves determinism
- [ ] No environment variables in core algorithm
- [ ] No machine-local files
- [ ] No system clock inside algorithms
### 12.3 Reachability Implementation Checklist
- [ ] Reachability algorithms only in Scanner.WebService
- [ ] Cache lazy and keyed by deterministic inputs
- [ ] Output includes explicit evidence pointers
- [ ] UI endpoints expose reachability state in structured form
- [ ] All modifiers recorded in `why[]`
### 12.4 Crypto-Sovereign Checklist
- [ ] No direct crypto calls in feature code; only via profile layer
- [ ] All attestations carry algorithm + key id + profile
- [ ] Offline bundle type exists for this workflow
- [ ] Tests for at least 2 different crypto profiles
### 12.5 Policy/Lattice Checklist
- [ ] Facts and policies serialized separately
- [ ] Lattice code in allowed services only
- [ ] Merge strategies named and versioned
- [ ] Artifacts record which lattice algorithm used
### 12.6 Proof-Linked VEX Checklist
- [ ] VEX schema includes pointers to all upstream artifacts (sbomDigest, scanId, reachMapDigest, policyDigest, signerKeyId)
- [ ] No duplication of SBOM/scan content inside VEX
- [ ] DSSE used as standard envelope type
### 12.7 Unknowns Triage Checklist
- [ ] Persist all traces for deterministic replay
- [ ] Ranking depends only on manifest-declared parameters
- [ ] All uncertainty factors are explicit flags
- [ ] Scoring reproducible under identical inputs
- [ ] Scheduler decision table deterministic and tested
- [ ] API exposes full reasoning
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL 16, Angular v17

View File

@@ -0,0 +1,291 @@
# Rekor Integration Technical Reference
**Source Advisories**:
- 30-Nov-2025 - Rekor Receipt Checklist for Stella Ops
**Last Updated**: 2025-12-14
---
## 1. REQUIREMENTS
- Rekor receipts must be deterministic, tenant-scoped, and verifiable offline
- For Authority/Sbomer/Vexer flows
- Field-level ownership map for receipts and bundles
- Offline verifier expectations
- Mirror snapshot rules
- DSSE/receipt schema pointers
## 2. DETERMINISM & OFFLINE
- Bundle TSA/time anchors with receipts
- Prefer mirror snapshots
- Avoid live log fetches in examples
## 3. DELIVERABLES
- Schema draft
- Offline verifier stub
- Module dossier updates
## 4. REKOR ENTRY STRUCTURE
```json
{
"dsseSha256": "sha256:...",
"rekor": {
"uuid": "...",
"logIndex": 12345,
"logId": "...",
"integratedTime": 1733736000,
"inclusionProof": {
"rootHash": "...",
"hashes": ["...", "..."],
"checkpoint": "..."
}
}
}
```
## 5. REKOR CLIENT INTERFACE
```csharp
public interface IRekorClient
{
Task<RekorEntry> SubmitDsseAsync(
DsseEnvelope envelope,
CancellationToken ct = default
);
Task<bool> VerifyInclusionAsync(
RekorEntry entry,
byte[] payloadDigest,
byte[] rekorPublicKey,
CancellationToken ct = default
);
}
public record RekorEntry(
string Uuid,
long LogIndex,
string LogId,
long IntegratedTime,
InclusionProof Proof
);
public record InclusionProof(
string RootHash,
string[] Hashes,
string Checkpoint
);
```
## 6. CLI VERIFICATION
### 6.1 Rekor CLI Commands
```bash
rekor-cli verify --rekor_server https://rekor.sigstore.dev \
--signature artifact.sig \
--public-key cosign.pub \
--artifact artifact.bin
```
### 6.2 Persistence per Entry
- Rekor UUID
- Log index
- Integrated time
- Inclusion proof data
## 7. OFFLINE REKOR MIRROR
### 7.1 Mirror Structure
```
/evidence/tlog/
checkpoint.sig # signed tree head
entries/ # *.jsonl (Merkle leaves) + proofs
```
### 7.2 Verification Steps
```
1. Recompute Merkle root from entries
2. Check matches `checkpoint.sig` (after verifying signature with tlog root key)
3. For each attestation:
- Verify UUID/digest appears in entry pack
- Verify inclusion proof resolves
```
## 8. REKOR STORAGE SCHEMA
```sql
CREATE TABLE rekor_entries (
dsse_sha256 VARCHAR(64) PRIMARY KEY,
log_index BIGINT NOT NULL,
log_id TEXT NOT NULL,
integrated_time BIGINT NOT NULL,
inclusion_proof JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_rekor_log_index ON rekor_entries(log_index);
CREATE INDEX idx_rekor_integrated_time ON rekor_entries(integrated_time);
```
## 9. REKOR FAILURE HANDLING
### 9.1 Rekor Unavailable
```
If Rekor unavailable:
- Store DSSE envelope locally
- Queue for retry
- Mark proof chain as "rekorStatus: pending"
- Internal-only until Rekor sync succeeds
- Flag in verification results
```
### 9.2 Rekor Verification Failed
```
If verification fails:
- Log error with structured fields (rekorUuid, dsseDigest, failureReason)
- Mark envelope as "rekor_verification_failed"
- Do not accept as valid proof
- Alert security team
```
## 10. INTEGRATION POINTS
### 10.1 Authority Module
- Submit signed attestations to Rekor
- Store receipts with DSSE envelopes
- Verify inclusion proofs on retrieval
### 10.2 Sbomer Module
- Submit SBOM attestations to Rekor
- Link Rekor UUID to SBOM entries
### 10.3 Vexer Module
- Submit VEX statements to Rekor
- Store receipts with VEX decisions
## 11. METRICS & OBSERVABILITY
```
rekor_submit_total{status="success|failed"}
rekor_submit_latency_seconds
rekor_verify_total{result="pass|fail"}
rekor_verify_latency_seconds
rekor_queue_depth (pending submissions)
rekor_retry_attempts_total
```
## 12. CONFIGURATION
```yaml
rekor:
server_url: https://rekor.sigstore.dev
public_key_path: /etc/stellaops/rekor-pub.pem
offline_mode: false
retry:
max_attempts: 3
initial_delay_ms: 1000
max_delay_ms: 10000
timeout_seconds: 30
```
## 13. OFFLINE BUNDLE INTEGRATION
### 13.1 Rekor Receipt in Offline Kit
**rekor-receipt.json**:
```json
{
"uuid": "string",
"logIndex": int,
"rootHash": "string",
"hashes": ["string"],
"checkpoint": "string"
}
```
### 13.2 Offline Verification
```
1. Load Rekor public key from offline bundle
2. Verify checkpoint signature
3. Recompute Merkle root from inclusion proof
4. Verify root hash matches checkpoint
5. Verify DSSE envelope hash appears in proof
```
## 14. SECURITY CONSIDERATIONS
### 14.1 Trust Model
- Rekor provides transparency, not trust
- Trust derives from key verification
- Inclusion proof demonstrates timestamp
- Does not prove correctness of content
### 14.2 Key Pinning
- Pin Rekor public key via out-of-band distribution
- Verify checkpoint signatures before trusting
- Maintain key version history
### 14.3 Replay Protection
- Use integrated_time to detect backdated entries
- Compare with local clock (within reasonable skew)
- Alert on time anomalies
## 15. TESTING REQUIREMENTS
### 15.1 Integration Tests
- Submit DSSE to Rekor (staging)
- Verify inclusion proof
- Offline verification with mirror
- Retry on failure
- Timeout handling
### 15.2 Failure Scenarios
- Rekor unavailable
- Network timeout
- Invalid inclusion proof
- Signature verification failure
- Malformed response
## 16. OPERATIONAL PROCEDURES
### 16.1 Rekor Mirror Sync
```bash
# Download latest checkpoint
curl https://rekor.sigstore.dev/api/v1/log/checkpoint > checkpoint.sig
# Verify checkpoint signature
rekor-cli verify --checkpoint checkpoint.sig --public-key rekor-pub.pem
# Sync entries since last update
rekor-cli sync --since <last_log_index> --output ./entries/
```
### 16.2 Monitoring
- Alert on Rekor submission failures >1% over 5 minutes
- Alert on verification failures >0.1% over 5 minutes
- Alert on queue depth >1000 for >10 minutes
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,255 @@
# Smart-Diff Technical Reference
**Source Advisories**:
- 09-Dec-2025 - SmartDiff and ProvenanceRich Binaries
- 12-Dec-2025 - SmartDiff Detects Meaningful Risk Shifts
- 13-Dec-2025 - SmartDiff - Defining Meaningful Risk Change
- 05-Dec-2025 - Design Notes on SmartDiff and CallStack Analysis
**Last Updated**: 2025-12-14
---
## 1. SMART-DIFF PREDICATE SCHEMA
```json
{
"predicateType": "stellaops.dev/predicates/smart-diff@v1",
"predicate": {
"baseImage": {"name":"...", "digest":"sha256:..."},
"targetImage": {"name":"...", "digest":"sha256:..."},
"diff": {
"filesAdded": [...],
"filesRemoved": [...],
"filesChanged": [{"path":"...", "hunks":[...]}],
"packagesChanged": [{"name":"openssl","from":"1.1.1u","to":"3.0.14"}]
},
"context": {
"entrypoint":["/app/start"],
"env":{"FEATURE_X":"true"},
"user":{"uid":1001,"caps":["NET_BIND_SERVICE"]}
},
"reachabilityGate": {"reachable":true,"configActivated":true,"runningUser":false,"class":6},
"scanner": {"name":"StellaOps.Scanner","version":"...","ruleset":"reachability-2025.12"}
}
}
```
## 2. REACHABILITY GATE (3-BIT SEVERITY)
**Data Model:**
```csharp
public sealed record ReachabilityGate(
bool? Reachable, // true / false / null for unknown
bool? ConfigActivated,
bool? RunningUser,
int Class, // 0..7 derived from the bits when all known
string Rationale // short explanation, human-readable
);
```
**Class Computation:** 0-7 based on 3 binary gates (reachable, config-activated, running user)
**Unknown Handling:**
- Never silently treat `null` as `false` or `true`
- If any bit is `null`, set `Class = -1` or compute from known bits only
## 3. DELTA DATA STRUCTURES
```csharp
// Delta.Packages
{
added[],
removed[],
changed[{name, fromVer, toVer}]
}
// Delta.Layers
{
changed[{path, fromHash, toHash, licenseDelta}]
}
// Delta.Functions
{
added[],
removed[],
changed[{symbol, file, signatureHashFrom, signatureHashTo}]
}
// PatchDelta
{
addedSymbols[],
removedSymbols[],
changedSignatures[]
}
```
## 4. SMART-DIFF ALGORITHMS
**Core Diff Computation:**
```pseudo
prev = load_snapshot(t-1)
curr = load_snapshot(t)
Δ.pkg = diff_packages(prev.lock, curr.lock)
Δ.layers= diff_layers(prev.sbom, curr.sbom)
Δ.funcs = diff_cfg(prev.cfgIndex, curr.cfgIndex)
scope = union(
impact_of(Δ.pkg.changed),
impact_of_files(Δ.layers.changed),
reachability_of(Δ.funcs.changed)
)
for f in scope.functions:
rescore(f)
for v in impacted_vulns(scope):
annotate(v, patch_delta(Δ))
link_evidence(v, dsse_attestation(), proof_links())
for v in previously_flagged where vulnerable_apis_now_absent(v, curr):
emit_vex_candidate(v, status="not_affected", rationale="API not present", evidence=proof_links())
```
## 5. MATERIAL RISK CHANGE DETECTION RULES
**FindingKey:**
```
FindingKey = (component_purl, component_version, cve_id)
```
**RiskState Fields:**
- `reachable: bool | unknown`
- `vex_status: enum` (AFFECTED | NOT_AFFECTED | FIXED | UNDER_INVESTIGATION | UNKNOWN)
- `in_affected_range: bool | unknown`
- `kev: bool`
- `epss_score: float | null`
- `policy_flags: set<string>`
- `evidence_links: list<EvidenceLink>`
**Rule R1: Reachability Flip**
- `reachable` changes: `false → true` (risk ↑) or `true → false` (risk ↓)
**Rule R2: VEX Status Flip**
- Meaningful changes: `AFFECTED ↔ NOT_AFFECTED`, `UNDER_INVESTIGATION → NOT_AFFECTED`
**Rule R3: Affected Range Boundary**
- `in_affected_range` flips: `false → true` or `true → false`
**Rule R4: Intelligence/Policy Flip**
- `kev` changes `false → true`
- `epss_score` crosses configured threshold
- `policy_flag` changes severity (warn → block)
## 6. SUPPRESSION RULES
**Suppression Conditions (ALL must apply):**
1. `reachable == false`
2. `vex_status == NOT_AFFECTED`
3. `kev == false`
4. No policy override
**Patch Churn Suppression:**
- If version changes AND `in_affected_range` remains false in both AND no KEV/policy flip → suppress
## 7. CALL-STACK ANALYSIS
**C# Roslyn Skeleton:**
```csharp
public static class SmartDiff
{
public static async Task<HashSet<string>> ReachableSinks(string solutionPath, string[] entrypoints, string[] sinks)
{
var workspace = MSBuild.MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(solutionPath);
var index = new HashSet<string>();
foreach (var proj in solution.Projects)
{
var comp = await proj.GetCompilationAsync();
if (comp is null) continue;
var epSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
.OfType<IMethodSymbol>().Where(m => entrypoints.Contains(m.ToDisplayString())).ToList();
var sinkSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
.OfType<IMethodSymbol>().Where(m => sinks.Contains(m.ToDisplayString())).ToList();
foreach (var ep in epSymbols)
foreach (var sink in sinkSymbols)
{
var refs = await SymbolFinder.FindReferencesAsync(sink, solution);
if (refs.SelectMany(r => r.Locations).Any())
index.Add($"{ep.ToDisplayString()} -> {sink.ToDisplayString()}");
}
}
return index;
static IEnumerable<ISymbol> Descend(INamespaceOrTypeSymbol sym)
{
foreach (var m in sym.GetMembers())
{
yield return m;
if (m is INamespaceOrTypeSymbol nt)
foreach (var x in Descend(nt)) yield return x;
}
}
}
}
```
**Go SSA Skeleton:**
```go
package main
import (
"fmt"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
)
func main() {
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Tests: false}
pkgs, _ := packages.Load(cfg, "./...")
prog, pkgsSSA := ssa.NewProgram(pkgs[0].Fset, ssa.BuilderMode(0))
for _, p := range pkgsSSA { prog.CreatePackage(p, p.Syntax, p.TypesInfo, true) }
prog.Build()
cg := cha.CallGraph(prog)
fmt.Println("nodes:", len(cg.Nodes))
}
```
## 8. SINK TAXONOMY
```yaml
sinks:
- CMD_EXEC
- UNSAFE_DESER
- SQL_RAW
- SSRF
- FILE_WRITE
- PATH_TRAVERSAL
- TEMPLATE_INJECTION
- CRYPTO_WEAK
- AUTHZ_BYPASS
```
## 9. POLICY SCORING FORMULA
**Priority Score:**
```
score =
+ 1000 if new.kev
+ 500 if new.reachable
+ 200 if reason includes RANGE_FLIP to affected
+ 150 if VEX_FLIP to AFFECTED
+ 0..100 based on EPSS (epss * 100)
+ policy weight: +300 if decision BLOCK, +100 if WARN
```
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17

View File

@@ -0,0 +1,455 @@
# Testing and Quality Guardrails Technical Reference
**Source Advisories**:
- 29-Nov-2025 - Acceptance Tests Pack and Guardrails
- 29-Nov-2025 - SCA Failure Catalogue for StellaOps Tests
- 30-Nov-2025 - Ecosystem Reality Test Cases for StellaOps
- 14-Dec-2025 - Create a small groundtruth corpus
**Last Updated**: 2025-12-14
---
## 1. ACCEPTANCE TEST PACK SCHEMA
### 1.1 Required Artifacts (MVP for DONE)
- Advisory summary under `docs/process/`
- Checklist stub referencing AT1AT10
- Fixture pack path: `tests/acceptance/packs/guardrails/` (no network)
- Links into sprint tracker (`SPRINT_0300_0001_0001_documentation_process.md`)
### 1.2 Determinism & Offline
- Freeze scanner/db versions; record in `inputs.lock`
- All fixtures reproducible from seeds
- Include DSSE envelopes for pack manifests
## 2. SCA FAILURE CATALOGUE (FC1-FC10)
### 2.1 Required Artifacts
- Catalogue plus fixture pack root: `tests/fixtures/sca/catalogue/`
- Sprint Execution Log entry when published
### 2.2 Fixture Requirements
- Pin scanner versions and feeds
- Include `inputs.lock` and DSSE manifest per case
- Normalize results (ordering, casing) for stable comparisons
## 3. ECOSYSTEM REALITY TEST CASES (ET1-ET10)
**Fixture Path**: `tests/fixtures/sca/catalogue/`
**Requirements**:
- Map each incident to acceptance tests and fixture paths
- Pin tool versions and feeds; no live network
- Populate fixtures and acceptance specs
## 4. GROUND-TRUTH CORPUS SCHEMA
### 4.1 Service Structure
Each service under `/toys/svc-XX-<name>/`:
```
app/
infra/ # Dockerfile, compose, network policy
tests/ # positive + negative reachability tests
labels.yaml # ground truth
evidence/ # generated by tests (trace, tags, manifests)
fix/ # minimal patch proving remediation
```
### 4.2 labels.yaml Schema
```yaml
service: svc-01-password-reset
vulns:
- id: V1
cve: CVE-2022-XXXXX
type: dep_runtime|dep_build|code|config|os_pkg|supply_chain
package: string
version: string
reachable: true|false
reachability_level: R0|R1|R2|R3|R4
entrypoint: string # route:/reset, topic:jobs, cli:command
preconditions: [string] # flags/env/auth
path_tags: [string]
proof:
artifacts: [string]
tags: [string]
fix:
type: upgrade|config|code
patch_path: string
expected_delta: string
negative_proof: string # if unreachable
```
### 4.3 Reachability Tiers
- **R0 Present**: component exists in SBOM, not imported/loaded
- **R1 Loaded**: imported/linked/initialized, no executed path
- **R2 Executed**: vulnerable function executed (deterministic trace)
- **R3 Tainted execution**: execution with externally influenced input
- **R4 Exploitable**: controlled, non-harmful PoC (optional)
### 4.4 Evidence Requirements per Tier
- **R0**: SBOM + file hash/package metadata
- **R1**: runtime startup logs or module load trace tag
- **R2**: callsite tag + stack trace snippet
- **R3**: R2 + taint marker showing external data reached call
- **R4**: only if safe/necessary; non-weaponized, sandboxed
### 4.5 Canonical Tag Format
```
TAG:route:<method> <path>
TAG:topic:<name>
TAG:call:<sink>
TAG:taint:<boundary>
TAG:flag:<name>=<value>
```
### 4.6 Evidence Artifact Schema
**evidence/trace.json**:
```json
{
"ts": "UTC ISO-8601",
"corr": "correlation-id",
"tags": ["TAG:route:POST /reset", "TAG:taint:http.body.email", "TAG:call:Crypto.MD5"]
}
```
### 4.7 Evidence Manifest
**evidence/manifest.json**:
```json
{
"git_sha": "string",
"image_digest": "string",
"tool_versions": {"scanner": "string", "db": "string"},
"timestamps": {"started_at": "UTC ISO-8601", "completed_at": "UTC ISO-8601"},
"evidence_hashes": {"trace.json": "sha256:...", "tags.log": "sha256:..."}
}
```
## 5. CORE TEST METRICS
| Metric | Definition |
|--------|------------|
| Recall (by class) | % of labeled vulns detected (runtime deps, OS pkgs, code, config) |
| Precision | 1 - false positive rate |
| Reachability accuracy | % correct R0/R1/R2/R3 classifications |
| Overreach | Predicted reachable but labeled R0/R1 |
| Underreach | Labeled R2/R3 but predicted non-reachable |
| TTFS | Time-to-first-signal (first evidence-backed blocking issue) |
| Fix validation | % of applied fixes producing expected delta |
## 6. TEST QUALITY GATES (CI ENFORCEMENT THRESHOLDS)
```yaml
thresholds:
runtime_dependency_recall: >= 0.95
unreachable_false_positives: <= 0.05
reachability_underreport: <= 0.10
ttfs_regression: <= +10% vs main
fix_validation_pass_rate: 100%
```
## 7. SERVICE DEFINITION OF DONE
A service PR is DONE only if it includes:
- [ ] `labels.yaml` validated by `schemas/labels.schema.json`
- [ ] Docker build reproducible (digest pinned, lockfiles committed)
- [ ] Positive tests generating evidence proving reachability tiers
- [ ] Negative tests proving "unreachable" claims
- [ ] `fix/` patch removing/mitigating weakness with measurable delta
- [ ] `evidence/manifest.json` capturing tool versions, git sha, image digest, timestamps, evidence hashes
## 8. REVIEWER REJECTION CRITERIA
Reject PR if any fail:
- [ ] Labels complete, schema-valid, stable IDs preserved
- [ ] Proof artifacts deterministic and generated by tests
- [ ] Reachability tier justified and matches evidence
- [ ] Unreachable claims have negative proofs
- [ ] Docker build uses pinned digests + committed lockfiles
- [ ] `fix/` produces measurable delta without new unlabeled issues
- [ ] No network egress required; tests hermetic
## 9. TEST HARNESS PATTERNS
### 9.1 xUnit Test Template
```csharp
public class ReachabilityAcceptanceTests : IClassFixture<PostgresFixture>
{
private readonly PostgresFixture _db;
public ReachabilityAcceptanceTests(PostgresFixture db)
{
_db = db;
}
[Theory]
[InlineData("svc-01-password-reset", "V1", ReachabilityLevel.R2)]
[InlineData("svc-02-file-upload", "V1", ReachabilityLevel.R0)]
public async Task VerifyReachabilityClassification(
string serviceId,
string vulnId,
ReachabilityLevel expectedLevel)
{
// Arrange
var labels = await LoadLabels($"toys/{serviceId}/labels.yaml");
var expectedVuln = labels.Vulns.First(v => v.Id == vulnId);
// Act
var result = await _scanner.ScanAsync(serviceId);
var actualVuln = result.Findings.First(f => f.VulnId == vulnId);
// Assert
Assert.Equal(expectedLevel, actualVuln.ReachabilityLevel);
Assert.NotEmpty(actualVuln.Evidence);
}
}
```
### 9.2 Testcontainers Pattern
```csharp
public class PostgresFixture : IAsyncLifetime
{
private PostgreSqlContainer? _container;
public string ConnectionString { get; private set; } = null!;
public async Task InitializeAsync()
{
_container = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.WithDatabase("stellaops_test")
.WithUsername("test")
.WithPassword("test")
.Build();
await _container.StartAsync();
ConnectionString = _container.GetConnectionString();
// Run migrations
await RunMigrations(ConnectionString);
}
public async Task DisposeAsync()
{
if (_container != null)
await _container.DisposeAsync();
}
}
```
## 10. FIXTURE ORGANIZATION
```
tests/
fixtures/
sca/
catalogue/
FC001_openssl_version_range/
inputs.lock
sbom.cdx.json
expected_findings.json
dsse_manifest.json
acceptance/
packs/
guardrails/
AT001_reachability_present/
AT002_reachability_loaded/
AT003_reachability_executed/
micro/
motion/
error/
offline/
toys/
svc-01-password-reset/
app/
infra/
tests/
labels.yaml
evidence/
fix/
```
## 11. DETERMINISTIC TEST REQUIREMENTS
### 11.1 Time Handling
- Freeze timers to `2025-12-04T12:00:00Z` in stories/e2e
- Use `FakeTimeProvider` in .NET tests
- Playwright: `useFakeTimers`
### 11.2 Random Number Generation
- Seed RNG with `0x5EED2025` unless scenario-specific
- Never use `Random()` without explicit seed
### 11.3 Network Isolation
- No network calls in test execution
- Offline assets bundled
- Testcontainers for external dependencies
- Mock external APIs
### 11.4 Snapshot Testing
- All fixtures stored under `tests/fixtures/`
- Golden outputs checked into git
- Stable ordering for arrays/objects
- Strip volatile fields (timestamps, UUIDs) unless semantic
## 12. COVERAGE REQUIREMENTS
### 12.1 Unit Tests
- **Target**: ≥85% line coverage for core modules
- **Critical paths**: 100% coverage required
- **Exceptions**: UI glue code, generated code
### 12.2 Integration Tests
- **Database operations**: All repositories tested with Testcontainers
- **API endpoints**: All endpoints tested with WebApplicationFactory
- **External integrations**: Mocked or stubbed
### 12.3 End-to-End Tests
- **Critical workflows**: User registration → scan → triage → decision
- **Happy paths**: All major features
- **Error paths**: Authentication failures, network errors, data validation
## 13. PERFORMANCE TESTING
### 13.1 Benchmark Tests
```csharp
[MemoryDiagnoser]
public class ScannerBenchmarks
{
[Benchmark]
public async Task ScanMediumImage()
{
// 100k LOC .NET service
await _scanner.ScanAsync("medium-service");
}
[Benchmark]
public async Task ComputeReachability()
{
await _reachability.ComputeAsync(_testGraph);
}
}
```
### 13.2 Performance Targets
| Operation | Target |
|-----------|--------|
| Medium service scan | < 2 minutes |
| Reachability compute | < 30 seconds |
| Query GET finding | < 200ms p95 |
| SBOM ingestion | < 5 seconds |
## 14. MUTATION TESTING
### 14.1 Stryker Configuration
```json
{
"stryker-config": {
"mutate": [
"src/**/*.cs",
"!src/**/*.Designer.cs",
"!src/**/Migrations/**"
],
"test-runner": "dotnet",
"threshold-high": 90,
"threshold-low": 70,
"threshold-break": 60
}
}
```
### 14.2 Mutation Score Targets
- **Critical modules**: 90%
- **Standard modules**: 70%
- **Break build**: <60%
## 15. SECURITY TESTING
### 15.1 OWASP Top 10 Coverage
- [ ] SQL Injection
- [ ] XSS (Cross-Site Scripting)
- [ ] CSRF (Cross-Site Request Forgery)
- [ ] Authentication bypasses
- [ ] Authorization bypasses
- [ ] Sensitive data exposure
- [ ] XML External Entities (XXE)
- [ ] Broken Access Control
- [ ] Security Misconfiguration
- [ ] Insecure Deserialization
### 15.2 Dependency Scanning
```bash
# SBOM generation
dotnet sbom-tool generate -b ./bin -bc ./src -pn StellaOps -pv 1.0.0
# Vulnerability scanning
dotnet list package --vulnerable --include-transitive
```
## 16. CI/CD INTEGRATION
### 16.1 GitHub Actions Workflow
```yaml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage"
- name: Upload coverage
uses: codecov/codecov-action@v4
```
### 16.2 Quality Gates
- All tests pass
- Coverage 85%
- No high/critical vulnerabilities
- Mutation score 70%
- Performance regressions <10%
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL 16, Angular v17

View File

@@ -0,0 +1,390 @@
# Triage and Unknowns Technical Reference
**Source Advisories**:
- 30-Nov-2025 - Unknowns Decay & Triage Heuristics
- 14-Dec-2025 - Dissect triage and evidence workflows
- 04-Dec-2025 - Ranking Unknowns in Reachability Graphs
**Last Updated**: 2025-12-14
---
## 1. EVIDENCE-FIRST PRINCIPLES
1. **Evidence before detail**: Opening alert shows best available evidence immediately
2. **Fast first signal**: UI renders credible "first signal" quickly
3. **Determinism reduces hesitation**: Sorting, graphs, diffs stable across refreshes
4. **Offline by design**: If evidence exists locally, render without network
5. **Audit-ready by default**: Every decision reproducible, attributable, exportable
## 2. MINIMAL EVIDENCE BUNDLE (PER FINDING)
1. **Reachability proof**: Function-level path or package-level import chain
2. **Call-stack snippet**: 510 frames around sink/source with file:line anchors
3. **Provenance**: Attestation/DSSE + build ancestry (image → layer → artifact → commit)
4. **VEX/CSAF status**: affected/not-affected/under-investigation + reason
5. **Diff**: SBOM or VEX delta since last scan (smart-diff)
## 3. KPIS
### 3.1 TTFS (Time-to-First-Signal)
**Definition**: p50/p95 from alert creation to first rendered evidence
**Target**: p95 < 1.5s (with 100ms RTT, 1% loss)
### 3.2 Clicks-to-Closure
**Definition**: Median interactions per decision type
**Target**: median < 6 clicks
### 3.3 Evidence Completeness Score
**Definition**: 04 (reachability, call-stack, provenance, VEX present)
**Target**: 90% of decisions include all evidence + reason + replay token
### 3.4 Offline Friendliness
**Definition**: % evidence resolvable with no network
**Target**: 95% with local bundle
### 3.5 Audit Log Completeness
**Requirement**: Every decision has evidence hash set, actor, policy context, replay token
## 4. KEYBOARD SHORTCUTS
- `J`: Jump to first incomplete evidence pane
- `Y`: Copy DSSE (attestation block or Rekor entry ref)
- `R`: Toggle reachability view (path list compact graph textual proof)
- `/`: Search within graph (node/func/package)
- `S`: Deterministic sort (reachabilityseverityagecomponent)
- `A`, `N`, `U`: Quick VEX set (Affected / Not-affected / Under-investigation)
- `?`: Keyboard help overlay
## 5. UX FLOW
### 5.1 Alert Row
- TTFS timer
- Reachability badge
- Decision state
- Diff-dot
### 5.2 Open Alert → Evidence Tab (Not Details)
**Top strip**: 3 proof pills (Reachability / Call-stack / Provenance ✓)
Click to expand inline
### 5.3 Decision Drawer (Pinned Right)
- VEX/CSAF radio (A/N/U)
- Reason presets "Record decision"
- Audit-ready summary (hashes, timestamps, policy)
### 5.4 Diff Tab
SBOM/VEX delta, grouped by "meaningful risk shift"
### 5.5 Activity Tab
Immutable audit log; export as signed bundle
## 6. GRAPH PERFORMANCE (LARGE CALL GRAPHS)
### 6.1 Minimal-Latency Snapshots
- Pre-render static PNG/SVG thumbnails server-side
### 6.2 Progressive Neighborhood Expansion
- Load 1-hop first, expand on demand
- First TTFS < 500ms
### 6.3 Stable Node Ordering
- Deterministic layout with consistent anchors
### 6.4 Chunked Graph Edges
- Capped fan-out
- Collapse identical library paths into reachability macro-edge
**Targets**:
- Preview < 300ms
- Interactive hydration < 2.0s for large graphs
## 7. OFFLINE DESIGN
### 7.1 Local Evidence Cache
Store (SBOM slices, path proofs, DSSE attestations, compiled call-stacks) in signed bundle beside SARIF/VEX
### 7.2 Deferred Enrichment
Mark fields needing internet; queue background "enricher" when network returns
### 7.3 Predictable Fallbacks
Show embedded DSSE + "verification pending" if provenance server missing
## 8. AUDIT & REPLAY
### 8.1 Deterministic Replay Token
```
replay_token = hash(feed_manifests + rules + lattice_policy + inputs)
```
### 8.2 One-Click "Reproduce"
CLI snippet pinned to exact versions and policies
### 8.3 Evidence Hash-Set
Content-address each proof artifact; audit entry stores hashes + signer
## 9. TELEMETRY IMPLEMENTATION
```typescript
ttfs.start (alert creation)
ttfs.signal (first evidence card paint)
close.clicks (decision recorded)
```
Log evidence bitset (reach, stack, prov, vex) at decision time
## 10. API REQUIREMENTS
### 10.1 Endpoints
```
GET /alerts?filters… → list view
GET /alerts/{id}/evidence → evidence payload (reachability, call stack, provenance, hashes)
POST /alerts/{id}/decisions → record decision event (append-only)
GET /alerts/{id}/audit → audit timeline
GET /alerts/{id}/diff?baseline=… → SBOM/VEX diff
GET /bundles/{id}, POST /bundles/verify → offline bundle download/verify
```
### 10.2 Evidence Payload Schema
```json
{
"alert_id": "a123",
"reachability": { "status": "available|loading|unavailable|error", "hash": "sha256:…", "proof": {...} },
"callstack": { "status": "...", "hash": "...", "frames": [...] },
"provenance": { "status": "...", "hash": "...", "dsse": {...} },
"vex": { "status": "...", "current": {...}, "history": [...] },
"hashes": ["sha256:…", ...]
}
```
**Guidelines**:
- Deterministic ordering for arrays and nodes
- Explicit `status` per evidence section
- Include `hash` per artifact
## 11. DECISION EVENT SCHEMA
Store per decision:
- `alert_id`, `artifact_id` (image digest/commit hash)
- `actor_id`, `timestamp`
- `decision_status` (Affected/Not affected/Under investigation)
- `reason_code` (preset) + `reason_text`
- `evidence_hashes[]` (content-addressed)
- `policy_context` (ruleset version, policy id)
- `replay_token` (hash of inputs)
## 12. OFFLINE BUNDLE FORMAT
Single file (`.stella.bundle.tgz`) containing:
- Alert metadata snapshot
- Evidence artifacts (reachability proofs, call stacks, provenance attestations)
- SBOM slice(s) for diffs
- VEX decision history
- Manifest with content hashes
- **Must be signed and verifiable**
## 13. PERFORMANCE BUDGETS
- **TTFS**: <200ms skeleton, <500ms first evidence pill, <1.5s p95 full evidence
- **Graph**: Preview <300ms, interactive <2.0s
- **Interaction response**: 100ms
- **Animation frame budget**: 16ms avg / 50ms p95
- **Keyboard coverage**: 90% of triage actions
- **Offline replay**: 100% of decisions re-render from bundle
## 14. ERROR HANDLING
Never show empty states without explanation. Distinguish:
- "not computed yet"
- "not possible due to missing inputs"
- "blocked by permissions"
- "offlineenrichment pending"
- "verification failed"
## 15. RBAC
Gate:
- Viewing provenance attestations
- Recording decisions
- Exporting audit bundles
All decision events immutable; corrections are new events (append-only)
## 16. UNKNOWNS DECAY & TRIAGE HEURISTICS
### 16.1 Problem
Stale "unknown" findings create noise; need deterministic decay and triage rules
### 16.2 Requirements
- Confidence decay card
- Triage queue UI
- Export artifacts for planning
### 16.3 Determinism
- Decay windows and thresholds must be deterministic
- Exports reproducible without live dependencies
### 16.4 Decay Logic
**Decay Windows**: Define time-based decay windows
**Thresholds**: Set confidence thresholds for promotion/demotion
**UI/Export Snapshot Expectations**: Deterministic decay logic description
## 17. UNKNOWNS RANKING ALGORITHM
### 17.1 Score Formula
```
Score = clamp01(
wP·P + # Popularity impact
wE·E + # Exploit consequence potential
wU·U + # Uncertainty density
wC·C + # Graph centrality
wS·S # Evidence staleness
)
```
### 17.2 Default Weights
```
wP = 0.25 (deployment impact)
wE = 0.25 (potential consequence)
wU = 0.25 (uncertainty density)
wC = 0.15 (graph centrality)
wS = 0.10 (evidence staleness)
```
### 17.3 Heuristics
```
P = min(1, log10(1 + deployments)/log10(1 + 100))
U = sum of flags, capped at 1.0:
+0.30 if no provenance anchor
+0.25 if version_range
+0.20 if conflicting_feeds
+0.15 if missing_vector
+0.10 if unreachable source advisory
S = min(1, age_days / 14)
```
### 17.4 Band Assignment
```
Score ≥ 0.70 → HOT (immediate rescan + VEX escalation)
0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h)
Score < 0.40 → COLD (weekly batch)
```
## 18. UNKNOWNS DATABASE SCHEMA
```sql
CREATE TABLE unknowns (
unknown_id uuid PRIMARY KEY,
pkg_id text,
pkg_version text,
digest_anchor bytea,
unknown_flags jsonb,
popularity_p float,
potential_e float,
uncertainty_u float,
centrality_c float,
staleness_s float,
score float,
band text CHECK(band IN ('HOT','WARM','COLD')),
graph_slice_hash bytea,
evidence_set_hash bytea,
normalization_trace jsonb,
callgraph_attempt_hash bytea,
created_at timestamptz,
updated_at timestamptz
);
CREATE TABLE deploy_refs (
pkg_id text,
image_id text,
env text,
first_seen timestamptz,
last_seen timestamptz
);
CREATE TABLE graph_metrics (
pkg_id text PRIMARY KEY,
degree_c float,
betweenness_c float,
last_calc_at timestamptz
);
```
## 19. TRIAGE QUEUE UI
### 19.1 Queue Views
- **HOT**: Red badge, sort by score desc
- **WARM**: Yellow badge, sort by score desc
- **COLD**: Gray badge, sort by age asc
### 19.2 Bulk Actions
- Mark as reviewed
- Escalate to HOT
- Suppress (with reason)
- Export selected
### 19.3 Filters
- By band
- By package
- By environment
- By date range
## 20. DECISION WORKFLOW CHECKLIST
For any triage decision:
- [ ] Evidence reviewed (reachability, call-stack, provenance, VEX)
- [ ] Decision status selected (A/N/U)
- [ ] Reason provided (preset or custom)
- [ ] Replay token generated
- [ ] Evidence hashes captured
- [ ] Audit event recorded
- [ ] Decision immutable (append-only)
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL 16, Angular v17

View File

@@ -0,0 +1,723 @@
# UX and Time-to-Evidence Technical Reference
**Source Advisories**:
- 01-Dec-2025 - Tracking UX Health with TimetoEvidence
- 12-Dec-2025 - Measure UX Efficiency Through TTFS
- 13-Dec-2025 - Define a north star metric for TTFS
- 14-Dec-2025 - Add a dedicated "first_signal" event
- 04-Dec-2025 - Designing Traceable Evidence in Security UX
- 05-Dec-2025 - Designing Triage UX That Stays Quiet on Purpose
- 30-Nov-2025 - UI Micro-Interactions for StellaOps
- 11-Dec-2025 - Stella DevOps UX Implementation Guide
**Last Updated**: 2025-12-14
---
## 1. PERFORMANCE TARGETS & SLOS
### 1.1 Time-to-Evidence (TTE)
**Definition**: `TTE = t_first_proof_rendered t_open_finding`
**Primary SLO**: P95 ≤ 15s (stretch: P99 ≤ 30s)
**Guardrail**: P50 < 3s
**By proof type**:
- Simple proof (SBOM row): P95 5s
- Complex proof (reachability graph): P95 15s
**Backend budget**: 12s backend + 3s UI/render margin = 15s P95
**Query performance**: O(log n) on indexed columns
### 1.2 Time-to-First-Signal (TTFS)
**Definition**: Time from user action/CI start first meaningful signal rendered/logged
**Primary SLO**: P50 < 2s, P95 < 5s (all surfaces: UI, CLI, CI)
**Warm path**: P50 < 700ms, P95 < 2500ms
**Cold path**: P95 4000ms
**Component budgets**:
- Frontend: 150ms (skeleton + last known state)
- Edge/API: 250ms (signal frame fast path from cache)
- Core services: 5001500ms (pre-indexed failures, warm summaries)
- Slow work: async (scan, lattice merge, provenance)
### 1.3 General UX Performance
- **Interaction response**: 100ms
- **Animation frame budget**: 16ms avg / 50ms P95
- **LCP placeholder**: shown immediately
- **Layout shift**: <0.05
- **Motion durations**: 80/140/200/260/320ms
- **Reduced-motion**: 0-80ms clamp
### 1.4 Cache Performance
- **Cache-hit response**: P95 250ms
- **Cold response**: P95 500ms
- **Endpoint error rate**: < 0.1% under expected concurrency
## 2. METRICS DEFINITIONS & FORMULAS
### 2.1 TTE Metrics
```typescript
// Core TTE calculation
tte_ms = proof_rendered.timestamp - finding_open.timestamp
// Dimensions
{
tenant: string,
finding_id: string,
proof_kind: 'sbom' | 'reachability' | 'vex',
source: 'local' | 'remote' | 'cache',
page: string
}
```
**SQL Rollup (hourly)**:
```sql
SELECT
proof_kind,
percentile_cont(0.95) WITHIN GROUP (ORDER BY tte_ms) AS p95_ms
FROM tte_events
WHERE ts >= now() - interval '1 hour'
GROUP BY proof_kind;
```
### 2.2 TTFS Metrics
```typescript
// Core TTFS calculation
ttfs_ms = signal_rendered.timestamp - start.timestamp
// Dimensions
{
surface: 'ui' | 'cli' | 'ci',
cache_hit: boolean,
signal_source: 'snapshot' | 'cold_start' | 'failure_index',
kind: string,
repo_size_bucket: string,
provider: string,
branch: string,
run_type: 'PR' | 'main',
network_state: string
}
```
### 2.3 Secondary Metrics
- **OpenAction time**: Time from opening run to first user action
- **Bounce rate**: Close page within 10s without interaction
- **MTTR proxy**: Time from failure to first rerun or fix commit
- **Signal availability rate**: % of run views showing first signal within 3s
- **Signal accuracy score**: Engineer confirms "helpful vs not" (sampled)
- **Extractor failure rate**: Parsing errors / missing mappings / timeouts
### 2.4 DORA Metrics
- **Deployment Frequency**: Deploys per day/week
- **Lead Time for Changes**: Commit deployment completion
- **Change Failure Rate**: Failed deployments / total deployments
- **Time to Restore**: Incident start resolution
### 2.5 Quality Metrics
- **Error budget burn**: Minutes over target per day
- **Top regressions**: Last 7 days vs prior 7
- **Extraction failure rate**: < 1% for sampled runs
## 3. EVENT SCHEMAS
### 3.1 TTE Events
**finding_open**:
```typescript
{
event: 'finding_open',
findingId: string,
tenantId: string,
userId: string,
userRole: 'admin' | 'dev' | 'triager',
entryPoint: 'list' | 'search' | 'notification' | 'deep_link',
uiVersion: string,
buildSha: string,
t: number // performance.now()
}
```
**proof_rendered**:
```typescript
{
event: 'proof_rendered',
findingId: string,
proofKind: 'sbom' | 'reachability' | 'vex' | 'logs' | 'other',
source: 'local_cache' | 'backend_api' | '3rd_party',
proofHeight: number, // pixel offset from top
t: number // performance.now()
}
```
### 3.2 TTFS Events
**ttfs_start**:
```typescript
{
event: 'ttfs_start',
runId: string,
surface: 'ui' | 'cli' | 'ci',
provider: string,
repo: string,
branch: string,
runType: 'PR' | 'main',
device: string,
release: string,
networkState: string,
t: number
}
```
**ttfs_signal_rendered**:
```typescript
{
event: 'ttfs_signal_rendered',
runId: string,
surface: 'ui' | 'cli' | 'ci',
cacheHit: boolean,
signalSource: 'snapshot' | 'cold_start' | 'failure_index',
kind: string,
t: number
}
```
### 3.3 FirstSignal Event Contract
```typescript
interface FirstSignal {
version: '1.0',
signalId: string,
jobId: string,
timestamp: string, // ISO-8601
kind: 'queued' | 'started' | 'phase' | 'blocked' | 'failed' | 'succeeded' | 'canceled' | 'unavailable',
phase: 'resolve' | 'fetch' | 'restore' | 'analyze' | 'policy' | 'report' | 'unknown',
scope: {
type: 'repo' | 'image' | 'artifact',
id: string
},
summary: string,
etaSeconds?: number,
lastKnownOutcome?: {
signatureId: string,
errorCode: string,
token: string,
excerpt: string,
confidence: 'low' | 'medium' | 'high',
firstSeenAt: string,
hitCount: number
},
nextActions?: Array<{
type: 'open_logs' | 'open_job' | 'docs' | 'retry' | 'cli_command',
label: string,
target: string
}>,
diagnostics: {
cacheHit: boolean,
source: 'snapshot' | 'failure_index' | 'cold_start',
correlationId: string
}
}
```
### 3.4 UI Telemetry Schema
**ui.micro.* events**:
```typescript
{
version: string,
tenant: string,
surface: string,
component: string,
action: string,
latency_ms: number,
outcome: string,
reduced_motion: boolean,
offline_mode: boolean,
error_code?: string
}
```
*Schema location*: `docs/modules/ui/telemetry/ui-micro.schema.json`
## 4. API CONTRACTS
### 4.1 First Signal Endpoint
**GET** `/api/runs/{runId}/first-signal`
**Headers**:
- `If-None-Match: W/"..."` (supported)
**Response**:
```json
{
"runId": "123",
"firstSignal": {
"type": "stage_failed",
"stage": "build",
"step": "dotnet restore",
"message": "401 Unauthorized: token expired",
"at": "2025-12-11T09:22:31Z",
"artifact": {
"kind": "log",
"range": { "start": 1880, "end": 1896 }
}
},
"summaryEtag": "W/\"a1b2c3\""
}
```
**Status codes**:
- `200`: Full first signal object
- `304`: Not modified
- `404`: Run not found
- `204`: Run exists but signal not available yet
**Response headers**:
- `ETag`
- `Cache-Control`
- `X-Correlation-Id`
- `Cache-Status: hit|miss|bypass`
### 4.2 Summary Endpoint
**GET** `/api/runs/{runId}/summary`
Returns: Status, first failing stage/job, timestamps, blocking policies, artifact counts
### 4.3 SSE Events Endpoint
**GET** `/api/runs/{runId}/events` (Server-Sent Events)
**Event payloads**:
- `status` (kind+phase+message)
- `hint` (token+errorCode+confidence)
- `policy` (blocked + policyId)
- `complete` (terminal)
## 5. FRONTEND PATTERNS & COMPONENT SPECIFICATIONS
### 5.1 UI Contract (Evidence First)
**Above the fold**:
- Always show compact **Proof panel** first (not hidden behind tabs)
- **Skeletons over spinners**: Reserve space; render partial proof as ready
- **Plain text copy affordance**: "Copy SBOM line / path" button next to proof
- **Defer non-proof widgets**: CVSS badges, remediation prose, charts load *after* proof
- **Empty-state truth**: "No proof available yet" + loader for *that* proof type only
### 5.2 Progressive Rendering Pattern
**Immediate render**:
1. Title, status badge, pipeline metadata (run id, commit, branch)
2. Skeleton for details area
**First signal fetch**:
3. Render `FirstSignalCard` immediately when available
4. Fire telemetry event when card is in DOM and visible
**Lazy-load**:
5. Stage graph
6. Full logs viewer
7. Artifacts list
8. Security findings
9. Trends, flaky tests, etc.
### 5.3 Component Specifications
#### FirstSignalCard Component
- Standalone, minimal dependencies
- Shows: summary + at least one next action button (Open job/logs)
- Updates in-place on deltas from SSE
- Falls back to polling when SSE fails
#### EvidencePanel Component
```typescript
interface EvidencePanel {
tabs: ['SBOM', 'Reachability', 'VEX', 'Logs', 'Other'],
firstProofType: ProofKind,
copyEnabled: boolean,
emptyStateMessage?: string
}
```
#### ProofSpine Component
- Displays: hashes, Rekor link
- Verification status: `Verified` | `Unverified` | `Failed verification` | `Expired/Outdated`
- "Verify locally" copy button with exact commands
### 5.4 Prefetch Strategy
From runs list view:
- Use `IntersectionObserver` to prefetch summaries/first signals for items in viewport
- Store results in in-memory cache (`Map<runId, FirstSignal>`)
- Respect ETag to avoid redundant payloads
## 6. TELEMETRY REQUIREMENTS
### 6.1 Client-Side Telemetry
**Frontend events**:
```typescript
// On route enter
metrics.emit('finding_open', { findingId, t: performance.now() });
// When first proof node/line hits DOM
metrics.emit('proof_rendered', { findingId, proofKind, t: performance.now() });
```
**Sampling**:
- **Staging**: 100%
- **Production**: 25% of sessions (ideal: 100%)
**Clock handling**:
- Use `performance.now()` for TTE (monotonic within tab)
- Don't mix backend clocks into TTE calculation
### 6.2 Backend Telemetry
**Endpoint metrics**:
- `signal_endpoint_latency_ms`
- `signal_payload_bytes`
- `signal_error_rate`
**Server-side timing logs** (debug-level):
- Cache read time
- DB read time
- Cold path time
**Tracing**:
- Correlation ID propagated in:
- API response header
- Worker logs
- Events
### 6.3 Dashboard Requirements
**Core widgets**:
1. TTE distribution (P50/P90/P95/P99) per day, split by proof_kind
2. TTE by page/surface (listdetail, deep links, bookmarks)
3. TTE by user segment (new vs power users, roles)
4. Error budget: "Minutes over SLO per day"
5. Correlation: TTE vs session length, TTE vs "clicked ignore/snooze"
**Operational panels**:
- Update granularity: Real-time or 15 min
- Retention: 90 days
- Breakdowns: backend_region, build_version
**TTFS dashboards**:
- By surface (ui/cli/ci)
- Cache hit rate
- Endpoint latency percentiles
- Repo size bucket
- Kind/phase
**Alerts**:
- Page when `p95(ttfs_ms) > 5000` for 5 mins
- Page when `signal_endpoint_error_rate > 1%`
- Alert when **P95 TTE > 15s** for 15 minutes
## 7. DATABASE SCHEMAS
### 7.1 TTE Events Table
```sql
CREATE TABLE tte_events (
id SERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
tenant TEXT NOT NULL,
finding_id TEXT NOT NULL,
proof_kind TEXT NOT NULL,
source TEXT NOT NULL,
tte_ms INT NOT NULL,
page TEXT,
user_role TEXT
);
CREATE INDEX ON tte_events (ts DESC);
CREATE INDEX ON tte_events (proof_kind, ts DESC);
```
### 7.2 First Signal Snapshots
```sql
CREATE TABLE first_signal_snapshots (
job_id TEXT PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
kind TEXT NOT NULL,
phase TEXT NOT NULL,
summary TEXT NOT NULL,
eta_seconds INT NULL,
payload_json JSONB NOT NULL
);
CREATE INDEX ON first_signal_snapshots (updated_at DESC);
```
### 7.3 Failure Signatures
```sql
CREATE TABLE failure_signatures (
signature_id TEXT PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
scope_type TEXT NOT NULL,
scope_id TEXT NOT NULL,
toolchain_hash TEXT NOT NULL,
error_code TEXT NULL,
token TEXT NOT NULL,
excerpt TEXT NULL,
confidence TEXT NOT NULL,
first_seen_at TIMESTAMPTZ NOT NULL,
last_seen_at TIMESTAMPTZ NOT NULL,
hit_count INT NOT NULL DEFAULT 1
);
CREATE INDEX ON failure_signatures (scope_type, scope_id, toolchain_hash);
CREATE INDEX ON failure_signatures (token);
```
## 8. MOTION & ANIMATION TOKENS
### 8.1 Duration Tokens
| Token | Value | Use Case |
|-------|-------|----------|
| `duration-xs` | 80ms | Quick hover, focus |
| `duration-sm` | 140ms | Button press, small transitions |
| `duration-md` | 200ms | Modal open/close, panel slide |
| `duration-lg` | 260ms | Page transitions |
| `duration-xl` | 320ms | Complex animations |
**Reduced-motion override**: Clamp all to 0-80ms
### 8.2 Easing Tokens
- `standard`: Default transition
- `decel`: Element entering (start fast, slow down)
- `accel`: Element exiting (slow start, speed up)
- `emphasized`: Important state changes
### 8.3 Distance Scales
- `XS`: 4px
- `SM`: 8px
- `MD`: 16px
- `LG`: 24px
- `XL`: 32px
**Location**: `src/Web/StellaOps.Web/src/styles/tokens/motion.{ts,scss}`
## 9. ACCESSIBILITY REQUIREMENTS
### 9.1 WCAG 2.1 AA Compliance
- Focus order: logical and consistent
- Keyboard: all interactive elements accessible
- Contrast:
- Text: 4.5:1
- UI elements: 3:1
- Reduced motion: honored via `prefers-reduced-motion`
- Status messaging: `aria-live=polite` for updates
### 9.2 Reduced-Motion Rules
When `prefers-reduced-motion: reduce`:
- Durations clamp to 0-80ms
- Disable parallax/auto-animations
- Focus/hover states remain visible
- No animated GIF/Lottie autoplay
### 9.3 Screen Reader Support
- **Undo window**: 8s with keyboard focus and `aria-live=polite`
- **Loading states**: Announce state changes
- **Error messages**: Informative, not generic
## 10. EVIDENCE & PROOF SPECIFICATIONS
### 10.1 Evidence Bundle Minimum Requirements
**Component presence**:
- SBOM fragment (SPDX/CycloneDX) with component identity and provenance
- Signed attestation for SBOM artifact
**Vulnerability match**:
- Matching rule details (CPE/purl/range) + scanner identity/version
- Signed vulnerability report attestation
**Reachable vulnerability**:
- Call path: entrypoint frames vulnerable symbol
- Hash/digest of call graph slice (tamper-evident)
- Tool info + limitations (reflection/dynamic dispatch uncertainty)
**Not affected via VEX**:
- VEX statement (OpenVEX/CSAF) + signer
- Justification for `not_affected`
- Align to CISA minimum requirements
**Gate decision**:
- Input digests (SBOM digest, scan attestation digests, VEX doc digests)
- Policy version + rule ID
- Deterministic **decision hash** over (policy + input digests)
### 10.2 Evidence Object Structure
```typescript
interface Evidence {
sbom_snippet_attestation: DSSEEnvelope,
reachability_proof: {
entrypoint: string,
frames: CallFrame[],
file_hashes: string[],
graph_digest: string
},
attestation_chain: DSSESummary[],
transparency_receipt: {
logIndex: number,
uuid: string,
inclusionProof: string,
checkpoint: string
}
}
```
### 10.3 Proof Panel Requirements
**Four artifacts**:
1. **SBOM snippet (signed)**: DSSE attestation, verify with cosign
2. **Call-stack slice**: Entrypoint vulnerable symbol, status pill (`Reachable`, `Potentially reachable`, `Unreachable`)
3. **Attestation chain**: DSSE envelope summary, verification status, "Verify locally" command
4. **Transparency receipt**: Rekor inclusion proof, "Verify inclusion" command
**One-click export**:
- "Export Evidence (.tar.gz)" bundling: SBOM slice, call-stack JSON, DSSE attestation, Rekor proof JSON
## 11. CONFIGURATION & FEATURE FLAGS
### 11.1 TTFS Feature Flags
```yaml
ttfs:
first_signal_enabled: true # Default ON in staging
cache_enabled: true
failure_index_enabled: true
sse_enabled: true
policy_preeval_enabled: true
```
### 11.2 Cache Configuration
```yaml
cache:
backend: valkey | postgres | none # TTFS_CACHE_BACKEND
ttl_seconds: 86400 # TTFS_CACHE_TTL_SECONDS
key_pattern: "signal:job:{jobId}"
```
### 11.3 Air-Gapped Profile
- Skip Valkey; use Postgres-only
- Use `first_signal_snapshots` table
- NOTIFY/LISTEN for streaming updates
## 12. TESTING REQUIREMENTS
### 12.1 Acceptance Criteria (TTE)
- [ ] First paint shows real proof snippet (not summary)
- [ ] "Copy proof" button works within 1 click
- [ ] TTE P95 in staging 10s; in prod 15s
- [ ] If proof missing, explicit empty-state + retry path
- [ ] Telemetry sampled 50% of sessions (or 100% for internal)
### 12.2 Acceptance Tests (TTFS)
- Run with early fail first signal < 1s, shows exact command + exit code
- Run with policy gate fail rule name + fix hint visible first
- Offline/slow network cached summary still renders actionable hint
### 12.3 Determinism Requirements
- Freeze timers to `2025-12-04T12:00:00Z` in stories/e2e
- Seed RNG with `0x5EED2025` unless scenario-specific
- All fixtures stored under `tests/fixtures/micro/`
- No network calls; offline assets bundled
- Playwright runs with `--disable-animations` and reduced-motion emulation
### 12.4 Load Tests
`/jobs/{id}/signal`:
- Cache-hit P95 250ms
- Cold path P95 500ms
- Error rate < 0.1% under expected concurrency
## 13. REDACTION & SECURITY
### 13.1 Excerpt Redaction Rules
- Strip: bearer tokens, API keys, access tokens, private URLs
- Cap excerpt length: 240 chars
- Normalize whitespace
- Never include excerpts in telemetry attributes
### 13.2 Tenant Isolation
Cache keys include tenant boundary:
```
tenant:{tenantId}:signal:job:{jobId}
```
Failure signatures looked up within same tenant only.
### 13.3 Secret Scanning
Runtime guardrails:
- If excerpt contains forbidden patterns replace with "[redacted]"
- Security review sign-off required for snapshot + signature + telemetry
## 14. LOCALIZATION
### 14.1 Micro-Copy Requirements
- Keys and ICU messages for micro-interaction copy
- Defaults: EN
- Fallbacks present
- No hard-coded strings in components
- i18n extraction shows zero TODO keys
### 14.2 Snapshot Verification
Verify translated skeleton/error/undo copy in snapshots.
## 15. DELIVERABLES MAP
| Category | Location | Description |
|----------|----------|-------------|
| Motion tokens | `src/Web/StellaOps.Web/src/styles/tokens/motion.{ts,scss}` | Duration, easing, distance scales + reduced-motion overrides |
| Storybook stories | `apps/storybook/src/stories/micro/*` | Slow, error, offline, reduced-motion, undo flows |
| Playwright suite | `tests/e2e/micro-interactions.spec.ts` | MI2/MI3/MI4/MI8 coverage |
| Telemetry schema | `docs/modules/ui/telemetry/ui-micro.schema.json` | Event schema + validators |
| Component map | `docs/modules/ui/micro-interactions-map.md` | Components interaction type token usage |
| Fixtures | `tests/fixtures/micro/` | Deterministic test fixtures |
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL 16, Angular v17