consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -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',

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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,

View File

@@ -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
};
}

View File

@@ -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>

View File

@@ -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
};
}

View File

@@ -132,7 +132,10 @@ public enum VexStatus
Fixed,
[JsonPropertyName("under_investigation")]
UnderInvestigation
UnderInvestigation,
[JsonPropertyName("unknown")]
Unknown
}
/// <summary>

View File

@@ -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
};

View File

@@ -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;
}

View File

@@ -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
};
}

View File

@@ -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
};
}

View File

@@ -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()
};

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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)

View File

@@ -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
""";

View File

@@ -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
};

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}