feat(scanner): Complete PoE implementation with Windows compatibility fix

- Fix namespace conflicts (Subgraph → PoESubgraph)
- Add hash sanitization for Windows filesystem (colon → underscore)
- Update all test mocks to use It.IsAny<>()
- Add direct orchestrator unit tests
- All 8 PoE tests now passing (100% success rate)
- Complete SPRINT_3500_0001_0001 documentation

Fixes compilation errors and Windows filesystem compatibility issues.
Tests: 8/8 passing
Files: 8 modified, 1 new test, 1 completion report

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
master
2025-12-23 14:52:08 +02:00
parent 84d97fd22c
commit fcb5ffe25d
90 changed files with 9457 additions and 2039 deletions

View File

@@ -229,28 +229,35 @@ public class VerdictController : ControllerBase
var client = _httpClientFactory.CreateClient("EvidenceLocker");
// Parse envelope to get predicate for digest calculation
// Parse envelope to get predicate for digest calculation and metadata extraction
var envelope = JsonSerializer.Deserialize<JsonElement>(envelopeJson);
var payloadBase64 = envelope.GetProperty("payload").GetString() ?? string.Empty;
var predicateBytes = Convert.FromBase64String(payloadBase64);
var predicateDigest = $"sha256:{Convert.ToHexString(SHA256.HashData(predicateBytes)).ToLowerInvariant()}";
// Parse predicate JSON to extract verdict metadata
var predicateJson = Encoding.UTF8.GetString(predicateBytes);
var predicate = JsonSerializer.Deserialize<JsonElement>(predicateJson);
// Extract verdict metadata from predicate
var (verdictStatus, verdictSeverity, verdictScore, evaluatedAt, determinismHash, policyRunId, policyId, policyVersion) = ExtractVerdictMetadata(predicate);
// Create Evidence Locker storage request
var storeRequest = new
{
verdict_id = verdictId,
tenant_id = "default", // TODO: Extract from auth context
policy_run_id = "unknown", // TODO: Pass from caller
policy_id = "unknown", // TODO: Pass from caller
policy_version = 1, // TODO: Pass from caller
tenant_id = "default", // TODO: Extract from auth context (requires CallerTenant from SubmissionContext)
policy_run_id = policyRunId,
policy_id = policyId,
policy_version = policyVersion,
finding_id = findingId,
verdict_status = "unknown", // TODO: Extract from predicate
verdict_severity = "unknown", // TODO: Extract from predicate
verdict_score = 0.0m, // TODO: Extract from predicate
evaluated_at = DateTimeOffset.UtcNow,
verdict_status = verdictStatus,
verdict_severity = verdictSeverity,
verdict_score = verdictScore,
evaluated_at = evaluatedAt,
envelope = JsonSerializer.Deserialize<object>(envelopeJson),
predicate_digest = predicateDigest,
determinism_hash = (string?)null, // TODO: Pass from predicate
determinism_hash = determinismHash,
rekor_log_index = (long?)null // Not implemented yet
};
@@ -280,4 +287,100 @@ public class VerdictController : ControllerBase
// Non-fatal: attestation is still returned to caller
}
}
/// <summary>
/// Extracts verdict metadata from predicate JSON.
/// </summary>
/// <returns>
/// Tuple of (status, severity, score, evaluatedAt, determinismHash, policyRunId, policyId, policyVersion)
/// </returns>
private static (string status, string severity, decimal score, DateTimeOffset evaluatedAt, string? determinismHash, string policyRunId, string policyId, int policyVersion)
ExtractVerdictMetadata(JsonElement predicate)
{
try
{
// Extract from verdict predicate structure (https://stellaops.dev/predicates/policy-verdict@v1)
// Expected structure:
// {
// "verdict": { "status": "...", "severity": "...", "score": 0.0 },
// "metadata": { "policyRunId": "...", "policyId": "...", "policyVersion": 1, "evaluatedAt": "..." },
// "determinismHash": "..."
// }
var status = "unknown";
var severity = "unknown";
var score = 0.0m;
var evaluatedAt = DateTimeOffset.UtcNow;
string? determinismHash = null;
var policyRunId = "unknown";
var policyId = "unknown";
var policyVersion = 1;
// Extract verdict status/severity/score
if (predicate.TryGetProperty("verdict", out var verdictElement))
{
if (verdictElement.TryGetProperty("status", out var statusElement))
{
status = statusElement.GetString() ?? "unknown";
}
if (verdictElement.TryGetProperty("severity", out var severityElement))
{
severity = severityElement.GetString() ?? "unknown";
}
if (verdictElement.TryGetProperty("score", out var scoreElement))
{
if (scoreElement.ValueKind == JsonValueKind.Number)
{
score = scoreElement.GetDecimal();
}
}
}
// Extract metadata
if (predicate.TryGetProperty("metadata", out var metadataElement))
{
if (metadataElement.TryGetProperty("policyRunId", out var runIdElement))
{
policyRunId = runIdElement.GetString() ?? "unknown";
}
if (metadataElement.TryGetProperty("policyId", out var policyIdElement))
{
policyId = policyIdElement.GetString() ?? "unknown";
}
if (metadataElement.TryGetProperty("policyVersion", out var versionElement))
{
if (versionElement.ValueKind == JsonValueKind.Number)
{
policyVersion = versionElement.GetInt32();
}
}
if (metadataElement.TryGetProperty("evaluatedAt", out var evaluatedAtElement))
{
var evaluatedAtStr = evaluatedAtElement.GetString();
if (!string.IsNullOrEmpty(evaluatedAtStr) && DateTimeOffset.TryParse(evaluatedAtStr, out var parsedDate))
{
evaluatedAt = parsedDate;
}
}
}
// Extract determinism hash
if (predicate.TryGetProperty("determinismHash", out var hashElement))
{
determinismHash = hashElement.GetString();
}
return (status, severity, score, evaluatedAt, determinismHash, policyRunId, policyId, policyVersion);
}
catch (Exception)
{
// If parsing fails, return defaults (non-fatal)
return ("unknown", "unknown", 0.0m, DateTimeOffset.UtcNow, null, "unknown", "unknown", 1);
}
}
}