consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS vexlens.consensus_projections (
|
||||
tenant_id TEXT,
|
||||
|
||||
-- Consensus result
|
||||
status TEXT NOT NULL CHECK (status IN ('not_affected', 'affected', 'fixed', 'under_investigation')),
|
||||
status TEXT NOT NULL CHECK (status IN ('not_affected', 'affected', 'fixed', 'under_investigation', 'unknown')),
|
||||
justification TEXT CHECK (justification IS NULL OR justification IN (
|
||||
'component_not_present',
|
||||
'vulnerable_code_not_present',
|
||||
|
||||
@@ -1,292 +1,580 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens", "StellaOps.VexLens", "{4D88C818-3BB3-BC3B-D3D1-F21617D09654}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Persistence", "StellaOps.VexLens.Persistence", "{A08207CC-507B-A415-392F-6FF2DB6342CA}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Core", "StellaOps.VexLens.Core", "{B0727DBA-83FD-619F-5D33-3F4652753F2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Tests", "__Tests", "{CC393719-C997-B814-42B3-0BA762B74BF7}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Core.Tests", "StellaOps.VexLens.Core.Tests", "{C314BF02-26DE-60BD-BAA5-AA4C52869724}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__External", "__External", "{5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aoc", "Aoc", "{03DFF14F-7321-1784-D4C7-4E99D4120F48}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{BDD326D6-7616-84F0-B914-74743BFBA520}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Aoc", "StellaOps.Aoc", "{EC506DBE-AB6D-492E-786E-8B176021BF2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Attestor", "Attestor", "{5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor", "StellaOps.Attestor", "{33B1AE27-692A-1778-48C1-CCEC2B9BC78F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Envelope", "StellaOps.Attestor.Envelope", "{018E0E11-1CCE-A2BE-641D-21EE14D2E90D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Core", "StellaOps.Attestor.Core", "{5F27FB4E-CF09-3A6B-F5B4-BF5A709FA609}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{AB67BDB9-D701-3AC9-9CDF-ECCDCCD8DB6D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.ProofChain", "StellaOps.Attestor.ProofChain", "{45F7FA87-7451-6970-7F6E-F8BAE45E081B}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Concelier", "Concelier", "{157C3671-CA0B-69FA-A7C9-74A1FDA97B99}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{F39E09D6-BF93-B64A-CFE7-2BA92815C0FE}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Concelier.RawModels", "StellaOps.Concelier.RawModels", "{1DCF4EBB-DBC4-752C-13D4-D1EECE4E8907}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Concelier.SourceIntel", "StellaOps.Concelier.SourceIntel", "{F2B58F4E-6F28-A25F-5BFB-CDEBAD6B9A3E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Excititor", "Excititor", "{7D49FA52-6EA1-EAC8-4C5A-AC07188D6C57}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{C9CF27FC-12DB-954F-863C-576BA8E309A5}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Excititor.Core", "StellaOps.Excititor.Core", "{6DCAF6F3-717F-27A9-D96C-F2BFA5550347}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Feedser", "Feedser", "{C4A90603-BE42-0044-CAB4-3EB910AD51A5}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.BinaryAnalysis", "StellaOps.Feedser.BinaryAnalysis", "{054761F9-16D3-B2F8-6F4D-EFC2248805CD}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.Core", "StellaOps.Feedser.Core", "{B54CE64C-4167-1DD1-B7D6-2FD7A5AEF715}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Policy", "Policy", "{8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy.RiskProfile", "StellaOps.Policy.RiskProfile", "{BC12ED55-6015-7C8B-8384-B39CE93C76D6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{FF70543D-AFF9-1D38-4950-4F8EE18D60BB}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy", "StellaOps.Policy", "{831265B0-8896-9C95-3488-E12FD9F6DC53}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Provenance", "Provenance", "{316BBD0A-04D2-85C9-52EA-7993CC6C8930}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Provenance.Attestation", "StellaOps.Provenance.Attestation", "{9D6AB85A-85EA-D85A-5566-A121D34016E6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signer", "Signer", "{3247EE0D-B3E9-9C11-B0AE-FE719410390B}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer", "StellaOps.Signer", "{CD7C09DA-FEC8-2CC5-D00C-E525638DFF4A}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer.Core", "StellaOps.Signer.Core", "{79B10804-91E9-972E-1913-EE0F0B11663E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Canonical.Json", "StellaOps.Canonical.Json", "{79E122F4-2325-3E92-438E-5825A307B594}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography", "StellaOps.Cryptography", "{66557252-B5C4-664B-D807-07018C627474}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Kms", "StellaOps.Cryptography.Kms", "{5AC9EE40-1881-5F8A-46A2-2C303950D3C8}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Infrastructure.Postgres", "StellaOps.Infrastructure.Postgres", "{61B23570-4F2D-B060-BE1F-37995682E494}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Ingestion.Telemetry", "StellaOps.Ingestion.Telemetry", "{1182764D-2143-EEF0-9270-3DCE392F5D06}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Aoc", "..\\Aoc\__Libraries\StellaOps.Aoc\StellaOps.Aoc.csproj", "{776E2142-804F-03B9-C804-D061D64C6092}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Core", "..\\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj", "{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Envelope", "..\\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj", "{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.ProofChain", "..\\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj", "{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Canonical.Json", "..\\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj", "{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.RawModels", "..\\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj", "{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.SourceIntel", "..\\Concelier\__Libraries\StellaOps.Concelier.SourceIntel\StellaOps.Concelier.SourceIntel.csproj", "{EB093C48-CDAC-106B-1196-AE34809B34C0}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography", "..\\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj", "{F664A948-E352-5808-E780-77A03F19E93E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Kms", "..\\__Libraries\StellaOps.Cryptography.Kms\StellaOps.Cryptography.Kms.csproj", "{F3A27846-6DE0-3448-222C-25A273E86B2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Excititor.Core", "..\\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj", "{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.BinaryAnalysis", "..\\Feedser\StellaOps.Feedser.BinaryAnalysis\StellaOps.Feedser.BinaryAnalysis.csproj", "{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core", "..\\Feedser\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj", "{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Infrastructure.Postgres", "..\\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj", "{8C594D82-3463-3367-4F06-900AC707753D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Ingestion.Telemetry", "..\\__Libraries\StellaOps.Ingestion.Telemetry\StellaOps.Ingestion.Telemetry.csproj", "{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy", "..\\Policy\__Libraries\StellaOps.Policy\StellaOps.Policy.csproj", "{19868E2D-7163-2108-1094-F13887C4F070}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy.RiskProfile", "..\\Policy\StellaOps.Policy.RiskProfile\StellaOps.Policy.RiskProfile.csproj", "{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Provenance.Attestation", "..\\Provenance\StellaOps.Provenance.Attestation\StellaOps.Provenance.Attestation.csproj", "{A78EBC0F-C62C-8F56-95C0-330E376242A2}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Signer.Core", "..\\Signer\StellaOps.Signer\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj", "{0AF13355-173C-3128-5AFC-D32E540DA3EF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens", "StellaOps.VexLens\StellaOps.VexLens.csproj", "{33565FF8-EBD5-53F8-B786-95111ACDF65F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Core", "StellaOps.VexLens\StellaOps.VexLens.Core\StellaOps.VexLens.Core.csproj", "{12F72803-F28C-8F72-1BA0-3911231DD8AF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Core.Tests", "StellaOps.VexLens\__Tests\StellaOps.VexLens.Core.Tests\StellaOps.VexLens.Core.Tests.csproj", "{3A4678E5-957B-1E59-9A19-50C8A60F53DF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Persistence", "StellaOps.VexLens.Persistence\StellaOps.VexLens.Persistence.csproj", "{0F9CBD78-C279-951B-A38F-A0AA57B62517}"
|
||||
|
||||
EndProject
|
||||
|
||||
Global
|
||||
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
Release|Any CPU = Release|Any CPU
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens", "StellaOps.VexLens", "{4D88C818-3BB3-BC3B-D3D1-F21617D09654}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Persistence", "StellaOps.VexLens.Persistence", "{A08207CC-507B-A415-392F-6FF2DB6342CA}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Core", "StellaOps.VexLens.Core", "{B0727DBA-83FD-619F-5D33-3F4652753F2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Tests", "__Tests", "{CC393719-C997-B814-42B3-0BA762B74BF7}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VexLens.Core.Tests", "StellaOps.VexLens.Core.Tests", "{C314BF02-26DE-60BD-BAA5-AA4C52869724}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__External", "__External", "{5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aoc", "Aoc", "{03DFF14F-7321-1784-D4C7-4E99D4120F48}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{BDD326D6-7616-84F0-B914-74743BFBA520}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Aoc", "StellaOps.Aoc", "{EC506DBE-AB6D-492E-786E-8B176021BF2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Attestor", "Attestor", "{5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor", "StellaOps.Attestor", "{33B1AE27-692A-1778-48C1-CCEC2B9BC78F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Envelope", "StellaOps.Attestor.Envelope", "{018E0E11-1CCE-A2BE-641D-21EE14D2E90D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Core", "StellaOps.Attestor.Core", "{5F27FB4E-CF09-3A6B-F5B4-BF5A709FA609}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{AB67BDB9-D701-3AC9-9CDF-ECCDCCD8DB6D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.ProofChain", "StellaOps.Attestor.ProofChain", "{45F7FA87-7451-6970-7F6E-F8BAE45E081B}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Concelier", "Concelier", "{157C3671-CA0B-69FA-A7C9-74A1FDA97B99}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{F39E09D6-BF93-B64A-CFE7-2BA92815C0FE}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Concelier.RawModels", "StellaOps.Concelier.RawModels", "{1DCF4EBB-DBC4-752C-13D4-D1EECE4E8907}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Concelier.SourceIntel", "StellaOps.Concelier.SourceIntel", "{F2B58F4E-6F28-A25F-5BFB-CDEBAD6B9A3E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Excititor", "Excititor", "{7D49FA52-6EA1-EAC8-4C5A-AC07188D6C57}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{C9CF27FC-12DB-954F-863C-576BA8E309A5}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Excititor.Core", "StellaOps.Excititor.Core", "{6DCAF6F3-717F-27A9-D96C-F2BFA5550347}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Feedser", "Feedser", "{C4A90603-BE42-0044-CAB4-3EB910AD51A5}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.BinaryAnalysis", "StellaOps.Feedser.BinaryAnalysis", "{054761F9-16D3-B2F8-6F4D-EFC2248805CD}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.Core", "StellaOps.Feedser.Core", "{B54CE64C-4167-1DD1-B7D6-2FD7A5AEF715}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Policy", "Policy", "{8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy.RiskProfile", "StellaOps.Policy.RiskProfile", "{BC12ED55-6015-7C8B-8384-B39CE93C76D6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{FF70543D-AFF9-1D38-4950-4F8EE18D60BB}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy", "StellaOps.Policy", "{831265B0-8896-9C95-3488-E12FD9F6DC53}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Provenance", "Provenance", "{316BBD0A-04D2-85C9-52EA-7993CC6C8930}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Provenance.Attestation", "StellaOps.Provenance.Attestation", "{9D6AB85A-85EA-D85A-5566-A121D34016E6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signer", "Signer", "{3247EE0D-B3E9-9C11-B0AE-FE719410390B}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer", "StellaOps.Signer", "{CD7C09DA-FEC8-2CC5-D00C-E525638DFF4A}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer.Core", "StellaOps.Signer.Core", "{79B10804-91E9-972E-1913-EE0F0B11663E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Canonical.Json", "StellaOps.Canonical.Json", "{79E122F4-2325-3E92-438E-5825A307B594}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography", "StellaOps.Cryptography", "{66557252-B5C4-664B-D807-07018C627474}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Kms", "StellaOps.Cryptography.Kms", "{5AC9EE40-1881-5F8A-46A2-2C303950D3C8}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Infrastructure.Postgres", "StellaOps.Infrastructure.Postgres", "{61B23570-4F2D-B060-BE1F-37995682E494}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Ingestion.Telemetry", "StellaOps.Ingestion.Telemetry", "{1182764D-2143-EEF0-9270-3DCE392F5D06}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Aoc", "..\\Aoc\__Libraries\StellaOps.Aoc\StellaOps.Aoc.csproj", "{776E2142-804F-03B9-C804-D061D64C6092}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Core", "..\\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj", "{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Envelope", "..\\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj", "{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.ProofChain", "..\\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj", "{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Canonical.Json", "..\\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj", "{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.RawModels", "..\\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj", "{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.SourceIntel", "..\\Concelier\__Libraries\StellaOps.Concelier.SourceIntel\StellaOps.Concelier.SourceIntel.csproj", "{EB093C48-CDAC-106B-1196-AE34809B34C0}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography", "..\\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj", "{F664A948-E352-5808-E780-77A03F19E93E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Kms", "..\\__Libraries\StellaOps.Cryptography.Kms\StellaOps.Cryptography.Kms.csproj", "{F3A27846-6DE0-3448-222C-25A273E86B2E}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Excititor.Core", "..\\Concelier\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj", "{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.BinaryAnalysis", "..\\Concelier\StellaOps.Feedser.BinaryAnalysis\StellaOps.Feedser.BinaryAnalysis.csproj", "{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core", "..\\Concelier\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj", "{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Infrastructure.Postgres", "..\\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj", "{8C594D82-3463-3367-4F06-900AC707753D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Ingestion.Telemetry", "..\\__Libraries\StellaOps.Ingestion.Telemetry\StellaOps.Ingestion.Telemetry.csproj", "{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy", "..\\Policy\__Libraries\StellaOps.Policy\StellaOps.Policy.csproj", "{19868E2D-7163-2108-1094-F13887C4F070}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy.RiskProfile", "..\\Policy\StellaOps.Policy.RiskProfile\StellaOps.Policy.RiskProfile.csproj", "{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Provenance.Attestation", "..\\Attestor\StellaOps.Provenance.Attestation\StellaOps.Provenance.Attestation.csproj", "{A78EBC0F-C62C-8F56-95C0-330E376242A2}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Signer.Core", "..\\Attestor\StellaOps.Signer\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj", "{0AF13355-173C-3128-5AFC-D32E540DA3EF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens", "StellaOps.VexLens\StellaOps.VexLens.csproj", "{33565FF8-EBD5-53F8-B786-95111ACDF65F}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Core", "StellaOps.VexLens\StellaOps.VexLens.Core\StellaOps.VexLens.Core.csproj", "{12F72803-F28C-8F72-1BA0-3911231DD8AF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Core.Tests", "StellaOps.VexLens\__Tests\StellaOps.VexLens.Core.Tests\StellaOps.VexLens.Core.Tests.csproj", "{3A4678E5-957B-1E59-9A19-50C8A60F53DF}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VexLens.Persistence", "StellaOps.VexLens.Persistence\StellaOps.VexLens.Persistence.csproj", "{0F9CBD78-C279-951B-A38F-A0AA57B62517}"
|
||||
|
||||
EndProject
|
||||
|
||||
Global
|
||||
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
Release|Any CPU = Release|Any CPU
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{EB093C48-CDAC-106B-1196-AE34809B34C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{EB093C48-CDAC-106B-1196-AE34809B34C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{EB093C48-CDAC-106B-1196-AE34809B34C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{EB093C48-CDAC-106B-1196-AE34809B34C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{F664A948-E352-5808-E780-77A03F19E93E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{F664A948-E352-5808-E780-77A03F19E93E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{F664A948-E352-5808-E780-77A03F19E93E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{F664A948-E352-5808-E780-77A03F19E93E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{F3A27846-6DE0-3448-222C-25A273E86B2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{F3A27846-6DE0-3448-222C-25A273E86B2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{F3A27846-6DE0-3448-222C-25A273E86B2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{F3A27846-6DE0-3448-222C-25A273E86B2E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{8C594D82-3463-3367-4F06-900AC707753D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{8C594D82-3463-3367-4F06-900AC707753D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{8C594D82-3463-3367-4F06-900AC707753D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{8C594D82-3463-3367-4F06-900AC707753D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{19868E2D-7163-2108-1094-F13887C4F070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{19868E2D-7163-2108-1094-F13887C4F070}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{19868E2D-7163-2108-1094-F13887C4F070}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{19868E2D-7163-2108-1094-F13887C4F070}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{A78EBC0F-C62C-8F56-95C0-330E376242A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{A78EBC0F-C62C-8F56-95C0-330E376242A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{A78EBC0F-C62C-8F56-95C0-330E376242A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{A78EBC0F-C62C-8F56-95C0-330E376242A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{0AF13355-173C-3128-5AFC-D32E540DA3EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{0AF13355-173C-3128-5AFC-D32E540DA3EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{0AF13355-173C-3128-5AFC-D32E540DA3EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{0AF13355-173C-3128-5AFC-D32E540DA3EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{33565FF8-EBD5-53F8-B786-95111ACDF65F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{33565FF8-EBD5-53F8-B786-95111ACDF65F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{33565FF8-EBD5-53F8-B786-95111ACDF65F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{33565FF8-EBD5-53F8-B786-95111ACDF65F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{12F72803-F28C-8F72-1BA0-3911231DD8AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{12F72803-F28C-8F72-1BA0-3911231DD8AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{12F72803-F28C-8F72-1BA0-3911231DD8AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{12F72803-F28C-8F72-1BA0-3911231DD8AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{3A4678E5-957B-1E59-9A19-50C8A60F53DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{3A4678E5-957B-1E59-9A19-50C8A60F53DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{3A4678E5-957B-1E59-9A19-50C8A60F53DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{3A4678E5-957B-1E59-9A19-50C8A60F53DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
{0F9CBD78-C279-951B-A38F-A0AA57B62517}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{0F9CBD78-C279-951B-A38F-A0AA57B62517}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
{0F9CBD78-C279-951B-A38F-A0AA57B62517}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
{0F9CBD78-C279-951B-A38F-A0AA57B62517}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
|
||||
{B0727DBA-83FD-619F-5D33-3F4652753F2E} = {4D88C818-3BB3-BC3B-D3D1-F21617D09654}
|
||||
|
||||
{CC393719-C997-B814-42B3-0BA762B74BF7} = {4D88C818-3BB3-BC3B-D3D1-F21617D09654}
|
||||
|
||||
{C314BF02-26DE-60BD-BAA5-AA4C52869724} = {CC393719-C997-B814-42B3-0BA762B74BF7}
|
||||
|
||||
{03DFF14F-7321-1784-D4C7-4E99D4120F48} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{BDD326D6-7616-84F0-B914-74743BFBA520} = {03DFF14F-7321-1784-D4C7-4E99D4120F48}
|
||||
|
||||
{EC506DBE-AB6D-492E-786E-8B176021BF2E} = {BDD326D6-7616-84F0-B914-74743BFBA520}
|
||||
|
||||
{5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{33B1AE27-692A-1778-48C1-CCEC2B9BC78F} = {5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}
|
||||
|
||||
{018E0E11-1CCE-A2BE-641D-21EE14D2E90D} = {5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}
|
||||
|
||||
{5F27FB4E-CF09-3A6B-F5B4-BF5A709FA609} = {33B1AE27-692A-1778-48C1-CCEC2B9BC78F}
|
||||
|
||||
{AB67BDB9-D701-3AC9-9CDF-ECCDCCD8DB6D} = {5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}
|
||||
|
||||
{45F7FA87-7451-6970-7F6E-F8BAE45E081B} = {AB67BDB9-D701-3AC9-9CDF-ECCDCCD8DB6D}
|
||||
|
||||
{157C3671-CA0B-69FA-A7C9-74A1FDA97B99} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{F39E09D6-BF93-B64A-CFE7-2BA92815C0FE} = {157C3671-CA0B-69FA-A7C9-74A1FDA97B99}
|
||||
|
||||
{1DCF4EBB-DBC4-752C-13D4-D1EECE4E8907} = {F39E09D6-BF93-B64A-CFE7-2BA92815C0FE}
|
||||
|
||||
{F2B58F4E-6F28-A25F-5BFB-CDEBAD6B9A3E} = {F39E09D6-BF93-B64A-CFE7-2BA92815C0FE}
|
||||
|
||||
{7D49FA52-6EA1-EAC8-4C5A-AC07188D6C57} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{C9CF27FC-12DB-954F-863C-576BA8E309A5} = {7D49FA52-6EA1-EAC8-4C5A-AC07188D6C57}
|
||||
|
||||
{6DCAF6F3-717F-27A9-D96C-F2BFA5550347} = {C9CF27FC-12DB-954F-863C-576BA8E309A5}
|
||||
|
||||
{C4A90603-BE42-0044-CAB4-3EB910AD51A5} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{054761F9-16D3-B2F8-6F4D-EFC2248805CD} = {C4A90603-BE42-0044-CAB4-3EB910AD51A5}
|
||||
|
||||
{B54CE64C-4167-1DD1-B7D6-2FD7A5AEF715} = {C4A90603-BE42-0044-CAB4-3EB910AD51A5}
|
||||
|
||||
{8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{BC12ED55-6015-7C8B-8384-B39CE93C76D6} = {8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6}
|
||||
|
||||
{FF70543D-AFF9-1D38-4950-4F8EE18D60BB} = {8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6}
|
||||
|
||||
{831265B0-8896-9C95-3488-E12FD9F6DC53} = {FF70543D-AFF9-1D38-4950-4F8EE18D60BB}
|
||||
|
||||
{316BBD0A-04D2-85C9-52EA-7993CC6C8930} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{9D6AB85A-85EA-D85A-5566-A121D34016E6} = {316BBD0A-04D2-85C9-52EA-7993CC6C8930}
|
||||
|
||||
{3247EE0D-B3E9-9C11-B0AE-FE719410390B} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{CD7C09DA-FEC8-2CC5-D00C-E525638DFF4A} = {3247EE0D-B3E9-9C11-B0AE-FE719410390B}
|
||||
|
||||
{79B10804-91E9-972E-1913-EE0F0B11663E} = {CD7C09DA-FEC8-2CC5-D00C-E525638DFF4A}
|
||||
|
||||
{1345DD29-BB3A-FB5F-4B3D-E29F6045A27A} = {5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}
|
||||
|
||||
{79E122F4-2325-3E92-438E-5825A307B594} = {1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}
|
||||
|
||||
{66557252-B5C4-664B-D807-07018C627474} = {1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}
|
||||
|
||||
{5AC9EE40-1881-5F8A-46A2-2C303950D3C8} = {1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}
|
||||
|
||||
{61B23570-4F2D-B060-BE1F-37995682E494} = {1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}
|
||||
|
||||
{1182764D-2143-EEF0-9270-3DCE392F5D06} = {1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}
|
||||
|
||||
{776E2142-804F-03B9-C804-D061D64C6092} = {EC506DBE-AB6D-492E-786E-8B176021BF2E}
|
||||
|
||||
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59} = {5F27FB4E-CF09-3A6B-F5B4-BF5A709FA609}
|
||||
|
||||
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6} = {018E0E11-1CCE-A2BE-641D-21EE14D2E90D}
|
||||
|
||||
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728} = {45F7FA87-7451-6970-7F6E-F8BAE45E081B}
|
||||
|
||||
{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60} = {79E122F4-2325-3E92-438E-5825A307B594}
|
||||
|
||||
{34EFF636-81A7-8DF6-7CC9-4DA784BAC7F3} = {1DCF4EBB-DBC4-752C-13D4-D1EECE4E8907}
|
||||
|
||||
{EB093C48-CDAC-106B-1196-AE34809B34C0} = {F2B58F4E-6F28-A25F-5BFB-CDEBAD6B9A3E}
|
||||
|
||||
{F664A948-E352-5808-E780-77A03F19E93E} = {66557252-B5C4-664B-D807-07018C627474}
|
||||
|
||||
{F3A27846-6DE0-3448-222C-25A273E86B2E} = {5AC9EE40-1881-5F8A-46A2-2C303950D3C8}
|
||||
|
||||
{9151601C-8784-01A6-C2E7-A5C0FAAB0AEF} = {6DCAF6F3-717F-27A9-D96C-F2BFA5550347}
|
||||
|
||||
{CB296A20-2732-77C1-7F23-27D5BAEDD0C7} = {054761F9-16D3-B2F8-6F4D-EFC2248805CD}
|
||||
|
||||
{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F} = {B54CE64C-4167-1DD1-B7D6-2FD7A5AEF715}
|
||||
|
||||
{8C594D82-3463-3367-4F06-900AC707753D} = {61B23570-4F2D-B060-BE1F-37995682E494}
|
||||
|
||||
{9588FBF9-C37E-D16E-2E8F-CFA226EAC01D} = {1182764D-2143-EEF0-9270-3DCE392F5D06}
|
||||
|
||||
{19868E2D-7163-2108-1094-F13887C4F070} = {831265B0-8896-9C95-3488-E12FD9F6DC53}
|
||||
|
||||
{CC319FC5-F4B1-C3DD-7310-4DAD343E0125} = {BC12ED55-6015-7C8B-8384-B39CE93C76D6}
|
||||
|
||||
{A78EBC0F-C62C-8F56-95C0-330E376242A2} = {9D6AB85A-85EA-D85A-5566-A121D34016E6}
|
||||
|
||||
{0AF13355-173C-3128-5AFC-D32E540DA3EF} = {79B10804-91E9-972E-1913-EE0F0B11663E}
|
||||
|
||||
{33565FF8-EBD5-53F8-B786-95111ACDF65F} = {4D88C818-3BB3-BC3B-D3D1-F21617D09654}
|
||||
|
||||
{12F72803-F28C-8F72-1BA0-3911231DD8AF} = {B0727DBA-83FD-619F-5D33-3F4652753F2E}
|
||||
|
||||
{3A4678E5-957B-1E59-9A19-50C8A60F53DF} = {C314BF02-26DE-60BD-BAA5-AA4C52869724}
|
||||
|
||||
{0F9CBD78-C279-951B-A38F-A0AA57B62517} = {A08207CC-507B-A415-392F-6FF2DB6342CA}
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
SolutionGuid = {E7529811-6776-97C9-97A9-F3C3CEFB1173}
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
EndGlobal
|
||||
|
||||
|
||||
|
||||
@@ -130,7 +130,9 @@ public sealed record ProjectionSummary(
|
||||
int StatementCount,
|
||||
int ConflictCount,
|
||||
DateTimeOffset ComputedAt,
|
||||
bool StatusChanged);
|
||||
bool StatusChanged,
|
||||
string? UnknownRationale,
|
||||
IReadOnlyList<string>? UnknownProvenanceTrace);
|
||||
|
||||
/// <summary>
|
||||
/// Detailed projection response.
|
||||
@@ -150,7 +152,9 @@ public sealed record ProjectionDetailResponse(
|
||||
DateTimeOffset ComputedAt,
|
||||
DateTimeOffset StoredAt,
|
||||
string? PreviousProjectionId,
|
||||
bool StatusChanged);
|
||||
bool StatusChanged,
|
||||
string? UnknownRationale,
|
||||
IReadOnlyList<string>? UnknownProvenanceTrace);
|
||||
|
||||
/// <summary>
|
||||
/// Response from projection history query.
|
||||
|
||||
@@ -652,6 +652,11 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
|
||||
private static ProjectionDetailResponse MapToDetailResponse(ConsensusProjection projection)
|
||||
{
|
||||
var unknownRationale = projection.Status == VexStatus.Unknown
|
||||
? projection.RationaleSummary
|
||||
: null;
|
||||
var unknownProvenanceTrace = BuildUnknownProvenanceTrace(projection);
|
||||
|
||||
return new ProjectionDetailResponse(
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
@@ -667,11 +672,18 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
ComputedAt: projection.ComputedAt,
|
||||
StoredAt: projection.StoredAt,
|
||||
PreviousProjectionId: projection.PreviousProjectionId,
|
||||
StatusChanged: projection.StatusChanged);
|
||||
StatusChanged: projection.StatusChanged,
|
||||
UnknownRationale: unknownRationale,
|
||||
UnknownProvenanceTrace: unknownProvenanceTrace);
|
||||
}
|
||||
|
||||
private static ProjectionSummary MapToSummary(ConsensusProjection projection)
|
||||
{
|
||||
var unknownRationale = projection.Status == VexStatus.Unknown
|
||||
? projection.RationaleSummary
|
||||
: null;
|
||||
var unknownProvenanceTrace = BuildUnknownProvenanceTrace(projection);
|
||||
|
||||
return new ProjectionSummary(
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
@@ -683,7 +695,30 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
StatementCount: projection.StatementCount,
|
||||
ConflictCount: projection.ConflictCount,
|
||||
ComputedAt: projection.ComputedAt,
|
||||
StatusChanged: projection.StatusChanged);
|
||||
StatusChanged: projection.StatusChanged,
|
||||
UnknownRationale: unknownRationale,
|
||||
UnknownProvenanceTrace: unknownProvenanceTrace);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string>? BuildUnknownProvenanceTrace(ConsensusProjection projection)
|
||||
{
|
||||
if (projection.Status != VexStatus.Unknown)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var trace = new List<string>
|
||||
{
|
||||
$"projection:{projection.ProjectionId}"
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(projection.PreviousProjectionId))
|
||||
{
|
||||
trace.Add($"previous:{projection.PreviousProjectionId}");
|
||||
}
|
||||
|
||||
trace.Add($"computed_at:{projection.ComputedAt:O}");
|
||||
return trace;
|
||||
}
|
||||
|
||||
private static IssuerSummary MapToIssuerSummary(IssuerRecord issuer)
|
||||
|
||||
@@ -88,15 +88,22 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
List<WeightedStatement> statements,
|
||||
ConsensusPolicy policy)
|
||||
{
|
||||
var ordered = statements.OrderByDescending(s => s.Weight.Weight).ToList();
|
||||
var ordered = OrderStatementsForDeterministicMerge(statements).ToList();
|
||||
var winner = ordered[0];
|
||||
var conflicts = DetectConflicts(ordered, policy);
|
||||
|
||||
var contributions = CreateContributions(ordered, winner.Statement.StatementId);
|
||||
var hasUnresolvedTie = ordered.Count > 1 && AreUnresolvedStatementTie(ordered[0], ordered[1]);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : winner.Statement.StatementId;
|
||||
var contributions = CreateContributions(ordered, winnerId);
|
||||
var statusWeights = ComputeStatusWeights(ordered);
|
||||
|
||||
var outcome = DetermineOutcome(ordered, winner, conflicts);
|
||||
var confidence = ComputeConfidence(ordered, winner, conflicts);
|
||||
var consensusStatus = hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status;
|
||||
var outcome = hasUnresolvedTie
|
||||
? ConsensusOutcome.Indeterminate
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
var confidence = hasUnresolvedTie
|
||||
? 0.0
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
|
||||
var factors = new List<string>
|
||||
{
|
||||
@@ -104,6 +111,11 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
$"Issuer: {winner.Issuer?.Name ?? winner.Statement.StatementId}"
|
||||
};
|
||||
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
factors.Add("Conflict unresolved after weight+timestamp+source tie-break; retaining explicit unknown outcome");
|
||||
}
|
||||
|
||||
if (conflicts.Count > 0)
|
||||
{
|
||||
factors.Add($"Resolved {conflicts.Count} conflict(s) by weight");
|
||||
@@ -112,12 +124,14 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
return new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winner.Statement.Status,
|
||||
ConsensusJustification: winner.Statement.Justification,
|
||||
ConsensusStatus: consensusStatus,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : winner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Highest weight consensus: {winner.Statement.Status}",
|
||||
Summary: hasUnresolvedTie
|
||||
? "Highest weight consensus unresolved: unknown"
|
||||
: $"Highest weight consensus: {winner.Statement.Status}",
|
||||
Factors: factors,
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
@@ -133,47 +147,93 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var statusWeights = ComputeStatusWeights(statements);
|
||||
var totalWeight = statusWeights.Values.Sum();
|
||||
|
||||
// Find the status with highest total weight
|
||||
var winningStatus = statusWeights
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.First();
|
||||
var statusSelections = statusWeights
|
||||
.Select(kv =>
|
||||
{
|
||||
var supporting = statements
|
||||
.Where(s => s.Statement.Status == kv.Key)
|
||||
.ToList();
|
||||
var latestTimestamp = supporting
|
||||
.Select(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.DefaultIfEmpty(DateTimeOffset.MinValue)
|
||||
.Max();
|
||||
var lexicalSource = supporting
|
||||
.Where(s => GetStatementOrderingTimestamp(s.Statement) == latestTimestamp)
|
||||
.Select(GetSourceOrderingKey)
|
||||
.DefaultIfEmpty(string.Empty)
|
||||
.OrderBy(s => s, StringComparer.Ordinal)
|
||||
.First();
|
||||
|
||||
return new StatusSelection(kv.Key, kv.Value, latestTimestamp, lexicalSource);
|
||||
})
|
||||
.OrderByDescending(s => s.Weight)
|
||||
.ThenByDescending(s => s.LatestTimestamp)
|
||||
.ThenBy(s => s.LexicalSource, StringComparer.Ordinal)
|
||||
.ThenBy(s => _configuration.StatusLattice.StatusOrder.GetValueOrDefault(s.Status, int.MaxValue))
|
||||
.ToList();
|
||||
|
||||
var winningStatus = statusSelections[0];
|
||||
var hasUnresolvedTie = statusSelections.Count > 1 && statusSelections[1] is { } second &&
|
||||
Math.Abs(second.Weight - winningStatus.Weight) < 0.000_001 &&
|
||||
second.LatestTimestamp == winningStatus.LatestTimestamp &&
|
||||
string.Equals(second.LexicalSource, winningStatus.LexicalSource, StringComparison.Ordinal);
|
||||
|
||||
var winningStatements = statements
|
||||
.Where(s => s.Statement.Status == winningStatus.Key)
|
||||
.Where(s => s.Statement.Status == winningStatus.Status)
|
||||
.OrderByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var primaryWinner = winningStatements[0];
|
||||
var conflicts = DetectConflicts(statements, policy);
|
||||
var contributions = CreateContributions(statements, primaryWinner.Statement.StatementId);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : primaryWinner.Statement.StatementId;
|
||||
var contributions = CreateContributions(statements, winnerId);
|
||||
|
||||
var voteFraction = totalWeight > 0 ? winningStatus.Value / totalWeight : 0;
|
||||
var voteFraction = totalWeight > 0 ? winningStatus.Weight / totalWeight : 0;
|
||||
var outcome = voteFraction >= 0.5
|
||||
? ConsensusOutcome.Majority
|
||||
: ConsensusOutcome.Plurality;
|
||||
|
||||
if (statements.All(s => s.Statement.Status == winningStatus.Key))
|
||||
if (statements.All(s => s.Statement.Status == winningStatus.Status))
|
||||
{
|
||||
outcome = ConsensusOutcome.Unanimous;
|
||||
}
|
||||
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
outcome = ConsensusOutcome.Indeterminate;
|
||||
}
|
||||
|
||||
var confidence = voteFraction * ComputeWeightSpreadFactor(statements);
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
confidence = 0.0;
|
||||
}
|
||||
|
||||
var factors = new List<string>
|
||||
{
|
||||
$"Weighted vote: {winningStatus.Key} received {voteFraction:P1} of total weight",
|
||||
$"Weighted vote: {winningStatus.Status} received {voteFraction:P1} of total weight",
|
||||
$"{winningStatements.Count} statement(s) support this status"
|
||||
};
|
||||
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
factors.Add("Weighted totals, latest timestamps, and lexical source IDs tied; retaining explicit unknown outcome");
|
||||
}
|
||||
|
||||
return new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winningStatus.Key,
|
||||
ConsensusJustification: primaryWinner.Statement.Justification,
|
||||
ConsensusStatus: hasUnresolvedTie ? VexStatus.Unknown : winningStatus.Status,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : primaryWinner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Weighted vote consensus: {winningStatus.Key} ({voteFraction:P1})",
|
||||
Summary: hasUnresolvedTie
|
||||
? "Weighted vote consensus unresolved: unknown"
|
||||
: $"Weighted vote consensus: {winningStatus.Status} ({voteFraction:P1})",
|
||||
Factors: factors,
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
@@ -198,6 +258,9 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var lowestStatements = statements
|
||||
.Where(s => s.Statement.Status == lowestStatus)
|
||||
.OrderByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var primaryWinner = lowestStatements[0];
|
||||
@@ -244,21 +307,30 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var ordered = statements
|
||||
.OrderByDescending(s => IsAuthoritative(s.Issuer))
|
||||
.ThenByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var winner = ordered[0];
|
||||
var conflicts = DetectConflicts(ordered, policy);
|
||||
var contributions = CreateContributions(ordered, winner.Statement.StatementId);
|
||||
var hasUnresolvedTie = ordered.Count > 1 && AreUnresolvedStatementTie(ordered[0], ordered[1]);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : winner.Statement.StatementId;
|
||||
var contributions = CreateContributions(ordered, winnerId);
|
||||
var statusWeights = ComputeStatusWeights(ordered);
|
||||
|
||||
var isAuthoritative = IsAuthoritative(winner.Issuer);
|
||||
var outcome = isAuthoritative
|
||||
? ConsensusOutcome.Unanimous // Authoritative source takes precedence
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
var outcome = hasUnresolvedTie
|
||||
? ConsensusOutcome.Indeterminate
|
||||
: isAuthoritative
|
||||
? ConsensusOutcome.Unanimous // Authoritative source takes precedence
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
|
||||
var confidence = isAuthoritative
|
||||
? 0.95
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
var confidence = hasUnresolvedTie
|
||||
? 0.0
|
||||
: isAuthoritative
|
||||
? 0.95
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
|
||||
var factors = new List<string>
|
||||
{
|
||||
@@ -268,15 +340,22 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
$"Weight: {winner.Weight.Weight:F4}"
|
||||
};
|
||||
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
factors.Add("Authoritative/weight/timestamp/source precedence tie remains unresolved; retaining explicit unknown outcome");
|
||||
}
|
||||
|
||||
return new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winner.Statement.Status,
|
||||
ConsensusJustification: winner.Statement.Justification,
|
||||
ConsensusStatus: hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : winner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Authoritative-first consensus: {winner.Statement.Status}",
|
||||
Summary: hasUnresolvedTie
|
||||
? "Authoritative-first consensus unresolved: unknown"
|
||||
: $"Authoritative-first consensus: {winner.Statement.Status}",
|
||||
Factors: factors,
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
@@ -353,6 +432,11 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
return ConflictSeverity.Low;
|
||||
}
|
||||
|
||||
if (status1 == VexStatus.Unknown || status2 == VexStatus.Unknown)
|
||||
{
|
||||
return ConflictSeverity.Low;
|
||||
}
|
||||
|
||||
return ConflictSeverity.Medium;
|
||||
}
|
||||
|
||||
@@ -377,6 +461,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
{
|
||||
return statements
|
||||
.GroupBy(s => s.Statement.Status)
|
||||
.OrderBy(g => g.Key.ToString(), StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(s => s.Weight.Weight));
|
||||
@@ -387,8 +472,9 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
string winnerId)
|
||||
{
|
||||
var totalWeight = statements.Sum(s => s.Weight.Weight);
|
||||
var ordered = OrderStatementsForDeterministicMerge(statements);
|
||||
|
||||
return statements.Select(s => new StatementContribution(
|
||||
return ordered.Select(s => new StatementContribution(
|
||||
StatementId: s.Statement.StatementId,
|
||||
IssuerId: s.Issuer?.Id,
|
||||
Status: s.Statement.Status,
|
||||
@@ -459,6 +545,42 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
return 1.0 - (spread * 0.5);
|
||||
}
|
||||
|
||||
private static IOrderedEnumerable<WeightedStatement> OrderStatementsForDeterministicMerge(
|
||||
IEnumerable<WeightedStatement> statements)
|
||||
{
|
||||
return statements
|
||||
.OrderByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private static DateTimeOffset GetStatementOrderingTimestamp(NormalizedStatement statement)
|
||||
{
|
||||
return statement.LastSeen ?? statement.FirstSeen ?? DateTimeOffset.MinValue;
|
||||
}
|
||||
|
||||
private static string GetSourceOrderingKey(WeightedStatement statement)
|
||||
{
|
||||
return statement.SourceDocumentId ??
|
||||
statement.Issuer?.Id ??
|
||||
statement.Statement.StatementId;
|
||||
}
|
||||
|
||||
private static bool AreUnresolvedStatementTie(WeightedStatement first, WeightedStatement second)
|
||||
{
|
||||
return Math.Abs(first.Weight.Weight - second.Weight.Weight) < 0.000_001 &&
|
||||
GetStatementOrderingTimestamp(first.Statement) == GetStatementOrderingTimestamp(second.Statement) &&
|
||||
string.Equals(GetSourceOrderingKey(first), GetSourceOrderingKey(second), StringComparison.Ordinal) &&
|
||||
first.Statement.Status != second.Statement.Status;
|
||||
}
|
||||
|
||||
private sealed record StatusSelection(
|
||||
VexStatus Status,
|
||||
double Weight,
|
||||
DateTimeOffset LatestTimestamp,
|
||||
string LexicalSource);
|
||||
|
||||
private static VexConsensusResult CreateNoDataResult(
|
||||
VexConsensusRequest request,
|
||||
string? reason = null)
|
||||
@@ -499,9 +621,10 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
StatusOrder: new Dictionary<VexStatus, int>
|
||||
{
|
||||
[VexStatus.Affected] = 0,
|
||||
[VexStatus.UnderInvestigation] = 1,
|
||||
[VexStatus.Fixed] = 2,
|
||||
[VexStatus.NotAffected] = 3
|
||||
[VexStatus.Unknown] = 1,
|
||||
[VexStatus.UnderInvestigation] = 2,
|
||||
[VexStatus.Fixed] = 3,
|
||||
[VexStatus.NotAffected] = 4
|
||||
},
|
||||
BottomStatus: VexStatus.Affected,
|
||||
TopStatus: VexStatus.NotAffected),
|
||||
@@ -848,6 +971,9 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var ordered = statements
|
||||
.OrderBy(s => lattice.StatusOrder.GetValueOrDefault(s.Statement.Status, int.MaxValue))
|
||||
.ThenByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
// Record merge steps
|
||||
@@ -929,7 +1055,12 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
// Compute final result
|
||||
var finalStatus = currentPosition;
|
||||
var winningStatements = statements.Where(s => s.Statement.Status == finalStatus).ToList();
|
||||
var primaryWinner = winningStatements.OrderByDescending(s => s.Weight.Weight).First();
|
||||
var primaryWinner = winningStatements
|
||||
.OrderByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.First();
|
||||
|
||||
var contributions = CreateContributions(statements, primaryWinner.Statement.StatementId);
|
||||
var outcome = statements.All(s => s.Statement.Status == finalStatus)
|
||||
@@ -974,8 +1105,9 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
ConsensusPolicy policy,
|
||||
VexProofBuilder builder)
|
||||
{
|
||||
var ordered = statements.OrderByDescending(s => s.Weight.Weight).ToList();
|
||||
var ordered = OrderStatementsForDeterministicMerge(statements).ToList();
|
||||
var winner = ordered[0];
|
||||
var hasUnresolvedTie = ordered.Count > 1 && AreUnresolvedStatementTie(ordered[0], ordered[1]);
|
||||
var conflicts = DetectConflicts(ordered, policy);
|
||||
|
||||
// Record merge steps (simple: initialize with highest weight)
|
||||
@@ -1000,8 +1132,8 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
(decimal)stmt.Weight.Weight,
|
||||
MergeAction.Merge,
|
||||
hasConflict,
|
||||
hasConflict ? "weight_lower" : null,
|
||||
winner.Statement.Status);
|
||||
hasConflict ? (hasUnresolvedTie ? "unresolved_tie" : "weight_lower") : null,
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status);
|
||||
}
|
||||
|
||||
// Record conflicts
|
||||
@@ -1016,7 +1148,9 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
conflict.Status2,
|
||||
severity,
|
||||
conflict.Resolution,
|
||||
conflict.Statement1Id == winner.Statement.StatementId ? conflict.Statement1Id : conflict.Statement2Id);
|
||||
hasUnresolvedTie
|
||||
? null
|
||||
: conflict.Statement1Id == winner.Statement.StatementId ? conflict.Statement1Id : conflict.Statement2Id);
|
||||
|
||||
conflictPenalty += severity switch
|
||||
{
|
||||
@@ -1029,25 +1163,38 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
|
||||
builder.WithConflictPenalty(-conflictPenalty);
|
||||
|
||||
var contributions = CreateContributions(ordered, winner.Statement.StatementId);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : winner.Statement.StatementId;
|
||||
var contributions = CreateContributions(ordered, winnerId);
|
||||
var statusWeights = ComputeStatusWeights(ordered);
|
||||
var outcome = DetermineOutcome(ordered, winner, conflicts);
|
||||
var confidence = ComputeConfidence(ordered, winner, conflicts);
|
||||
var outcome = hasUnresolvedTie
|
||||
? ConsensusOutcome.Indeterminate
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
var confidence = hasUnresolvedTie
|
||||
? 0.0
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
|
||||
builder.WithFinalStatus(winner.Statement.Status, winner.Statement.Justification);
|
||||
builder.WithFinalStatus(
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status,
|
||||
hasUnresolvedTie ? null : winner.Statement.Justification);
|
||||
builder.WithWeightSpread((decimal)confidence);
|
||||
|
||||
var result = new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winner.Statement.Status,
|
||||
ConsensusJustification: winner.Statement.Justification,
|
||||
ConsensusStatus: hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : winner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Highest weight consensus: {winner.Statement.Status}",
|
||||
Factors: [$"Selected statement with highest weight: {winner.Weight.Weight:F4}",
|
||||
$"Issuer: {winner.Issuer?.Name ?? winner.Statement.StatementId}"],
|
||||
Summary: hasUnresolvedTie
|
||||
? "Highest weight consensus unresolved: unknown"
|
||||
: $"Highest weight consensus: {winner.Statement.Status}",
|
||||
Factors: hasUnresolvedTie
|
||||
? [$"Selected statement with highest weight: {winner.Weight.Weight:F4}",
|
||||
$"Issuer: {winner.Issuer?.Name ?? winner.Statement.StatementId}",
|
||||
"Conflict unresolved after weight+timestamp+source tie-break; retaining explicit unknown outcome"]
|
||||
: [$"Selected statement with highest weight: {winner.Weight.Weight:F4}",
|
||||
$"Issuer: {winner.Issuer?.Name ?? winner.Statement.StatementId}"],
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
Conflicts: conflicts.Count > 0 ? conflicts : null,
|
||||
@@ -1064,23 +1211,55 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
{
|
||||
var statusWeights = ComputeStatusWeights(statements);
|
||||
var totalWeight = statusWeights.Values.Sum();
|
||||
var statusSelections = statusWeights
|
||||
.Select(kv =>
|
||||
{
|
||||
var supporting = statements
|
||||
.Where(s => s.Statement.Status == kv.Key)
|
||||
.ToList();
|
||||
var latestTimestamp = supporting
|
||||
.Select(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.DefaultIfEmpty(DateTimeOffset.MinValue)
|
||||
.Max();
|
||||
var lexicalSource = supporting
|
||||
.Where(s => GetStatementOrderingTimestamp(s.Statement) == latestTimestamp)
|
||||
.Select(GetSourceOrderingKey)
|
||||
.DefaultIfEmpty(string.Empty)
|
||||
.OrderBy(s => s, StringComparer.Ordinal)
|
||||
.First();
|
||||
|
||||
var winningStatus = statusWeights.OrderByDescending(kv => kv.Value).First();
|
||||
return new StatusSelection(kv.Key, kv.Value, latestTimestamp, lexicalSource);
|
||||
})
|
||||
.OrderByDescending(s => s.Weight)
|
||||
.ThenByDescending(s => s.LatestTimestamp)
|
||||
.ThenBy(s => s.LexicalSource, StringComparer.Ordinal)
|
||||
.ThenBy(s => _configuration.StatusLattice.StatusOrder.GetValueOrDefault(s.Status, int.MaxValue))
|
||||
.ToList();
|
||||
|
||||
var winningStatus = statusSelections[0];
|
||||
var hasUnresolvedTie = statusSelections.Count > 1 && statusSelections[1] is { } second &&
|
||||
Math.Abs(second.Weight - winningStatus.Weight) < 0.000_001 &&
|
||||
second.LatestTimestamp == winningStatus.LatestTimestamp &&
|
||||
string.Equals(second.LexicalSource, winningStatus.LexicalSource, StringComparison.Ordinal);
|
||||
var winningStatements = statements
|
||||
.Where(s => s.Statement.Status == winningStatus.Key)
|
||||
.Where(s => s.Statement.Status == winningStatus.Status)
|
||||
.OrderByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var primaryWinner = winningStatements[0];
|
||||
var conflicts = DetectConflicts(statements, policy);
|
||||
var contributions = CreateContributions(statements, primaryWinner.Statement.StatementId);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : primaryWinner.Statement.StatementId;
|
||||
var contributions = CreateContributions(statements, winnerId);
|
||||
|
||||
// Record merge steps
|
||||
var stepNumber = 1;
|
||||
foreach (var stmt in statements.OrderByDescending(s => s.Weight.Weight))
|
||||
foreach (var stmt in OrderStatementsForDeterministicMerge(statements))
|
||||
{
|
||||
var isFirst = stepNumber == 1;
|
||||
var hasConflict = stmt.Statement.Status != winningStatus.Key;
|
||||
var hasConflict = stmt.Statement.Status != winningStatus.Status;
|
||||
|
||||
builder.AddMergeStep(
|
||||
stepNumber++,
|
||||
@@ -1089,8 +1268,8 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
(decimal)stmt.Weight.Weight,
|
||||
isFirst ? MergeAction.Initialize : MergeAction.Merge,
|
||||
hasConflict,
|
||||
hasConflict ? "status_outvoted" : null,
|
||||
winningStatus.Key);
|
||||
hasConflict ? (hasUnresolvedTie ? "unresolved_tie" : "status_outvoted") : null,
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winningStatus.Status);
|
||||
}
|
||||
|
||||
// Record conflicts
|
||||
@@ -1118,32 +1297,49 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
|
||||
builder.WithConflictPenalty(-conflictPenalty);
|
||||
|
||||
var voteFraction = totalWeight > 0 ? winningStatus.Value / totalWeight : 0;
|
||||
var voteFraction = totalWeight > 0 ? winningStatus.Weight / totalWeight : 0;
|
||||
var outcome = voteFraction >= 0.5
|
||||
? ConsensusOutcome.Majority
|
||||
: ConsensusOutcome.Plurality;
|
||||
|
||||
if (statements.All(s => s.Statement.Status == winningStatus.Key))
|
||||
if (statements.All(s => s.Statement.Status == winningStatus.Status))
|
||||
{
|
||||
outcome = ConsensusOutcome.Unanimous;
|
||||
}
|
||||
|
||||
var confidence = voteFraction * ComputeWeightSpreadFactor(statements);
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
outcome = ConsensusOutcome.Indeterminate;
|
||||
}
|
||||
|
||||
builder.WithFinalStatus(winningStatus.Key, primaryWinner.Statement.Justification);
|
||||
var confidence = voteFraction * ComputeWeightSpreadFactor(statements);
|
||||
if (hasUnresolvedTie)
|
||||
{
|
||||
confidence = 0.0;
|
||||
}
|
||||
|
||||
builder.WithFinalStatus(
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winningStatus.Status,
|
||||
hasUnresolvedTie ? null : primaryWinner.Statement.Justification);
|
||||
builder.WithWeightSpread((decimal)confidence);
|
||||
|
||||
var result = new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winningStatus.Key,
|
||||
ConsensusJustification: primaryWinner.Statement.Justification,
|
||||
ConsensusStatus: hasUnresolvedTie ? VexStatus.Unknown : winningStatus.Status,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : primaryWinner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Weighted vote consensus: {winningStatus.Key} ({voteFraction:P1})",
|
||||
Factors: [$"Weighted vote: {winningStatus.Key} received {voteFraction:P1} of total weight",
|
||||
$"{winningStatements.Count} statement(s) support this status"],
|
||||
Summary: hasUnresolvedTie
|
||||
? "Weighted vote consensus unresolved: unknown"
|
||||
: $"Weighted vote consensus: {winningStatus.Status} ({voteFraction:P1})",
|
||||
Factors: hasUnresolvedTie
|
||||
? [$"Weighted vote: {winningStatus.Status} received {voteFraction:P1} of total weight",
|
||||
$"{winningStatements.Count} statement(s) support this status",
|
||||
"Weighted totals, latest timestamps, and lexical source IDs tied; retaining explicit unknown outcome"]
|
||||
: [$"Weighted vote: {winningStatus.Status} received {voteFraction:P1} of total weight",
|
||||
$"{winningStatements.Count} statement(s) support this status"],
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
Conflicts: conflicts.Count > 0 ? conflicts : null,
|
||||
@@ -1161,11 +1357,16 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var ordered = statements
|
||||
.OrderByDescending(s => IsAuthoritative(s.Issuer))
|
||||
.ThenByDescending(s => s.Weight.Weight)
|
||||
.ThenByDescending(s => GetStatementOrderingTimestamp(s.Statement))
|
||||
.ThenBy(s => GetSourceOrderingKey(s), StringComparer.Ordinal)
|
||||
.ThenBy(s => s.Statement.StatementId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var winner = ordered[0];
|
||||
var hasUnresolvedTie = ordered.Count > 1 && AreUnresolvedStatementTie(ordered[0], ordered[1]);
|
||||
var conflicts = DetectConflicts(ordered, policy);
|
||||
var contributions = CreateContributions(ordered, winner.Statement.StatementId);
|
||||
var winnerId = hasUnresolvedTie ? string.Empty : winner.Statement.StatementId;
|
||||
var contributions = CreateContributions(ordered, winnerId);
|
||||
var statusWeights = ComputeStatusWeights(ordered);
|
||||
|
||||
// Record merge steps
|
||||
@@ -1190,8 +1391,8 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
(decimal)stmt.Weight.Weight,
|
||||
MergeAction.Merge,
|
||||
hasConflict,
|
||||
hasConflict ? "non_authoritative_deferred" : null,
|
||||
winner.Statement.Status);
|
||||
hasConflict ? (hasUnresolvedTie ? "unresolved_tie" : "non_authoritative_deferred") : null,
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status);
|
||||
}
|
||||
|
||||
// Record conflicts
|
||||
@@ -1206,7 +1407,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
conflict.Status2,
|
||||
severity,
|
||||
"authoritative_first",
|
||||
winner.Statement.StatementId);
|
||||
hasUnresolvedTie ? null : winner.Statement.StatementId);
|
||||
|
||||
conflictPenalty += severity switch
|
||||
{
|
||||
@@ -1220,15 +1421,21 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
builder.WithConflictPenalty(-conflictPenalty);
|
||||
|
||||
var isAuthoritative = IsAuthoritative(winner.Issuer);
|
||||
var outcome = isAuthoritative
|
||||
? ConsensusOutcome.Unanimous
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
var outcome = hasUnresolvedTie
|
||||
? ConsensusOutcome.Indeterminate
|
||||
: isAuthoritative
|
||||
? ConsensusOutcome.Unanimous
|
||||
: DetermineOutcome(ordered, winner, conflicts);
|
||||
|
||||
var confidence = isAuthoritative
|
||||
? 0.95
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
var confidence = hasUnresolvedTie
|
||||
? 0.0
|
||||
: isAuthoritative
|
||||
? 0.95
|
||||
: ComputeConfidence(ordered, winner, conflicts);
|
||||
|
||||
builder.WithFinalStatus(winner.Statement.Status, winner.Statement.Justification);
|
||||
builder.WithFinalStatus(
|
||||
hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status,
|
||||
hasUnresolvedTie ? null : winner.Statement.Justification);
|
||||
builder.WithWeightSpread((decimal)confidence);
|
||||
|
||||
if (isAuthoritative)
|
||||
@@ -1239,16 +1446,24 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
var result = new VexConsensusResult(
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
ProductKey: request.ProductKey,
|
||||
ConsensusStatus: winner.Statement.Status,
|
||||
ConsensusJustification: winner.Statement.Justification,
|
||||
ConsensusStatus: hasUnresolvedTie ? VexStatus.Unknown : winner.Statement.Status,
|
||||
ConsensusJustification: hasUnresolvedTie ? null : winner.Statement.Justification,
|
||||
ConfidenceScore: confidence,
|
||||
Outcome: outcome,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: $"Authoritative-first consensus: {winner.Statement.Status}",
|
||||
Factors: [isAuthoritative
|
||||
? $"Authoritative source: {winner.Issuer?.Name ?? "unknown"}"
|
||||
: $"No authoritative source; using highest weight",
|
||||
$"Weight: {winner.Weight.Weight:F4}"],
|
||||
Summary: hasUnresolvedTie
|
||||
? "Authoritative-first consensus unresolved: unknown"
|
||||
: $"Authoritative-first consensus: {winner.Statement.Status}",
|
||||
Factors: hasUnresolvedTie
|
||||
? [isAuthoritative
|
||||
? $"Authoritative source: {winner.Issuer?.Name ?? "unknown"}"
|
||||
: $"No authoritative source; using highest weight",
|
||||
$"Weight: {winner.Weight.Weight:F4}",
|
||||
"Authoritative/weight/timestamp/source precedence tie remains unresolved; retaining explicit unknown outcome"]
|
||||
: [isAuthoritative
|
||||
? $"Authoritative source: {winner.Issuer?.Name ?? "unknown"}"
|
||||
: $"No authoritative source; using highest weight",
|
||||
$"Weight: {winner.Weight.Weight:F4}"],
|
||||
StatusWeights: statusWeights),
|
||||
Contributions: contributions,
|
||||
Conflicts: conflicts.Count > 0 ? conflicts : null,
|
||||
|
||||
@@ -170,6 +170,7 @@ public sealed class PolicyEngineIntegration : IPolicyEngineIntegration
|
||||
VexStatus.Fixed => (0.0, "VEX indicates fixed"),
|
||||
VexStatus.Affected => (1.0, "VEX confirms affected"),
|
||||
VexStatus.UnderInvestigation => (0.8, "VEX indicates under investigation"),
|
||||
VexStatus.Unknown => (0.9, "VEX status unresolved (unknown)"),
|
||||
_ => (1.0, "Unknown VEX status")
|
||||
};
|
||||
|
||||
@@ -202,6 +203,7 @@ public sealed class PolicyEngineIntegration : IPolicyEngineIntegration
|
||||
ModelsVexStatus.NotAffected => VexStatus.NotAffected,
|
||||
ModelsVexStatus.Fixed => VexStatus.Fixed,
|
||||
ModelsVexStatus.UnderInvestigation => VexStatus.UnderInvestigation,
|
||||
ModelsVexStatus.Unknown => VexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -214,7 +216,8 @@ public sealed class PolicyEngineIntegration : IPolicyEngineIntegration
|
||||
ModelsVexStatus.NotAffected => VexStatus.NotAffected,
|
||||
ModelsVexStatus.Fixed => VexStatus.Fixed,
|
||||
ModelsVexStatus.UnderInvestigation => VexStatus.UnderInvestigation,
|
||||
_ => VexStatus.UnderInvestigation
|
||||
ModelsVexStatus.Unknown => VexStatus.Unknown,
|
||||
_ => VexStatus.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
@@ -478,6 +481,7 @@ public sealed class VulnExplorerIntegration : IVulnExplorerIntegration
|
||||
ModelsVexStatus.NotAffected => VexStatus.NotAffected,
|
||||
ModelsVexStatus.Fixed => VexStatus.Fixed,
|
||||
ModelsVexStatus.UnderInvestigation => VexStatus.UnderInvestigation,
|
||||
ModelsVexStatus.Unknown => VexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -490,7 +494,8 @@ public sealed class VulnExplorerIntegration : IVulnExplorerIntegration
|
||||
ModelsVexStatus.NotAffected => VexStatus.NotAffected,
|
||||
ModelsVexStatus.Fixed => VexStatus.Fixed,
|
||||
ModelsVexStatus.UnderInvestigation => VexStatus.UnderInvestigation,
|
||||
_ => VexStatus.UnderInvestigation
|
||||
ModelsVexStatus.Unknown => VexStatus.Unknown,
|
||||
_ => VexStatus.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
@@ -503,6 +508,7 @@ public sealed class VulnExplorerIntegration : IVulnExplorerIntegration
|
||||
VexStatus.NotAffected => ModelsVexStatus.NotAffected,
|
||||
VexStatus.Fixed => ModelsVexStatus.Fixed,
|
||||
VexStatus.UnderInvestigation => ModelsVexStatus.UnderInvestigation,
|
||||
VexStatus.Unknown => ModelsVexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed record VexSignal
|
||||
/// <summary>The product (purl or cpe).</summary>
|
||||
public required string Product { get; init; }
|
||||
|
||||
/// <summary>VEX status (affected, not_affected, fixed, under_investigation).</summary>
|
||||
/// <summary>VEX status (affected, not_affected, fixed, under_investigation, unknown).</summary>
|
||||
public required VexStatus Status { get; init; }
|
||||
|
||||
/// <summary>Justification (if not_affected).</summary>
|
||||
@@ -63,7 +63,10 @@ public enum VexStatus
|
||||
Fixed,
|
||||
|
||||
/// <summary>Under investigation.</summary>
|
||||
UnderInvestigation
|
||||
UnderInvestigation,
|
||||
|
||||
/// <summary>Unknown / unresolved.</summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -126,6 +126,7 @@ public static class VexDeltaMapper
|
||||
VexStatus.NotAffected => VexDeltaStatus.NotAffected,
|
||||
VexStatus.Fixed => VexDeltaStatus.Fixed,
|
||||
VexStatus.UnderInvestigation => VexDeltaStatus.UnderInvestigation,
|
||||
VexStatus.Unknown => VexDeltaStatus.Unknown,
|
||||
_ => VexDeltaStatus.Unknown
|
||||
};
|
||||
|
||||
@@ -138,6 +139,7 @@ public static class VexDeltaMapper
|
||||
VexDeltaStatus.NotAffected => VexStatus.NotAffected,
|
||||
VexDeltaStatus.Fixed => VexStatus.Fixed,
|
||||
VexDeltaStatus.UnderInvestigation => VexStatus.UnderInvestigation,
|
||||
_ => VexStatus.UnderInvestigation
|
||||
VexDeltaStatus.Unknown => VexStatus.Unknown,
|
||||
_ => VexStatus.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
@@ -132,7 +132,10 @@ public enum VexStatus
|
||||
Fixed,
|
||||
|
||||
[JsonPropertyName("under_investigation")]
|
||||
UnderInvestigation
|
||||
UnderInvestigation,
|
||||
|
||||
[JsonPropertyName("unknown")]
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -423,6 +423,7 @@ public sealed class NoiseGateService : INoiseGate
|
||||
VexStatus.NotAffected => "not_affected",
|
||||
VexStatus.Fixed => "fixed",
|
||||
VexStatus.UnderInvestigation => "under_investigation",
|
||||
VexStatus.Unknown => "unknown",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
@@ -433,6 +434,7 @@ public sealed class NoiseGateService : INoiseGate
|
||||
"not_affected" => VexStatus.NotAffected,
|
||||
"fixed" => VexStatus.Fixed,
|
||||
"under_investigation" => VexStatus.UnderInvestigation,
|
||||
"unknown" => VexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
|
||||
|
||||
@@ -430,6 +430,47 @@ public sealed class CsafVexNormalizer : IVexNormalizer
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit unknown status
|
||||
if (productStatus.TryGetProperty("known_unknown", out var knownUnknown) &&
|
||||
knownUnknown.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var productRef in knownUnknown.EnumerateArray())
|
||||
{
|
||||
var product = ResolveProduct(productRef, productTree);
|
||||
if (product != null)
|
||||
{
|
||||
statements.Add(CreateStatement(
|
||||
startIndex + localIndex++,
|
||||
vulnerabilityId,
|
||||
aliases,
|
||||
product,
|
||||
VexStatus.Unknown,
|
||||
null,
|
||||
vuln));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (productStatus.TryGetProperty("unknown", out var unknown) &&
|
||||
unknown.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var productRef in unknown.EnumerateArray())
|
||||
{
|
||||
var product = ResolveProduct(productRef, productTree);
|
||||
if (product != null)
|
||||
{
|
||||
statements.Add(CreateStatement(
|
||||
startIndex + localIndex++,
|
||||
vulnerabilityId,
|
||||
aliases,
|
||||
product,
|
||||
VexStatus.Unknown,
|
||||
null,
|
||||
vuln));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
|
||||
@@ -446,7 +446,26 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
||||
if (vuln.TryGetProperty("analysis", out var analysis))
|
||||
{
|
||||
var stateStr = analysis.TryGetProperty("state", out var state) ? state.GetString() : null;
|
||||
status = MapAnalysisState(stateStr) ?? VexStatus.UnderInvestigation;
|
||||
if (string.IsNullOrWhiteSpace(stateStr))
|
||||
{
|
||||
status = VexStatus.UnderInvestigation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapped = MapAnalysisState(stateStr);
|
||||
if (mapped.HasValue)
|
||||
{
|
||||
status = mapped.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = VexStatus.Unknown;
|
||||
warnings.Add(new NormalizationWarning(
|
||||
"WARN_CDX_008",
|
||||
$"Unknown analysis.state '{stateStr}' for {vulnerabilityId}; defaulting to unknown",
|
||||
"vulnerabilities[].analysis.state"));
|
||||
}
|
||||
}
|
||||
|
||||
var justificationStr = analysis.TryGetProperty("justification", out var just) ? just.GetString() : null;
|
||||
justification = MapJustification(justificationStr);
|
||||
@@ -607,6 +626,8 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
||||
"exploitable" or "in_triage" => VexStatus.Affected,
|
||||
"resolved" or "resolved_with_pedigree" => VexStatus.Fixed,
|
||||
"false_positive" => VexStatus.NotAffected,
|
||||
"under_investigation" => VexStatus.UnderInvestigation,
|
||||
"unknown" => VexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -341,9 +341,9 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
{
|
||||
warnings.Add(new NormalizationWarning(
|
||||
"WARN_OPENVEX_006",
|
||||
$"Unknown status '{statusStr}'; defaulting to under_investigation",
|
||||
$"Unknown status '{statusStr}'; defaulting to unknown",
|
||||
$"statements[{index}].status"));
|
||||
status = VexStatus.UnderInvestigation;
|
||||
status = VexStatus.Unknown;
|
||||
}
|
||||
|
||||
// Extract justification
|
||||
@@ -456,6 +456,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
"affected" => VexStatus.Affected,
|
||||
"fixed" => VexStatus.Fixed,
|
||||
"under_investigation" => VexStatus.UnderInvestigation,
|
||||
"unknown" => VexStatus.Unknown,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
private static bool IsHighSeverityChange(VexStatus previous, VexStatus current)
|
||||
{
|
||||
// Alert when moving from safe to potentially affected
|
||||
if (previous == VexStatus.NotAffected && current is VexStatus.Affected or VexStatus.UnderInvestigation)
|
||||
if (previous == VexStatus.NotAffected && current is VexStatus.Affected or VexStatus.UnderInvestigation or VexStatus.Unknown)
|
||||
return true;
|
||||
|
||||
// Alert when a fixed status regresses
|
||||
@@ -233,6 +233,7 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
VexStatus.NotAffected => "Not Affected",
|
||||
VexStatus.Fixed => "Fixed",
|
||||
VexStatus.UnderInvestigation => "Under Investigation",
|
||||
VexStatus.Unknown => "Unknown",
|
||||
_ => status.ToString()
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<ItemGroup>
|
||||
<!-- LIN-BE-026: Delta attestation support -->
|
||||
<ProjectReference Include="..\..\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Signer\StellaOps.Signer\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Attestor\StellaOps.Signer\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj" />
|
||||
<!-- VEX delta repository and models from Excititor -->
|
||||
<ProjectReference Include="..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Excititor\__Libraries\StellaOps.Excititor.Persistence\StellaOps.Excititor.Persistence.csproj" />
|
||||
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Excititor.Persistence\StellaOps.Excititor.Persistence.csproj" />
|
||||
<!-- NG-001: Noise-gating dependencies -->
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.ReachGraph\StellaOps.ReachGraph.csproj" />
|
||||
<ProjectReference Include="..\..\Policy\StellaOps.Policy.Engine\StellaOps.Policy.Engine.csproj" />
|
||||
|
||||
@@ -121,6 +121,8 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
var latest = history
|
||||
.OrderByDescending(p => p.ComputedAt)
|
||||
.ThenByDescending(p => p.StoredAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.FirstOrDefault();
|
||||
|
||||
return Task.FromResult(latest);
|
||||
@@ -192,20 +194,66 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
list = query.SortBy switch
|
||||
{
|
||||
ProjectionSortField.ComputedAt => query.SortDescending
|
||||
? list.OrderByDescending(p => p.ComputedAt).ToList()
|
||||
: list.OrderBy(p => p.ComputedAt).ToList(),
|
||||
? list.OrderByDescending(p => p.ComputedAt)
|
||||
.ThenByDescending(p => p.StoredAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: list.OrderBy(p => p.ComputedAt)
|
||||
.ThenBy(p => p.StoredAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList(),
|
||||
ProjectionSortField.StoredAt => query.SortDescending
|
||||
? list.OrderByDescending(p => p.StoredAt).ToList()
|
||||
: list.OrderBy(p => p.StoredAt).ToList(),
|
||||
? list.OrderByDescending(p => p.StoredAt)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: list.OrderBy(p => p.StoredAt)
|
||||
.ThenBy(p => p.ComputedAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList(),
|
||||
ProjectionSortField.VulnerabilityId => query.SortDescending
|
||||
? list.OrderByDescending(p => p.VulnerabilityId).ToList()
|
||||
: list.OrderBy(p => p.VulnerabilityId).ToList(),
|
||||
? list.OrderByDescending(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: list.OrderBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList(),
|
||||
ProjectionSortField.ProductKey => query.SortDescending
|
||||
? list.OrderByDescending(p => p.ProductKey).ToList()
|
||||
: list.OrderBy(p => p.ProductKey).ToList(),
|
||||
? list.OrderByDescending(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: list.OrderBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList(),
|
||||
ProjectionSortField.ConfidenceScore => query.SortDescending
|
||||
? list.OrderByDescending(p => p.ConfidenceScore).ToList()
|
||||
: list.OrderBy(p => p.ConfidenceScore).ToList(),
|
||||
? list.OrderByDescending(p => p.ConfidenceScore)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: list.OrderBy(p => p.ConfidenceScore)
|
||||
.ThenByDescending(p => p.ComputedAt)
|
||||
.ThenBy(p => p.VulnerabilityId, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProductKey, StringComparer.Ordinal)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.ToList(),
|
||||
_ => list
|
||||
};
|
||||
|
||||
@@ -237,6 +285,8 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
var ordered = history
|
||||
.OrderByDescending(p => p.ComputedAt)
|
||||
.ThenByDescending(p => p.StoredAt)
|
||||
.ThenBy(p => p.ProjectionId, StringComparer.Ordinal)
|
||||
.AsEnumerable();
|
||||
|
||||
if (limit.HasValue)
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
WHERE vulnerability_id = @vulnerability_id
|
||||
AND product_key = @product_key
|
||||
AND (@tenant_id IS NULL OR tenant_id = @tenant_id)
|
||||
ORDER BY computed_at DESC
|
||||
ORDER BY computed_at DESC, stored_at DESC, id ASC
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
@@ -92,7 +92,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
WHERE vulnerability_id = @vulnerability_id
|
||||
AND product_key = @product_key
|
||||
AND (@tenant_id IS NULL OR tenant_id = @tenant_id)
|
||||
ORDER BY computed_at DESC
|
||||
ORDER BY computed_at DESC, stored_at DESC, id ASC
|
||||
LIMIT @limit
|
||||
""";
|
||||
|
||||
@@ -368,6 +368,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
VexStatus.Affected => "affected",
|
||||
VexStatus.Fixed => "fixed",
|
||||
VexStatus.UnderInvestigation => "under_investigation",
|
||||
VexStatus.Unknown => "unknown",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
@@ -377,7 +378,8 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
"affected" => VexStatus.Affected,
|
||||
"fixed" => VexStatus.Fixed,
|
||||
"under_investigation" => VexStatus.UnderInvestigation,
|
||||
_ => VexStatus.UnderInvestigation
|
||||
"unknown" => VexStatus.Unknown,
|
||||
_ => VexStatus.Unknown
|
||||
};
|
||||
|
||||
private static string MapJustification(VexJustification justification) => justification switch
|
||||
@@ -503,7 +505,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
rationale_summary, computed_at, stored_at, previous_projection_id, status_changed
|
||||
FROM {Schema}.consensus_projections
|
||||
{whereClause}
|
||||
ORDER BY {orderColumn} {orderDirection}
|
||||
ORDER BY {orderColumn} {orderDirection}, computed_at DESC, stored_at DESC, vulnerability_id ASC, product_key ASC, id ASC
|
||||
LIMIT @limit OFFSET @offset
|
||||
""";
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ public sealed class TrustWeightEngine : ITrustWeightEngine
|
||||
VexStatus.Fixed => config.FixedBonus,
|
||||
VexStatus.Affected => config.AffectedNeutral,
|
||||
VexStatus.UnderInvestigation => config.UnderInvestigationPenalty,
|
||||
VexStatus.Unknown => config.UnderInvestigationPenalty,
|
||||
_ => 0.0
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Trust;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.VexLens.Tests.Consensus;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ConsensusDeterminismAndUnknownTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task WeightedVote_TieOnWeight_UsesLatestTimestampAsSecondaryTieBreak()
|
||||
{
|
||||
var evaluationTime = new DateTimeOffset(2026, 3, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
var engine = new VexConsensusEngine();
|
||||
var statements = new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-affected", VexStatus.Affected, 1.0, "source-z", "issuer-z", new DateTimeOffset(2026, 3, 1, 0, 0, 0, TimeSpan.Zero)),
|
||||
CreateWeightedStatement("stmt-fixed", VexStatus.Fixed, 1.0, "source-a", "issuer-a", new DateTimeOffset(2026, 3, 2, 0, 0, 0, TimeSpan.Zero))
|
||||
};
|
||||
|
||||
var result = await engine.ComputeConsensusAsync(CreateWeightedVoteRequest(statements, evaluationTime));
|
||||
|
||||
result.ConsensusStatus.Should().Be(VexStatus.Fixed);
|
||||
result.Outcome.Should().Be(ConsensusOutcome.Majority);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WeightedVote_TieOnWeightAndTimestamp_UsesLexicalSourceTieBreak()
|
||||
{
|
||||
var evaluationTime = new DateTimeOffset(2026, 3, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
var engine = new VexConsensusEngine();
|
||||
var sharedTimestamp = new DateTimeOffset(2026, 3, 2, 0, 0, 0, TimeSpan.Zero);
|
||||
var statements = new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-affected", VexStatus.Affected, 1.0, "source-a", "issuer-a", sharedTimestamp),
|
||||
CreateWeightedStatement("stmt-fixed", VexStatus.Fixed, 1.0, "source-z", "issuer-z", sharedTimestamp)
|
||||
};
|
||||
|
||||
var result = await engine.ComputeConsensusAsync(CreateWeightedVoteRequest(statements, evaluationTime));
|
||||
|
||||
result.ConsensusStatus.Should().Be(VexStatus.Affected);
|
||||
result.Outcome.Should().Be(ConsensusOutcome.Majority);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WeightedVote_UnresolvedTieRetainsExplicitUnknownOutcome()
|
||||
{
|
||||
var evaluationTime = new DateTimeOffset(2026, 3, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
var engine = new VexConsensusEngine();
|
||||
var sharedTimestamp = new DateTimeOffset(2026, 3, 2, 0, 0, 0, TimeSpan.Zero);
|
||||
var statements = new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-affected", VexStatus.Affected, 1.0, "source-shared", "issuer-a", sharedTimestamp),
|
||||
CreateWeightedStatement("stmt-fixed", VexStatus.Fixed, 1.0, "source-shared", "issuer-z", sharedTimestamp)
|
||||
};
|
||||
|
||||
var result = await engine.ComputeConsensusAsync(CreateWeightedVoteRequest(statements, evaluationTime));
|
||||
|
||||
result.ConsensusStatus.Should().Be(VexStatus.Unknown);
|
||||
result.Outcome.Should().Be(ConsensusOutcome.Indeterminate);
|
||||
result.ConfidenceScore.Should().Be(0.0);
|
||||
}
|
||||
|
||||
private static VexConsensusRequest CreateWeightedVoteRequest(
|
||||
IReadOnlyList<WeightedStatement> statements,
|
||||
DateTimeOffset evaluationTime)
|
||||
{
|
||||
return new VexConsensusRequest(
|
||||
VulnerabilityId: "CVE-2026-1234",
|
||||
ProductKey: "pkg:npm/demo@1.0.0",
|
||||
Statements: statements,
|
||||
Context: new ConsensusContext(
|
||||
TenantId: "tenant-1",
|
||||
EvaluationTime: evaluationTime,
|
||||
Policy: new ConsensusPolicy(
|
||||
Mode: ConsensusMode.WeightedVote,
|
||||
MinimumWeightThreshold: 0.0,
|
||||
ConflictThreshold: 0.3,
|
||||
RequireJustificationForNotAffected: false,
|
||||
PreferredIssuers: null)));
|
||||
}
|
||||
|
||||
private static WeightedStatement CreateWeightedStatement(
|
||||
string statementId,
|
||||
VexStatus status,
|
||||
double weight,
|
||||
string sourceDocumentId,
|
||||
string issuerId,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
var statement = new NormalizedStatement(
|
||||
StatementId: statementId,
|
||||
VulnerabilityId: "CVE-2026-1234",
|
||||
VulnerabilityAliases: null,
|
||||
Product: new NormalizedProduct(
|
||||
Key: "pkg:npm/demo@1.0.0",
|
||||
Name: "demo",
|
||||
Version: "1.0.0",
|
||||
Purl: "pkg:npm/demo@1.0.0",
|
||||
Cpe: null,
|
||||
Hashes: null),
|
||||
Status: status,
|
||||
StatusNotes: null,
|
||||
Justification: null,
|
||||
ImpactStatement: null,
|
||||
ActionStatement: null,
|
||||
ActionStatementTimestamp: null,
|
||||
Versions: null,
|
||||
Subcomponents: null,
|
||||
FirstSeen: timestamp,
|
||||
LastSeen: timestamp);
|
||||
|
||||
var trustWeight = new TrustWeightResult(
|
||||
Statement: statement,
|
||||
Weight: weight,
|
||||
Breakdown: new TrustWeightBreakdown(
|
||||
IssuerWeight: 1.0,
|
||||
SignatureWeight: 1.0,
|
||||
FreshnessWeight: 1.0,
|
||||
SourceFormatWeight: 1.0,
|
||||
StatusSpecificityWeight: 0.0,
|
||||
CustomWeight: 0.0),
|
||||
Factors: [],
|
||||
Warnings: []);
|
||||
|
||||
var issuer = new VexIssuer(
|
||||
Id: issuerId,
|
||||
Name: issuerId,
|
||||
Category: IssuerCategory.Vendor,
|
||||
TrustTier: TrustTier.Trusted,
|
||||
KeyFingerprints: null);
|
||||
|
||||
return new WeightedStatement(statement, trustWeight, issuer, sourceDocumentId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Normalization;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.VexLens.Tests.Normalization;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class UnknownStatusNormalizationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task OpenVexNormalizer_UnrecognizedStatus_MapsToUnknown()
|
||||
{
|
||||
var normalizer = new OpenVexNormalizer(new FixedGuidProvider(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")));
|
||||
var document = new
|
||||
{
|
||||
@context = "https://openvex.dev/ns/v0.2.0",
|
||||
@id = "openvex-doc-1",
|
||||
statements = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
vulnerability = "CVE-2026-1001",
|
||||
products = new[] { "pkg:npm/demo@1.0.0" },
|
||||
status = "pending_vendor_confirmation"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var content = JsonSerializer.Serialize(document);
|
||||
var context = new NormalizationContext(
|
||||
SourceUri: null,
|
||||
NormalizedAt: DateTimeOffset.Parse("2026-03-04T10:00:00Z", CultureInfo.InvariantCulture),
|
||||
Normalizer: "OpenVEX",
|
||||
Options: null);
|
||||
|
||||
var result = await normalizer.NormalizeAsync(content, context);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Document.Should().NotBeNull();
|
||||
result.Document!.Statements.Should().HaveCount(1);
|
||||
result.Document.Statements[0].Status.Should().Be(VexStatus.Unknown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CycloneDxNormalizer_UnrecognizedAnalysisState_MapsToUnknown()
|
||||
{
|
||||
var normalizer = new CycloneDxVexNormalizer();
|
||||
var content = """
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"serialNumber": "urn:uuid:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
||||
"metadata": {
|
||||
"timestamp": "2026-03-04T10:00:00Z"
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "pkg:npm/demo@1.0.0",
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"purl": "pkg:npm/demo@1.0.0"
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "CVE-2026-1002",
|
||||
"affects": [
|
||||
{
|
||||
"ref": "pkg:npm/demo@1.0.0"
|
||||
}
|
||||
],
|
||||
"analysis": {
|
||||
"state": "pending_vendor_confirmation"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
var context = new NormalizationContext(
|
||||
SourceUri: null,
|
||||
NormalizedAt: DateTimeOffset.Parse("2026-03-04T10:00:00Z", CultureInfo.InvariantCulture),
|
||||
Normalizer: "CycloneDX",
|
||||
Options: null);
|
||||
|
||||
var result = await normalizer.NormalizeAsync(content, context);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Document.Should().NotBeNull();
|
||||
result.Document!.Statements.Should().HaveCount(1);
|
||||
result.Document.Statements[0].Status.Should().Be(VexStatus.Unknown);
|
||||
result.Warnings.Should().Contain(w => w.Code == "WARN_CDX_008");
|
||||
}
|
||||
|
||||
private sealed class FixedGuidProvider : IGuidProvider
|
||||
{
|
||||
private readonly Guid _value;
|
||||
|
||||
public FixedGuidProvider(Guid value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public Guid NewGuid() => _value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.VexLens.Tests.Storage;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class InMemoryConsensusProjectionStoreDeterminismTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetLatestAndHistory_UseDeterministicSecondaryOrderingForEqualTimestamps()
|
||||
{
|
||||
var fixedTime = new DateTimeOffset(2026, 3, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
var timeProvider = new FakeTimeProvider(fixedTime);
|
||||
var guidProvider = new SequenceGuidProvider(
|
||||
[
|
||||
Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"),
|
||||
Guid.Parse("00000000-0000-0000-0000-000000000000")
|
||||
]);
|
||||
|
||||
var store = new InMemoryConsensusProjectionStore(
|
||||
timeProvider: timeProvider,
|
||||
guidProvider: guidProvider);
|
||||
|
||||
var first = CreateConsensusResult(VexStatus.Affected, fixedTime);
|
||||
var second = CreateConsensusResult(VexStatus.Fixed, fixedTime);
|
||||
var options = new StoreProjectionOptions(
|
||||
TenantId: "tenant-1",
|
||||
TrackHistory: true,
|
||||
EmitEvent: false);
|
||||
|
||||
await store.StoreAsync(first, options);
|
||||
await store.StoreAsync(second, options);
|
||||
|
||||
var latest = await store.GetLatestAsync("CVE-2026-2001", "pkg:npm/demo@1.0.0", "tenant-1");
|
||||
latest.Should().NotBeNull();
|
||||
latest!.ProjectionId.Should().Be("proj-00000000000000000000000000000000");
|
||||
latest.Status.Should().Be(VexStatus.Fixed);
|
||||
|
||||
var history = await store.GetHistoryAsync("CVE-2026-2001", "pkg:npm/demo@1.0.0", "tenant-1");
|
||||
history.Should().HaveCount(2);
|
||||
history[0].ProjectionId.Should().Be("proj-00000000000000000000000000000000");
|
||||
history[1].ProjectionId.Should().Be("proj-ffffffffffffffffffffffffffffffff");
|
||||
}
|
||||
|
||||
private static VexConsensusResult CreateConsensusResult(VexStatus status, DateTimeOffset computedAt)
|
||||
{
|
||||
return new VexConsensusResult(
|
||||
VulnerabilityId: "CVE-2026-2001",
|
||||
ProductKey: "pkg:npm/demo@1.0.0",
|
||||
ConsensusStatus: status,
|
||||
ConsensusJustification: null,
|
||||
ConfidenceScore: 0.7,
|
||||
Outcome: ConsensusOutcome.Plurality,
|
||||
Rationale: new ConsensusRationale(
|
||||
Summary: "test",
|
||||
Factors: ["test"],
|
||||
StatusWeights: new Dictionary<VexStatus, double>
|
||||
{
|
||||
[status] = 0.7
|
||||
}),
|
||||
Contributions:
|
||||
[
|
||||
new StatementContribution(
|
||||
StatementId: "stmt-1",
|
||||
IssuerId: "issuer-1",
|
||||
Status: status,
|
||||
Justification: null,
|
||||
Weight: 0.7,
|
||||
Contribution: 1.0,
|
||||
IsWinner: true)
|
||||
],
|
||||
Conflicts: null,
|
||||
ComputedAt: computedAt);
|
||||
}
|
||||
|
||||
private sealed class SequenceGuidProvider : IGuidProvider
|
||||
{
|
||||
private readonly Queue<Guid> _values;
|
||||
|
||||
public SequenceGuidProvider(IEnumerable<Guid> values)
|
||||
{
|
||||
_values = new Queue<Guid>(values);
|
||||
}
|
||||
|
||||
public Guid NewGuid()
|
||||
{
|
||||
if (_values.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No GUIDs remaining in sequence.");
|
||||
}
|
||||
|
||||
return _values.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,4 +72,73 @@ public sealed class PostgresConsensusProjectionStoreProxyTests
|
||||
projection.Status.Should().Be(VexStatus.Affected);
|
||||
projection.Outcome.Should().Be(ConsensusOutcome.Majority);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapProjection_ParsesUnknownStatusWithoutCollapsingToUnderInvestigation()
|
||||
{
|
||||
var table = new DataTable();
|
||||
table.Columns.Add("id", typeof(Guid));
|
||||
table.Columns.Add("vulnerability_id", typeof(string));
|
||||
table.Columns.Add("product_key", typeof(string));
|
||||
table.Columns.Add("tenant_id", typeof(string));
|
||||
table.Columns.Add("status", typeof(string));
|
||||
table.Columns.Add("justification", typeof(string));
|
||||
table.Columns.Add("confidence_score", typeof(double));
|
||||
table.Columns.Add("outcome", typeof(string));
|
||||
table.Columns.Add("statement_count", typeof(int));
|
||||
table.Columns.Add("conflict_count", typeof(int));
|
||||
table.Columns.Add("rationale_summary", typeof(string));
|
||||
table.Columns.Add("computed_at", typeof(DateTimeOffset));
|
||||
table.Columns.Add("stored_at", typeof(DateTimeOffset));
|
||||
table.Columns.Add("previous_projection_id", typeof(Guid));
|
||||
table.Columns.Add("status_changed", typeof(bool));
|
||||
|
||||
table.Rows.Add(
|
||||
Guid.Parse("22222222-2222-2222-2222-222222222222"),
|
||||
"CVE-2026-0004",
|
||||
"pkg:npm/example@4.0.0",
|
||||
DBNull.Value,
|
||||
"unknown",
|
||||
DBNull.Value,
|
||||
0.10,
|
||||
"indeterminate",
|
||||
2,
|
||||
1,
|
||||
"summary",
|
||||
new DateTimeOffset(2026, 1, 15, 9, 30, 0, TimeSpan.Zero),
|
||||
new DateTimeOffset(2026, 1, 15, 10, 0, 0, TimeSpan.Zero),
|
||||
DBNull.Value,
|
||||
false);
|
||||
|
||||
table.Rows.Add(
|
||||
Guid.Parse("33333333-3333-3333-3333-333333333333"),
|
||||
"CVE-2026-0005",
|
||||
"pkg:npm/example@5.0.0",
|
||||
DBNull.Value,
|
||||
"unexpected_status",
|
||||
DBNull.Value,
|
||||
0.10,
|
||||
"indeterminate",
|
||||
2,
|
||||
1,
|
||||
"summary",
|
||||
new DateTimeOffset(2026, 1, 15, 9, 30, 0, TimeSpan.Zero),
|
||||
new DateTimeOffset(2026, 1, 15, 10, 0, 0, TimeSpan.Zero),
|
||||
DBNull.Value,
|
||||
false);
|
||||
|
||||
var method = typeof(PostgresConsensusProjectionStoreProxy).GetMethod(
|
||||
"MapProjection",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
|
||||
using var reader = table.CreateDataReader();
|
||||
reader.Read();
|
||||
var explicitUnknown = (ConsensusProjection)method!.Invoke(null, new object[] { reader })!;
|
||||
reader.Read();
|
||||
var fallbackUnknown = (ConsensusProjection)method.Invoke(null, new object[] { reader })!;
|
||||
|
||||
explicitUnknown.Status.Should().Be(VexStatus.Unknown);
|
||||
fallbackUnknown.Status.Should().Be(VexStatus.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user